use crate::{FUSED, arc, buffer::{DType, FloatWidth}, complex as cx, dd::Double, domain::Range, float::{Native, horner}, ln::{ln, log1p}, pow as p};
use num_complex::Complex;
const E: (f64, f64) = (2.718281828459045, 1.4456468917292502e-16);
const BRANCH: [f64; 36] = [
-1.240275440042247e-07, 1.8054472775101644e-07, -2.630378619271863e-07, 3.835663851491814e-07, -5.598551881378796e-07, 8.179940565280035e-07,
-1.1964453089157258e-06, 1.7520067268263412e-06, -2.5687050390550955e-06, 3.7710949611072535e-06, -5.544203208567359e-06, 8.163531982496613e-06,
-1.2040352739559977e-05, 1.7790345805079586e-05, -2.63380647472311e-05, 3.907668486743905e-05, -5.811360750441382e-05, 8.665035805208128e-05,
-0.00012957426685274883, 0.00019438727605453933, -0.00029267722472962746, 0.0004424730618146209, -0.0006720616311561362, 0.0010262633205076071,
-0.0015769303446867841, 0.0024408779911439826, -0.0038112980348919993, 0.006014543252956118, -0.009616892024299432, 0.01563563253233392,
-0.02598471487360376, 0.044502314814814814, -0.07962962962962963, 0.1527777777777778, -0.3333333333333333, 1.0,
];
const NEAR: f64 = 0.5;
fn branch_offset<const FUSED: bool>(x: f64, epsilon: f64) -> f64 {
let ex = Double::new(E.0, E.1).mul_float::<FUSED>(x);
let q = (ex.hi + 1.0) + ex.lo;
if (q < 0.0) & (q >= -0.5 * epsilon) { 0.0 } else { q }
}
pub(crate) fn real_domain(dtype: DType, x: Range) -> Option<[Range; 1]> {
let width = dtype.numpy_float().max(FloatWidth::F32);
(x.lo >= 0.0 || branch_offset::<FUSED>(x.lo, width.epsilon()) >= 0.0).then_some([x])
}
#[inline(always)]
pub(crate) fn lambertw<R: Native, const FUSED: bool>(x: R) -> R {
let x = x.wide();
let q = branch_offset::<FUSED>(x, R::epsilon().wide());
let p = (2.0 * q).sqrt();
let near = p * horner::<f64, FUSED, 36>(p, BRANCH) - 1.0;
let [.., m4, m3, m2, m1] = BRANCH;
let l = log1p::<f64, FUSED>(x);
let w = if p < 1.0 { p * horner::<f64, FUSED, 4>(p, [m4, m3, m2, m1]) - 1.0 } else { l * (1.0 - log1p::<f64, FUSED>(l) / (2.0 + l)) };
let fritsch = |w: f64| {
let z = ln::<f64, FUSED>(x / w) - w;
let t = 1.0 + w;
let s = 2.0 * t * (t + z * (2.0 / 3.0));
w * (1.0 + z / t * (s - z) / (s - 2.0 * z))
};
let w = fritsch(fritsch(w));
let w = if p < NEAR { near } else { w };
let w = if (x == 0.0) | (x == f64::INFINITY) { x } else { w };
R::narrow(if q < 0.0 { f64::NAN } else { w })
}
const STEPS: usize = 32;
pub(crate) fn complex<R: Native>(z: Complex<R>) -> Complex<R> {
let w = complex_wide(Complex::new(z.re.wide(), z.im.wide()));
Complex::new(R::narrow(w.re), R::narrow(w.im))
}
fn complex_wide(z: Complex<f64>) -> Complex<f64> {
let norm = |v: Complex<f64>| p::hypot::<f64, FUSED>(v.re, v.im);
if z.re.is_nan() || z.im.is_nan() { return Complex::new(f64::NAN, f64::NAN); }
if z.re.is_infinite() || z.im.is_infinite() { return Complex::new(f64::INFINITY, arc::atan2::<f64, FUSED>(z.im, z.re)); }
if z.re == 0.0 && z.im == 0.0 { return z; }
let ex = Double::new(E.0, E.1).mul_float::<FUSED>(z.re);
let p = cx::sqrt::<f64, FUSED>(Complex::new((ex.hi + 1.0) + ex.lo, E.0 * z.im) * 2.0);
if norm(p) < NEAR { return p * BRANCH.iter().fold(Complex::new(0.0, 0.0), |s, &c| s * p + c) - 1.0; }
let mut w = if norm(z + (1.0 / std::f64::consts::E)) < 0.3 {
let [.., m4, m3, m2, m1] = BRANCH;
p * (((p * m4 + m3) * p + m2) * p + m1) - 1.0
} else if -1.0 < z.re && z.re < 1.5 && z.im.abs() < 1.0 && -2.5 * z.im.abs() - 0.2 < z.re {
z * ((z * (17.0 / 60.0) + 1.9) * z + 1.0) / ((z * (101.0 / 60.0) + 2.9) * z + 1.0)
} else {
let l1 = cx::ln::<f64, FUSED>(z);
let l2 = cx::ln::<f64, FUSED>(l1);
l1 - l2 + l2 / l1
};
let mut last = f64::INFINITY;
for _ in 0..STEPS {
let g = w - z * cx::exp::<f64, FUSED>(-w);
let d = g / ((w + 1.0) - (w + 2.0) * g / (w * 2.0 + 2.0));
if norm(d) >= last { break; }
w -= d;
last = norm(d);
if last <= 0.5 * f64::EPSILON * norm(w) { break; }
}
w
}