// scenes.jsx
// Two-systems-converge animation. Pure abstract geometry.

const PALETTE = {
  bg: '#f6f4ef',
  ink: 'oklch(22% 0.015 250)',
  inkSoft: 'oklch(45% 0.01 250)',
  inkFaint: 'oklch(75% 0.008 250)',
  inkVeryFaint: 'oklch(88% 0.005 250)',
  signal: 'oklch(55% 0.085 245)',   // satellite — cool
  experience: 'oklch(62% 0.09 55)', // product — warm
  unified: 'oklch(28% 0.02 250)',
};

// Stage: 1280×720, 24s
// Phases:
//   intro     0.0 –  1.2  (titles/labels fade in)
//   build     1.2 –  6.5  (two systems fully expressed; pulses flowing)
//   strip     6.5 – 11.5  (filter boxes / experience containers fade away)
//   align    11.5 – 15.5  (both systems STRAIGHTEN: System B's curve goes straight,
//                          colors begin to drift toward unified ink)
//   converge 15.5 – 19.0  (both systems slide to center, rotate to horizontal,
//                          and dissolve into the unified flow which is rising)
//   resolve  19.0 – 24.0  (single elegant horizontal flow; calm pulse; caption)

// ── Utility: blend two colors via interpolated mix at a given t ─────────────
// We'll use CSS color-mix at the call site since it handles oklch nicely.
const mix = (a, b, t) => `color-mix(in oklch, ${a} ${(1 - t) * 100}%, ${b} ${t * 100}%)`;

// ─────────────────────────────────────────────────────────────────────────────
// SYSTEM A — Satellite communication
// Vertical signal flow through nodes / filters / transmission layers.
// Located on the left half. Centered around x=320, full vertical extent.
// ─────────────────────────────────────────────────────────────────────────────

function SystemA() {
  const t = useTime();

  // Phase progress
  const buildIn = clamp((t - 0.4) / 1.6, 0, 1);
  const stripT  = clamp((t - 6.5)  / 4.5, 0, 1);    // filter boxes recede
  const alignT  = clamp((t - 11.5) / 4.0, 0, 1);    // (System A is already vertical; this is mostly color drift)
  const mergeT  = clamp((t - 15.5) / 3.5, 0, 1);    // slide to center + rotate to horizontal + fade

  // System opacity: fades out as the unified flow takes over (16.5 → 18.5)
  const sysOpacity = 1 - clamp((t - 16.8) / 1.7, 0, 1);

  // Slide horizontally toward canvas center during merge
  const xBase = 320;
  const xTarget = 640;
  const dx = (xTarget - xBase) * Easing.easeInOutCubic(mergeT);

  // Color: cool signal → unified ink
  const colorMix = Easing.easeInOutSine(clamp((t - 12.5) / 4.0, 0, 1));
  const stroke = mix(PALETTE.signal, PALETTE.unified, colorMix);
  const strokeFaint = mix('oklch(78% 0.04 245)', PALETTE.inkFaint, colorMix);

  // No rotation — both systems are vertical, both converge to a single vertical flow.
  // "Recognition, not transformation shock."

  // The vertical pipeline structure has:
  //  - A vertical spine from y=80 to y=640
  //  - Three "node + filter" stations at y = 180, 360, 540
  //  - Pulses traveling top-to-bottom continuously
  const spineTop = 80;
  const spineBot = 640;

  // Strip: filter boxes shrink to 0 width
  const filterScale = 1 - Easing.easeInOutCubic(stripT);

  // Spine fades very slightly during alignment for a softening effect
  const spineOpacity = 1 - 0.1 * alignT;

  return (
    <div style={{
      position: 'absolute',
      left: 0, top: 0, width: 1280, height: 720,
      opacity: sysOpacity,
      transform: `translate(${dx}px, 0)`,
      willChange: 'transform, opacity',
    }}>
      <svg width="1280" height="720" style={{ position: 'absolute', inset: 0, overflow: 'visible' }}>
        {/* ── Outer frame: very faint container hint (only during build, fades during strip) */}
        <g opacity={(1 - stripT) * 0.6 * buildIn}>
          <rect
            x={xBase - 140} y={60}
            width={280} height={600}
            fill="none"
            stroke={PALETTE.inkVeryFaint}
            strokeWidth={1}
            strokeDasharray="2 4"
          />
        </g>

        {/* ── Layer labels (left side): fade out during strip ── */}
        <g
          opacity={(1 - stripT) * buildIn}
          style={{
            fontFamily: 'JetBrains Mono, ui-monospace, monospace',
            fontSize: 10,
            letterSpacing: '0.12em',
            textTransform: 'uppercase',
            fill: PALETTE.inkSoft,
          }}
        >
          <text x={xBase - 130} y={185}>Encode</text>
          <text x={xBase - 130} y={365}>Filter</text>
          <text x={xBase - 130} y={545}>Transmit</text>
        </g>

        {/* ── Vertical spine (signal pathway) ── */}
        <line
          x1={xBase} y1={spineTop}
          x2={xBase} y2={spineBot}
          stroke={stroke}
          strokeWidth={1.5}
          opacity={buildIn * spineOpacity}
        />

        {/* ── Origin / destination caps ── */}
        <circle cx={xBase} cy={spineTop} r={4} fill={stroke} opacity={buildIn} />
        <circle cx={xBase} cy={spineBot} r={4} fill={stroke} opacity={buildIn} />

        {/* ── Three filter stations ── */}
        {[180, 360, 540].map((cy, i) => (
          <FilterStation
            key={i}
            cx={xBase}
            cy={cy}
            scale={filterScale}
            buildIn={buildIn}
            stroke={stroke}
            strokeFaint={strokeFaint}
            phase={i}
          />
        ))}

        {/* ── Traveling pulses (continuous top-to-bottom) ── */}
        <Pulses
          xBase={xBase}
          spineTop={spineTop}
          spineBot={spineBot}
          color={stroke}
          buildIn={buildIn}
          fadeOut={mergeT}
        />
      </svg>
    </div>
  );
}

function FilterStation({ cx, cy, scale, buildIn, stroke, strokeFaint, phase }) {
  const w = 90 * scale;
  const h = 28 * scale;
  // Each station: a horizontal "filter" rectangle bisected by the spine,
  // with a small node circle on the spine.

  return (
    <g opacity={buildIn}>
      {/* Filter rectangle — very thin, sits across the spine */}
      <rect
        x={cx - w / 2}
        y={cy - h / 2}
        width={w}
        height={h}
        fill="none"
        stroke={strokeFaint}
        strokeWidth={1}
        opacity={scale}
      />
      {/* Internal tick marks — represent filter cells */}
      {scale > 0.05 && [-2, -1, 0, 1, 2].map((i) => (
        <line
          key={i}
          x1={cx + i * (w / 6)}
          y1={cy - h / 2}
          x2={cx + i * (w / 6)}
          y2={cy + h / 2}
          stroke={strokeFaint}
          strokeWidth={0.75}
          opacity={scale}
        />
      ))}
      {/* Node on spine */}
      <circle
        cx={cx}
        cy={cy}
        r={5}
        fill={PALETTE.bg}
        stroke={stroke}
        strokeWidth={1.5}
      />
      <circle
        cx={cx}
        cy={cy}
        r={2}
        fill={stroke}
      />
    </g>
  );
}

function Pulses({ xBase, spineTop, spineBot, color, buildIn, fadeOut }) {
  const t = useTime();
  const len = spineBot - spineTop;
  const pulseCount = 4;
  const cycle = 4.0; // seconds per pulse to traverse
  const pulses = [];

  const opacity = buildIn * (1 - fadeOut);
  if (opacity < 0.01) return null;

  for (let i = 0; i < pulseCount; i++) {
    const offset = (i / pulseCount) * cycle;
    const local = ((t - 0.5 + offset) % cycle) / cycle; // 0..1
    if (local < 0 || local > 1) continue;
    const y = spineTop + len * local;
    // Each pulse has a short trail
    const trailLen = 40;
    const trailY = Math.max(spineTop, y - trailLen);
    const fade = 1 - Math.abs(local - 0.5) * 0.3; // brighter mid-flight

    pulses.push(
      <g key={i} opacity={opacity * fade}>
        <line
          x1={xBase} y1={trailY}
          x2={xBase} y2={y}
          stroke={color}
          strokeWidth={3}
          strokeLinecap="round"
          opacity={0.4}
        />
        <circle cx={xBase} cy={y} r={3.5} fill={color} />
      </g>
    );
  }
  return <>{pulses}</>;
}

// ─────────────────────────────────────────────────────────────────────────────
// SYSTEM B — Product experience delivery
// Curved organic paths through "behavioral pathways" / decision points.
// Located on right half. Drifts toward center during merge.
// ─────────────────────────────────────────────────────────────────────────────

function SystemB() {
  const t = useTime();

  const buildIn = clamp((t - 0.4) / 1.6, 0, 1);
  const stripT  = clamp((t - 6.5)  / 4.5, 0, 1);    // containers fade
  const alignT  = clamp((t - 11.5) / 4.0, 0, 1);    // CURVE STRAIGHTENS during this phase
  const mergeT  = clamp((t - 15.5) / 3.5, 0, 1);    // slide to center + rotate + fade

  const sysOpacity = 1 - clamp((t - 16.8) / 1.7, 0, 1);

  const xBase = 960;
  const xTarget = 640;
  const dx = (xTarget - xBase) * Easing.easeInOutCubic(mergeT);

  const colorMix = Easing.easeInOutSine(clamp((t - 12.5) / 4.0, 0, 1));
  const stroke = mix(PALETTE.experience, PALETTE.unified, colorMix);
  const strokeFaint = mix('oklch(80% 0.045 55)', PALETTE.inkFaint, colorMix);

  // The curve straightens during the ALIGN phase — by t=15.5 it's a clean vertical line
  // matching System A's geometry. Then both rotate together during merge.
  const curveAmount = 1 - Easing.easeInOutCubic(alignT);

  // No rotation; both systems remain vertical and overlap at center.

  // Container fade (strip)
  const containerOpacity = (1 - stripT) * buildIn;

  return (
    <div style={{
      position: 'absolute',
      left: 0, top: 0, width: 1280, height: 720,
      opacity: sysOpacity,
      transform: `translate(${dx}px, 0)`,
      willChange: 'transform, opacity',
    }}>
      <svg width="1280" height="720" style={{ position: 'absolute', inset: 0, overflow: 'visible' }}>
        {/* Touchpoint containers: rounded boxes that fade during strip */}
        <g opacity={containerOpacity}>
          <RoundedHint cx={xBase} cy={170} w={260} h={110} stroke={PALETTE.inkVeryFaint} />
          <RoundedHint cx={xBase} cy={360} w={220} h={110} stroke={PALETTE.inkVeryFaint} />
          <RoundedHint cx={xBase} cy={550} w={260} h={110} stroke={PALETTE.inkVeryFaint} />
        </g>

        {/* Stage labels */}
        <g
          opacity={containerOpacity}
          style={{
            fontFamily: 'JetBrains Mono, ui-monospace, monospace',
            fontSize: 10,
            letterSpacing: '0.12em',
            textTransform: 'uppercase',
            fill: PALETTE.inkSoft,
          }}
        >
          <text x={xBase + 140} y={175}>Intent</text>
          <text x={xBase + 120} y={365}>Decision</text>
          <text x={xBase + 140} y={555}>Outcome</text>
        </g>

        {/* The behavioral pathway — a curved S-shape from top to bottom */}
        <BehaviorPath
          xBase={xBase}
          curveAmount={curveAmount}
          stroke={stroke}
          buildIn={buildIn}
        />

        {/* Decision nodes along the path */}
        <DecisionNodes
          xBase={xBase}
          curveAmount={curveAmount}
          stroke={stroke}
          strokeFaint={strokeFaint}
          buildIn={buildIn}
          containerOpacity={containerOpacity}
        />

        {/* Behavioral pulses traveling along the curve */}
        <BehaviorPulses
          xBase={xBase}
          curveAmount={curveAmount}
          color={stroke}
          buildIn={buildIn}
          fadeOut={mergeT}
        />
      </svg>
    </div>
  );
}

function RoundedHint({ cx, cy, w, h, stroke }) {
  return (
    <rect
      x={cx - w / 2}
      y={cy - h / 2}
      width={w}
      height={h}
      rx={14}
      ry={14}
      fill="none"
      stroke={stroke}
      strokeWidth={1}
      strokeDasharray="2 4"
    />
  );
}

// Build an S-curve path from top-cap (xBase, 80) to bot-cap (xBase, 640)
// passing through three node points at y=170, 360, 550.
// curveAmount: 1 = full curve, 0 = straight vertical line
function buildPathD(xBase, curveAmount) {
  const offset = 50 * curveAmount;
  const top = { x: xBase, y: 80 };
  const n1  = { x: xBase + offset, y: 170 };
  const n2  = { x: xBase - offset, y: 360 };
  const n3  = { x: xBase + offset, y: 550 };
  const bot = { x: xBase, y: 640 };

  return `M ${top.x} ${top.y}
          C ${top.x + offset} ${top.y + 30}, ${n1.x - offset / 2} ${n1.y - 30}, ${n1.x} ${n1.y}
          C ${n1.x + offset / 2} ${n1.y + 50}, ${n2.x + offset / 2} ${n2.y - 50}, ${n2.x} ${n2.y}
          C ${n2.x - offset / 2} ${n2.y + 50}, ${n3.x - offset / 2} ${n3.y - 50}, ${n3.x} ${n3.y}
          C ${n3.x + offset / 2} ${n3.y + 30}, ${bot.x + offset} ${bot.y - 30}, ${bot.x} ${bot.y}`;
}

function BehaviorPath({ xBase, curveAmount, stroke, buildIn }) {
  const d = buildPathD(xBase, curveAmount);
  return (
    <>
      <path
        d={d}
        fill="none"
        stroke={stroke}
        strokeWidth={1.5}
        opacity={buildIn}
      />
      <circle cx={xBase} cy={80} r={4} fill={stroke} opacity={buildIn} />
      <circle cx={xBase} cy={640} r={4} fill={stroke} opacity={buildIn} />
    </>
  );
}

function DecisionNodes({ xBase, curveAmount, stroke, strokeFaint, buildIn, containerOpacity }) {
  const offset = 50 * curveAmount;
  const nodes = [
    { x: xBase + offset, y: 170 },
    { x: xBase - offset, y: 360 },
    { x: xBase + offset, y: 550 },
  ];
  return (
    <g>
      {nodes.map((n, i) => (
        <g key={i} opacity={buildIn}>
          {/* Decision diamond — rotated square — fades during strip */}
          <g opacity={containerOpacity} transform={`translate(${n.x}, ${n.y}) rotate(45)`}>
            <rect
              x={-12} y={-12} width={24} height={24}
              fill="none"
              stroke={strokeFaint}
              strokeWidth={1}
            />
          </g>
          {/* Node circle */}
          <circle cx={n.x} cy={n.y} r={5} fill={PALETTE.bg} stroke={stroke} strokeWidth={1.5} />
          <circle cx={n.x} cy={n.y} r={2} fill={stroke} />
        </g>
      ))}
    </g>
  );
}

// Sample the path at t∈[0,1] using equivalent bezier math approximation
// We'll use SVG getPointAtLength via a hidden path. To keep it pure-React,
// we'll approximate by sampling the same parametric curve formula manually.
// Easier approach: render an invisible <path> ref and use getPointAtLength.
function BehaviorPulses({ xBase, curveAmount, color, buildIn, fadeOut }) {
  const t = useTime();
  const pathRef = React.useRef(null);
  const [len, setLen] = React.useState(0);
  const d = buildPathD(xBase, curveAmount);

  React.useEffect(() => {
    if (pathRef.current) {
      setLen(pathRef.current.getTotalLength());
    }
  }, [d]);

  const opacity = buildIn * (1 - fadeOut);
  if (opacity < 0.01 || len === 0) {
    return (
      <path
        ref={pathRef}
        d={d}
        fill="none"
        stroke="none"
      />
    );
  }

  const pulseCount = 4;
  const cycle = 4.5;
  const pulses = [];

  for (let i = 0; i < pulseCount; i++) {
    const offset = (i / pulseCount) * cycle;
    const local = ((t - 0.5 + offset) % cycle) / cycle;
    if (local < 0 || local > 1) continue;
    const pt = pathRef.current.getPointAtLength(local * len);
    const ahead = pathRef.current.getPointAtLength(Math.max(0, local * len - 35));
    const fade = 1 - Math.abs(local - 0.5) * 0.3;

    pulses.push(
      <g key={i} opacity={opacity * fade}>
        <line
          x1={ahead.x} y1={ahead.y}
          x2={pt.x} y2={pt.y}
          stroke={color}
          strokeWidth={3}
          strokeLinecap="round"
          opacity={0.4}
        />
        <circle cx={pt.x} cy={pt.y} r={3.5} fill={color} />
      </g>
    );
  }

  return (
    <>
      <path ref={pathRef} d={d} fill="none" stroke="none" />
      {pulses}
    </>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// UNIFIED FLOW — Final state
// A single horizontal flow from origin → optimization → destination
// ─────────────────────────────────────────────────────────────────────────────

function UnifiedFlow() {
  const t = useTime();
  // Begins materializing as the two systems converge at center (16.5 → 18.5)
  const fadeIn = clamp((t - 16.5) / 2.0, 0, 1);
  if (fadeIn < 0.01) return null;

  // Subtle "breathing" calm — barely perceptible
  const breathe = 1 + 0.004 * Math.sin((t - 19) * 1.0);

  // Vertical flow at canvas center — matches the geometry both systems
  // simplified into. Shorter than the originals (more refined / resolved).
  const cx = 640;
  const yTop = 130;
  const yBot = 590;

  const stroke = PALETTE.unified;

  return (
    <div
      style={{
        position: 'absolute',
        inset: 0,
        opacity: fadeIn,
        transform: `scale(${breathe})`,
        transformOrigin: '640px 360px',
      }}
    >
      <svg width="1280" height="720" style={{ position: 'absolute', inset: 0 }}>
        {/* Single vertical line */}
        <line
          x1={cx} y1={yTop}
          x2={cx} y2={yBot}
          stroke={stroke}
          strokeWidth={1.5}
        />
        {/* Origin (top) */}
        <circle cx={cx} cy={yTop} r={5} fill={stroke} />
        {/* Destination (bottom) */}
        <circle cx={cx} cy={yBot} r={5} fill={stroke} />
        {/* Single optimization node at center */}
        <circle cx={cx} cy={360} r={6} fill={PALETTE.bg} stroke={stroke} strokeWidth={1.5} />
        <circle cx={cx} cy={360} r={2.5} fill={stroke} />

        {/* Calibration ticks at quarter points */}
        {[0.25, 0.75].map((p) => {
          const y = yTop + (yBot - yTop) * p;
          return (
            <g key={p} opacity={0.45}>
              <line x1={cx - 3} y1={y} x2={cx + 3} y2={y} stroke={stroke} strokeWidth={1} />
            </g>
          );
        })}

        {/* Single calm pulse traveling top to bottom */}
        <UnifiedPulse cx={cx} y1={yTop} y2={yBot} color={stroke} startTime={18.5} />

        {/* Endpoint labels — minimal monospace, to the right of the line */}
        <g
          style={{
            fontFamily: 'JetBrains Mono, ui-monospace, monospace',
            fontSize: 10,
            letterSpacing: '0.18em',
            textTransform: 'uppercase',
            fill: PALETTE.inkSoft,
          }}
          opacity={clamp((t - 19.5) / 1.5, 0, 1)}
        >
          <text x={cx + 18} y={yTop + 4}>Origin</text>
          <text x={cx + 18} y={364}>Optimize</text>
          <text x={cx + 18} y={yBot + 4}>Destination</text>
        </g>
      </svg>
    </div>
  );
}

function UnifiedPulse({ cx, y1, y2, color, startTime }) {
  const t = useTime();
  if (t < startTime) return null;
  const local = ((t - startTime) % 4.5) / 4.5;
  const y = y1 + (y2 - y1) * local;
  const trailY = Math.max(y1, y - 55);
  const fade = Math.sin(local * Math.PI);

  return (
    <g opacity={fade}>
      <line
        x1={cx} y1={trailY}
        x2={cx} y2={y}
        stroke={color}
        strokeWidth={3}
        strokeLinecap="round"
        opacity={0.4}
      />
      <circle cx={cx} cy={y} r={3.5} fill={color} />
    </g>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// CHROME — System headers, captions
// ─────────────────────────────────────────────────────────────────────────────

function SystemHeaders() {
  const t = useTime();
  // Headers appear briefly at the start, then fade as the systems begin to converge
  const a = clamp((t - 0.6) / 1.0, 0, 1);
  const b = 1 - clamp((t - 6.0) / 2.5, 0, 1);
  const opacity = a * b;

  const headerStyle = {
    position: 'absolute',
    fontFamily: 'JetBrains Mono, ui-monospace, monospace',
    fontSize: 11,
    letterSpacing: '0.22em',
    textTransform: 'uppercase',
    color: PALETTE.inkSoft,
    opacity,
    whiteSpace: 'nowrap',
  };

  return (
    <>
      <div style={{ ...headerStyle, left: 320, top: 32, transform: 'translateX(-50%)' }}>
        System A · Signal
      </div>
      <div style={{ ...headerStyle, left: 320, top: 52, transform: 'translateX(-50%)', fontSize: 10, letterSpacing: '0.14em', textTransform: 'none', color: PALETTE.inkFaint }}>
        moving information through structure
      </div>

      <div style={{ ...headerStyle, left: 960, top: 32, transform: 'translateX(-50%)' }}>
        System B · Experience
      </div>
      <div style={{ ...headerStyle, left: 960, top: 52, transform: 'translateX(-50%)', fontSize: 10, letterSpacing: '0.14em', textTransform: 'none', color: PALETTE.inkFaint }}>
        moving intent through behavior
      </div>
    </>
  );
}

function ConvergenceCaption() {
  const t = useTime();
  // Appears late, around the merge / resolve phase
  const opacity = clamp((t - 20.5) / 1.5, 0, 1) * (1 - clamp((t - 23.5) / 0.5, 0, 1));

  return (
    <div style={{
      position: 'absolute',
      left: 640,
      top: 642,
      transform: 'translateX(-50%)',
      fontFamily: 'Inter, system-ui, sans-serif',
      fontSize: 16,
      fontWeight: 400,
      letterSpacing: '0.005em',
      color: PALETTE.inkSoft,
      opacity,
      whiteSpace: 'nowrap',
      textAlign: 'center',
    }}>
      The same problem · different mediums
    </div>
  );
}

function PhaseIndicator() {
  // Tiny, unobtrusive phase marker in the corner — monospace
  const t = useTime();
  let phase = 'Two systems';
  if (t > 6.5)  phase = 'Reduction';
  if (t > 11.5) phase = 'Alignment';
  if (t > 15.5) phase = 'Convergence';
  if (t > 19.0) phase = 'Resolution';

  return (
    <div style={{
      position: 'absolute',
      left: 32, top: 32,
      fontFamily: 'JetBrains Mono, ui-monospace, monospace',
      fontSize: 10,
      letterSpacing: '0.2em',
      textTransform: 'uppercase',
      color: PALETTE.inkFaint,
    }}>
      <span style={{ color: PALETTE.inkSoft }}>{phase}</span>
    </div>
  );
}

function FrameCorners() {
  // Subtle hairline frame markers at the canvas corners — design-system feel
  const corner = (style) => (
    <div style={{
      position: 'absolute',
      width: 18, height: 18,
      borderColor: PALETTE.inkVeryFaint,
      ...style,
    }} />
  );
  return null; // keeping the canvas pure for now
}

// ─────────────────────────────────────────────────────────────────────────────
// SCENE ROOT
// ─────────────────────────────────────────────────────────────────────────────

function Scene() {
  return (
    <>
      <PhaseIndicator />
      <SystemHeaders />
      <SystemA />
      <SystemB />
      <UnifiedFlow />
      <ConvergenceCaption />
      <FrameCorners />
    </>
  );
}

window.Scene = Scene;
