// articles-atoms.jsx — shared atoms for Articles.html (index) and Article.html (reader).
// Defines: DibMark, Arrow, MiniLattice, BlogNav, Avatar, Placeholder, useReveal.
const { useEffect, useRef, useState, useMemo } = React;

/* ─── Accent presets + typefaces (mirrors dib-app.jsx) ──────────────── */
window.BLOG_ACCENT_PRESETS = {
  cyan:   '#22e3ff',
  blue:   '#5b8cff',
  purple: '#a45bff',
  mint:   '#3df0b3',
  amber:  '#ffb547',
  pink:   '#ff5b9c',
};

window.BLOG_TYPEFACES = {
  sora:    { display: "'Sora', system-ui, sans-serif",          body: "'Inter', system-ui, sans-serif" },
  inter:   { display: "'Inter', system-ui, sans-serif",         body: "'Inter', system-ui, sans-serif" },
  grotesk: { display: "'Space Grotesk', system-ui, sans-serif", body: "'Inter', system-ui, sans-serif" },
};

function hexToRgb(hex) {
  const h = hex.replace('#', '');
  return `${parseInt(h.slice(0,2),16)}, ${parseInt(h.slice(2,4),16)}, ${parseInt(h.slice(4,6),16)}`;
}

/** Push all tweaks to CSS custom properties and body classes. */
window.applyBlogTweaks = function applyBlogTweaks(t) {
  const root  = document.documentElement;
  const color = t.accent === 'custom' ? t.customColor
              : (window.BLOG_ACCENT_PRESETS[t.accent] || window.BLOG_ACCENT_PRESETS.cyan);
  const rgb   = hexToRgb(color);
  const alpha = ((t.glowIntensity ?? 35) / 100).toFixed(2);

  root.style.setProperty('--accent',      color);
  root.style.setProperty('--accent-dim',  `rgba(${rgb}, 0.20)`);
  root.style.setProperty('--accent-glow', `rgba(${rgb}, ${alpha})`);
  root.style.setProperty('--accent-soft', `rgba(${rgb}, 0.08)`);

  const tp = window.BLOG_TYPEFACES[t.typeface] || window.BLOG_TYPEFACES.sora;
  root.style.setProperty('--display', tp.display);
  root.style.setProperty('--body',    tp.body);

  document.body.classList.remove('motion-low', 'motion-medium', 'motion-high');
  document.body.classList.add(`motion-${t.motion || 'medium'}`);

  document.documentElement.setAttribute('data-theme', t.theme || 'dark');
};

/* ─── Atoms ─────────────────────────────────────────────────────────── */
function DibMark({ size = 22 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden="true">
      <g stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round" strokeLinecap="round">
        <path d="M12 3 L21 7.5 L21 16.5 L12 21 L3 16.5 L3 7.5 Z" />
        <path d="M3 7.5 L12 12 L21 7.5" />
        <path d="M12 12 L12 21" opacity="0.6" />
      </g>
    </svg>
  );
}

function Arrow({ size = 16 }) {
  return (
    <svg className="arrow" width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
      <path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5"
            strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

/** Deterministic coloured avatar circle with author initials. */
function Avatar({ author, size = 36 }) {
  const a = window.BLOG_AUTHORS[author] || window.BLOG_AUTHORS.studio;
  const gradient = `linear-gradient(135deg, hsl(${a.hue} 60% 35%) 0%, hsl(${(a.hue + 40) % 360} 70% 20%) 100%)`;
  return (
    <span style={{
      width: size, height: size, borderRadius: '50%',
      display: 'inline-grid', placeItems: 'center', flexShrink: 0,
      background: gradient,
      fontFamily: 'var(--mono)', fontSize: size * 0.32, fontWeight: 600,
      letterSpacing: '0.04em', color: '#fff',
      border: '1px solid var(--line-strong)',
      boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)',
    }} aria-hidden="true">
      {a.initials}
    </span>
  );
}

/** IntersectionObserver-based reveal — re-binds when deps change. */
function useReveal(deps) {
  useEffect(() => {
    const els = document.querySelectorAll('.reveal:not(.in)');
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); }
      });
    }, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  }, deps || []);
}

/* ─── MiniLattice — animated isometric cube grid canvas ─────────────── */
function MiniLattice({ opacity = 0.45 }) {
  const canvasRef = useRef(null);
  const reduced = useMemo(() => window.matchMedia('(prefers-reduced-motion: reduce)').matches, []);

  useEffect(() => {
    if (reduced) return;
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let w = 0, h = 0, raf;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);

    const resize = () => {
      const r = canvas.getBoundingClientRect();
      w = r.width; h = r.height;
      canvas.width  = Math.max(2, w * dpr);
      canvas.height = Math.max(2, h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    const HALF = 0.45;
    const verts = [
      [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
      [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
    ].map(v => v.map(c => c * HALF));
    const edges = [[0,1],[1,2],[2,3],[3,0],[4,5],[5,6],[6,7],[7,4],[0,4],[1,5],[2,6],[3,7]];

    const GRID = 3, STEP = 1.5;
    const cubes = [];
    for (let x = 0; x < GRID; x++)
      for (let y = 0; y < GRID; y++)
        for (let z = 0; z < GRID; z++)
          cubes.push({
            p: [(x-(GRID-1)/2)*STEP, (y-(GRID-1)/2)*STEP, (z-(GRID-1)/2)*STEP],
            active: Math.random() < 0.22,
            phase:  Math.random() * Math.PI * 2,
            speed:  0.4 + Math.random() * 0.6,
          });

    const FOCAL = 5.5;
    const proj = (x, y, z, cx, cy, f0) => {
      const f = FOCAL / (FOCAL + z);
      return [cx + x * f * f0, cy + y * f * f0, f];
    };
    const rot = ([x, y, z], rx, ry) => {
      const cy0 = Math.cos(rx), sy0 = Math.sin(rx);
      let ny = y * cy0 - z * sy0, nz = y * sy0 + z * cy0;
      y = ny; z = nz;
      const cx0 = Math.cos(ry), sx0 = Math.sin(ry);
      let nx = x * cx0 + z * sx0; nz = -x * sx0 + z * cx0;
      return [nx, y, nz];
    };

    const start = performance.now();
    const tick = (now) => {
      const t = (now - start) / 1000;
      const cx = w/2, cy = h/2;
      const focal = Math.min(w, h) * 0.20;
      const rx = Math.sin(t * 0.18) * 0.45 + 0.55;
      const ry = t * 0.18;
      const accent = getComputedStyle(document.documentElement)
                      .getPropertyValue('--accent').trim() || '#22e3ff';

      ctx.clearRect(0, 0, w, h);
      cubes
        .map(c => ({ c, rp: rot(c.p, rx, ry) }))
        .sort((a, b) => b.rp[2] - a.rp[2])
        .forEach(({ c, rp }) => {
          const pulse = c.active ? 0.6 + 0.4 * Math.sin(t * c.speed * 2 + c.phase) : 0;
          ctx.lineWidth = c.active ? 1.1 : 0.6;
          for (const [a, b] of edges) {
            const va = rot([c.p[0]+verts[a][0], c.p[1]+verts[a][1], c.p[2]+verts[a][2]], rx, ry);
            const vb = rot([c.p[0]+verts[b][0], c.p[1]+verts[b][1], c.p[2]+verts[b][2]], rx, ry);
            const [ax, ay, af] = proj(va[0], va[1], va[2], cx, cy, focal);
            const [bx, by, bf] = proj(vb[0], vb[1], vb[2], cx, cy, focal);
            const depth = (af + bf) / 2;
            const baseAlpha = Math.max(0.05, Math.min(1, (depth - 0.6) * 1.4));
            if (c.active) {
              ctx.strokeStyle = accent;
              ctx.globalAlpha = baseAlpha * (0.4 + pulse * 0.6);
              ctx.shadowColor = accent;
              ctx.shadowBlur  = 12 * pulse;
            } else {
              ctx.strokeStyle = '#a1a1aa';
              ctx.globalAlpha = baseAlpha * 0.32;
              ctx.shadowBlur  = 0;
            }
            ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke();
          }
          if (c.active) {
            const [cpx, cpy] = proj(rp[0], rp[1], rp[2], cx, cy, focal);
            ctx.globalAlpha = 0.55 + pulse * 0.4;
            ctx.fillStyle   = accent;
            ctx.shadowColor = accent;
            ctx.shadowBlur  = 18;
            ctx.beginPath(); ctx.arc(cpx, cpy, 1.8 + pulse * 1.4, 0, Math.PI * 2); ctx.fill();
          }
        });
      ctx.globalAlpha = 1; ctx.shadowBlur = 0;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [reduced]);

  if (reduced) return null;
  return (
    <div aria-hidden="true" style={{ position: 'absolute', inset: 0, opacity, pointerEvents: 'none' }}>
      <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />
      <div style={{
        position: 'absolute', inset: 0,
        background: 'radial-gradient(ellipse at center, transparent 30%, var(--bg) 100%)',
        pointerEvents: 'none',
      }} />
    </div>
  );
}

/* ─── BlogNav ───────────────────────────────────────────────────────── */
/**
 * Articles navigation bar.
 * Middle links: Home → index.html, Articles → Articles.html, and optionally
 * the current article label as a non-link accent span.
 * Right side: appearance dropdown (theme + accent + glow) + Book a call CTA.
 */
function BlogNav({ pageLabel, tweaks, setTweak, presets, langs }) {
  const [appearOpen, setAppearOpen] = useState(false);
  const appearRef = useRef(null);

  useEffect(() => {
    const onDoc = e => {
      if (appearRef.current && !appearRef.current.contains(e.target)) setAppearOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  const currentColor = tweaks.accent === 'custom'
    ? tweaks.customColor
    : (presets[tweaks.accent] || presets.cyan);
  const isLight = tweaks.theme === 'light';

  return (
    <nav className="nav" aria-label="Main navigation">
      {/* Logo */}
      <a href="index.html" className="nav__logo" aria-label="Digital Immersive Box — home">
        <span className="nav__mark">
          <img src="dib_solo.png" alt="dib" width="28" height="28"
               style={{ objectFit: 'contain', display: 'block' }} />
        </span>
        Digital Immersive Box
      </a>

      {/* Middle links */}
      <div className="nav__links">
        <a href="index.html">Home</a>
        <a href="Articles.html" className={pageLabel === 'Articles' ? 'nav__links-current' : ''}>
          Articles
        </a>
        {pageLabel !== 'Articles' && (
          <span className="nav__links-current" aria-current="page">{pageLabel}</span>
        )}
      </div>

      {/* Right controls */}
      <div className="nav__right">
        {/* Appearance: theme + accent + glow */}
        <div className="nav__pop" ref={appearRef}>
          <button
            className="nav__icon-btn nav__icon-btn--appear"
            onClick={() => setAppearOpen(o => !o)}
            aria-label="Appearance settings"
            aria-expanded={appearOpen}
          >
            <span className={`nav__theme-icon ${isLight ? 'is-light' : 'is-dark'}`}>
              {isLight ? (
                <svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
                  <path d="M13 9.5A5.5 5.5 0 0 1 6.5 3a5.5 5.5 0 1 0 6.5 6.5z" fill="currentColor"/>
                </svg>
              ) : (
                <svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
                  <circle cx="8" cy="8" r="3" fill="currentColor"/>
                  <g stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
                    <line x1="8" y1="1" x2="8" y2="3"/>
                    <line x1="8" y1="13" x2="8" y2="15"/>
                    <line x1="1" y1="8" x2="3" y2="8"/>
                    <line x1="13" y1="8" x2="15" y2="8"/>
                    <line x1="3" y1="3" x2="4.4" y2="4.4"/>
                    <line x1="11.6" y1="11.6" x2="13" y2="13"/>
                    <line x1="3" y1="13" x2="4.4" y2="11.6"/>
                    <line x1="11.6" y1="4.4" x2="13" y2="3"/>
                  </g>
                </svg>
              )}
            </span>
            <span className="nav__color-dot" style={{ background: currentColor }} />
          </button>

          {appearOpen && (
            <div className="nav__menu">
              <div className="nav__theme-row">
                <span className="nav__appear-label">Theme</span>
                <div className="nav__theme-btns">
                  <button className={`nav__theme-opt ${!isLight ? 'on' : ''}`}
                          onClick={() => setTweak('theme', 'dark')}>Dark</button>
                  <button className={`nav__theme-opt ${isLight ? 'on' : ''}`}
                          onClick={() => setTweak('theme', 'light')}>Light</button>
                </div>
              </div>
              <div className="nav__swatches">
                {Object.entries(presets).map(([key, color]) => (
                  <button key={key}
                          className={`nav__swatch ${tweaks.accent === key ? 'on' : ''}`}
                          style={{
                            background: color,
                            boxShadow: tweaks.accent === key
                              ? `0 0 0 2px var(--bg-2), 0 0 0 4px ${color}` : undefined,
                          }}
                          onClick={() => setTweak('accent', key)}
                          aria-label={key} title={key} />
                ))}
              </div>
              <label className="nav__custom">
                <span>Custom</span>
                <input type="color" value={tweaks.customColor}
                       onChange={e => setTweak({ accent: 'custom', customColor: e.target.value })} />
                <span className="nav__custom-hex">{tweaks.customColor}</span>
              </label>
              <div className="nav__glow-row">
                <span>Glow</span>
                <input type="range" className="nav__glow-slider" min="0" max="100" step="1"
                       value={tweaks.glowIntensity ?? 35}
                       onChange={e => setTweak('glowIntensity', Number(e.target.value))} />
                <span className="nav__glow-val">{tweaks.glowIntensity ?? 35}%</span>
              </div>
              {langs && (
                <div className="nav__lang-row">
                  <span className="nav__appear-label">Language</span>
                  <div className="nav__lang-btns">
                    {langs.map(l => (
                      <button key={l.value}
                              className={`nav__lang-opt ${tweaks.language === l.value ? 'on' : ''}`}
                              onClick={() => setTweak('language', l.value)}
                              title={l.label}
                              aria-label={l.label}
                              aria-pressed={tweaks.language === l.value}>
                        {l.flag}
                      </button>
                    ))}
                  </div>
                </div>
              )}
            </div>
          )}
        </div>

        <a href="contact.html" className="nav__cta">Get in touch</a>
      </div>
    </nav>
  );
}

/* ─── Hatched placeholder figure ──────────────────────────────────────
   aspect="auto" fills the parent (parent controls height).
   Otherwise reserves intrinsic height from aspect-ratio. */
function Placeholder({ caption, label, aspect = '16 / 10', className = '' }) {
  const style = aspect === 'auto'
    ? { width: '100%', height: '100%', position: 'relative' }
    : { aspectRatio: aspect, width: '100%', position: 'relative' };
  return (
    <div className={`ph ${className}`} style={style} data-cap={caption}>
      {label && <span className="ph__icon">{label}</span>}
      <span className="tl" /><span className="tr" /><span className="bl" /><span className="br" />
    </div>
  );
}

Object.assign(window, {
  DibMark, Arrow, Avatar, MiniLattice, BlogNav, Placeholder, useReveal,
});
