/* page-story.jsx — public teaser story page */

function storyClamp01(v) {
  return Math.max(0, Math.min(1, v));
}

function storyPickText(lang, zh, en) {
  return lang === 'zh' ? zh : en;
}

function PageStory({ nav, route }) {
  const { lang } = useLang();
  const slug = (route || '').replace(/^\/story\//, '');
  const work = getWorkBySlug(slug);

  if (!work || !work.hero) {
    const privateSlug = work && (work.privateSlug || work.slug);
    return (
      <div className="story-page">
        <div className="story-page__inner">
          <button className="story-back" onClick={() => nav('/')}>
            <T zh="← 返回作品索引" en="← Back to works" />
          </button>
          <h1 className="story-title">
            <T zh={work ? '故事在会员阅读器中' : '故事未找到'} en={work ? 'Story in member reader' : 'Story not found'} />
          </h1>
          {privateSlug && (
            <a className="story-private-link" href={PRIVATE_APP_URL + '/#/read/story/' + privateSlug}>
              <T zh="进入会员阅读器" en="Open member reader" />
            </a>
          )}
        </div>
      </div>
    );
  }

  const sp = workSpecies(work);

  return (
    <div
      className={'story-page story-page--' + sp.id}
      style={{
        '--story-accent': work.hero.accent,
        '--story-accent-2': work.hero.accent2,
        '--story-ink': sp.id === 'human' ? '#0a0e1a' : '#e8e6e0',
      }}
    >
      <div className="story-page__inner">
        <button className="story-back" onClick={() => nav('/')}>
          <T zh="← 作品索引" en="← Works index" />
        </button>

        <header className="story-hero">
          <div className="story-hero__copy">
            <div className="story-kicker">
              <span>{work.y}</span>
              <span>{lang === 'zh' ? sp.zh : sp.en}</span>
              <span>{work.tag}</span>
            </div>
            <h1 className="story-title">
              {lang === 'zh' ? work.zh : work.en}
              <span>{lang === 'zh' ? work.en : work.zh}</span>
            </h1>
            <p className="story-deck">{lang === 'zh' ? work.tZh : work.tEn}</p>
          </div>
          <StoryHeroArtifact work={work} />
        </header>
      </div>
    </div>
  );
}

function StoryHeroArtifact({ work }) {
  const { lang } = useLang();
  const [m, setM] = React.useState({ x: 0.5, y: 0.45 });
  const [pressed, setPressed] = React.useState(false);
  const [t, setT] = React.useState(0);

  React.useEffect(() => {
    let raf = 0;
    const start = performance.now();
    const loop = now => {
      setT((now - start) / 1000);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, []);

  const move = e => {
    const r = e.currentTarget.getBoundingClientRect();
    setM({
      x: storyClamp01((e.clientX - r.left) / r.width),
      y: storyClamp01((e.clientY - r.top) / r.height),
    });
  };
  const keyMove = e => {
    const step = e.shiftKey ? 0.12 : 0.05;
    if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) return;
    e.preventDefault();
    setM(p => ({
      x: storyClamp01(p.x + (e.key === 'ArrowRight' ? step : e.key === 'ArrowLeft' ? -step : 0)),
      y: storyClamp01(p.y + (e.key === 'ArrowDown' ? step : e.key === 'ArrowUp' ? -step : 0)),
    }));
  };

  const state = { m, t, pressed, lang, work };

  return (
    <div
      className="story-artifact"
      tabIndex="0"
      role="img"
      aria-label={storyPickText(lang, work.zh + ' 互动主视觉', work.en + ' interactive hero')}
      onPointerMove={move}
      onPointerDown={e => {
        setPressed(true);
        if (e.currentTarget.setPointerCapture) e.currentTarget.setPointerCapture(e.pointerId);
        move(e);
      }}
      onPointerUp={() => setPressed(false)}
      onPointerCancel={() => setPressed(false)}
      onPointerLeave={() => setPressed(false)}
      onKeyDown={keyMove}
    >
      {renderEchoFilter(state)}
    </div>
  );
}

function renderStoryFrame({ work, lang, children, label, metric }) {
  const a = work.hero.accent;
  const b = work.hero.accent2;
  return (
    <svg viewBox="0 0 900 520" width="100%" height="100%" preserveAspectRatio="xMidYMid meet">
      <defs>
        <radialGradient id={'story-glow-' + work.slug} cx="50%" cy="50%" r="70%">
          <stop offset="0%" stopColor={a} stopOpacity="0.20" />
          <stop offset="58%" stopColor={b} stopOpacity="0.08" />
          <stop offset="100%" stopColor="#000000" stopOpacity="0" />
        </radialGradient>
        <filter id={'story-soft-' + work.slug}>
          <feGaussianBlur stdDeviation="2.5" />
        </filter>
      </defs>
      <rect x="0" y="0" width="900" height="520" fill="#07090d" />
      <rect x="0" y="0" width="900" height="520" fill={'url(#story-glow-' + work.slug + ')'} />
      {Array.from({ length: 12 }).map((_, i) => (
        <line key={'v' + i} x1={i * 82} x2={i * 82} y1="0" y2="520" stroke={a} strokeOpacity="0.055" />
      ))}
      {Array.from({ length: 7 }).map((_, i) => (
        <line key={'h' + i} x1="0" x2="900" y1={i * 86} y2={i * 86} stroke={a} strokeOpacity="0.045" />
      ))}
      {children}
      <g transform="translate(28 34)">
        <text fill={a} fontFamily="var(--font-mono)" fontSize="12" letterSpacing="2">
          {work.tag} · {storyPickText(lang, work.zh, work.en)}
        </text>
        <text y="24" fill="#e8e6e0" opacity="0.62" fontFamily="var(--font-sans-cjk)" fontSize="12">
          {label}
        </text>
      </g>
      <g transform="translate(28 480)">
        <rect x="0" y="-25" width="844" height="34" fill="#05070acc" stroke={a} strokeOpacity="0.28" />
        <text x="14" y="-4" fill="#e8e6e0" opacity="0.82" fontFamily="var(--font-sans-cjk)" fontSize="13">
          {metric}
        </text>
        <text x="830" y="-4" textAnchor="end" fill={b} fontFamily="var(--font-mono)" fontSize="11" letterSpacing="1">
          {work.y}
        </text>
      </g>
    </svg>
  );
}

function renderEchoFilter(s) {
  const { m, t, pressed, work, lang } = s;
  const rows = 18;
  const cols = 92;
  const cells = [];
  const gate = m.x;
  for (let c = 0; c < cols; c++) {
    for (let r = 0; r < rows; r++) {
      const x = c / cols;
      const y = r / rows;
      const signal = Math.sin(x * 28 - t * 1.1) + Math.sin(x * 47 + y * 7) * 0.45;
      const thunder = Math.exp(-Math.pow((x - 0.72) / 0.055, 2)) * (0.5 + y);
      const v = Math.max(0, signal * 0.25 + thunder + Math.exp(-Math.pow((y - 0.58) / 0.28, 2)) * 0.35);
      const revealed = x < gate;
      if (v < 0.1) continue;
      cells.push(
        <rect
          key={c + '-' + r}
          x={90 + c * 7.4}
          y={112 + (rows - r) * 14}
          width="7.8"
          height="14.4"
          fill={revealed ? `hsl(${190 + r * 3}, 72%, ${22 + v * 38}%)` : '#263644'}
          opacity={revealed ? Math.min(0.96, v) : 0.16}
        />
      );
    }
  }
  const thunderOpen = gate > 0.66;
  return renderStoryFrame({
    work,
    lang,
    label: storyPickText(lang, '720 年噪声滤镜 · 右侧藏有雷声', '720-year noise filter · thunder hidden at the edge'),
    metric: storyPickText(
      lang,
      `滤镜开度 ${(gate * 100).toFixed(0)}% · ${thunderOpen ? '雷声已通过' : '仍被归类为噪声'}`,
      `filter ${(gate * 100).toFixed(0)}% · ${thunderOpen ? 'thunder admitted' : 'still labelled noise'}`,
    ),
    children: (
      <g>
        <rect x="84" y="108" width="700" height="278" fill="#020608" stroke={work.hero.accent} strokeOpacity="0.28" />
        {cells}
        <rect x={90 + gate * 680} y="100" width={pressed ? 6 : 4} height="300" fill={work.hero.accent2} />
        {[0, 1, 2].map(i => (
          <circle
            key={i}
            cx={610}
            cy={260}
            r={20 + ((t * 24 + i * 55) % 150)}
            fill="none"
            stroke={thunderOpen ? work.hero.accent2 : '#789'}
            strokeOpacity={thunderOpen ? 0.32 - i * 0.07 : 0.08}
          />
        ))}
      </g>
    ),
  });
}

Object.assign(window, { PageStory, StoryHeroArtifact });
