/* TitleVerifi — Nodes mark system
   The chain of title, drawn as a graph.
   One canonical mark, with animated and dense variations.
*/

/* ============================================================
   ANIMATION KEYFRAMES — one calm traveling pulse along the chain
   ============================================================ */
if (typeof document !== 'undefined' && !document.getElementById('tv-mark-anim')) {
  const s = document.createElement('style');
  s.id = 'tv-mark-anim';
  s.textContent = `
    /* A single circle travels the chain path; nodes brighten as it passes. */
    @keyframes tv-travel {
      0%   { offset-distance: 0%; opacity: 0; }
      6%   { opacity: 1; }
      94%  { opacity: 1; }
      100% { offset-distance: 100%; opacity: 0; }
    }
    /* Each node receives a brief brightening at its time-slot in the cycle. */
    @keyframes tv-node-active-1 {
      0%, 8%, 100% { fill-opacity: 0.45; r: 3.2; }
      14%, 22%     { fill-opacity: 1;    r: 3.8; }
    }
    @keyframes tv-node-active-2 {
      0%, 32%, 100% { fill-opacity: 0.45; r: 3.2; }
      38%, 46%      { fill-opacity: 1;    r: 3.8; }
    }
    @keyframes tv-node-active-3 {
      0%, 56%, 100% { fill-opacity: 0.45; r: 3.2; }
      62%, 70%      { fill-opacity: 1;    r: 3.8; }
    }
    @keyframes tv-node-active-4 {
      0%, 80%, 100% { fill-opacity: 0.45; r: 3.2; }
      86%, 94%      { fill-opacity: 1;    r: 3.8; }
    }
    /* Subtle accent pulse — only on the highlighted node when set explicitly. */
    @keyframes tv-accent-glow {
      0%, 100% { fill-opacity: 1; r: 4; }
      50%      { fill-opacity: 0.7; r: 4.4; }
    }

    /* === SEMANTIC STATES =================================================
       Each "state" tells the mark what's happening in the UI.
       The sequence runs over a configurable cycle length.
       ==================================================================== */

    /* PROCESSING — node "scans": each node briefly inflates with an accent ring
       and brightens, in sequence. Reads as "examining each record." */
    @keyframes tv-proc-scan {
      0%, 100%  { r: 3.2; fill-opacity: 0.4; }
      18%, 28%  { r: 4.4; fill-opacity: 1; }
    }
    @keyframes tv-proc-ring {
      0%, 10%   { r: 3.2; opacity: 0; stroke-width: 1.2; }
      18%       { r: 4.4; opacity: 0.9; }
      40%       { r: 7;   opacity: 0; stroke-width: 0.4; }
      100%      { r: 7;   opacity: 0; }
    }
    /* Edges dim, then briefly carry charge along when the upstream node fires. */
    @keyframes tv-proc-edge {
      0%, 100% { opacity: 0.2; }
      15%, 30% { opacity: 0.85; }
    }

    /* CONNECTING — hub lands first, edges draw outward from hub,
       then peripheral nodes materialize at the edge tips. Loops cleanly. */
    @keyframes tv-conn-hub {
      0%       { transform: scale(0); opacity: 0; }
      8%       { transform: scale(1.25); opacity: 1; }
      14%, 100%{ transform: scale(1); opacity: 1; }
    }
    @keyframes tv-conn-edge {
      0%, 14%   { stroke-dashoffset: 24; opacity: 0; }
      28%, 88%  { stroke-dashoffset: 0;  opacity: 0.55; }
      96%, 100% { stroke-dashoffset: 0;  opacity: 0; }
    }
    @keyframes tv-conn-node {
      0%, 28%   { transform: scale(0); opacity: 0; }
      36%       { transform: scale(1.35); opacity: 1; }
      44%, 88%  { transform: scale(1); opacity: 1; }
      96%, 100% { transform: scale(1); opacity: 0; }
    }

    /* RESOLVING — whole mark rotates 360° once, then settles. */
    @keyframes tv-resolve-spin {
      0%   { transform: rotate(0deg); }
      80%  { transform: rotate(360deg); }
      100% { transform: rotate(360deg); }
    }

    /* COMPLETE — brief outward bloom on the accent, all nodes settle lit. */
    @keyframes tv-complete-bloom {
      0%   { r: 3.5; fill-opacity: 0.7; }
      40%  { r: 5;   fill-opacity: 1; }
      100% { r: 4;   fill-opacity: 1; }
    }
    @keyframes tv-complete-ring {
      0%   { r: 3.5; opacity: 0.6; }
      100% { r: 12;  opacity: 0; }
    }

    /* ERROR — one node shakes laterally, accent flashes. */
    @keyframes tv-error-shake {
      0%, 100% { transform: translateX(0); }
      20%      { transform: translateX(-1.5px); }
      40%      { transform: translateX(1.5px); }
      60%      { transform: translateX(-1px); }
      80%      { transform: translateX(0.5px); }
    }
    @keyframes tv-error-flash {
      0%, 100% { fill-opacity: 1; }
      30%, 60% { fill-opacity: 0.4; }
    }

    /* TV monogram — peripheral nodes brighten as the pulse passes their
       position on the figure-8 chain. Times are tuned to the pulsePath. */
    @keyframes tv-tv-node-0 { 0%, 100% { fill-opacity: 0.55; r: 2.1; } 8%, 14% { fill-opacity: 1; r: 2.5; } }
    @keyframes tv-tv-node-2 { 0%, 100% { fill-opacity: 0.55; r: 2.1; } 26%, 32% { fill-opacity: 1; r: 2.5; } }
    @keyframes tv-tv-node-4 { 0%, 100% { fill-opacity: 0.55; r: 2.1; } 44%, 52% { fill-opacity: 1; r: 2.5; } }
    @keyframes tv-tv-node-3 { 0%, 100% { fill-opacity: 0.55; r: 2.1; } 78%, 86% { fill-opacity: 1; r: 2.5; } }
    @keyframes cv-shimmer {
      0%,100% { fill-opacity: var(--shimmer-base, 0.78); }
      50% { fill-opacity: 1; }
    }
    @keyframes cv-canonical {
      0%,100% { fill-opacity: 1; }
      50% { fill-opacity: 0.74; }
    }
  `;
  document.head.appendChild(s);
}

/* ============================================================
   CANONICAL — Nodes (4 nodes), now with semantic UI states.

   Props:
     - state: 'idle' | 'processing' | 'connecting' | 'resolving'
              | 'complete' | 'error'
              Default 'idle' (calm traveling pulse, the rest state).
     - activeNode: 0..3 → lock that node lit (overrides animation).
                   Use this for tab/section wayfinding.
     - cycle: animation cycle length in seconds (default 2.4s for active
              states, 4s for idle).
     - animate: master kill-switch (false = static, used by print).
   ============================================================ */
const MarkNodes = ({
  size = 32, color = 'currentColor', accent = 'var(--accent)',
  animate = true, activeNode = null, state = 'idle', cycle,
}) => {
  // 4 nodes: a chain that reads left → center → right → bottom-right.
  const nodes = [
    { x: 7,  y: 9 },   // 0: prior record
    { x: 16, y: 16 },  // 1: hub (the active title) — accent
    { x: 25, y: 9 },   // 2: prior record
    { x: 25, y: 23 },  // 3: derived record
  ];
  const edges = [
    { from: 0, to: 1, dur: 0.0 }, // edge 0
    { from: 2, to: 1, dur: 0.15 }, // edge 1
    { from: 3, to: 1, dur: 0.30 }, // edge 2
  ];
  // Path the pulse travels along.
  const pulsePath = "M 7 9 L 16 16 L 25 9 L 16 16 L 25 23 L 16 16 L 7 9";

  const isLocked = activeNode !== null && activeNode !== undefined;
  const isStatic = !animate;
  // When activeNode is set, force idle behavior + lock — wayfinding wins.
  const effectiveState = isLocked ? 'locked' : (isStatic ? 'static' : state);

  const c = cycle || (effectiveState === 'idle' ? 4 : 2.4);

  // ---- Per-state node renderer -------------------------------------------
  const renderNode = (n, i) => {
    const isHub = i === 1;
    const baseFill = isHub ? accent : color;

    // LOCKED — exactly one node lit, others dimmed, no animation.
    if (effectiveState === 'locked') {
      const lit = activeNode === i;
      return (
        <circle key={i} cx={n.x} cy={n.y}
          r={lit ? 3.8 : 3.2}
          fill={baseFill}
          fillOpacity={lit || isHub ? 1 : 0.35}/>
      );
    }
    // STATIC — print/favicon, no animation.
    if (effectiveState === 'static') {
      return <circle key={i} cx={n.x} cy={n.y} r={isHub ? 3.5 : 3.2}
        fill={baseFill} fillOpacity={1}/>;
    }
    // IDLE — calm traveling pulse: nodes sequentially brighten.
    if (effectiveState === 'idle') {
      return (
        <circle key={i} cx={n.x} cy={n.y} r={3.2} fill={baseFill}
          fillOpacity={0.45}
          style={{ animation: `tv-node-active-${i + 1} ${c}s ease-in-out infinite` }}/>
      );
    }
    // PROCESSING — each node "scans": staggered inflate + accent ring sweep.
    if (effectiveState === 'processing') {
      // Scan order: 0 → 2 → 1 (hub) → 3. Hub fires last so it reads as the resolution.
      const order = [0, 2, 1, 3];
      const slot = order.indexOf(i);
      const delay = slot * (c / 4);
      return (
        <g key={i}>
          <circle cx={n.x} cy={n.y} r={3.2}
            fill={isHub ? accent : color}
            style={{
              animation: `tv-proc-scan ${c}s ease-in-out infinite`,
              animationDelay: `${delay}s`,
            }}/>
          <circle cx={n.x} cy={n.y} r={3.2}
            fill="none" stroke={isHub ? accent : color}
            style={{
              animation: `tv-proc-ring ${c}s ease-out infinite`,
              animationDelay: `${delay}s`,
            }}/>
        </g>
      );
    }
    // CONNECTING — hub lands first, then peripheral nodes materialize at edge tips.
    if (effectiveState === 'connecting') {
      // Hub: lands at t=0. Peripheral nodes: land after their edge draws.
      // Edges draw 14%→28% with stagger 0/0.15/0.30. Node pops at 36% absolute.
      const peripheralOrder = { 0: 0, 2: 1, 3: 2 }; // 0,2,3 stagger
      const stagger = isHub ? 0 : (peripheralOrder[i] || 0) * 0.12;
      return (
        <g key={i} style={{
          transformOrigin: `${n.x}px ${n.y}px`,
          transformBox: 'fill-box',
          animation: `${isHub ? 'tv-conn-hub' : 'tv-conn-node'} ${c}s cubic-bezier(.5,1.4,.5,1) infinite`,
          animationDelay: `${stagger}s`,
        }}>
          <circle cx={n.x} cy={n.y} r={isHub ? 3.6 : 3.2}
            fill={isHub ? accent : color}/>
        </g>
      );
    }
    // RESOLVING — handled at group level; just render the nodes lit.
    if (effectiveState === 'resolving') {
      return <circle key={i} cx={n.x} cy={n.y} r={isHub ? 3.6 : 3.2}
        fill={baseFill} fillOpacity={1}/>;
    }
    // COMPLETE — all nodes settle lit, accent blooms once + ring expands.
    if (effectiveState === 'complete') {
      return (
        <circle key={i} cx={n.x} cy={n.y}
          r={isHub ? 3.8 : 3.2}
          fill={baseFill}
          fillOpacity={1}
          style={isHub ? {
            animation: `tv-complete-bloom ${c}s ease-out 1`,
            animationFillMode: 'forwards',
          } : undefined}/>
      );
    }
    // ERROR — accent flashes; node 3 shakes (the "wrong" record).
    if (effectiveState === 'error') {
      const isShaker = i === 3;
      return (
        <g key={i} style={isShaker ? {
          transformOrigin: `${n.x}px ${n.y}px`,
          transformBox: 'fill-box',
          animation: `tv-error-shake ${c}s ease-in-out infinite`,
        } : undefined}>
          <circle cx={n.x} cy={n.y} r={isHub ? 3.6 : 3.2}
            fill={isHub ? accent : color}
            fillOpacity={isHub || isShaker ? 1 : 0.55}
            style={isHub ? {
              animation: `tv-error-flash ${c}s ease-in-out infinite`,
            } : undefined}/>
        </g>
      );
    }
    return null;
  };

  // ---- Per-state edge renderer -------------------------------------------
  const renderEdge = (e, i) => {
    const a = nodes[e.from], b = nodes[e.to];
    if (effectiveState === 'connecting') {
      // Draw FROM hub OUTWARD to peripheral node. Edge order: 0→1, 2→1, 3→1
      // becomes hub(b) → peripheral(a). Stagger: 0 / 0.12 / 0.24.
      const stagger = i * 0.12;
      // Use pathLength so dashoffset works regardless of geometry.
      return (
        <line key={i} x1={b.x} y1={b.y} x2={a.x} y2={a.y}
          stroke={color} strokeWidth="1.6" strokeLinecap="round"
          pathLength="24" strokeDasharray="24" strokeDashoffset="24"
          style={{
            animation: `tv-conn-edge ${c}s ease-out infinite`,
            animationDelay: `${stagger}s`,
          }}/>
      );
    }
    if (effectiveState === 'processing') {
      // Edge brightens when its upstream (non-hub) node fires.
      const order = [0, 2, 1, 3];
      const slot = order.indexOf(e.from);
      const delay = slot * (c / 4);
      return (
        <line key={i} x1={a.x} y1={a.y} x2={b.x} y2={b.y}
          stroke={color} strokeWidth="1.6" strokeLinecap="round"
          style={{
            animation: `tv-proc-edge ${c}s ease-in-out infinite`,
            animationDelay: `${delay}s`,
          }}/>
      );
    }
    return (
      <line key={i} x1={a.x} y1={a.y} x2={b.x} y2={b.y}
        stroke={color} strokeWidth="1.6" strokeLinecap="round" opacity="0.55"/>
    );
  };

  // ---- Group-level wrapper for resolving (full 360° spin) ----------------
  const groupStyle = effectiveState === 'resolving' ? {
    transformOrigin: '16px 16px',
    transformBox: 'fill-box',
    animation: `tv-resolve-spin ${c}s cubic-bezier(.65,.05,.35,1) infinite`,
  } : undefined;

  return (
    <svg width={size} height={size} viewBox="0 0 32 32" fill="none">
      <g style={groupStyle}>
        {edges.map(renderEdge)}
        {nodes.map(renderNode)}
      </g>

      {/* Idle traveling pulse */}
      {effectiveState === 'idle' && (
        <circle r="1.8" fill={accent}
          style={{
            offsetPath: `path('${pulsePath}')`,
            offsetRotate: '0deg',
            animation: `tv-travel ${c}s linear infinite`,
          }}/>
      )}

      {/* Complete: expanding ring around hub */}
      {effectiveState === 'complete' && (
        <circle cx="16" cy="16" r="3.5"
          fill="none" stroke={accent} strokeWidth="1"
          style={{
            animation: `tv-complete-ring ${c}s ease-out 1`,
            animationFillMode: 'forwards',
          }}/>
      )}
    </svg>
  );
};

/* ============================================================
   STATIC — Nodes, no animation
   ============================================================ */
const MarkNodesStatic = ({ size = 32, color = 'currentColor', accent = 'var(--accent)' }) => (
  <MarkNodes size={size} color={color} accent={accent} animate={false}/>
);



/* ============================================================
   Wordmark only
   ============================================================ */
const MarkNone = () => null;

/* ============================================================
   TV — node-monogram. The letterforms T and V drawn as a graph.
     N0  ─────  N1  ─────  N2     T's bar, with N1 as the shared joint
                │     ╲      ╲
                │       ╲      ╲
                │         ╲      ╲
                N3         N4     T's stem foot · V's vertex

   Strokes:
     N0—N1   T-bar (left half)
     N1—N2   T-bar (right half) — also the visual "lid" of the V
     N1—N3   T-stem (vertical)
     N1—N4   V-left-stroke
     N2—N4   V-right-stroke
   N1 is the 4-way hub — it carries the accent.
   ============================================================ */
const MarkTV = ({
  size = 32, color = 'currentColor', accent = 'var(--accent)',
  animate = true, activeNode = null, state = 'idle', cycle,
}) => {
  const nodes = [
    { x: 4,  y: 7  },  // 0  T-bar-left tip
    { x: 13, y: 7  },  // 1  HUB · shared joint (T-stem top + T-bar mid + V-left top)
    { x: 24, y: 7  },  // 2  T-bar-right tip · V-right top
    { x: 13, y: 22 },  // 3  T-stem foot
    { x: 18.5, y: 22 },// 4  V-vertex (bottom point)
  ];
  const edges = [
    [0, 1], // T-bar L
    [1, 2], // T-bar R (also V-lid)
    [1, 3], // T-stem
    [1, 4], // V-left
    [2, 4], // V-right
  ];
  const HUB = 1;
  const pulsePath = "M 4 7 L 13 7 L 24 7 L 18.5 22 L 13 7 L 13 22 L 13 7";

  const isLocked = activeNode !== null && activeNode !== undefined;
  const isStatic = !animate;
  const effectiveState = isLocked ? 'locked' : (isStatic ? 'static' : state);
  const c = cycle || (effectiveState === 'idle' ? 4.5 : 2.4);

  return (
    <svg width={size} height={size} viewBox="0 0 32 32" fill="none">
      {/* Edges */}
      {edges.map(([a, b], i) => (
        <line key={i}
          x1={nodes[a].x} y1={nodes[a].y}
          x2={nodes[b].x} y2={nodes[b].y}
          stroke={color} strokeWidth="2" strokeLinecap="round"
          opacity={effectiveState === 'static' ? 0.85 : 0.6}/>
      ))}
      {/* Nodes */}
      {nodes.map((n, i) => {
        const isHub = i === HUB;
        const fill = isHub ? accent : color;
        if (effectiveState === 'locked') {
          const lit = activeNode === i;
          return <circle key={i} cx={n.x} cy={n.y} r={lit ? 2.6 : 2.1}
            fill={fill} fillOpacity={lit || isHub ? 1 : 0.4}/>;
        }
        if (effectiveState === 'idle') {
          // Hub stays solid accent; peripherals pulse subtly with the traveling dot.
          if (isHub) return <circle key={i} cx={n.x} cy={n.y} r="2.6" fill={fill}/>;
          return <circle key={i} cx={n.x} cy={n.y} r="2.1" fill={fill}
            fillOpacity="0.55"
            style={{ animation: `tv-tv-node-${i} ${c}s ease-in-out infinite` }}/>;
        }
        // static / fallback
        return <circle key={i} cx={n.x} cy={n.y} r={isHub ? 2.6 : 2.1}
          fill={fill}/>;
      })}
      {/* Idle traveling pulse */}
      {effectiveState === 'idle' && (
        <circle r="1.4" fill={accent}
          style={{
            offsetPath: `path('${pulsePath}')`,
            offsetRotate: '0deg',
            animation: `tv-travel ${c}s linear infinite`,
          }}/>
      )}
    </svg>
  );
};

/* ============================================================== */
/* CANONICAL MARK · The Chain · Strata (B1)                       */
/* ============================================================== */
/* Four stacked deed bars, oldest faded at top, canonical at      */
/* bottom in vermilion. Vertical tie-spine on the left.           */
/* The unbroken chain of title made visible.                      */
/*                                                                 */
/* This component intentionally ignores `state`/`activeNode`      */
/* props passed by legacy callsites — the LOGO is fixed.          */
/* Status indicators live in product UI, not on the mark.         */

const MarkStrata = ({ size = 32, color = 'currentColor', accent = 'var(--accent)', animate }) => {
  const motion = animate === false ? 'static' : 'ambient';
  /* Four grey strata (oldest faded) on a 3.5-unit rhythm, then the vermilion canonical bar.
     Spine ends exactly flush with canonical bottom. */
  const bars = [
    { y: 6,    h: 1.6, op: 0.35 },
    { y: 9.5,  h: 1.6, op: 0.55 },
    { y: 13,   h: 1.6, op: 0.75 },
    { y: 16.5, h: 1.6, op: 0.92 },
  ];
  return (
    <svg width={size} height={size} viewBox="0 0 32 32" fill="none">
      <line x1="5" y1="6" x2="5" y2="22.8" stroke={color} strokeWidth="1.2"/>
      {bars.map((b, i) => (
        <rect key={i} x="7" y={b.y} width="20" height={b.h} fill={color} fillOpacity={b.op}
          style={motion === 'ambient' ? { animation: `cv-shimmer 5s ease-in-out infinite ${i*0.3}s` } : undefined}/>
      ))}
      <rect x="7" y="20" width="20" height="2.8" fill={accent}
        style={motion === 'ambient' ? { animation: 'cv-canonical 4s ease-in-out infinite' } : undefined}/>
    </svg>
  );
};

const MARKS = {
  nodes:       MarkStrata,       // ★ canonical — Chain · Strata (B1)
  strata:      MarkStrata,       // explicit alias
  tv:          MarkStrata,       // legacy alias → canonical
  nodesStatic: (props) => <MarkStrata {...props} animate={false}/>,
  none:        MarkNone,
};

const Mark = ({ variant = 'nodes', size = 32, color = 'currentColor', accent = 'var(--accent)', animate }) => {
  const C = MARKS[variant] || MarkStrata;
  if (variant === 'none') return null;
  return <C size={size} color={color} accent={accent}
    animate={animate !== false && variant !== 'nodesStatic'}/>;
};

const WordmarkStrataMark = ({ size = 18, color = 'currentColor', accent = 'var(--accent)', animate }) => {
  const motion = animate === false ? 'static' : 'ambient';
  const width = size;
  const height = size / 1.2;
  const bars = [
    { y: 0, h: 1.55, op: 0.35 },
    { y: 4.2, h: 1.55, op: 0.55 },
    { y: 8.4, h: 1.55, op: 0.75 },
    { y: 12.6, h: 1.55, op: 0.92 },
  ];
  return (
    <svg width={width} height={height} viewBox="0 0 24 20" fill="none" style={{ display: 'block', flexShrink: 0 }}>
      <line x1="0.8" y1="0" x2="0.8" y2="20" stroke={color} strokeWidth="1.25"/>
      {bars.map((b, i) => (
        <rect key={i} x="3.2" y={b.y} width="20" height={b.h} fill={color} fillOpacity={b.op}
          style={motion === 'ambient' ? { animation: `cv-shimmer 5s ease-in-out infinite ${i*0.3}s` } : undefined}/>
      ))}
      <rect x="3.2" y="17.1" width="20" height="2.9" fill={accent}
        style={motion === 'ambient' ? { animation: 'cv-canonical 4s ease-in-out infinite' } : undefined}/>
    </svg>
  );
};

const Wordmark = ({
  size = 18, color = 'var(--ink)', accent = 'var(--accent)',
  variant = 'nodes', accentLetter = 'V', animate, activeNode, state,
}) => {
  const showMark = variant !== 'none';
  let nameEl;
  if (accentLetter === 'V') nameEl = <>Title<span style={{ color: accent }}>V</span>erifi</>;
  else if (accentLetter === 'i-dot') nameEl = <>TitleVer<span style={{ color: accent }}>i</span>fi</>;
  else nameEl = 'TitleVerifi';
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'flex-end', gap: Math.max(3, size * 0.12),
      fontFamily: 'var(--font-sans)', fontWeight: 650, fontSize: size,
      letterSpacing: 0, lineHeight: 1, color,
    }}>
      {showMark && <WordmarkStrataMark size={size} color={color} accent={accent} animate={animate} activeNode={activeNode} state={state}/>}
      <span style={{ display: 'inline-block', lineHeight: 0.86, transform: `translateY(${Math.max(1, size * 0.075)}px)` }}>{nameEl}</span>
    </span>
  );
};

window.Mark = Mark;
window.MARKS = MARKS;
window.Wordmark = Wordmark;
