
// Complex arithmetic helpers (a,b represent a + i b)
function cadd(a, b) { return [a[0] + b[0], a[1] + b[1]]; }
function csub(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
function cmul(a, b) { return [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]; }
function cdiv(a, b) { const d = b[0]*b[0] + b[1]*b[1]; return [(a[0]*b[0] + a[1]*b[1]) / d, (a[1]*b[0] - a[0]*b[1]) / d]; }
function creal(a) { return a[0]; }
function cabs2(a) { return a[0]*a[0] + a[1]*a[1]; }


/**
 * Canonical Bohren–Huffman Mie solver (reference implementation)
 * Computes scattering/extinction cross-sections and asymmetry parameter for a
 * homogeneous sphere embedded in a host medium.
 *
 * @param {number} wavelength Vacuum wavelength (meters)
 * @param {number} radius Sphere radius (meters)
 * @param {[number,number]} n_p Particle complex RI [n, k]
 * @param {[number,number]} n_med Medium complex RI [n, k]
 * @returns {{sigma_a_med:number, C_ext:number, C_sca:number, g:number}}
 */
export function compute_bhmie_optical_properties(wavelength, radius, n_p, n_med) {
  // Geometry
  const k0 = 2 * Math.PI / wavelength;
  const nmed_r = n_med[0];
  const x = k0 * nmed_r * radius; // size parameter in the (weakly absorbing) medium

  if (!(x > 0)) {
    return { sigma_a_med: 4 * Math.PI * n_med[1] / wavelength, C_ext: 0, C_sca: 0, g: 0 };
  }

  // Relative refractive index m = n_p / n_med
  const m = cdiv([n_p[0], n_p[1]], [n_med[0], n_med[1]]);
  // mx is complex (m * x)
  const mx = [m[0] * x, m[1] * x];

  // Truncation for series
  const nstop = Math.max(2, Math.round(x + 4.0 * Math.cbrt(x) + 2));

  // Downward recurrence for D_n(mx) = ψ'_n(mx)/ψ_n(mx), complex n from nstop..1
  const D = new Array(nstop + 1);
  D[nstop] = [0, 0];
  for (let n = nstop; n >= 1; --n) {
    // D_{n-1} = n/(mx) - 1 / (D_n + n/(mx))
    const n_over_mx = cdiv([n, 0], mx);
    const denom = cadd(D[n], n_over_mx);
    const invDen = cdiv([1, 0], denom);
    D[n - 1] = csub(n_over_mx, invDen);
  }

  // Upward recurrence for real ψ_n(x) and complex ξ_n(x) at real x
  const sx = Math.sin(x);
  const cx = Math.cos(x);
  // ψ_0, ψ_1 (real)
  let psi_nm1 = sx;                      // ψ_0
  let psi_n = sx / x - cx;               // ψ_1
  // ξ_0, ξ_1 (complex): ξ_n = ψ_n + i χ_n, with χ_0 = -cos x, χ_1 = -cos x / x - sin x
  let xi_nm1 = [sx, -cx];                // ξ_0
  let xi_n = [psi_n, -(cx / x + sx)];    // ξ_1

  // Sums
  let sum_ext = 0; // Σ (2n+1) Re(a_n + b_n)
  let sum_sca = 0; // Σ (2n+1) (|a_n|^2 + |b_n|^2)

  const a = new Array(nstop + 1); // 1..nstop
  const b = new Array(nstop + 1);

  for (let n = 1; n <= nstop; ++n) {
    // Next ψ and ξ (prepare ψ_{n+1}, ξ_{n+1}) using recurrence
    const two_n_plus_1 = 2 * n + 1;
    const psi_np1 = two_n_plus_1 * psi_n / x - psi_nm1; // ψ_{n+1}
    const xi_np1 = [ (two_n_plus_1 / x) * xi_n[0] - xi_nm1[0], (two_n_plus_1 / x) * xi_n[1] - xi_nm1[1] ]; // ξ_{n+1}

    // Derivatives at x (real x): ψ'_n = ψ_{n-1} - n/x ψ_n ; ξ'_n = ξ_{n-1} - n/x ξ_n
    const n_over_x = n / x;
    const psi_prime = psi_nm1 - n_over_x * psi_n;
    const xi_prime = [xi_nm1[0] - n_over_x * xi_n[0], xi_nm1[1] - n_over_x * xi_n[1]];

    // Log-derivative at mx
    const Dn = D[n];

    // Compute a_n and b_n using D-forms (no ψ_n(mx) factors)
    // a_n = [m ψ'_n(x) − D(mx) ψ_n(x)] / [m ξ'_n(x) − D(mx) ξ_n(x)]
    const m_psi_prime = [m[0] * psi_prime, m[1] * psi_prime];
    const Dn_psi_n = [Dn[0] * psi_n, Dn[1] * psi_n];
    const num_a = csub(m_psi_prime, Dn_psi_n);
    const m_xi_prime = cmul(m, xi_prime);
    const Dn_xi_n = cmul(Dn, xi_n);
    const den_a = csub(m_xi_prime, Dn_xi_n);
    const a_n = cdiv(num_a, den_a);

    // b_n = [ψ'_n(x) − m D(mx) ψ_n(x)] / [ξ'_n(x) − m D(mx) ξ_n(x)]
    const mDn = cmul(m, Dn);
    const mDn_psi_n = [mDn[0] * psi_n, mDn[1] * psi_n];
    const num_b = csub([psi_prime, 0], mDn_psi_n);
    const mDn_xi_n = cmul(mDn, xi_n);
    const den_b = csub(xi_prime, mDn_xi_n);
    const b_n = cdiv(num_b, den_b);

    a[n] = a_n;
    b[n] = b_n;

    const weight = 2 * n + 1;
    sum_ext += weight * (creal(a_n) + creal(b_n));
    sum_sca += weight * (cabs2(a_n) + cabs2(b_n));

    // Shift recurrences (n→n+1)
    psi_nm1 = psi_n;
    psi_n = psi_np1;
    xi_nm1 = xi_n;
    xi_n = xi_np1;
  }

  const x2 = x * x;
  const Q_ext = (2 / x2) * sum_ext;
  const Q_sca = (2 / x2) * sum_sca;

  // Asymmetry parameter g using BH expression
  // sum_g accumulates the two standard terms:
  //  Σ (2n+1)/(n(n+1)) Re(a_n b_n*) + Σ n(n+2)/(n+1) Re(a_n a_{n+1}* + b_n b_{n+1}*)
  let sum_g = 0;
  for (let n = 1; n <= nstop; ++n) {
    const a_n = a[n];
    const b_n = b[n];
    const re_ab = a_n[0]*b_n[0] + a_n[1]*b_n[1]; // Re(a_n b_n*)
    sum_g += ((2*n + 1) / (n * (n + 1))) * re_ab;
  }
  for (let n = 1; n <= nstop - 1; ++n) {
    const a_n = a[n], a_np1 = a[n+1];
    const b_n = b[n], b_np1 = b[n+1];
    const re_aa1 = a_n[0]*a_np1[0] + a_n[1]*(-a_np1[1]); // Re(a_n a_{n+1}*)
    const re_bb1 = b_n[0]*b_np1[0] + b_n[1]*(-b_np1[1]); // Re(b_n b_{n+1}*)
    sum_g += (n * (n + 2)) / (n + 1) * (re_aa1 + re_bb1);
  }

  // Using relation g = 2 * sum_g / sum_sca to avoid re-scaling by x^2
  let g = 0;
  if (sum_sca > 1e-30) {
    g = (2 * sum_g) / sum_sca;
    if (g > 1) g = 1;
    if (g < -1) g = -1;
  }

  const area = Math.PI * radius * radius;
  const C_ext = Q_ext * area;
  const C_sca = Q_sca * area;

  const sigma_a_med = (4 * Math.PI * n_med[1]) / wavelength;

  return { sigma_a_med, C_ext, C_sca, g };
}
