// ============ COMMON COMPONENTS + SFX ============

const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ---------- AUDIO ENGINE (WebAudio chiptune sfx) ----------
let audioCtx = null;
function getAudio() {
  if (!audioCtx) {
    try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
    catch (e) { return null; }
  }
  return audioCtx;
}

function beep({ freq = 440, dur = 0.08, type = "square", vol = 0.07, slide = 0 }) {
  const ctx = getAudio();
  if (!ctx) return;
  const now = ctx.currentTime;
  const o = ctx.createOscillator();
  const g = ctx.createGain();
  o.type = type;
  o.frequency.setValueAtTime(freq, now);
  if (slide) o.frequency.exponentialRampToValueAtTime(Math.max(40, freq + slide), now + dur);
  g.gain.setValueAtTime(vol, now);
  g.gain.exponentialRampToValueAtTime(0.0001, now + dur);
  o.connect(g).connect(ctx.destination);
  o.start(now);
  o.stop(now + dur + 0.02);
}

const SFX = {
  hover: () => beep({ freq: 880, dur: 0.04, vol: 0.04 }),
  select: () => { beep({ freq: 660, dur: 0.05 }); setTimeout(() => beep({ freq: 990, dur: 0.06 }), 50); },
  back: () => beep({ freq: 440, dur: 0.06, slide: -200 }),
  ok: () => { beep({ freq: 700, dur: 0.06 }); setTimeout(() => beep({ freq: 1050, dur: 0.08 }), 60); },
  bad: () => beep({ freq: 200, dur: 0.18, type: "sawtooth", vol: 0.05 }),
  tick: () => beep({ freq: 1320, dur: 0.03, vol: 0.04 }),
  trophy: () => {
    [523, 659, 784, 1046].forEach((f, i) => setTimeout(() => beep({ freq: f, dur: 0.12, type: "triangle", vol: 0.06 }), i * 80));
  },
  power: () => {
    beep({ freq: 220, dur: 0.3, slide: 600, type: "square", vol: 0.05 });
  }
};

// ---------- SOUND CONTEXT ----------
const SoundCtx = React.createContext({ on: true, play: () => {} });

function SoundProvider({ on, children }) {
  const onRef = useRef(on);
  useEffect(() => { onRef.current = on; }, [on]);
  const play = useCallback((name) => { if (onRef.current && SFX[name]) SFX[name](); }, []);
  return <SoundCtx.Provider value={{ on, play }}>{children}</SoundCtx.Provider>;
}
const useSound = () => React.useContext(SoundCtx);

// ---------- BACKGROUND ANIMATIONS ----------
function Stars() {
  const stars = useMemo(() => Array.from({ length: 60 }, () => ({
    x: Math.random() * 100, y: Math.random() * 100,
    delay: Math.random() * 3, size: Math.random() * 2 + 1
  })), []);
  return (
    <div className="bg-stars">
      {stars.map((s, i) => (
        <div key={i} className="star" style={{
          left: `${s.x}%`, top: `${s.y}%`,
          width: `${s.size}px`, height: `${s.size}px`,
          animationDelay: `${s.delay}s`
        }}></div>
      ))}
    </div>
  );
}

function DvdBouncer() {
  const [pos, setPos] = useState({ x: 80, y: 100, dx: 1.6, dy: 1.1, hue: 0 });
  useEffect(() => {
    let raf;
    const step = () => {
      setPos((p) => {
        let { x, y, dx, dy, hue } = p;
        x += dx; y += dy;
        const w = window.innerWidth, h = window.innerHeight;
        let bounced = false;
        if (x < 0) { x = 0; dx = -dx; bounced = true; }
        if (x > w - 240) { x = w - 240; dx = -dx; bounced = true; }
        if (y < 60) { y = 60; dy = -dy; bounced = true; }
        if (y > h - 80) { y = h - 80; dy = -dy; bounced = true; }
        if (bounced) hue = (hue + 67) % 360;
        return { x, y, dx, dy, hue };
      });
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, []);
  const colors = ["#ffd84d", "#ff4d6d", "#5fd3e0", "#a78bfa", "#7af09a"];
  const color = colors[Math.floor(pos.hue / 67) % colors.length];
  return (
    <div className="dvd-bouncer">
      <div className="logo" style={{ left: pos.x, top: pos.y, color }}>WISH<sup style={{fontSize:".4em"}}>™</sup></div>
    </div>
  );
}

function Background({ kind }) {
  if (kind === "stars") return <Stars />;
  if (kind === "dvd") return <DvdBouncer />;
  return null;
}

// ---------- SCANLINES ----------
function Scanlines({ on }) {
  if (!on) return null;
  return <div className="scanlines"><div className="moving-line"></div></div>;
}

// ---------- LOADING ----------
function LoadingInterstitial({ label = "Loading" }) {
  return (
    <div className="loading">
      <div className="loading-disc"></div>
      <div className="loading-text">{label}</div>
    </div>
  );
}

// ---------- ACHIEVEMENT TOAST ----------
function AchievementToast({ trophy, onDone }) {
  useEffect(() => {
    const t = setTimeout(onDone, 4000);
    return () => clearTimeout(t);
  }, [onDone]);
  if (!trophy) return null;
  return (
    <div className="achievement" key={trophy.id + trophy.t}>
      <div className="big-icon">{trophy.icon}</div>
      <div>
        <div className="at">★ Achievement Unlocked</div>
        <div className="an">{trophy.name}</div>
        <div className="ad">{trophy.desc}</div>
      </div>
    </div>
  );
}

// ---------- CHROME BAR ----------
function ChromeBar({ onHome, onAdd, onStats, current, ownerMode }) {
  const [now, setNow] = useState(new Date());
  const { play } = useSound();
  useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(t);
  }, []);
  const fmt = (d) => {
    const hh = String(d.getHours()).padStart(2, "0");
    const mm = String(d.getMinutes()).padStart(2, "0");
    const ss = String(d.getSeconds()).padStart(2, "0");
    return `${hh}:${mm}:${ss}`;
  };
  const wrap = (fn, sfx = "select") => () => { play(sfx); fn(); };
  return (
    <div className="chrome-bar">
      <div className="chrome-logo">WISHTRAK<span className="tm">™</span></div>
      <div className="chrome-clock">▶ REC {fmt(now)}</div>
      <div className="chrome-nav">
        <button className="chrome-btn" onClick={wrap(onHome, "back")} onMouseEnter={() => play("hover")}>◀ MENU</button>
        {ownerMode && <button className="chrome-btn" onClick={wrap(onAdd)} onMouseEnter={() => play("hover")}>＋ NEW</button>}
        <button className="chrome-btn" onClick={wrap(onStats)} onMouseEnter={() => play("hover")}>◇ STATS</button>
        <span className={`chrome-mode ${ownerMode?"owner":"visitor"}`}>{ownerMode?"✦ OWNER":"🔒 VISITOR"}</span>
      </div>
    </div>
  );
}

// ---------- CATEGORIES ----------
const CATEGORIES = {
  product:    { id: "product",    label: "PRODUCT",    short: "PRD", icon: "P", glyph: "□" },
  experience: { id: "experience", label: "EXPERIENCE", short: "EXP", icon: "E", glyph: "✈" },
  goal:       { id: "goal",       label: "GOAL",       short: "GOL", icon: "G", glyph: "▲" },
  media:      { id: "media",      label: "MEDIA",      short: "MED", icon: "M", glyph: "▶" },
  skill:      { id: "skill",      label: "SKILL",      short: "SKL", icon: "S", glyph: "♪" },
};
const CAT_LIST = Object.values(CATEGORIES);

// progress mode by category
function progressForItem(item) {
  const c = item.category;
  if (c === "product") return "savings";   // $X / $Y
  if (c === "experience") return "savings";
  if (c === "goal") return "checklist";
  if (c === "media") return "manual";
  if (c === "skill") return "hours";
  return "manual";
}

function pctOf(item) {
  const m = progressForItem(item);
  if (m === "savings") {
    const t = Number(item.target || 0);
    if (!t) return 0;
    return Math.min(100, Math.round((Number(item.saved || 0) / t) * 100));
  }
  if (m === "checklist") {
    const total = (item.checklist || []).length || 1;
    const done = (item.checklist || []).filter(s => s.done).length;
    return Math.round((done / total) * 100);
  }
  if (m === "hours") {
    const t = Number(item.targetHours || 0);
    if (!t) return 0;
    return Math.min(100, Math.round((Number(item.hours || 0) / t) * 100));
  }
  return Math.min(100, Math.round(Number(item.pct || 0)));
}

// ============ WISHTRAK PAYMENT API ============
// WishPay™ — powered by Stripe under the hood.
// The brand, design, and UX are WishPay's. Stripe is the engine.
//
// Initialise Stripe.js once using the publishable key from config.
// Falls back to mock mode if the key isn't set (local dev / demo).
const _stripeKey = (window.WISHTRAK_CONFIG || {}).stripePublishableKey || "";
window.stripeInstance = (_stripeKey && window.Stripe) ? window.Stripe(_stripeKey) : null;

const WishPay = {
  // True when real Stripe credentials are present.
  get live() { return !!window.stripeInstance; },

  // charge({ amount, name, message, wishId, paymentMethodId })
  //   paymentMethodId — a Stripe pm_xxx ID tokenised BEFORE phase change (live mode)
  //   In mock mode this is ignored.
  //   Returns a txn object compatible with the mock shape.
  async charge({ amount, name, message, wishId, paymentMethodId }) {
    if (Number(amount) <= 0)    throw new Error("Amount must be positive.");
    if (Number(amount) > 10000) throw new Error("Amount exceeds maximum.");

    // ── LIVE MODE (real Stripe) ──────────────────────────────
    if (this.live) {
      // 1. Ask the serverless function for a PaymentIntent client_secret.
      const res = await fetch("/api/create-payment-intent", {
        method:  "POST",
        headers: { "Content-Type": "application/json" },
        body:    JSON.stringify({ amount, wishId }),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body.error || "Could not initialise payment.");
      }
      const { clientSecret } = await res.json();

      // 2. Confirm the payment using the already-tokenised payment method ID.
      //    We use the ID (not the Element) because the Element may have been
      //    unmounted when the form transitioned to the "processing" phase.
      const { error, paymentIntent } = await window.stripeInstance.confirmCardPayment(
        clientSecret,
        { payment_method: paymentMethodId }
      );

      if (error) throw new Error(error.message || "Card declined.");

      return {
        txnId:   paymentIntent.id,
        amount:  paymentIntent.amount / 100,
        name:    (name    || "Anonymous").trim() || "Anonymous",
        message: (message || "").trim().slice(0, 140),
        wishId,
        ts:      Date.now(),
        method:  "stripe",
      };
    }

    // ── MOCK / DEMO MODE (no Stripe key configured) ──────────
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (/decline|fail/i.test(name || "") && Math.random() < 0.5)
          return reject(new Error("Card declined by issuer."));
        resolve({
          txnId:   "TXN-" + Math.random().toString(36).slice(2, 8).toUpperCase() + "-" + Date.now().toString(36).toUpperCase().slice(-4),
          amount:  Number(amount),
          name:    (name    || "Anonymous").trim() || "Anonymous",
          message: (message || "").trim().slice(0, 140),
          wishId,
          ts:      Date.now(),
          method:  "mock_visa_4242",
        });
      }, 900 + Math.random() * 700);
    });
  },
};

// ============ LOCK BADGE ============
function LockBadge({ ownerMode, onClick }) {
  const { play } = useSound();
  return (
    <div className={`lock-badge ${ownerMode ? "unlocked" : ""}`}
      onMouseEnter={() => play("hover")}
      onClick={() => { play(ownerMode ? "back" : "select"); onClick(); }}
      title={ownerMode ? "Owner mode — click to lock" : "Visitor mode — click to sign in"}>
      <span className="lk-ico">{ownerMode ? "✦" : "🔒"}</span>
      <span className="lk-tx">{ownerMode ? "OWNER" : "VISITOR"}</span>
    </div>
  );
}

// expose
Object.assign(window, {
  SoundProvider, useSound,
  Background, Scanlines, LoadingInterstitial,
  AchievementToast, ChromeBar, LockBadge,
  CATEGORIES, CAT_LIST, progressForItem, pctOf,
  WishPay
});
