/* global React, ReactDOM, TweaksPanel, useTweaks, TweakSection, TweakSlider, TweakRadio, TweakSelect, TweakToggle, TweakColor, TweakButton */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

const LOGO = "logo.png";

// ============================================================
// SCENES
// ============================================================

function ScenePulse({ glitch }) {
  return (
    <div className="scene">
      <div className="pulse-stack">
        <div className="pulse-rings" />
        <div className="pulse-rings b" />
        <div className="pulse-rings c" />
        <div className="layer r" style={{ backgroundImage: `url(${LOGO})`, animationDuration: `${0.18 / glitch}s` }} />
        <div className="layer g" style={{ backgroundImage: `url(${LOGO})`, animationDuration: `${0.18 / glitch}s` }} />
        <div className="layer b" style={{ backgroundImage: `url(${LOGO})`, animationDuration: `${0.18 / glitch}s` }} />
      </div>
      <div className="pulse-tick">// PULSE — 124 BPM</div>
    </div>
  );
}

const GLITCH_WORDS = ["DECAY", "VOID", "MACHINE", "FLESH", "STATIC", "NEON", "BLOOD", "CRTRL", "DRONE", "PULSE", "FAITH", "CIRCUIT", "CORPSE", "BINARY", "EUTHANOIZE"];

function SceneGlitchText({ glitch }) {
  const [w, setW] = useState(GLITCH_WORDS[0]);
  useEffect(() => {
    let i = 0;
    const id = setInterval(() => {
      i = (i + 1) % GLITCH_WORDS.length;
      setW(GLITCH_WORDS[i]);
    }, 1100);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="scene">
      <div className="glitch-stage">
        <h1 className="glitch-word" data-text={w} style={{ animationDuration: `${0.32 / glitch}s` }}>{w}</h1>
        <div className="glitch-meta">
          <div>SIG <b>▼</b> CORRUPT</div>
          <div>BUF <b>▼</b> 0xFA9E</div>
          <div>RX  <b>▼</b> {Math.floor(Math.random()*9999)}</div>
        </div>
      </div>
    </div>
  );
}

function SceneTunnel({ tunnelOpts }) {
  const {
    rings    = 18,
    cycle    = 5,
    rotSpeed = 4,     // seconds per full 360° rotation
    shape    = "square",
    color    = "white",
    yaw      = true,
  } = tunnelOpts || {};
  const items = [];
  for (let i = 0; i < rings; i++) {
    const flyDelay = -(cycle / rings) * i;
    const colorClass =
      color === "accent" ? "tr-accent" :
      color === "mix"    ? (i % 2 === 0 ? "tr-accent" : "") :
      "";
    items.push(
      <div
        key={i}
        className="tunnel-ring-fly"
        style={{ animation: `tunnelfly ${cycle}s linear infinite`, animationDelay: `${flyDelay}s` }}
      >
        <div
          className={`tunnel-ring tr-${shape} ${colorClass}`}
          style={{ animation: `tunnelrotate ${rotSpeed}s linear infinite` }}
        />
      </div>
    );
  }
  return (
    <div className="scene">
      <div className="tunnel-wrap">
        <div
          className="tunnel-grid"
          style={{ animation: yaw ? "tunnelyaw 14s ease-in-out infinite" : "none" }}
        >
          {items}
        </div>
        <div className="tunnel-cross" />
        <div className="tunnel-logo" style={{ backgroundImage: `url(${LOGO})` }} />
        <style>{`
          @keyframes tunnelfly {
            0%   { transform: translateZ(-2800px); opacity: 0; }
            8%   { opacity: 1; }
            85%  { opacity: 1; }
            100% { transform: translateZ(700px); opacity: 0; }
          }
          @keyframes tunnelrotate {
            from { transform: rotateZ(0deg); }
            to   { transform: rotateZ(360deg); }
          }
          @keyframes tunnelyaw {
            0%, 100% { transform: rotateZ(0deg); }
            50%      { transform: rotateZ(3deg); }
          }
        `}</style>
      </div>
    </div>
  );
}

function SceneStrobe({ glitch }) {
  return (
    <div className="scene">
      <div className="strobe-wrap">
        <div className="strobe-flash" style={{ animationDuration: `${0.42 / glitch}s` }} />
        <div className="strobe-bars" />
        <div className="strobe-logo" style={{ backgroundImage: `url(${LOGO})` }} />
        <div className="strobe-text">▰▰▰ 124 BPM ▰▰▰ KICK ▰▰▰ KICK ▰▰▰</div>
      </div>
    </div>
  );
}

function SceneOscilloscope() {
  const canvasRef = useRef(null);
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    let raf;
    const resize = () => {
      canvas.width = canvas.offsetWidth;
      canvas.height = canvas.offsetHeight;
    };
    resize();
    window.addEventListener("resize", resize);
    let t = 0;
    const draw = () => {
      const w = canvas.width, h = canvas.height;
      ctx.clearRect(0, 0, w, h);
      // Glow trails
      ctx.lineCap = "round";
      ctx.shadowBlur = 18;
      ctx.shadowColor = "oklch(0.78 0.13 220)";
      ctx.strokeStyle = "rgba(190,230,255,0.95)";
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      const cy = h / 2;
      for (let x = 0; x < w; x += 2) {
        const px = x / w;
        const y =
          Math.sin(px * 18 + t * 1.3) * 60 +
          Math.sin(px * 5 + t * 0.6) * 100 * Math.sin(t * 0.4) +
          Math.sin(px * 60 + t * 4) * 12 +
          Math.cos(px * 3 + t * 0.8) * 80;
        if (x === 0) ctx.moveTo(x, cy + y);
        else ctx.lineTo(x, cy + y);
      }
      ctx.stroke();
      // Second wave (inverse, red)
      ctx.shadowColor = "oklch(0.55 0.22 25)";
      ctx.strokeStyle = "rgba(220,90,90,0.6)";
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      for (let x = 0; x < w; x += 2) {
        const px = x / w;
        const y =
          Math.sin(px * 9 - t * 0.9) * 80 +
          Math.sin(px * 25 + t * 2) * 40 +
          Math.cos(px * 4 - t * 0.5) * 60;
        if (x === 0) ctx.moveTo(x, cy + y);
        else ctx.lineTo(x, cy + y);
      }
      ctx.stroke();
      t += 0.04;
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", resize);
    };
  }, []);
  return (
    <div className="scene">
      <div className="osc-wrap">
        <div className="osc-grid" />
        <div className="osc-logo-mask" style={{ backgroundImage: `url(${LOGO})` }} />
        <canvas ref={canvasRef} className="osc-canvas" />
        <div className="osc-label">
          OSC▸▸ INPUT 01
          <span className="freq">124.0 HZ</span>
        </div>
      </div>
    </div>
  );
}

function SceneOccult() {
  // distribute tick marks around an outer ring
  const ticks = 24;
  const tickEls = [];
  for (let i = 0; i < ticks; i++) {
    const ang = (i / ticks) * 360;
    const label = ["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII"][i % 12];
    tickEls.push(
      <div
        key={i}
        className="occult-tick"
        style={{
          transform: `rotate(${ang}deg) translateY(-46vmin)`,
        }}
      >
        <span style={{ transform: `rotate(${-ang}deg) translateX(-50%)` }}>{label}</span>
      </div>
    );
  }
  return (
    <div className="scene">
      <div className="occult-wrap">
        <div className="occult-stack">
          <div className="occult-ring" />
          <div className="occult-ring r2" />
          <div className="occult-ring r3" />
          <div className="occult-ring r4" />
          <div className="occult-ring r5" />
          <div className="occult-cross" />
          <div className="occult-center" style={{ backgroundImage: `url(${LOGO})` }} />
          {tickEls}
        </div>
        <div className="occult-text">— I N — N O M I N E — M A C H I N A —</div>
      </div>
    </div>
  );
}

function SceneRorschach() {
  const [phrase, setPhrase] = useState("INDUSTRIAL");
  const phrases = ["INDUSTRIAL", "ELECTRO·GOTH", "EBM", "CLUB·HELL", "DEUS·EX", "NOIR"];
  useEffect(() => {
    let i = 0;
    const id = setInterval(() => {
      i = (i + 1) % phrases.length;
      setPhrase(phrases[i]);
    }, 2400);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="scene">
      <div className="ror-wrap">
        <div className="ror-half left" />
        <div className="ror-half right" />
        <div className="ror-tint" />
        <div className="ror-text">
          {phrase}
          <small>// RORSCHACH ▸ LIVE</small>
        </div>
      </div>
    </div>
  );
}

function SceneData() {
  // Random bar widths memoized so the stripe is stable
  const barcode = useMemo(() => {
    const arr = [];
    for (let i = 0; i < 80; i++) {
      arr.push(2 + Math.floor(Math.random() * 9));
    }
    return arr;
  }, []);
  return (
    <div className="scene">
      <div className="data-wrap">
        <div className="data-row top">
          <div className="word">CLUBHELL</div>
          <div className="word alt">▶ INDUSTRIAL</div>
          <div className="word outl">EBM</div>
          <div className="word">CLUBHELL</div>
          <div className="word alt">▶ ELECTRO</div>
          <div className="word outl">GOTH</div>
          <div className="word">CLUBHELL</div>
          <div className="word alt">▶ INDUSTRIAL</div>
          <div className="word outl">EBM</div>
          <div className="word">CLUBHELL</div>
          <div className="word alt">▶ ELECTRO</div>
          <div className="word outl">GOTH</div>
        </div>
        <div className="data-bar">
          <div className="blk">
            <div className="square"></div>
            <div className="square red"></div>
            <div className="square"></div>
            <span>FREQ·124</span>
          </div>
          <div className="barcode">
            {barcode.map((w, i) => (
              <i key={i} style={{ width: `${w}px`, opacity: i % 5 === 0 ? 0.85 : 1 }} />
            ))}
          </div>
          <div className="blk">
            <span>SIG·OK</span>
            <div className="square red"></div>
            <div className="square"></div>
            <div className="square"></div>
          </div>
        </div>
        <div className="data-row bot">
          <div className="word outl">EUTHANOIZE</div>
          <div className="word">▮ STATIC</div>
          <div className="word alt">FLESH</div>
          <div className="word outl">VOID</div>
          <div className="word">▮ NEON</div>
          <div className="word alt">DRONE</div>
          <div className="word outl">EUTHANOIZE</div>
          <div className="word">▮ STATIC</div>
          <div className="word alt">FLESH</div>
          <div className="word outl">VOID</div>
          <div className="word">▮ NEON</div>
          <div className="word alt">DRONE</div>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// SCENE LIST + PLAYER
// ============================================================

const SCENES = [
  { id: "pulse",  name: "PULSE",        Comp: ScenePulse,        dur: 16 },
  { id: "glitch", name: "GLITCH TEXT",  Comp: SceneGlitchText,   dur: 18 },
  { id: "tunnel", name: "TUNNEL",       Comp: SceneTunnel,       dur: 20 },
  { id: "strobe", name: "STROBE",       Comp: SceneStrobe,       dur: 14 },
  { id: "osc",    name: "OSCILLOSCOPE", Comp: SceneOscilloscope, dur: 18 },
  { id: "occult", name: "OCCULT GRID",  Comp: SceneOccult,       dur: 22 },
  { id: "ror",    name: "RORSCHACH",    Comp: SceneRorschach,    dur: 16 },
  { id: "data",   name: "DATA STREAM",  Comp: SceneData,         dur: 16 },
];

function App() {
  const [t, setTweak] = useTweaks(window.TWEAK_DEFAULTS);
  const [idx, setIdx] = useState(0);
  const wipeRef    = useRef(null);
  const wsRef      = useRef(null);
  const pausedRef  = useRef(t.paused); // updated every render, guards the timer callback
  // Refs so WS closure always has latest setters without reconnecting
  const setTweakRef = useRef(setTweak);
  const setIdxRef   = useRef(setIdx);
  setTweakRef.current = setTweak;
  setIdxRef.current   = setIdx;
  pausedRef.current   = t.paused;

  // Filter scenes based on which are enabled
  const activeScenes = useMemo(
    () => SCENES.filter(s => t.enabled[s.id] !== false),
    [t.enabled]
  );

  // safety
  const safeIdx = activeScenes.length === 0 ? 0 : idx % activeScenes.length;
  const current = activeScenes[safeIdx] || SCENES[0];

  // auto-advance
  useEffect(() => {
    if (activeScenes.length === 0) return;
    if (t.paused) return;
    const dur = (current.dur * 1000) / t.speed;
    const id = setTimeout(() => {
      if (pausedRef.current) return; // guard: pause pressed while timer was running
      if (wipeRef.current) {
        wipeRef.current.classList.remove("flash");
        void wipeRef.current.offsetWidth;
        wipeRef.current.classList.add("flash");
      }
      setIdx(i => (i + 1) % activeScenes.length);
    }, dur);
    return () => clearTimeout(id);
  }, [safeIdx, activeScenes.length, t.speed, t.paused, current.dur]);

  // HUD update
  useEffect(() => {
    const el = document.getElementById("hud-scn");
    if (el) el.textContent = String(safeIdx + 1).padStart(2, "0");
  }, [safeIdx]);

  // Apply palette to CSS variables
  useEffect(() => {
    const r = document.documentElement;
    const palettes = {
      blood: { accent: "oklch(0.55 0.22 25)",  cool: "oklch(0.78 0.13 220)" },
      cyan:  { accent: "oklch(0.78 0.13 220)", cool: "oklch(0.55 0.22 25)"  },
      mono:  { accent: "#f4ebe4",              cool: "#a8a39e"              },
      acid:  { accent: "oklch(0.78 0.20 130)", cool: "oklch(0.65 0.20 320)" },
    };
    const p = palettes[t.palette] || palettes.blood;
    r.style.setProperty("--accent", p.accent);
    r.style.setProperty("--cool", p.cool);
  }, [t.palette]);

  // Random vignette flicker
  useEffect(() => {
    const fl = document.getElementById("flicker");
    if (!fl) return;
    let raf, last = 0;
    const tick = (now) => {
      if (now - last > 500 + Math.random() * 4000) {
        last = now;
        if (Math.random() < 0.5 * t.flicker) {
          fl.style.opacity = String(0.7 + Math.random() * 0.3);
          fl.style.background = Math.random() < 0.7 ? "#000" : "#fff";
          setTimeout(() => { fl.style.opacity = "0"; }, 60 + Math.random() * 80);
        }
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [t.flicker]);

  // Attach wipeRef to the DOM element (it lives outside the React root)
  useEffect(() => {
    wipeRef.current = document.getElementById('wipe');
  }, []);

  // WebSocket — connect once, reconnect on close
  useEffect(() => {
    function triggerFlash() {
      const wipe = document.getElementById('wipe');
      if (wipe) {
        wipe.classList.remove('flash');
        void wipe.offsetWidth;
        wipe.classList.add('flash');
      }
      const overlay = document.getElementById('euthanoize-overlay');
      if (overlay) {
        overlay.classList.remove('show');
        void overlay.offsetWidth;
        overlay.classList.add('show');
      }
    }

    function connect() {
      let ws;
      const url = window.WS_URL || `ws://${location.host}`;
      try { ws = new WebSocket(url); }
      catch(e) { setTimeout(connect, 3000); return; }
      wsRef.current = ws;

      ws.onmessage = (evt) => {
        try {
          const msg = JSON.parse(evt.data);
          if (msg.type === 'SET_TWEAK') setTweakRef.current(msg.key, msg.value);
          if (msg.type === 'JUMP_SCENE') setIdxRef.current(msg.index);
          if (msg.type === 'FLASH')      triggerFlash();
        } catch(e) {}
      };

      ws.onclose = () => setTimeout(connect, 2000);
    }
    connect();
    return () => { if (wsRef.current) wsRef.current.close(); };
  }, []);

  // Broadcast state whenever scene or tweaks change
  useEffect(() => {
    const ws = wsRef.current;
    if (!ws || ws.readyState !== WebSocket.OPEN) return;
    ws.send(JSON.stringify({
      type: 'STATE',
      state: {
        currentScene: safeIdx,
        sceneId:      current.id,
        sceneName:    current.name,
        totalScenes:  activeScenes.length,
        tweaks:       t,
        scenes:       activeScenes.map(s => ({ id: s.id, name: s.name })),
      }
    }));
  }, [safeIdx, t, activeScenes.length]);

  // Manual scene jump
  useEffect(() => {
    const handler = (e) => {
      if (e.key === "ArrowRight" || e.key === " ") setIdx(i => (i + 1) % Math.max(activeScenes.length, 1));
      if (e.key === "ArrowLeft") setIdx(i => (i - 1 + Math.max(activeScenes.length,1)) % Math.max(activeScenes.length, 1));
      if (e.key === "f" || e.key === "F") {
        if (!document.fullscreenElement) document.documentElement.requestFullscreen?.();
        else document.exitFullscreen?.();
      }
      if (e.key === "h" || e.key === "H") {
        document.body.classList.toggle("show-cursor");
        const hud = document.getElementById("hud");
        if (hud) hud.style.display = hud.style.display === "none" ? "" : "none";
      }
      if (e.key === "t" || e.key === "T") {
        // Toggle Tweaks panel via host protocol
        const open = document.querySelector(".twk-panel");
        if (open) {
          window.postMessage({ type: "__deactivate_edit_mode" }, "*");
          window.parent.postMessage({ type: "__edit_mode_dismissed" }, "*");
        } else {
          window.postMessage({ type: "__activate_edit_mode" }, "*");
        }
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [activeScenes.length]);

  const Comp = current.Comp;

  return (
    <>
      <Comp glitch={t.glitch} tunnelOpts={t.tunnel} />
      <TweaksPanel title="Tweaks">
        <TweakSection title="Playback">
          <TweakSlider label="Speed" value={t.speed} min={0.3} max={3} step={0.1} onChange={v => setTweak("speed", v)} />
          <TweakToggle label="Pause auto-advance" value={t.paused} onChange={v => setTweak("paused", v)} />
          <TweakSlider label="Glitch intensity" value={t.glitch} min={0.5} max={3} step={0.1} onChange={v => setTweak("glitch", v)} />
          <TweakSlider label="Flicker" value={t.flicker} min={0} max={2} step={0.1} onChange={v => setTweak("flicker", v)} />
        </TweakSection>

        <TweakSection title="Palette">
          <TweakRadio
            label="Color"
            value={t.palette}
            options={[
              { label: "Blood",  value: "blood" },
              { label: "Cyan",   value: "cyan" },
              { label: "Mono",   value: "mono" },
              { label: "Acid",   value: "acid" },
            ]}
            onChange={v => setTweak("palette", v)}
          />
        </TweakSection>

        <TweakSection title="Tunnel scene">
          <TweakSlider label="Rings" value={t.tunnel.rings} min={6} max={40} step={1} onChange={v => setTweak("tunnel", { ...t.tunnel, rings: v })} />
          <TweakSlider label="Speed" value={t.tunnel.cycle} min={1.5} max={12} step={0.25} onChange={v => setTweak("tunnel", { ...t.tunnel, cycle: v })} />
          <TweakSlider label="Rot speed (s)" value={t.tunnel.rotSpeed} min={0.5} max={12} step={0.25} onChange={v => setTweak("tunnel", { ...t.tunnel, rotSpeed: v })} />
          <TweakSelect
            label="Shape"
            value={t.tunnel.shape}
            options={["square", "diamond", "circle", "hexagon", "cross"]}
            onChange={v => setTweak("tunnel", { ...t.tunnel, shape: v })}
          />
          <TweakSelect
            label="Color"
            value={t.tunnel.color}
            options={["white", "accent", "mix"]}
            onChange={v => setTweak("tunnel", { ...t.tunnel, color: v })}
          />
          <TweakToggle label="Yaw sway" value={t.tunnel.yaw} onChange={v => setTweak("tunnel", { ...t.tunnel, yaw: v })} />
        </TweakSection>

        <TweakSection title="Scenes">
          {SCENES.map((s, i) => (
            <TweakToggle
              key={s.id}
              label={`${String(i+1).padStart(2,"0")} · ${s.name}`}
              value={t.enabled[s.id] !== false}
              onChange={v => setTweak("enabled", { ...t.enabled, [s.id]: v })}
            />
          ))}
        </TweakSection>

        <TweakSection title="Jump">
          {activeScenes.map((s, i) => (
            <TweakButton
              key={s.id}
              label={`▶ ${s.name}`}
              onClick={() => setIdx(i)}
            />
          ))}
        </TweakSection>

        <TweakSection title="Help">
          <div style={{ fontFamily: "monospace", fontSize: 11, opacity: 0.7, lineHeight: 1.7 }}>
            T · toggle this panel<br/>
            ← / → · jump scene<br/>
            SPACE · next<br/>
            F · fullscreen<br/>
            H · hide HUD
          </div>
        </TweakSection>
      </TweaksPanel>
    </>
  );
}

// Tweak defaults — written to disk via __edit_mode_set_keys
window.TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "speed": 1,
  "glitch": 1,
  "flicker": 1,
  "paused": false,
  "palette": "blood",
  "enabled": {
    "pulse":  true,
    "glitch": true,
    "tunnel": true,
    "strobe": true,
    "osc":    true,
    "occult": true,
    "ror":    true,
    "data":   true
  },
  "tunnel": {
    "rings": 18,
    "cycle": 5,
    "rotSpeed": 4,
    "shape": "square",
    "color": "white",
    "yaw": true
  }
}/*EDITMODE-END*/;

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
