import { assert } from "../../../assert.js";
import { D65_spd_tabulated } from "../../../color/illuminant/D65_spd_tabulated.js";
import { xyz_cmf_tabulated } from "../../../color/xyz/xyz_cmf_tabulated.js";
import { xyz_to_rgb } from "../../../color/xyz/xyz_to_rgb.js";
import { compute_lorenz_mie_optical_properties } from "./compute_lorenz_mie_optical_properties.js";

/**
 * Compute scattering and extinction for a given particle/medium configuration.
 * Performs full wide visible spectrum sweep.
 * Energy is relative to D65 illuminant (standard for rec709 and sRGB).
 * @param {number} radius_m Particle radius in meters.
 * @param {function(result:number[], wavelength_nm:number)} n_p Particle Refraction Index. function of wavelength, Complex number.
 * @param {function(result:number[], wavelength_nm:number)} n_med Medium Refraction Index. function of wavelength, Complex number.
 * @param {number} [step_size_nm] In nanometers. Controls step size in spectral integration steps, smaller steps = more accurate. Use 1, or multiples of 5
 */
export function compute_mie_particle_properties_rgb(
    radius_m,
    n_p,
    n_med,
    step_size_nm = 5
) {

    assert.isFinite(radius_m, 'radius');
    assert.greaterThan(radius_m, 0, 'radius_m must be positive');
    assert.isFunction(n_p, 'n_p');
    assert.isFunction(n_med, 'n_med');
    assert.isNonNegativeInteger(step_size_nm, 'step_size_nm');

    const cmf = [0, 0, 0];
    let sum_Ey = 0;

    const scattering_coefficients = [0, 0, 0];
    const extinction_coefficients = [0, 0, 0];
    const sigma_a_med = [0, 0, 0];

    const ir_particle = [];
    const ir_medium = [];

    // Variables for G integration
    let g_weighted_accumulator = 0;
    let g_weight_total = 0;

    // integrate CMF
    const WAVELENGTH_MIN = 380;
    const WAVELENGTH_MAX = 780;

    for (let wavelength_nm = WAVELENGTH_MIN; wavelength_nm <= WAVELENGTH_MAX; wavelength_nm += step_size_nm) {

        // visible light sweep
        // Integrate over visible spectrum in nanometers for CMF/RI
        const wavelength_m = wavelength_nm * 1e-9;

        const E = D65_spd_tabulated(wavelength_nm);
        xyz_cmf_tabulated(cmf, wavelength_nm);

        // Calculate spectral weight W(λ) = E(λ) * CMF(λ)
        const wX = cmf[0] * E;
        const wY = cmf[1] * E;
        const wZ = cmf[2] * E;

        // accumulate illuminant-luminance denominator (single shared normalization)
        sum_Ey += wY;

        // compute IR coefficients
        n_p(ir_particle, wavelength_nm);
        n_med(ir_medium, wavelength_nm);

        const properties = compute_lorenz_mie_optical_properties(
            wavelength_m,
            radius_m,
            ir_particle,
            ir_medium,
        );

        scattering_coefficients[0] += properties.C_sca * wX;
        scattering_coefficients[1] += properties.C_sca * wY;
        scattering_coefficients[2] += properties.C_sca * wZ;

        extinction_coefficients[0] += properties.C_ext * wX;
        extinction_coefficients[1] += properties.C_ext * wY;
        extinction_coefficients[2] += properties.C_ext * wZ;

        sigma_a_med[0] += properties.sigma_a_med * wX;
        sigma_a_med[1] += properties.sigma_a_med * wY;
        sigma_a_med[2] += properties.sigma_a_med * wZ;

        // We weight 'g' by how much this wavelength contributes to
        // the Visible Luminance (Y) of the Scattered light.
        // If C_sca is 0, this wavelength should not affect the average G.
        const scatter_luminance_weight = properties.C_sca * wY;

        g_weighted_accumulator += properties.g * scatter_luminance_weight;
        g_weight_total += scatter_luminance_weight;
    }

    // normalize using a single shared luminance-based denominator: k = 1 / Σ E(λ)·ȳ(λ)
    const k = 1 / sum_Ey;

    scattering_coefficients[0] *= k;
    scattering_coefficients[1] *= k;
    scattering_coefficients[2] *= k;

    extinction_coefficients[0] *= k;
    extinction_coefficients[1] *= k;
    extinction_coefficients[2] *= k;

    sigma_a_med[0] *= k;
    sigma_a_med[1] *= k;
    sigma_a_med[2] *= k;

    // Resolve G
    // If the particle is invisible (no scattering), default to isotropic (0)
    const final_g = g_weight_total > 1e-20 ? (g_weighted_accumulator / g_weight_total) : 0;

    // convert XYZ -> sRGB

    xyz_to_rgb(scattering_coefficients, scattering_coefficients);
    xyz_to_rgb(extinction_coefficients, extinction_coefficients);
    xyz_to_rgb(sigma_a_med, sigma_a_med);

    // Perform gamut clamp to remove potential negative lobes
    scattering_coefficients[0] = Math.max(0, scattering_coefficients[0]);
    scattering_coefficients[1] = Math.max(0, scattering_coefficients[1]);
    scattering_coefficients[2] = Math.max(0, scattering_coefficients[2]);

    extinction_coefficients[0] = Math.max(0, extinction_coefficients[0]);
    extinction_coefficients[1] = Math.max(0, extinction_coefficients[1]);
    extinction_coefficients[2] = Math.max(0, extinction_coefficients[2]);

    sigma_a_med[0] = Math.max(0, sigma_a_med[0]);
    sigma_a_med[1] = Math.max(0, sigma_a_med[1]);
    sigma_a_med[2] = Math.max(0, sigma_a_med[2]);

    // Derive the albedo.
    // Prevent division by 0, and clamp gamut at the upper range to 1
    const albedo = [
        extinction_coefficients[0] !== 0 ? Math.min(1, scattering_coefficients[0] / extinction_coefficients[0]) : 0,
        extinction_coefficients[1] !== 0 ? Math.min(1, scattering_coefficients[1] / extinction_coefficients[1]) : 0,
        extinction_coefficients[2] !== 0 ? Math.min(1, scattering_coefficients[2] / extinction_coefficients[2]) : 0,
    ];

    return {
        scattering_coefficients,
        extinction_coefficients,
        extinction_intercept: sigma_a_med,
        albedo,
        g: final_g,
    }

}