import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
    #canvas;
    #tip;
    #resizeHandler;

    connect() {
        const root = this.element;
        const $ = id => root.querySelector("#" + id);

        // debug
        try{ console.debug && console.debug('calculateur: connect', root); }catch(e){}

        // Store references for cleanup
        this.#canvas = $("aero-chart");
        this.#tip = $("aero-tip");

        const C = {};
        const readConsts = () => {
            try {
                const ct = $("aero-ct"); if(ct) C.CT=parseFloat(ct.value);
                const sair = $("aero-sair"); if(sair) C.sAir=parseFloat(sair.value);
                const g = $("aero-g"); if(g) C.g=parseFloat(g.value);
                const sea = $("aero-sea"); if(sea) C.sea=parseFloat(sea.value);
                const k = $("aero-k"); if(k) C.k=parseFloat(k.value);
                const pmax = $("aero-pmax"); if(pmax) C.pMax=parseFloat(pmax.value);
                const ytop = $("aero-ytop"); if(ytop) C.yTop=parseFloat(ytop.value);
                const ybot = $("aero-ybot"); if(ybot) C.yBot=parseFloat(ybot.value);
            } catch(err) {
                console.error('calculateur:readConsts error:', err);
            }
        };

        const pressure = (y) => {
            let p = Math.exp(C.k * (y - C.sea));
            if (p > C.pMax) p = C.pMax;
            return p < 0 ? 0 : p;
        };

        const Y_OFFSET = 0.5;
        const propThrust = (P, N) => C.CT * Math.pow(Math.max(P, 0), 1.5) * N;
        const balloonForce = (V, y) => Math.max(V, 0) * C.sAir * C.g * pressure(y);
        const balloonLift = (V, y) => Math.max(V, 0) * C.sAir * pressure(y);

        const maxAltitude = (V, m) => {
            if (V <= 0 || m <= 0) return { y: C.yTop, status: "ceiling" };
            const t = m / (V * C.sAir);
            if (t > C.pMax) return { y: C.yBot, status: "grounded" };
            let y = C.sea + Math.log(t) / C.k;
            if (y >= C.yTop) return { y: C.yTop, status: "ceiling" };
            if (y <= C.yBot) return { y: C.yBot, status: "bottom" };
            return { y, status: "ok" };
        };

        const requiredVolume = (m, y) => m / (C.sAir * pressure(y));

        const nf = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 0 });
        const nf2 = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 2 });
        const nf3 = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 3 });
        const big = (x) => {
            if (!isFinite(x)) return "n/a";
            return Math.abs(x) >= 1000 ? nf.format(x) : nf2.format(x);
        };

        const updateProp = () => {
            try {
                const sails = $("aero-sails");
                const rpm = $("aero-rpm");
                const palt = $("aero-palt");
                const P = sails ? parseFloat(sails.value) || 0 : 0;
                const N = rpm ? parseFloat(rpm.value) || 0 : 0;
                const y = palt ? parseFloat(palt.value) || 0 : 0;
                const T = propThrust(P, N);
                const thrust = $("aero-othrust");
                const thrustapp = $("aero-othrustapp");
                if (thrust) thrust.innerHTML = big(Math.abs(T)) + ' <small>pN</small>';
                if (thrustapp) thrustapp.innerHTML = big(Math.abs(T) * pressure(y + Y_OFFSET)) + ' <small>pN</small>';
                try { console.debug && console.debug('calculateur:updateProp', { P, N, y, T }); } catch (e) {}
            } catch(err) {
                console.error('calculateur:updateProp error:', err);
            }
        };

        const updateBalloon = () => {
            try {
                const vol = $("aero-vol");
                const balt = $("aero-balt");
                const V = vol ? parseFloat(vol.value) || 0 : 0;
                const y = balt ? parseFloat(balt.value) || 0 : 0;
                const force = $("aero-oforce");
                const lift = $("aero-olift");
                const press = $("aero-opress");
                if (force) force.innerHTML = big(balloonForce(V, y)) + ' <small>pN</small>';
                if (lift) lift.innerHTML = big(balloonLift(V, y)) + ' <small>kpg</small>';
                if (press) press.textContent = nf3.format(pressure(y));
                try { console.debug && console.debug('calculateur:updateBalloon', { V, y }); } catch (e) {}
            } catch(err) {
                console.error('calculateur:updateBalloon error:', err);
            }
        };

        const updateVolume = () => {
            try {
                const vmass = $("aero-vmass");
                const valt = $("aero-valt");
                const m = vmass ? parseFloat(vmass.value) || 0 : 0;
                const y = valt ? parseFloat(valt.value) || 0 : 0;
                const V = requiredVolume(m, y);
                const volreq = $("aero-ovolreq");
                const cube = $("aero-ocube");
                const warn = $("aero-vwarn");
                if (volreq) volreq.innerHTML = big(V) + ' <small>m³</small>';
                if (cube) cube.textContent = (isFinite(V) && V > 0)
                    ? "≈ cube de " + nf2.format(Math.cbrt(V)) + " blocs de côté · " + nf.format(Math.ceil(V)) + " blocs d'air"
                    : "";
                if (warn) warn.innerHTML = (pressure(y) <= 0)
                    ? "<b>Altitude hors atmosphère :</b> pression nulle, aucun volume fini ne suffit." : "";
                try { console.debug && console.debug('calculateur:updateVolume', { m, y, V }); } catch (e) {}
            } catch(err) {
                console.error('calculateur:updateVolume error:', err);
            }
        };

        // ---- graphe ----
        let mode = "mass";
        // prefer document-level IDs for canvas/tip (unique across page)
        let canvas = null;
        let tip = null;
        let ctx = null;
        let geom = null;

        try {
            canvas = document.getElementById("aero-chart");
            tip = document.getElementById("aero-tip");
            if (!canvas || !tip) {
                throw new Error('canvas or tip element not found');
            }
            this.#canvas = canvas;
            this.#tip = tip;
            ctx = canvas.getContext("2d");
            if (!ctx) {
                throw new Error('2d context not available');
            }
        } catch (err) {
            console.error('calculateur: graph init failed:', err.message);
            try{ root.dataset.calculateurConnected = '1'; }catch(e){}
            // mark as connected but disable graph features
            canvas = null;
            tip = null;
            ctx = null;
        }

        const tv = (n) => getComputedStyle(root).getPropertyValue(n).trim() || "#888";

        const niceStep = (raw) => {
            const p = Math.pow(10, Math.floor(Math.log10(raw)));
            const n = raw / p;
            return (n < 1.5 ? 1 : n < 3 ? 2 : n < 7 ? 5 : 10) * p;
        };

        const abbr = (v) => {
            const a = Math.abs(v);
            if (a >= 1e6) return nf2.format(v / 1e6) + "M";
            if (a >= 1e3) return nf2.format(v / 1e3) + "k";
            return nf.format(v);
        };

        const drawChart = () => {
            // skip if graph not available
            if (!canvas || !ctx) return;
            const dpr = window.devicePixelRatio || 1;
            const cssW = canvas.parentElement.clientWidth;
            const cssH = Math.max(260, Math.round(cssW * 0.44));
            canvas.style.height = cssH + "px";
            canvas.width = Math.round(cssW * dpr);
            canvas.height = Math.round(cssH * dpr);
            ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
            ctx.clearRect(0, 0, cssW, cssH);

            const padL = 54, padR = 16, padT = 14, padB = 38, pw = cssW - padL - padR, ph = cssH - padT - padB;
            const muted = tv("--aero-muted"), border = tv("--aero-border"), accent = tv("--aero-accent"), warn = tv("--aero-warn");

            let xMin, xMax, xLabel, fixed;
            if (mode === "mass") {
                const fixvol = $("aero-fixvol");
                fixed = Math.max(1, fixvol ? parseFloat(fixvol.value) || 1 : 1);
                xMin = 1;
                xMax = Math.max(2, fixed * C.sAir * C.pMax * 1.15);
                xLabel = "poids m (kpg)";
            } else {
                const fixmass = $("aero-fixmass");
                fixed = Math.max(1, fixmass ? parseFloat(fixmass.value) || 1 : 1);
                xMin = Math.max(1, fixed / (C.sAir * C.pMax) * 0.5);
                xMax = Math.max(xMin * 1.5, 4 * fixed / C.sAir);
                xLabel = "volume V (m³)";
            }
            const yMin = C.yBot, yMax = C.yTop;
            const X = x => padL + (x - xMin) / (xMax - xMin) * pw;
            const Y = y => padT + (1 - (y - yMin) / (yMax - yMin)) * ph;

            ctx.font = '11px ' + (getComputedStyle(root).fontFamily || "sans-serif");

            // grille
            ctx.strokeStyle = border;
            ctx.fillStyle = muted;
            ctx.lineWidth = 1;
            ctx.textBaseline = "middle";
            ctx.textAlign = "right";
            const yStep = niceStep((yMax - yMin) / 6);
            for (let yy = Math.ceil(yMin / yStep) * yStep; yy <= yMax; yy += yStep) {
                const py = Y(yy);
                ctx.beginPath();
                ctx.moveTo(padL, py);
                ctx.lineTo(padL + pw, py);
                ctx.stroke();
                ctx.fillText(String(yy), padL - 7, py);
            }
            ctx.textAlign = "center";
            ctx.textBaseline = "top";
            const xStep = niceStep((xMax - xMin) / 6);
            for (let xx = Math.ceil(xMin / xStep) * xStep; xx <= xMax; xx += xStep) {
                const px = X(xx);
                ctx.beginPath();
                ctx.moveTo(px, padT);
                ctx.lineTo(px, padT + ph);
                ctx.stroke();
                ctx.fillText(abbr(xx), px, padT + ph + 7);
            }
            ctx.fillStyle = muted;
            ctx.fillText(xLabel, padL + pw / 2, padT + ph + 22);
            ctx.save();
            ctx.translate(13, padT + ph / 2);
            ctx.rotate(-Math.PI / 2);
            ctx.fillText("altitude max  y", 0, 0);
            ctx.restore();

            const refLine = (yv, label) => {
                if (yv < yMin || yv > yMax) return;
                const py = Y(yv);
                ctx.strokeStyle = muted;
                ctx.setLineDash([4, 4]);
                ctx.lineWidth = 1;
                ctx.beginPath();
                ctx.moveTo(padL, py);
                ctx.lineTo(padL + pw, py);
                ctx.stroke();
                ctx.setLineDash([]);
                ctx.fillStyle = muted;
                ctx.textAlign = "left";
                ctx.textBaseline = "bottom";
                ctx.fillText(label, padL + 5, py - 2);
            };
            refLine(C.sea, "niveau mer " + C.sea);
            refLine(C.yTop, "limite " + C.yTop);

            // courbe
            const steps = 240, pts = [];
            for (let i = 0; i <= steps; i++) {
                const x = xMin + (xMax - xMin) * i / steps;
                const r = (mode === "mass") ? maxAltitude(fixed, x) : maxAltitude(x, fixed);
                pts.push({ x, y: r.y, status: r.status });
            }

            // zone grounded
            ctx.fillStyle = warn;
            ctx.globalAlpha = .10;
            let gs = null;
            for (let i = 0; i <= steps; i++) {
                const g = pts[i].status === "grounded";
                if (g && gs === null) gs = pts[i].x;
                if ((!g || i === steps) && gs !== null) {
                    ctx.fillRect(X(gs), padT, X(pts[i].x) - X(gs), ph);
                    gs = null;
                }
            }
            ctx.globalAlpha = 1;
            ctx.lineWidth = 2;
            ctx.strokeStyle = accent;
            ctx.beginPath();
            let started = false;
            for (const p of pts) {
                if (p.status === "grounded") {
                    started = false;
                    continue;
                }
                const px = X(p.x), py = Y(p.y);
                if (!started) {
                    ctx.moveTo(px, py);
                    started = true;
                } else {
                    ctx.lineTo(px, py);
                }
            }
            ctx.stroke();

            geom = { padL, pw, padT, ph, xMin, xMax, X, Y, fixed };
        };

        // Event listeners (only if canvas available)
        if (canvas && tip) {
            canvas.addEventListener("mousemove", e => {
                if (!geom) return;
                const rect = canvas.getBoundingClientRect();
                const mx = e.clientX - rect.left;
                if (mx < geom.padL || mx > geom.padL + geom.pw) {
                    tip.style.opacity = 0;
                    return;
                }
                const x = geom.xMin + (mx - geom.padL) / geom.pw * (geom.xMax - geom.xMin);
                const r = (mode === "mass") ? maxAltitude(geom.fixed, x) : maxAltitude(x, geom.fixed);
                tip.textContent = r.status === "grounded" ? abbr(x) + " : ne décolle pas"
                    : r.status === "ceiling" ? abbr(x) + " : plafond (y " + Math.round(r.y) + ")"
                    : abbr(x) + " : y " + Math.round(r.y);
                tip.style.left = geom.X(x) + "px";
                tip.style.top = geom.Y(r.y) + "px";
                tip.style.opacity = 1;
            });

            canvas.addEventListener("mouseleave", () => {
                tip.style.opacity = 0;
            });
        } else {
            if (!canvas) console.warn('calculateur: canvas not available, graph interactions disabled');
            if (!tip) console.warn('calculateur: tip not available, graph tooltip disabled');
        }

        // Affiche uniquement l'input correspondant au mode courant
        const applyModeVisibility = () => {
            const fixvolwrap = $("aero-fixvolwrap");
            const fixmasswrap = $("aero-fixmasswrap");
            if (fixvolwrap) fixvolwrap.style.display = mode === "mass" ? "" : "none";
            if (fixmasswrap) fixmasswrap.style.display = mode === "vol" ? "" : "none";
        };

        // ---- Partage par URL (paramètres GET, tous optionnels) ----
        // Clé = id de l'input sans le préfixe "aero-". On ne sérialise que les
        // valeurs qui diffèrent de leur valeur par défaut → URLs courtes.
        const paramInputs = Array.from(root.querySelectorAll("input, select"))
            .filter(el => el.id && el.id.startsWith("aero-"));
        const keyOf = el => el.id.slice(5);
        const defaults = new Map(paramInputs.map(el => [el, el.value]));

        // Applique les paramètres présents dans l'URL aux inputs (au chargement)
        const applyUrlParams = () => {
            const params = new URLSearchParams(window.location.search);
            paramInputs.forEach(el => {
                const k = keyOf(el);
                if (params.has(k)) el.value = params.get(k);
            });
        };

        // Reflète l'état courant des inputs dans l'URL (sans polluer l'historique)
        const syncUrl = () => {
            try {
                const params = new URLSearchParams();
                paramInputs.forEach(el => {
                    if (el.value !== defaults.get(el)) params.set(keyOf(el), el.value);
                });
                const qs = params.toString();
                const url = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
                window.history.replaceState(null, "", url);
            } catch(err) {
                console.error('calculateur:syncUrl error:', err);
            }
        };

        // Store event handlers for cleanup
        const handleModeChange = e => {
            try {
                mode = e.target.value;
                applyModeVisibility();
                drawChart();
                syncUrl();
            } catch(err) {
                console.error('calculateur:handleModeChange error:', err);
            }
        };

        const handleInput = () => {
            try {
                readConsts();
                updateProp();
                updateBalloon();
                updateVolume();
                drawChart();
                syncUrl();
            } catch(err) {
                console.error('calculateur:handleInput error:', err);
            }
        };

        this.#resizeHandler = () => {
            if (geom) drawChart();
        };

        const modeEl = $("aero-mode");
        if (modeEl) modeEl.addEventListener("change", handleModeChange);
        root.querySelectorAll("input,select").forEach(el => el.addEventListener("input", handleInput));
        window.addEventListener("resize", this.#resizeHandler);

        // Store handlers for cleanup
        this.handleModeChange = handleModeChange;
        this.handleInput = handleInput;
        this.root = root;
        this.$ = $;

        try {
            // Charge l'état partagé depuis l'URL, puis synchronise le mode
            applyUrlParams();
            if (modeEl) mode = modeEl.value;
            applyModeVisibility();
            readConsts();
            updateProp();
            updateBalloon();
            updateVolume();
            drawChart();
        } catch(err) {
            console.error('calculateur: error during initialization:', err);
        }
        try{ root.dataset.calculateurConnected = '1'; }catch(e){}
    }

    disconnect() {
        // Clean up event listeners
        if (this.root && this.$) {
            const $ = this.$;
            if (this.handleModeChange) {
                const modeEl = $("aero-mode");
                if (modeEl) modeEl.removeEventListener("change", this.handleModeChange);
            }
            if (this.handleInput) {
                this.root.querySelectorAll("input,select").forEach(el =>
                    el.removeEventListener("input", this.handleInput)
                );
            }
        }
        if (this.#resizeHandler) {
            window.removeEventListener("resize", this.#resizeHandler);
        }
    }
}

