/* global React, ReactDOM, Portal, Gallery, Themes, Journal, Prints, About, Feedback, Atmosphere */
const { useState, useEffect, useRef } = React;

const TWEAKS = /*EDITMODE-BEGIN*/{
  "grainOpacity": 0.22,
  "whispersOn": true,
  "soundOn": false,
  "soundVolume": 0.35,
  "showAiThought": true,
  "palette": "violet-night"
} /*EDITMODE-END*/;

// Audio engine — Indian tanpura-like plucked drone + breath + occasional bowl
// Plus a softer "namaste" bell exposed on window.__darshanBell, triggered on chamber enter.
function useAmbientSound(on, volume) {
  const ctxRef = useRef(null);
  const masterRef = useRef(null);
  const reverbRef = useRef(null);
  const stopRef = useRef(() => {});

  // ── set up / tear down on `on` flip ─────────────────────────────
  useEffect(() => {
    if (!on) {
      const ctx = ctxRef.current;
      const m = masterRef.current;
      if (ctx && m) {
        m.gain.cancelScheduledValues(ctx.currentTime);
        m.gain.linearRampToValueAtTime(0.0001, ctx.currentTime + 1.4);
        // suspend after fade so plucks stop scheduling
        setTimeout(() => { try { ctx.suspend(); } catch (e) {} }, 1600);
      }
      return;
    }

    // First-time setup
    if (!ctxRef.current) {
      const AC = window.AudioContext || window.webkitAudioContext;
      if (!AC) return;
      const ctx = new AC();
      ctxRef.current = ctx;

      // ── master ──
      const master = ctx.createGain();
      master.gain.value = 0;
      master.connect(ctx.destination);
      masterRef.current = master;

      // ── simple feedback-delay "reverb" send ──
      const reverbIn = ctx.createGain();
      reverbIn.gain.value = 1;
      const delays = [0.087, 0.119, 0.151, 0.181].map((t, i) => {
        const d = ctx.createDelay(4);
        d.delayTime.value = t;
        const fb = ctx.createGain();
        fb.gain.value = 0.58 - i * 0.03;
        const lp = ctx.createBiquadFilter();
        lp.type = "lowpass";
        lp.frequency.value = 2200;
        d.connect(lp).connect(fb).connect(d);
        reverbIn.connect(d);
        d.connect(master);
        return d;
      });
      const reverbWet = ctx.createGain();
      reverbWet.gain.value = 0.65;
      delays.forEach((d) => d.connect(reverbWet));
      reverbWet.connect(master);
      reverbRef.current = reverbIn;

      // ── tanpura pluck (additive sine with harmonics, soft attack, long decay) ──
      // Traditional tanpura cycle: Pa - Sa - Sa - Sa (5th, then root x3)
      // Tuned to C (Sa = C2/C3, Pa = G2)
      const SA_LOW = 65.41;   // C2
      const PA = 98.00;       // G2
      const SA_MID = 130.81;  // C3
      const cycle = [PA, SA_MID, SA_MID, SA_LOW]; // pa, sa, sa, sa-low
      let cIdx = 0;

      const pluck = (freq, when, accent = 1.0) => {
        const harmonics = [
          { mult: 1,    gain: 0.50 },
          { mult: 2,    gain: 0.22 },
          { mult: 3,    gain: 0.12 },
          { mult: 4,    gain: 0.06 },
          { mult: 5.02, gain: 0.04 },  // slight detune for sympathetic shimmer
          { mult: 6,    gain: 0.025 }
        ];
        // Envelope — soft attack, long exponential decay
        const env = ctx.createGain();
        env.gain.setValueAtTime(0.0001, when);
        env.gain.linearRampToValueAtTime(0.28 * accent, when + 0.06);
        env.gain.exponentialRampToValueAtTime(0.0001, when + 7.0);
        env.connect(master);
        env.connect(reverbIn);

        // Soft body filter — keeps it warm
        const body = ctx.createBiquadFilter();
        body.type = "lowpass";
        body.frequency.setValueAtTime(2400, when);
        body.frequency.exponentialRampToValueAtTime(900, when + 6.5);
        body.Q.value = 0.4;
        body.connect(env);

        harmonics.forEach((h, i) => {
          const o = ctx.createOscillator();
          o.type = "sine";
          o.frequency.value = freq * h.mult;
          const g = ctx.createGain();
          g.gain.value = h.gain * (0.92 + Math.random() * 0.16);
          // gentle pitch wobble (tanpura "jhanjhar" character)
          const lfo = ctx.createOscillator();
          lfo.frequency.value = 0.18 + Math.random() * 0.22;
          const lfoG = ctx.createGain();
          lfoG.gain.value = 0.4 + i * 0.15;
          lfo.connect(lfoG).connect(o.frequency);
          o.connect(g).connect(body);
          o.start(when);
          o.stop(when + 7.3);
          lfo.start(when);
          lfo.stop(when + 7.3);
        });
      };

      // ── breath / distant wind ──
      const bufLen = 3 * ctx.sampleRate;
      const buf = ctx.createBuffer(1, bufLen, ctx.sampleRate);
      const ch = buf.getChannelData(0);
      for (let i = 0; i < bufLen; i++) ch[i] = (Math.random() * 2 - 1) * 0.6;
      const noise = ctx.createBufferSource();
      noise.buffer = buf;
      noise.loop = true;
      const noiseLP = ctx.createBiquadFilter();
      noiseLP.type = "lowpass";
      noiseLP.frequency.value = 340;
      const noiseHP = ctx.createBiquadFilter();
      noiseHP.type = "highpass";
      noiseHP.frequency.value = 80;
      const noiseGain = ctx.createGain();
      noiseGain.gain.value = 0.03;
      const windLfo = ctx.createOscillator();
      windLfo.frequency.value = 0.06;
      const windLfoG = ctx.createGain();
      windLfoG.gain.value = 140;
      windLfo.connect(windLfoG).connect(noiseLP.frequency);
      // slow breath on amplitude
      const breathLfo = ctx.createOscillator();
      breathLfo.frequency.value = 0.09;
      const breathLfoG = ctx.createGain();
      breathLfoG.gain.value = 0.015;
      breathLfo.connect(breathLfoG).connect(noiseGain.gain);
      noise.connect(noiseHP).connect(noiseLP).connect(noiseGain).connect(master);
      noise.start();
      windLfo.start();
      breathLfo.start();

      // ── tanpura pluck scheduler ──
      const PLUCK_INTERVAL = 2.6; // seconds between plucks
      let nextTime = ctx.currentTime + 0.6;
      const scheduler = setInterval(() => {
        if (!ctxRef.current) return;
        const lookahead = ctx.currentTime + 2.0;
        while (nextTime < lookahead) {
          const freq = cycle[cIdx % cycle.length];
          // every 4th pluck is the deep Sa — slightly louder
          const accent = (cIdx % cycle.length === 3) ? 1.15 : 1.0;
          pluck(freq, nextTime, accent);
          cIdx++;
          // tiny rubato — slight humanisation
          nextTime += PLUCK_INTERVAL + (Math.random() - 0.5) * 0.18;
        }
      }, 250);

      // ── singing bowl (rare, deep, long-decay) ──
      const bowl = (when) => {
        const root = 220 + Math.random() * 40; // wandering A3-ish
        // Inharmonic bell ratios (Risset bell)
        const partials = [
          { mult: 1.00, gain: 0.55, decay: 9 },
          { mult: 2.00, gain: 0.30, decay: 7 },
          { mult: 2.76, gain: 0.20, decay: 6 },
          { mult: 4.07, gain: 0.10, decay: 5 },
          { mult: 5.42, gain: 0.05, decay: 4.5 }
        ];
        const mainEnv = ctx.createGain();
        mainEnv.gain.value = 1;
        mainEnv.connect(master);
        mainEnv.connect(reverbIn);
        partials.forEach((p) => {
          const o = ctx.createOscillator();
          o.type = "sine";
          o.frequency.value = root * p.mult;
          const g = ctx.createGain();
          g.gain.setValueAtTime(0.0001, when);
          g.gain.linearRampToValueAtTime(p.gain * 0.16, when + 0.005);
          g.gain.exponentialRampToValueAtTime(0.0001, when + p.decay);
          o.connect(g).connect(mainEnv);
          o.start(when);
          o.stop(when + p.decay + 0.2);
        });
      };
      const scheduleBowl = () => {
        if (!ctxRef.current) return;
        bowl(ctx.currentTime + 0.2);
      };
      // first bowl ~30s after on, then every 60–110s
      const bowlT1 = setTimeout(function loop() {
        scheduleBowl();
        const next = 60000 + Math.random() * 50000;
        bowlIv = setTimeout(loop, next);
      }, 30000);
      let bowlIv = bowlT1;

      // ── expose namaste bell for chamber-enter ──
      window.__darshanBell = () => {
        const c = ctxRef.current;
        const m = masterRef.current;
        if (!c || !m) return;
        if (c.state === "suspended") return; // only when sound is on
        const t = c.currentTime + 0.02;
        // brighter, soft bell — fundamental ~ E5
        const root = 659.25;
        const partials = [
          { mult: 1.00, gain: 0.40, decay: 4.5 },
          { mult: 2.00, gain: 0.18, decay: 3.2 },
          { mult: 2.76, gain: 0.12, decay: 2.6 },
          { mult: 4.10, gain: 0.06, decay: 2.0 }
        ];
        const send = c.createGain();
        send.gain.value = 1;
        send.connect(m);
        if (reverbRef.current) send.connect(reverbRef.current);
        partials.forEach((p) => {
          const o = c.createOscillator();
          o.type = "sine";
          o.frequency.value = root * p.mult;
          const g = c.createGain();
          g.gain.setValueAtTime(0.0001, t);
          g.gain.linearRampToValueAtTime(p.gain * 0.10, t + 0.004);
          g.gain.exponentialRampToValueAtTime(0.0001, t + p.decay);
          o.connect(g).connect(send);
          o.start(t);
          o.stop(t + p.decay + 0.2);
        });
      };

      stopRef.current = () => {
        clearInterval(scheduler);
        clearTimeout(bowlIv);
      };

      ctx.resume().
      then(() => {
        master.gain.cancelScheduledValues(ctx.currentTime);
        master.gain.linearRampToValueAtTime(volume, ctx.currentTime + 2.4);
      }).
      catch(() => {});
    } else {
      // Already initialised — just resume + fade in
      const ctx = ctxRef.current;
      const m = masterRef.current;
      ctx.resume().
      then(() => {
        m.gain.cancelScheduledValues(ctx.currentTime);
        m.gain.linearRampToValueAtTime(volume, ctx.currentTime + 1.6);
      }).
      catch(() => {});
    }
    // NOTE: no teardown returned — we keep the engine alive across toggles.
  }, [on]);

  // ── separate effect for volume changes (no rebuild, no flicker) ──
  useEffect(() => {
    if (!on) return;
    const ctx = ctxRef.current;
    const m = masterRef.current;
    if (!ctx || !m) return;
    m.gain.cancelScheduledValues(ctx.currentTime);
    m.gain.linearRampToValueAtTime(volume, ctx.currentTime + 0.35);
  }, [volume, on]);

  // ── tear down on unmount only ──
  useEffect(() => {
    return () => {
      stopRef.current();
      try { ctxRef.current && ctxRef.current.close(); } catch (e) {}
      try { delete window.__darshanBell; } catch (e) {}
    };
  }, []);
}

function Veil() {
  const ref = useRef(null);
  const [done, setDone] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf;
    const HOLD = 1600,FADE = 1600;
    const start = performance.now();
    const tick = (now) => {
      const t = now - start;
      if (t < HOLD) {
        el.style.opacity = "1";
      } else {
        const p = Math.min(1, (t - HOLD) / FADE);
        el.style.opacity = String(1 - p);
        if (p >= 1) {setDone(true);return;}
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);
  if (done) return null;
  return (
    <div className="veil" ref={ref} style={{ opacity: 1 }}>
      <div className="veil-inner">
        <div className="veil-sig">
          <span className="veil-mark">रा॰</span>
          <span className="veil-name">Rahul Malviya</span>
        </div>
        <div className="veil-tag">photographer · since MMXX</div>
        <div className="veil-progress"></div>
      </div>
    </div>);

}

function App() {
  const t = window.useTweaks ? window.useTweaks(TWEAKS) : [TWEAKS, () => {}];
  const tweaks = t[0];const setTweak = t[1];

  const [scene, setScene] = useState("portal"); // portal | gallery | themes | prints | journal | about | feedback
  const [galleryStartId, setGalleryStartId] = useState(null);

  useAmbientSound(tweaks.soundOn, tweaks.soundVolume);

  // Deep-linking via URL hash: #gallery, #darshan/03, #themes, etc.
  useEffect(() => {
    const apply = () => {
      const h = location.hash.replace(/^#/, "");
      if (!h) return;
      if (h.startsWith("darshan/")) {
        const id = h.split("/")[1];
        setGalleryStartId(id);
        setScene("gallery");
      } else if (["gallery", "themes", "prints", "journal", "about", "feedback"].includes(h)) {
        setScene(h);
      }
    };
    apply();
    window.addEventListener("hashchange", apply);
    return () => window.removeEventListener("hashchange", apply);
  }, []);

  const go = (key) => {
    setScene(key);
    if (key === "portal") location.hash = "";else
    location.hash = key;
    try { window.__darshanBell && window.__darshanBell(); } catch (e) {}
  };
  const goHome = () => {
    setGalleryStartId(null);
    setScene("portal");
    location.hash = "";
    try { window.__darshanBell && window.__darshanBell(); } catch (e) {}
  };
  const back = goHome;
  const openPhoto = (id) => {setGalleryStartId(id);setScene("gallery");location.hash = `darshan/${id}`;try { window.__darshanBell && window.__darshanBell(); } catch (e) {}};

  const isGallery = scene === "gallery";
  const isPortal = scene === "portal";

  const SCENE_LABEL = {
    portal: "AT THE THRESHOLD",
    gallery: "DARSHAN · IN PASSAGE",
    themes: "CHAMBER · विषय / THEMES",
    prints: "CHAMBER · मुद्रण / PRINTS",
    journal: "CHAMBER · लेखन / JOURNAL",
    about: "CHAMBER · परिचय / ABOUT",
    feedback: "CHAMBER · प्रतिक्रिया / FEEDBACK"
  };

  return (
    <>
      <Veil />
      <Atmosphere
        grainOpacity={tweaks.grainOpacity}
        whispersOn={tweaks.whispersOn}
        sound={tweaks.soundOn} />
      

      {/* Top chrome */}
          {!isGallery && (
        <div className="chrome">
          <div className="left">
            <button className="brand-home sig" onClick={goHome} data-cursor title="return to the threshold">
              <span className="dev">रा॰</span>
              <span className="sig-name">Rahul Malviya</span>
            </button>
          </div>
          <div className="right">
            <span className="scene-pill"><span className="dot"></span>{SCENE_LABEL[scene] || "·"}</span>
            <button
              className={`sound-toggle ${tweaks.soundOn ? "" : "off"}`}
              onClick={() => setTweak("soundOn", !tweaks.soundOn)}
              data-cursor
              title={tweaks.soundOn ? "silence the drone" : "let the drone enter"}>
              
              <span className="wave"><i></i><i></i><i></i><i></i></span>
              <span>{tweaks.soundOn ? "tanpura · on" : "tanpura · off"}</span>
            </button>
          </div>
        </div>
      )}

      {/* Scenes */}
      <div className={`scene ${isPortal ? "active" : ""}`}>
        <Portal onEnter={go} />
      </div>
      <div className={`scene ${isGallery ? "active" : ""}`}>
        {isGallery && (
          <Gallery
            onExit={back}
            initialId={galleryStartId}
            soundOn={tweaks.soundOn}
            onToggleSound={() => setTweak("soundOn", !tweaks.soundOn)}
          />
        )}
      </div>
      <div className={`scene ${scene === "themes" ? "active" : ""}`}>
        {scene === "themes" && <Themes onBack={back} onOpenPhoto={openPhoto} />}
      </div>
      <div className={`scene ${scene === "journal" ? "active" : ""}`}>
        {scene === "journal" && <Journal onBack={back} onOpenPhoto={openPhoto} />}
      </div>
      <div className={`scene ${scene === "prints" ? "active" : ""}`}>
        {scene === "prints" && <Prints onBack={back} />}
      </div>
      <div className={`scene ${scene === "about" ? "active" : ""}`}>
        {scene === "about" && <About onBack={back} />}
      </div>
      <div className={`scene ${scene === "feedback" ? "active" : ""}`}>
        {scene === "feedback" && <Feedback onBack={back} />}
      </div>

      {/* corner hint */}
      <div className="hint-chip">
        {isPortal && <span>choose a threshold · or press <kbd>G</kbd> to enter the gallery</span>}
        {isGallery && <span>← / → to walk · <kbd>F</kbd> hold · <kbd>?</kbd> map · <kbd>esc</kbd> return</span>}
        {!isPortal && !isGallery && <span><kbd>esc</kbd> to return to the threshold</span>}
      </div>

      {/* tweaks panel */}
      {window.TweaksPanel &&
      <window.TweaksPanel title="Tweaks">
          <window.TweakSection title="Atmosphere">
            <window.TweakSlider
            label="Film grain"
            value={tweaks.grainOpacity}
            min={0} max={0.5} step={0.01}
            onChange={(v) => setTweak("grainOpacity", v)} />
          
            <window.TweakToggle
            label="Drifting whispers"
            value={tweaks.whispersOn}
            onChange={(v) => setTweak("whispersOn", v)} />
          
            <window.TweakToggle
            label="Ambient drone (sound)"
            value={tweaks.soundOn}
            onChange={(v) => setTweak("soundOn", v)} />
          
            <window.TweakSlider
            label="Drone volume"
            value={tweaks.soundVolume}
            min={0} max={1} step={0.05}
            onChange={(v) => setTweak("soundVolume", v)} />
          
          </window.TweakSection>
          <window.TweakSection title="AI Presence">
            <window.TweakToggle
            label="AI thoughts visible"
            value={tweaks.showAiThought}
            onChange={(v) => setTweak("showAiThought", v)} />
          
          </window.TweakSection>
        </window.TweaksPanel>
      }
    </>);

}

// hot-key: G to jump to gallery from anywhere; Esc returns
window.addEventListener("keydown", (e) => {
  if (e.target && /input|textarea/i.test(e.target.tagName)) return;
  if (e.key === "g" || e.key === "G") {
    if (!location.hash.startsWith("#gallery") && !location.hash.startsWith("#darshan/")) {
      location.hash = "gallery";
    }
  }
});

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