/* Kings Roofing — roof render engine + before/after reveal */
(function () {
  const { useState, useRef, useEffect, useCallback } = React;
  const MEAN_L = 98.34;

  function lum(r, g, b) { return 0.299 * r + 0.587 * g + 0.114 * b; }

  // Load an image -> Promise<HTMLImageElement>
  function loadImg(src) {
    return new Promise((res, rej) => {
      const im = new Image();
      im.crossOrigin = "anonymous";
      im.onload = () => res(im);
      im.onerror = rej;
      im.src = src;
    });
  }

  // Build an auto roof-mask for an arbitrary uploaded photo (best effort).
  function autoMask(data, w, h) {
    const mask = new Float32Array(w * h);
    let sum = 0, cnt = 0;
    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        const i = (y * w + x) * 4;
        const r = data[i], g = data[i + 1], b = data[i + 2];
        const L = lum(r, g, b);
        const mx = Math.max(r, g, b), mn = Math.min(r, g, b);
        const S = mx === 0 ? 0 : (mx - mn) / mx;
        const strongGreen = g > r + 8 && g > b + 4;
        const inUpper = y < h * 0.62 && y > h * 0.08;
        if (inUpper && L >= 50 && L <= 140 && S < 0.24 && !strongGreen) {
          mask[y * w + x] = 1; sum += L; cnt++;
        }
      }
    }
    // light feather
    const out = new Float32Array(w * h);
    for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
      let s = 0, n = 0;
      for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
        const yy = y + dy, xx = x + dx;
        if (xx < 0 || yy < 0 || xx >= w || yy >= h) continue;
        s += mask[yy * w + xx]; n++;
      }
      out[y * w + x] = s / n;
    }
    return { mask: out, meanL: cnt ? sum / cnt : MEAN_L };
  }

  // Engine: caches source pixels + mask + per-pixel luminance for fast recolour.
  class RoofEngine {
    constructor() { this.ready = false; }
    async init(houseSrc, maskSrc) {
      const house = await loadImg(houseSrc);
      this.w = house.naturalWidth; this.h = house.naturalHeight;
      const c = document.createElement("canvas");
      c.width = this.w; c.height = this.h;
      const ctx = c.getContext("2d");
      ctx.drawImage(house, 0, 0);
      this.src = ctx.getImageData(0, 0, this.w, this.h);
      this.lumArr = new Float32Array(this.w * this.h);
      const d = this.src.data;
      for (let p = 0; p < this.w * this.h; p++) {
        this.lumArr[p] = lum(d[p * 4], d[p * 4 + 1], d[p * 4 + 2]);
      }
      if (maskSrc) {
        const mImg = await loadImg(maskSrc);
        const mc = document.createElement("canvas");
        mc.width = this.w; mc.height = this.h;
        const mctx = mc.getContext("2d");
        mctx.drawImage(mImg, 0, 0, this.w, this.h);
        const md = mctx.getImageData(0, 0, this.w, this.h).data;
        this.mask = new Float32Array(this.w * this.h);
        for (let p = 0; p < this.w * this.h; p++) this.mask[p] = md[p * 4] / 255;
        this.meanL = MEAN_L;
      } else {
        const am = autoMask(d, this.w, this.h);
        this.mask = am.mask; this.meanL = am.meanL;
      }
      this.ready = true;
      return this;
    }
    // finish: 'Standard' | 'Matte' | 'Low Gloss'
    render(targetCtx, hex, finish) {
      const tr = parseInt(hex.slice(1, 3), 16),
            tg = parseInt(hex.slice(3, 5), 16),
            tb = parseInt(hex.slice(5, 7), 16);
      const out = new ImageData(
        new Uint8ClampedArray(this.src.data), this.w, this.h
      );
      const a = out.data;
      // finish tuning
      let loF = 0.45, hiF = 1.7, gloss = 0;
      if (finish === "Matte") { loF = 0.58; hiF = 1.42; gloss = -0.04; }
      else if (finish === "Low Gloss") { loF = 0.40; hiF = 1.9; gloss = 0.06; }
      for (let p = 0; p < this.w * this.h; p++) {
        const m = this.mask[p];
        if (m <= 0.01) continue;
        const i = p * 4;
        let f = this.lumArr[p] / this.meanL;
        f = Math.max(loF, Math.min(hiF, f)) + gloss;
        a[i]     = a[i]     * (1 - m) + Math.min(255, tr * f) * m;
        a[i + 1] = a[i + 1] * (1 - m) + Math.min(255, tg * f) * m;
        a[i + 2] = a[i + 2] * (1 - m) + Math.min(255, tb * f) * m;
      }
      targetCtx.canvas.width = this.w;
      targetCtx.canvas.height = this.h;
      targetCtx.putImageData(out, 0, 0);
    }
  }

  // Before / after compare slider with live canvas recolour
  function RoofReveal({ engine, photoSrc, aiSrc, colour, finish, loading }) {
    const canvasRef = useRef(null);
    const wrapRef = useRef(null);
    const [pos, setPos] = useState(58);

    // Simulated progress bar: gpt-image gives no real progress, so ease toward
    // ~95% over the expected render time, then the image landing completes it.
    const [progress, setProgress] = useState(0);
    useEffect(() => {
      if (!loading) return;
      setProgress(0);
      let p = 0;
      const id = setInterval(() => {
        p += (96 - p) * 0.035; // fast at first, asymptotic — never quite hits 100
        setProgress(Math.min(96, p));
      }, 400);
      return () => clearInterval(id);
    }, [loading]);
    const dragging = useRef(false);

    // Canvas recolour only runs in the mock path (no AI image available).
    useEffect(() => {
      if (aiSrc) return;
      if (!engine || !engine.ready || loading) return;
      if (!canvasRef.current) return;
      const ctx = canvasRef.current.getContext("2d");
      engine.render(ctx, colour.hex, finish);
    }, [engine, aiSrc, colour, finish, loading]);

    const move = useCallback((clientX) => {
      const r = wrapRef.current.getBoundingClientRect();
      let pct = ((clientX - r.left) / r.width) * 100;
      pct = Math.max(2, Math.min(98, pct));
      setPos(pct);
    }, []);

    useEffect(() => {
      const mm = (e) => { if (dragging.current) move(e.touches ? e.touches[0].clientX : e.clientX); };
      const mu = () => { dragging.current = false; };
      window.addEventListener("mousemove", mm);
      window.addEventListener("mouseup", mu);
      window.addEventListener("touchmove", mm, { passive: false });
      window.addEventListener("touchend", mu);
      return () => {
        window.removeEventListener("mousemove", mm);
        window.removeEventListener("mouseup", mu);
        window.removeEventListener("touchmove", mm);
        window.removeEventListener("touchend", mu);
      };
    }, [move]);

    return (
      <div className="reveal" ref={wrapRef}
        onMouseDown={(e) => { dragging.current = true; move(e.clientX); }}
        onTouchStart={(e) => { dragging.current = true; move(e.touches[0].clientX); }}>
        {/* base layer = the recoloured render (swapped). While the AI is still
            working (loading, no aiSrc, no ready canvas engine) show the real
            "before" image full-frame so the user sees their home instantly. */}
        {aiSrc
          ? <img className="stage-img" src={aiSrc} alt={colour.name + " roof render"} style={{ objectPosition: "center" }} />
          : (loading || !engine || !engine.ready)
            ? <img className="stage-img" src={photoSrc} alt="Your home" style={{ objectFit: "cover", objectPosition: "center", width: "100%", height: "100%" }} />
            : <canvas ref={canvasRef} style={{ objectFit: "cover", objectPosition: "center", width: "100%", height: "100%" }} />}
        {/* clipped overlay = the original photo (swapped) */}
        <div className="after-wrap" style={{ clipPath: `inset(0 ${100 - pos}% 0 0)` }}>
          <img src={photoSrc} alt="Your home" style={{ objectFit: "cover", objectPosition: "center", width: "100%", height: "100%" }} />
        </div>
        <span className="lbl before">Before</span>
        <span className="lbl after">{colour.name}</span>
        <div className="handle" style={{ left: `${pos}%` }}>
          <div className="grip">⇄</div>
        </div>
        {loading && (
          <div className="render-loading">
            <div className="spinner"></div>
            <div className="loading-tx">Painting your roof in {colour.name}…</div>
            <div className="loading-bar"><div className="loading-bar-fill" style={{ width: progress + "%" }}></div></div>
          </div>
        )}
      </div>
    );
  }

  window.RoofEngine = RoofEngine;
  window.RoofReveal = RoofReveal;
})();
