// Main app for Sam's surprise birthday site.
// Phases: intro -> cooking -> cards -> finale

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

const DEFAULTS = /*EDITMODE-BEGIN*/{
  "honoree": "Sam",
  "theme": "tramway",
  "intensity": "medium",
  "showEdgeHint": true
}/*EDITMODE-END*/;

const THEMES = {
  // Sam's favourite: Pantone 3415 C — HK Tram Green
  tramway: {
    paper: '#c2dfc9',
    paperEdge: '#96c0a2',
    ink: '#092315',
    accent: '#006B3D',
    gold: '#c9863a',
    handwrite: '#092315',
    cardFace: '#e4f2e8',
  },
  warm: {
    paper: '#f3e6cc',
    paperEdge: '#e6d4ad',
    ink: '#2a1b15',
    accent: '#b04a3a',
    gold: '#c89236',
    handwrite: '#3a2a1a',
    cardFace: '#fdf6e3',
  },
  takeout: {
    paper: '#1f0a0a',
    paperEdge: '#3a0f0f',
    ink: '#fdf6e3',
    accent: '#e8a830',
    gold: '#f5b942',
    handwrite: '#fdf6e3',
    cardFace: '#fdf6e3',
  },
  cinematic: {
    paper: '#1a1310',
    paperEdge: '#2a1f18',
    ink: '#f0e4cf',
    accent: '#d97a3a',
    gold: '#e8b85a',
    handwrite: '#f0e4cf',
    cardFace: '#f5ecdc',
  },
};

function App() {
  const [tweaks, setTweak] = useTweaks(DEFAULTS);
  const [phase, setPhase] = useState('intro'); // intro | cooking | cards | dish | finale
  const [opened, setOpened] = useState(() => {
    try {
      const parsed = JSON.parse(localStorage.getItem('sam-opened') || '[]');
      return parsed.filter(id => INGREDIENTS.some(i => i.id === id));
    } catch { return []; }
  });
  const [activeCard, setActiveCard] = useState(null);

  const theme = THEMES[tweaks.theme] || THEMES.warm;
  const allOpened = opened.length === INGREDIENTS.length;

  useEffect(() => {
    localStorage.setItem('sam-opened', JSON.stringify(opened));
  }, [opened]);

  const prefersReducedMotion = () =>
    typeof window !== 'undefined' &&
    window.matchMedia &&
    window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  useEffect(() => {
    const cardId = new URLSearchParams(window.location.search).get('card');
    if (cardId && INGREDIENTS.some(i => i.id === cardId)) {
      setPhase('cards');
      setActiveCard(cardId);
      setOpened(prev => prev.includes(cardId) ? prev : [...prev, cardId]);
    }
  }, []);

  const startCooking = () => {
    if (prefersReducedMotion()) {
      setPhase('cards');
      return;
    }
    setPhase('cooking');
    // Auto-advance to cards after toss animation completes
    const dur = tweaks.intensity === 'subtle' ? 3200 : tweaks.intensity === 'dramatic' ? 4800 : 3800;
    setTimeout(() => setPhase('cards'), dur);
  };

  const openCard = (id) => {
    setActiveCard(id);
    if (!opened.includes(id)) setOpened([...opened, id]);
  };

  const reset = () => {
    setOpened([]);
    setActiveCard(null);
    setPhase('intro');
    localStorage.removeItem('sam-opened');
  };

  const plateUp = () => {
    localStorage.removeItem('sam-opened');
    if (prefersReducedMotion()) {
      setPhase('finale');
      return;
    }
    setPhase('dish');
    setTimeout(() => setPhase('finale'), 2400);
  };

  const markAllRead = () => {
    setOpened(INGREDIENTS.map(i => i.id));
    setActiveCard(null);
    plateUp();
  };

  // Apply theme CSS vars at root
  useEffect(() => {
    const r = document.documentElement;
    Object.entries(theme).forEach(([k, v]) => r.style.setProperty(`--th-${k}`, v));
  }, [theme]);

  return (
    <div className="app" data-theme={tweaks.theme}>
      <PaperBackground />

      {phase === 'intro' && (
        <IntroScreen
          honoree={tweaks.honoree}
          onStart={startCooking}
          hasProgress={opened.length > 0 && opened.length < INGREDIENTS.length}
          onReset={reset}
        />
      )}

      {(phase === 'cooking' || phase === 'cards' || phase === 'dish') && (
        <KitchenScene
          phase={phase}
          intensity={tweaks.intensity}
          opened={opened}
          onCardClick={openCard}
          showEdgeHint={tweaks.showEdgeHint}
          allOpened={allOpened}
          onPlateUp={plateUp}
        />
      )}

      {phase === 'finale' && (
        <FinaleScreen honoree={tweaks.honoree} onReset={reset} theme={theme} />
      )}

      {activeCard && (
        <NoteModal
          ingredient={INGREDIENTS.find(i => i.id === activeCard)}
          note={NOTES[activeCard]}
          onClose={() => setActiveCard(null)}
        />
      )}

      <TweaksPanel title="Tweaks">
        <TweakSection label="Honoree">
          <TweakText label="Name" value={tweaks.honoree} onChange={v => setTweak('honoree', v)} />
        </TweakSection>
        <TweakSection label="Vibe">
          <TweakRadio
            label="Theme"
            value={tweaks.theme}
            options={['tramway', 'warm', 'takeout', 'cinematic']}
            onChange={v => setTweak('theme', v)}
          />
          <TweakRadio
            label="Toss intensity"
            value={tweaks.intensity}
            options={['subtle', 'medium', 'dramatic']}
            onChange={v => setTweak('intensity', v)}
          />
          <TweakToggle label="Progress pill" value={tweaks.showEdgeHint} onChange={v => setTweak('showEdgeHint', v)} />
        </TweakSection>
        <TweakSection label="Progress">
          <div style={{ fontSize: 12, opacity: 0.75, marginBottom: 8, padding: '0 14px' }}>
            {opened.length} / {INGREDIENTS.length} cards opened
          </div>
          <div style={{ padding: '0 14px' }}>
            <TweakButton label="Reset everything" onClick={reset} secondary />
          </div>
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

function PaperBackground() {
  // SVG noise + faint kitchen tile pattern
  return (
    <div className="paper-bg" aria-hidden="true">
      <svg className="paper-noise" xmlns="http://www.w3.org/2000/svg">
        <filter id="n"><feTurbulence baseFrequency="0.9" numOctaves="2" stitchTiles="stitch" /><feColorMatrix values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.06 0" /></filter>
        <rect width="100%" height="100%" filter="url(#n)" />
      </svg>
    </div>
  );
}

function IntroScreen({ honoree, onStart, hasProgress, onReset }) {
  return (
    <div className="intro">
      <div className="intro-card">
        <div className="intro-eyebrow">a surprise for —</div>
        <h1 className="intro-title">{honoree}</h1>
        <div className="intro-flourish">
          <svg viewBox="0 0 200 20" width="220" height="22">
            <path d="M2 10 C 30 4, 60 16, 100 10 C 140 4, 170 16, 198 10" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" />
            <circle cx="100" cy="10" r="2" fill="currentColor" />
          </svg>
        </div>
        <p className="intro-sub">Your friends made you something. <br />Step up to the wok.</p>
        <button className="btn-primary" onClick={onStart}>
          <span>Start cooking</span>
          <svg viewBox="0 0 24 24" width="18" height="18" style={{ marginLeft: 8 }}>
            <path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </button>
        {hasProgress && (
          <button className="btn-ghost" onClick={onReset}>Start over from the beginning</button>
        )}
      </div>
      <div className="intro-corner intro-corner-tl">
        <SteamWisps small />
      </div>
      <div className="intro-corner intro-corner-br">
        <SteamWisps small />
      </div>
    </div>
  );
}

function SteamWisps({ small }) {
  const w = small ? 80 : 200;
  return (
    <svg viewBox="0 0 80 120" width={w} height={w * 1.5} className="steam-wisps">
      <path d="M20 110 C 14 90, 30 75, 24 55 C 18 35, 34 22, 28 6" stroke="currentColor" strokeWidth="3" fill="none" strokeLinecap="round" opacity="0.55" />
      <path d="M40 110 C 46 86, 32 70, 42 50 C 50 32, 38 18, 46 4" stroke="currentColor" strokeWidth="3" fill="none" strokeLinecap="round" opacity="0.5" />
      <path d="M58 110 C 52 92, 66 78, 60 58 C 54 40, 68 26, 62 8" stroke="currentColor" strokeWidth="3" fill="none" strokeLinecap="round" opacity="0.4" />
    </svg>
  );
}

function KitchenScene({ phase, intensity, opened, onCardClick, showEdgeHint, allOpened, onPlateUp }) {
  const cooking = phase === 'cooking';
  const cards = phase === 'cards';
  const plating = phase === 'dish';

  const intensityMult = intensity === 'subtle' ? 0.55 : intensity === 'dramatic' ? 1.3 : 1;
  const tossDur = intensity === 'subtle' ? 1400 : intensity === 'dramatic' ? 1700 : 1500;
  const tossStagger = intensity === 'subtle' ? 80 : intensity === 'dramatic' ? 140 : 110;

  return (
    <div className={`scene scene-${phase}`}>
      <div className="counter">
        <div className="counter-edge" />
      </div>

      <div className={`wok-wrap ${cooking ? 'wok-cooking' : ''} ${cards ? 'wok-resting' : ''} ${plating ? 'wok-plating' : ''}`}>
        {(cooking || plating) && (
          <div className="wok-steam">
            <SteamWisps />
          </div>
        )}
        <Wok flames={cooking || plating} size={420} />
      </div>

      {/* Ingredients — tossed during cooking phase, settle to cards in cards phase */}
      <div className="stage">
        {INGREDIENTS.map((ing, i) => {
          const isOpen = opened.includes(ing.id);
          let className = 'ing';
          let style = {};

          if (cooking) {
            className += ' ing-tossing';
            // Spread evenly across the stage (vmin so it scales), alternating two
            // rows so neighbours never land on top of each other.
            const landX = -32 + (i / (INGREDIENTS.length - 1)) * 64;
            style = {
              '--dx': `${ing.tossDx * intensityMult}px`,
              '--dy': `${ing.tossDy * intensityMult}px`,
              '--land-x': `${landX}vmin`,
              '--land-y': `${i % 2 ? 18 : 6}vmin`,
              '--rot': `${ing.tossR}deg`,
              '--toss-dur': `${tossDur}ms`,
              '--toss-delay': `${i * tossStagger + 200}ms`,
            };
          } else if (cards) {
            className += ' ing-card';
            if (isOpen) className += ' ing-opened';
            style = {
              '--col': ing.cardX,
              '--row': ing.cardY,
              '--card-delay': `${i * 60}ms`,
              '--hue': ing.hue,
            };
          } else if (plating) {
            className += ' ing-returning';
            style = {
              '--return-delay': `${i * 50}ms`,
            };
          }

          return (
            <div key={ing.id} className={className} style={style}>
              {cards ? (
                <button className="card" onClick={() => onCardClick(ing.id)} aria-label={`Open ${ing.name} card`}>
                  <div className="card-tape card-tape-1" aria-hidden="true" />
                  <div className="card-tape card-tape-2" aria-hidden="true" />
                  <div className="card-ing">
                    <ing.Icon size={110} />
                  </div>
                  <div className="card-meta">
                    <div className="card-name">{ing.name}</div>
                    <div className="card-friend">from {ing.friend}</div>
                  </div>
                  {isOpen && (
                    <div className="card-opened-mark" aria-label="opened">
                      <svg viewBox="0 0 24 24" width="16" height="16"><path d="M4 12l5 5L20 6" stroke="currentColor" strokeWidth="3" fill="none" strokeLinecap="round" strokeLinejoin="round" /></svg>
                    </div>
                  )}
                </button>
              ) : (
                <div className="ing-illo"><ing.Icon size={100} /></div>
              )}
            </div>
          );
        })}
      </div>

      {cards && (
        <header className="cards-header">
          <h2 className="cards-title">Pick something off the counter.</h2>
          <p className="cards-sub">Ten ingredients — one note in each. Open them in any order.</p>
        </header>
      )}

      {/* Skip-to-pot removed: use "Plate it up" after opening all cards */}

      {cards && showEdgeHint && (
        <div className="progress-pill">
          <span className="progress-count">{opened.length} / {INGREDIENTS.length}</span>
          <span className="progress-label">opened</span>
        </div>
      )}

      {cards && allOpened && (
        <button className="btn-plate" onClick={onPlateUp}>
          <span>Plate it up</span>
          <svg viewBox="0 0 24 24" width="18" height="18" style={{ marginLeft: 10 }}>
            <path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" strokeWidth="2.2" fill="none" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </button>
      )}
    </div>
  );
}

function NoteModal({ ingredient, note, onClose }) {
  const modalRef = useRef(null);
  const closeRef = useRef(null);
  const returnFocusRef = useRef(null);

  // Capture the previously-focused element so we can restore focus on close.
  useEffect(() => {
    returnFocusRef.current = document.activeElement;
    // Focus the close button on mount so screen readers announce the dialog.
    if (closeRef.current) closeRef.current.focus();
    return () => {
      const target = returnFocusRef.current;
      if (target && typeof target.focus === 'function') target.focus();
    };
  }, []);

  // Keyboard: Escape closes, Tab traps focus inside.
  useEffect(() => {
    const h = (e) => {
      if (e.key === 'Escape') { onClose(); return; }
      if (e.key !== 'Tab' || !modalRef.current) return;
      const focusables = modalRef.current.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
      if (focusables.length === 0) return;
      const first = focusables[0];
      const last = focusables[focusables.length - 1];
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [onClose]);

  return (
    <div className="modal-back" onClick={onClose}>
      <div
        className="modal"
        ref={modalRef}
        role="dialog"
        aria-modal="true"
        aria-label={`Note from ${ingredient.friend}`}
        onClick={e => e.stopPropagation()}
      >
        <button className="modal-close" onClick={onClose} aria-label="Close" ref={closeRef}>
          <svg viewBox="0 0 24 24" width="20" height="20"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" /></svg>
        </button>

        <div className="note">
          <div className="note-pin" />
          <div className="note-food">
            <div className="note-food-illo" aria-hidden="true">
              <ingredient.Icon size={68} />
            </div>
            <div className="note-food-meta">
              <div className="note-food-name">{ingredient.name}</div>
              <div className="note-food-friend">from {ingredient.friend}</div>
            </div>
          </div>
          <p className="note-body">{note.body}</p>
          <div className="note-sign">{note.sign}</div>
        </div>
      </div>
    </div>
  );
}

function Confetti({ theme }) {
  const pieces = useMemo(() => {
    const palette = [theme.gold, theme.accent, theme.cardFace, theme.paperEdge, theme.handwrite];
    const count = 48;
    return Array.from({ length: count }, (_, i) => {
      const left = Math.random() * 100;
      const dx = (Math.random() - 0.5) * 280;
      const rot = (Math.random() - 0.5) * 1440;
      const delay = Math.random() * 400;
      const duration = 2200 + Math.random() * 900;
      const color = palette[i % palette.length];
      const shape = i % 3 === 0 ? 'cp-disc' : 'cp-strip';
      return { left, dx, rot, delay, duration, color, shape, i };
    });
  }, [theme]);
  return (
    <div className="confetti" aria-hidden="true">
      {pieces.map(p => (
        <span
          key={p.i}
          className={`confetti-piece ${p.shape}`}
          style={{
            left: `${p.left}%`,
            background: p.color,
            animationDelay: `${p.delay}ms`,
            animationDuration: `${p.duration}ms`,
            '--cx': `${p.dx}px`,
            '--cr': `${p.rot}deg`,
          }}
        />
      ))}
    </div>
  );
}

function FinaleScreen({ honoree, onReset, theme }) {
  const finaleRef = useRef(null);
  const [saving, setSaving] = useState(false);

  const saveKeepsake = async () => {
    if (!window.htmlToImage || !finaleRef.current) return;
    setSaving(true);
    try {
      const dataUrl = await window.htmlToImage.toPng(finaleRef.current, {
        pixelRatio: 2,
        cacheBust: true,
        filter: (node) => !node.classList || !node.classList.contains('keepsake-hide'),
        backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--th-paper').trim() || '#f3e6cc',
      });
      const a = document.createElement('a');
      a.href = dataUrl;
      a.download = `happy-birthday-${honoree.toLowerCase()}.png`;
      a.click();
    } catch (err) {
      console.error('Keepsake export failed:', err);
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="finale" ref={finaleRef}>
      <Confetti theme={theme} />
      <div className="finale-steam">
        <SteamWisps />
      </div>
      <div className="finale-dish">
        <Dish size={520} />
      </div>
      <div className="finale-text">
        <div className="finale-eyebrow">— and now, the dish —</div>
        <h1 className="finale-title">Happy Birthday {honoree}!!</h1>
        <p className="finale-sub">
          Every ingredient was someone in your life. You're the one who puts it all in the wok.
          We love you. Eat well. See you soon.
        </p>
        <div className="finale-actions keepsake-hide">
          <button className="btn-ghost" onClick={saveKeepsake} disabled={saving}>
            {saving ? 'Saving…' : 'Save the dish ↓'}
          </button>
          <button className="btn-ghost finale-restart" onClick={onReset}>Cook it again ↺</button>
        </div>
      </div>
    </div>
  );
}

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