/* parts.jsx — kinetisches Motiv + Phone-Mockup + Reveal-Hook */

const { useRef, useEffect, useState } = React;

/* ---------------------------------------------------------
   useReveal — IntersectionObserver-basierte Scroll-Reveals
   --------------------------------------------------------- */
function useReveal() {
  useEffect(() => {
    const motionOff = document.documentElement.getAttribute('data-motion') === 'off';
    const els = Array.from(document.querySelectorAll('[data-reveal]'));
    if (motionOff || window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
      els.forEach(el => el.classList.add('is-in'));
      return;
    }
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) { e.target.classList.add('is-in'); io.unobserve(e.target); }
      });
    }, { threshold: 0.16, rootMargin: '0px 0px -8% 0px' });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  });
}

/* ---------------------------------------------------------
   Kinetic — "Alles wird eins": verstreute Fragmente, die
   sanft zu einem Punkt verschmelzen und wieder ausbrechen.
   --------------------------------------------------------- */
function Kinetic({ accent = '#00e5b0', motion = true, density = 80 }) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let raf, w, h, dpr;
    const N = density;
    const pts = [];
    let running = motion && !window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    function resize() {
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      const r = canvas.parentElement.getBoundingClientRect();
      w = r.width; h = r.height;
      canvas.width = w * dpr; canvas.height = h * dpr;
      canvas.style.width = w + 'px'; canvas.style.height = h + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    resize();

    for (let i = 0; i < N; i++) {
      const a = Math.random() * Math.PI * 2;
      const rad = 120 + Math.random() * Math.max(w, h) * 0.55;
      pts.push({
        ang: a, rad,
        baseRad: rad,
        size: 0.8 + Math.random() * 2.2,
        spd: 0.0006 + Math.random() * 0.0014,
        phase: Math.random() * Math.PI * 2,
      });
    }

    let t = 0;
    function frame() {
      t += 0.006;
      ctx.clearRect(0, 0, w, h);
      const cx = w * 0.72, cy = h * 0.42;
      // pulse 0..1: fragments converge (1) then disperse (0)
      const conv = (Math.sin(t * 0.5) * 0.5 + 0.5);
      for (const p of pts) {
        p.ang += p.spd;
        const r = p.baseRad * (0.18 + (1 - conv) * 0.9) + Math.sin(t + p.phase) * 8;
        const x = cx + Math.cos(p.ang) * r;
        const y = cy + Math.sin(p.ang) * r * 0.82;
        const alpha = 0.10 + conv * 0.22 + (1 - r / (p.baseRad + 1)) * 0.1;
        ctx.beginPath();
        ctx.arc(x, y, p.size, 0, Math.PI * 2);
        ctx.fillStyle = accent;
        ctx.globalAlpha = Math.max(0.04, Math.min(0.5, alpha));
        ctx.fill();
      }
      // the "one" — central glowing core
      ctx.globalAlpha = 0.5 + conv * 0.4;
      const coreR = 3 + conv * 5;
      const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, coreR * 5);
      g.addColorStop(0, accent);
      g.addColorStop(1, 'transparent');
      ctx.fillStyle = g;
      ctx.beginPath(); ctx.arc(cx, cy, coreR * 5, 0, Math.PI * 2); ctx.fill();
      ctx.globalAlpha = 1;
      if (running) raf = requestAnimationFrame(frame);
    }
    // draw one static frame even when motion off
    if (running) frame(); else { t = Math.PI; frame(); }

    const onResize = () => resize();
    window.addEventListener('resize', onResize);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', onResize); };
  }, [accent, motion, density]);

  return <canvas ref={ref} className="kinetic" aria-hidden="true" />;
}

/* ---------------------------------------------------------
   Mini-Icons (schlichte Glyphen, kein SVG-Overkill)
   --------------------------------------------------------- */
function Glyph({ name, size = 15 }) {
  const s = size;
  const stroke = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round' };
  const paths = {
    id:     <><rect x="3" y="5" width="18" height="14" rx="2.5" {...stroke}/><circle cx="8.5" cy="11" r="2.2" {...stroke}/><path d="M13 9h5M13 13h5M5.5 15.5c.6-1.6 4.2-1.6 6 0" {...stroke}/></>,
    gov:    <><path d="M4 9l8-4 8 4M5 9v8M19 9v8M9 9v8M15 9v8M3 21h18" {...stroke}/></>,
    pay:    <><rect x="3" y="6" width="18" height="12" rx="2.5" {...stroke}/><path d="M3 10h18" {...stroke}/></>,
    mobility:<><circle cx="7" cy="17" r="2.2" {...stroke}/><circle cx="17" cy="17" r="2.2" {...stroke}/><path d="M5 17h-1l1-6h11l3 6h-1M9 17h6" {...stroke}/></>,
    health: <><path d="M12 21s-7-4.3-7-9.5A4.5 4.5 0 0 1 12 8a4.5 4.5 0 0 1 7 3.5C19 16.7 12 21 12 21z" {...stroke}/></>,
    doc:    <><path d="M6 3h8l4 4v14H6zM14 3v4h4M9 12h6M9 16h6" {...stroke}/></>,
  };
  return <svg width={s} height={s} viewBox="0 0 24 24" aria-hidden="true">{paths[name] || paths.id}</svg>;
}

/* ---------------------------------------------------------
   Phone — DE-eins App-Home (Platzhalter-UI)
   --------------------------------------------------------- */
function Phone() {
  const [active, setActive] = useState(null);
  const tiles = [
    { k: 'id', n: 'Identität', c: 'ID · eAusweis' },
    { k: 'gov', n: 'Behörden', c: 'Ämter · Anträge' },
    { k: 'pay', n: 'Bezahlen', c: 'Pay · Wallet' },
    { k: 'mobility', n: 'Mobilität', c: 'Ticket · Parken' },
    { k: 'health', n: 'Gesundheit', c: 'Karte · Rezept' },
    { k: 'doc', n: 'Dokumente', c: 'Papierkram' },
  ];
  return (
    <div className="phone-wrap">
      <div className="phone-glow" aria-hidden="true"></div>
      <div className="phone" role="img" aria-label="DE-eins App, Platzhalter-Vorschau">
        <div className="phone__notch" aria-hidden="true"></div>
        <div className="phone__screen">
          <div className="phone__status">
            <span>9:41</span>
            <span style={{ display: 'flex', gap: 5, alignItems: 'center', opacity: 0.7 }}>
              <span>5G</span><span>100%</span>
            </span>
          </div>
          <div className="phone__body">
            <div className="phone__greet">Guten Morgen, <b>Lena</b> 👋</div>

            <div className="phone__id-card">
              <div className="pic">
                <div className="chip" aria-hidden="true"></div>
                <div className="flag" aria-hidden="true">
                  <i style={{ background: '#111' }}></i>
                  <i style={{ background: 'var(--flag-red)' }}></i>
                  <i style={{ background: 'var(--flag-gold)' }}></i>
                </div>
              </div>
              <div className="nm">Personalausweis</div>
              <div className="mt">DE-EINS · VERIFIZIERT ·  ⌁</div>
            </div>

            <div className="phone__grid">
              {tiles.map(t => (
                <div
                  key={t.k}
                  className={"phone__tile" + (active === t.k ? " is-active" : "")}
                  onClick={() => setActive(active === t.k ? null : t.k)}
                >
                  <div className="ti"><Glyph name={t.k} size={15} /></div>
                  <div>
                    <div className="tl">{t.n}</div>
                    <div className="td">{t.c}</div>
                  </div>
                </div>
              ))}
            </div>

            <div className="phone__nav" aria-hidden="true">
              <span className="on"></span><span></span><span></span><span></span>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { useReveal, Kinetic, Glyph, Phone });

/* ---------------------------------------------------------
   ManifestShred — "Alles wird eins": die drei Bürokratie-
   Sätze zerfallen nacheinander in Partikel, die in einen
   einzigen glühenden Kern gesaugt werden (gelöscht → eins).
   --------------------------------------------------------- */
function _hexToRgb(hex) {
  const h = (hex || '#00e5b0').replace('#', '');
  const n = parseInt(h.length === 3 ? h.split('').map(c => c + c).join('') : h, 16);
  return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}
function _mix(a, b, t) { return { r: a.r + (b.r - a.r) * t, g: a.g + (b.g - a.g) * t, b: a.b + (b.b - a.b) * t }; }
const _easeIn = (t) => t * t * t;

function ManifestShred({ lines, accent = '#00e5b0', motion = true }) {
  const wrapRef = useRef(null);
  const canvasRef = useRef(null);

  useEffect(() => {
    const wrap = wrapRef.current, canvas = canvasRef.current;
    if (!wrap || !canvas) return;
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (!motion || reduce) { wrap.classList.add('shred-static'); return; }

    const ctx = canvas.getContext('2d');
    let raf = null, ran = false, info = null, parts = [], groupRows = {}, fontStr = '', ls = '0px', inkRGB = { r: 243, g: 245, b: 250 };
    const GROUP_DELAY = 0.5, HOLD = 0.35, P_DUR = 1.0;

    function setup() {
      const sampleEl = wrap.querySelector('.m-neg');
      const cs = getComputedStyle(sampleEl);
      const sizePx = parseFloat(cs.fontSize);
      fontStr = `${cs.fontWeight} ${sizePx}px ${cs.fontFamily}`;
      ls = cs.letterSpacing === 'normal' ? '0px' : cs.letterSpacing;
      const lineH = sizePx * 1.14;
      const W = wrap.clientWidth;
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const onInk = getComputedStyle(document.documentElement).getPropertyValue('--on-ink').trim();
      if (onInk) { const c = _hexToRgb(onInk); if (c) inkRGB = c; }

      ctx.font = fontStr; try { ctx.letterSpacing = ls; } catch (e) {}
      // wrap each statement into rows
      const rows = []; // {text, group, by}
      lines.forEach((str, gi) => {
        const words = str.split(' ');
        let line = '';
        const lineRows = [];
        for (const w of words) {
          const test = line ? line + ' ' + w : w;
          if (ctx.measureText(test).width > W && line) { lineRows.push(line); line = w; }
          else line = test;
        }
        if (line) lineRows.push(line);
        lineRows.forEach(r => rows.push({ text: r, group: gi }));
      });
      const totalH = Math.ceil(rows.length * lineH + sizePx * 0.5);
      canvas.width = Math.ceil(W * dpr); canvas.height = Math.ceil(totalH * dpr);
      canvas.style.width = W + 'px'; canvas.style.height = totalH + 'px';
      wrap.style.minHeight = totalH + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

      groupRows = {};
      const groupY = {};
      rows.forEach((row, i) => {
        const by = sizePx + i * lineH;
        (groupRows[row.group] = groupRows[row.group] || []).push({ text: row.text, by });
        const top = by - sizePx, bot = by + sizePx * 0.28;
        if (!groupY[row.group]) groupY[row.group] = [top, bot]; else groupY[row.group][1] = bot;
      });

      // draw text once to sample pixels
      ctx.font = fontStr; try { ctx.letterSpacing = ls; } catch (e) {}
      ctx.textBaseline = 'alphabetic';
      ctx.fillStyle = `rgb(${inkRGB.r|0},${inkRGB.g|0},${inkRGB.b|0})`;
      rows.forEach((row, i) => ctx.fillText(row.text, 0, sizePx + i * lineH));

      const img = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
      const step = Math.max(3, Math.round(sizePx / 10));
      parts = [];
      for (let y = 0; y < totalH; y += step) {
        let g = 0;
        for (const k in groupY) { const [t, b] = groupY[k]; if (y >= t - 2 && y <= b + 3) { g = +k; break; } }
        for (let x = 0; x < W; x += step) {
          const idx = (Math.floor(y * dpr) * canvas.width + Math.floor(x * dpr)) * 4;
          if (img[idx + 3] > 110) parts.push({ ox: x, oy: y, g });
        }
      }
      // NOTE: keep the crisp text on the canvas (no clear) so the handoff
      // from the now-hidden DOM text to the canvas has no blank flash.

      const cx = W * 0.92, cy = totalH * 0.5;
      parts.forEach(p => {
        p.delay = HOLD + p.g * GROUP_DELAY + Math.random() * 0.4;
        p.dur = P_DUR + Math.random() * 0.5;
        const mx = (p.ox + cx) / 2, my = (p.oy + cy) / 2;
        const nx = -(cy - p.oy), ny = (cx - p.ox), nl = Math.hypot(nx, ny) || 1;
        const off = (Math.random() - 0.5) * Math.min(240, Math.hypot(cx - p.ox, cy - p.oy) * 0.55);
        p.cx = mx + nx / nl * off; p.cy = my + ny / nl * off;
        p.s = 1 + Math.random() * 1.7;
      });
      info = { W, totalH, cx, cy, lineH, sizePx, accentRGB: _hexToRgb(accent) };
    }

    function run() {
      if (ran) return; ran = true;
      setup();
      wrap.classList.add('shred-running');
      const { W, totalH, cx, cy, accentRGB } = info;
      const endParticles = HOLD + (lines.length - 1) * GROUP_DELAY + P_DUR + 0.9;
      const t0 = performance.now();
      let heat = 0;

      function frame(now) {
        const t = (now - t0) / 1000;
        ctx.clearRect(0, 0, W, totalH);

        // crisp text for groups whose turn hasn't come
        ctx.font = fontStr; try { ctx.letterSpacing = ls; } catch (e) {}
        ctx.textBaseline = 'alphabetic';
        for (const gi in groupRows) {
          const gStart = HOLD + (+gi) * GROUP_DELAY;
          if (t < gStart) {
            ctx.globalAlpha = 1;
            ctx.fillStyle = `rgb(${inkRGB.r|0},${inkRGB.g|0},${inkRGB.b|0})`;
            groupRows[gi].forEach(r => ctx.fillText(r.text, 0, r.by));
          }
        }
        // particles for groups in progress
        let remaining = 0;
        for (const p of parts) {
          const gStart = HOLD + p.g * GROUP_DELAY;
          if (t < gStart) { remaining++; continue; }
          const lt = (t - p.delay) / p.dur;
          if (lt >= 1) continue;
          remaining++;
          if (lt < 0) {
            ctx.globalAlpha = 1;
            ctx.fillStyle = `rgb(${inkRGB.r|0},${inkRGB.g|0},${inkRGB.b|0})`;
            ctx.fillRect(p.ox, p.oy, p.s, p.s);
            continue;
          }
          const e = _easeIn(lt), mt = 1 - e;
          const x = mt * mt * p.ox + 2 * mt * e * p.cx + e * e * cx;
          const y = mt * mt * p.oy + 2 * mt * e * p.cy + e * e * cy;
          const c = _mix(inkRGB, accentRGB, Math.min(1, lt * 1.5));
          ctx.globalAlpha = 1 - e * e;
          ctx.fillStyle = `rgb(${c.r|0},${c.g|0},${c.b|0})`;
          const s = p.s * (1 - e * 0.45);
          ctx.fillRect(x, y, s, s);
        }
        // glowing core grows as particles are consumed
        const target = 1 - remaining / Math.max(1, parts.length);
        heat += (target - heat) * 0.12;
        if (heat > 0.015) {
          const R = 5 + heat * 52;
          const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, R);
          g.addColorStop(0, `rgba(${accentRGB.r},${accentRGB.g},${accentRGB.b},${0.95 * Math.min(1, heat * 1.7)})`);
          g.addColorStop(0.4, `rgba(${accentRGB.r},${accentRGB.g},${accentRGB.b},${0.25 * heat})`);
          g.addColorStop(1, 'rgba(0,0,0,0)');
          ctx.globalAlpha = 1; ctx.fillStyle = g;
          ctx.beginPath(); ctx.arc(cx, cy, R, 0, Math.PI * 2); ctx.fill();
        }

        if (t < endParticles) { raf = requestAnimationFrame(frame); return; }
        // ending: core flares then collapses
        const et = (t - endParticles) / 0.55;
        if (et < 1) {
          const R = (5 + 52) * (1 - _easeIn(et)) + 2;
          const a = (1 - et);
          const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, R + 2);
          g.addColorStop(0, `rgba(${accentRGB.r},${accentRGB.g},${accentRGB.b},${a})`);
          g.addColorStop(1, 'rgba(0,0,0,0)');
          ctx.globalAlpha = 1; ctx.fillStyle = g;
          ctx.beginPath(); ctx.arc(cx, cy, R + 2, 0, Math.PI * 2); ctx.fill();
          raf = requestAnimationFrame(frame);
        } else {
          ctx.clearRect(0, 0, W, totalH);
          wrap.classList.add('shred-done');
        }
      }
      raf = requestAnimationFrame(frame);
      // safety net: if rAF gets paused (e.g. backgrounded) force the end state
      setTimeout(() => {
        if (!wrap.classList.contains('shred-done')) {
          try { ctx.clearRect(0, 0, W, totalH); } catch (e) {}
          wrap.classList.add('shred-done');
        }
      }, (endParticles + 2) * 1000);
    }

    const io = new IntersectionObserver((es) => {
      es.forEach(e => { if (e.isIntersecting) { run(); io.disconnect(); } });
    }, { threshold: 0.45 });
    // wait for fonts so canvas text matches
    (document.fonts ? document.fonts.ready : Promise.resolve()).then(() => io.observe(wrap));

    return () => { if (raf) cancelAnimationFrame(raf); io.disconnect(); };
  }, [accent, motion, lines]);

  return (
    <div className="manifest__negs" ref={wrapRef}>
      {lines.map((l, i) => (
        <p key={i} className="m-neg"><span className="m-neg__text">{l}</span></p>
      ))}
      <canvas className="shred-canvas" ref={canvasRef} aria-hidden="true"></canvas>
      <div className="manifest__shred-confirm" aria-hidden="true">// drei Hürden gelöscht</div>
    </div>
  );
}

Object.assign(window, { ManifestShred });
