// demo.jsx — ThumbForge live demo.
// User drops a video file. We extract N frames at evenly-spaced times,
// then loop through them on a canvas to render a "moving thumbnail".
// A "Use sample" option runs the same pipeline against a procedurally-
// generated sample video so the demo works without uploads.

function ThumbForgeDemo() {
  const [state, setState] = React.useState("idle"); // idle | loading | ready | error
  const [error, setError] = React.useState("");
  const [filename, setFilename] = React.useState("");
  const [duration, setDuration] = React.useState(0);
  const [resolution, setResolution] = React.useState("");
  const [frames, setFrames] = React.useState([]); // ImageBitmap[]
  const [dragOver, setDragOver] = React.useState(false);

  // Tweakable rendering params
  const [frameCount, setFrameCount] = React.useState(8);
  const [fps, setFps] = React.useState(6);
  const [width, setWidth] = React.useState(640);
  const [aspect, setAspect] = React.useState("16:9");

  const canvasRef = React.useRef(null);
  const fileInputRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const startedRef = React.useRef(0);

  // ── Frame extraction ────────────────────────────────────────────
  async function extractFromVideoElement(video, count) {
    const tmp = document.createElement("canvas");
    const aspectRatio = video.videoWidth / video.videoHeight || 16/9;
    const h = Math.round(width / aspectRatio);
    tmp.width = width; tmp.height = h;
    const ctx = tmp.getContext("2d");
    const dur = video.duration;
    const stamps = [];
    for (let i = 0; i < count; i++) {
      stamps.push((dur * (i + 0.5)) / count);
    }
    const out = [];
    for (const t of stamps) {
      await seek(video, t);
      ctx.drawImage(video, 0, 0, tmp.width, tmp.height);
      const bmp = await createImageBitmap(tmp);
      out.push(bmp);
    }
    return out;
  }
  function seek(video, t) {
    return new Promise((resolve) => {
      const onSeeked = () => { video.removeEventListener("seeked", onSeeked); resolve(); };
      video.addEventListener("seeked", onSeeked);
      video.currentTime = Math.min(t, Math.max(0, video.duration - 0.05));
    });
  }

  async function handleFile(file) {
    setState("loading"); setError("");
    setFilename(file.name);
    const url = URL.createObjectURL(file);
    const video = document.createElement("video");
    video.muted = true; video.preload = "auto"; video.src = url; video.playsInline = true;
    try {
      await new Promise((res, rej) => {
        video.addEventListener("loadedmetadata", () => res());
        video.addEventListener("error", () => rej(new Error("Couldn't decode that file. Try MP4, WebM, or MOV.")));
        setTimeout(() => rej(new Error("Loading the video took too long.")), 12000);
      });
      setDuration(video.duration);
      setResolution(`${video.videoWidth}\u00d7${video.videoHeight}`);
      const f = await extractFromVideoElement(video, frameCount);
      setFrames(f);
      setState("ready");
    } catch (e) {
      setError(e.message || "Something went wrong.");
      setState("error");
    } finally {
      URL.revokeObjectURL(url);
    }
  }

  // ── Sample (procedural) ──────────────────────────────────────────
  async function useSample() {
    setState("loading"); setError("");
    setFilename("sample-footage.mp4");
    setDuration(12.0);
    setResolution("1920\u00d71080");
    // Generate frames procedurally so the demo always works.
    const out = [];
    const h = Math.round(width / (16/9));
    for (let i = 0; i < frameCount; i++) {
      const c = document.createElement("canvas");
      c.width = width; c.height = h;
      const ctx = c.getContext("2d");
      const t = i / frameCount;
      // sky gradient that shifts hue across frames
      const hue = 25 + t * 60;
      const g = ctx.createLinearGradient(0, 0, 0, h);
      g.addColorStop(0, `hsl(${hue}, 70%, 18%)`);
      g.addColorStop(1, `hsl(${hue + 30}, 80%, 55%)`);
      ctx.fillStyle = g; ctx.fillRect(0, 0, c.width, h);
      // distant mountains
      ctx.fillStyle = `hsla(${hue + 40}, 30%, 14%, 0.85)`;
      ctx.beginPath();
      ctx.moveTo(0, h * 0.78);
      for (let x = 0; x <= c.width; x += 20) {
        const y = h * 0.78 - Math.sin((x + i*30) / 70) * 18 - Math.sin((x + i*55) / 120) * 28;
        ctx.lineTo(x, y);
      }
      ctx.lineTo(c.width, h); ctx.lineTo(0, h); ctx.closePath(); ctx.fill();
      // foreground hill
      ctx.fillStyle = `hsla(${hue + 60}, 25%, 8%, 0.95)`;
      ctx.beginPath();
      ctx.moveTo(0, h * 0.92);
      for (let x = 0; x <= c.width; x += 16) {
        const y = h * 0.92 - Math.sin((x - i*60) / 50) * 10 - 6;
        ctx.lineTo(x, y);
      }
      ctx.lineTo(c.width, h); ctx.lineTo(0, h); ctx.closePath(); ctx.fill();
      // a moving "subject" (sun/disc) sweeping across
      const sx = c.width * (0.1 + t * 0.8);
      const sy = h * (0.32 - Math.sin(t * Math.PI) * 0.08);
      const grad = ctx.createRadialGradient(sx, sy, 4, sx, sy, 80);
      grad.addColorStop(0, `hsla(${hue - 10}, 95%, 88%, 1)`);
      grad.addColorStop(1, `hsla(${hue - 10}, 95%, 88%, 0)`);
      ctx.fillStyle = grad; ctx.fillRect(0, 0, c.width, h);
      ctx.beginPath(); ctx.arc(sx, sy, 14, 0, Math.PI*2);
      ctx.fillStyle = `hsl(${hue - 10}, 95%, 88%)`; ctx.fill();
      // timecode
      ctx.font = "12px ui-monospace, monospace";
      ctx.fillStyle = "rgba(255,255,255,0.75)";
      const tt = ((duration || 12) * (i + 0.5) / frameCount).toFixed(2);
      ctx.fillText(`t = ${tt}s`, 14, h - 14);
      out.push(await createImageBitmap(c));
    }
    setFrames(out);
    setState("ready");
  }

  // ── Re-render frames if user changes count after a load ─────────
  // (We keep it simple — sample regenerates; uploaded video needs re-import.)
  React.useEffect(() => {
    if (state === "ready" && filename === "sample-footage.mp4") {
      useSample();
    }
    // eslint-disable-next-line
  }, [frameCount]);

  // ── Animation loop ──────────────────────────────────────────────
  React.useEffect(() => {
    if (state !== "ready" || frames.length === 0) return;
    const cvs = canvasRef.current;
    if (!cvs) return;
    const first = frames[0];
    cvs.width = first.width; cvs.height = first.height;
    const ctx = cvs.getContext("2d");
    startedRef.current = performance.now();
    function tick(now) {
      const elapsed = (now - startedRef.current) / 1000;
      const idx = Math.floor(elapsed * fps) % frames.length;
      ctx.drawImage(frames[idx], 0, 0);
      rafRef.current = requestAnimationFrame(tick);
    }
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [state, frames, fps]);

  // ── DnD handlers ────────────────────────────────────────────────
  function onDrop(e) {
    e.preventDefault(); setDragOver(false);
    const file = [...(e.dataTransfer.files || [])].find(f => f.type.startsWith("video/")) || e.dataTransfer.files[0];
    if (file) handleFile(file);
  }

  return (
    <div className="tf-stage">
      {/* LEFT — drop zone / status */}
      <div
        className={"drop" + (dragOver ? " is-over" : "")}
        onDragOver={e => { e.preventDefault(); setDragOver(true); }}
        onDragLeave={() => setDragOver(false)}
        onDrop={onDrop}
      >
        <div className="row between center">
          <span className="kicker">Input</span>
          <span className="kicker" style={{color:"var(--accent-deep)"}}>
            <span style={{display:"inline-block",width:6,height:6,borderRadius:"50%",background:"var(--accent)",marginRight:8,verticalAlign:"middle"}}></span>
            Local · nothing uploaded
          </span>
        </div>

        {state === "idle" && (
          <div className="col gap-4 fill" style={{justifyContent:"center", paddingBlock: 18}}>
            <h3 style={{fontSize:30, lineHeight:1.1}}>Drop a video file here.</h3>
            <p className="muted" style={{maxWidth:"38ch"}}>
              ThumbForge runs on your machine. Drag any <span className="mono" style={{fontSize:14}}>.mp4 / .mov / .webm</span> — we'll extract frames, loop them, and you'll see exactly the moving thumbnail you'd ship.
            </p>
            <div className="row gap-3" style={{marginTop:8, flexWrap:"wrap"}}>
              <button className="btn" onClick={() => fileInputRef.current.click()}>Choose file</button>
              <button className="btn ghost" onClick={useSample}>Use sample footage</button>
            </div>
            <input ref={fileInputRef} type="file" accept="video/*"
              onChange={e => e.target.files[0] && handleFile(e.target.files[0])}/>
          </div>
        )}

        {state === "loading" && (
          <div className="col gap-3 fill" style={{justifyContent:"center"}}>
            <div className="mono" style={{fontSize:12, color:"var(--ink-mute)", letterSpacing:".12em"}}>
              EXTRACTING FRAMES…
            </div>
            <div style={{height:4, background:"var(--rule)", borderRadius:2, overflow:"hidden"}}>
              <div style={{height:"100%", width:"45%", background:"var(--accent)", animation:"tfBar 1.2s ease-in-out infinite"}}/>
            </div>
            <style>{`@keyframes tfBar { 0%{margin-left:-45%} 100%{margin-left:100%} }`}</style>
            <p className="muted" style={{fontSize:14}}>{filename}</p>
          </div>
        )}

        {state === "error" && (
          <div className="col gap-3 fill" style={{justifyContent:"center"}}>
            <div className="mono" style={{fontSize:12, color:"#c0392b", letterSpacing:".12em"}}>ERROR</div>
            <p>{error}</p>
            <div className="row gap-3"><button className="btn ghost" onClick={() => setState("idle")}>Try again</button></div>
          </div>
        )}

        {state === "ready" && (
          <div className="col gap-3 fill">
            <div className="row between" style={{flexWrap:"wrap", gap:8}}>
              <div>
                <div className="mono" style={{fontSize:11, color:"var(--ink-mute)", letterSpacing:".12em"}}>SOURCE</div>
                <div className="serif" style={{fontSize:22, lineHeight:1.2, maxWidth:"30ch", overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}>{filename}</div>
              </div>
              <button className="btn ghost" style={{fontSize:11, padding:"8px 12px"}} onClick={() => { setState("idle"); setFrames([]); }}>Reset</button>
            </div>
            <div className="tf-controls">
              <div className="ctrl">
                <label>Duration</label>
                <div className="v">{duration ? duration.toFixed(1) + "s" : "—"}</div>
              </div>
              <div className="ctrl">
                <label>Source</label>
                <div className="v">{resolution || "—"}</div>
              </div>
              <div className="ctrl">
                <label>Frames <span className="muted">({frameCount})</span></label>
                <input type="range" min="4" max="16" value={frameCount} onChange={e => setFrameCount(+e.target.value)} />
              </div>
              <div className="ctrl">
                <label>Loop fps <span className="muted">({fps})</span></label>
                <input type="range" min="2" max="24" value={fps} onChange={e => setFps(+e.target.value)} />
              </div>
            </div>
          </div>
        )}
      </div>

      {/* RIGHT — preview */}
      <div className="col gap-4" style={{justifyContent:"flex-start"}}>
        <div className="row between center">
          <span className="kicker">Output preview</span>
          <span className="kicker">Animated · {frames.length || 0} fr</span>
        </div>
        <div className="thumbnail-tile">
          {state === "ready" ? (
            <>
              <span className="badge live">● Live</span>
              <canvas ref={canvasRef} />
            </>
          ) : (
            <div style={{position:"absolute", inset:0, display:"grid", placeItems:"center", color:"#777", fontFamily:"var(--ff-mono)", fontSize:12, letterSpacing:".12em"}}>
              {state === "loading" ? "RENDERING…" : "AWAITING SOURCE"}
            </div>
          )}
        </div>
        <div className="col gap-2">
          <div className="row between">
            <span className="mono" style={{fontSize:11, color:"var(--ink-mute)", letterSpacing:".12em"}}>EXPORT</span>
            <span className="mono" style={{fontSize:11, color:"var(--ink-mute)"}}>{width} × {Math.round(width / (16/9))}</span>
          </div>
          <div className="row gap-2">
            {["GIF", "WebP", "MP4", "WebM", "APNG"].map(f => (
              <span key={f} className="mono" style={{fontSize:11, padding:"6px 10px", border:"1px solid var(--rule)", borderRadius:8, color:"var(--ink-soft)"}}>{f}</span>
            ))}
          </div>
          <p className="muted" style={{fontSize:13, marginTop:6}}>
            In the desktop app, hit <span className="mono" style={{fontSize:12}}>⌃E</span> to export the loop in any of these formats with custom encoder presets.
          </p>
        </div>
      </div>
    </div>
  );
}

window.ThumbForgeDemo = ThumbForgeDemo;
