// Main composition: orchestrates the full ~52s video.
//
// Stage is 1920×1080. We treat the canvas as a "world" and use a CSS transform
// on a wrapper to fake a camera (translate + scale + rotate). Each scene is
// positioned absolutely at known world coordinates; the camera target is a
// time-driven function that lerps between key shots.

const STAGE_W = 1920;
const STAGE_H = 1080;
const DURATION = 58;

// World coordinates for the two pages laid out in space, then a third "diagram"
// staging area below them. Camera will pan/zoom across these.
const WORLD = {
  // Pages laid horizontally, large 760×560 each
  badPagePos:  { x: 200,  y: 380 },   // top-left of bad page in world coords
  goodPagePos: { x: 1180, y: 380 },   // top-left of good page in world coords
  // Diagram lives in its own region offscreen-bottom; we'll cut to it in scene 5
};

// Camera state at key times: each entry { t, x, y, scale } where (x,y) is the
// world-point that should appear at the *center* of the viewport.
//
// Camera lerps with easeInOutCubic between adjacent keys.

const CAMERA_KEYS = [
  // Cold open: pull back from very-zoomed center
  { t: 0.0,  x: 960,  y: 660,  scale: 1.4,  rot: 0   },
  { t: 4.5,  x: 960,  y: 660,  scale: 0.92, rot: 0   },
  // Title card: stay centered, no movement
  { t: 6.5,  x: 960,  y: 660,  scale: 0.92, rot: 0   },
  { t: 11.5, x: 960,  y: 660,  scale: 0.95, rot: 0   },
  // Dolly into bad page
  { t: 13.0, x: 580,  y: 660,  scale: 1.20, rot: -1  },
  { t: 22.5, x: 580,  y: 660,  scale: 1.30, rot: -1  },
  // Pan across to good page
  { t: 25.5, x: 1560, y: 660,  scale: 1.18, rot: 1   },
  // Slow Ken-Burns over good page while letters land
  { t: 39.0, x: 1560, y: 660,  scale: 1.32, rot: 1   },
  // Pull way back — both pages diminish, prep diagram
  { t: 41.5, x: 1070, y: 660,  scale: 0.55, rot: 0   },
  // Cut to diagram region (we'll fade pages out)
  { t: 43.5, x: 960,  y: 540,  scale: 1.0,  rot: 0   },
  { t: 50.0, x: 960,  y: 540,  scale: 1.05, rot: 0   },
  { t: 58.0, x: 960,  y: 540,  scale: 1.05, rot: 0   },
];

function lerpCamera(t) {
  const keys = CAMERA_KEYS;
  if (t <= keys[0].t) return keys[0];
  if (t >= keys[keys.length - 1].t) return keys[keys.length - 1];
  for (let i = 0; i < keys.length - 1; i++) {
    const a = keys[i], b = keys[i + 1];
    if (t >= a.t && t <= b.t) {
      const local = (t - a.t) / (b.t - a.t);
      const eased = Easing.easeInOutCubic(local);
      return {
        x: a.x + (b.x - a.x) * eased,
        y: a.y + (b.y - a.y) * eased,
        scale: a.scale + (b.scale - a.scale) * eased,
        rot: a.rot + (b.rot - a.rot) * eased,
      };
    }
  }
  return keys[keys.length - 1];
}

// ── Scene wrapper that updates the data-screen-label with current timestamp ──

function TimestampLabel() {
  const time = useTime();
  const sec = Math.floor(time);
  React.useEffect(() => {
    const root = document.querySelector('[data-video-root]');
    if (root) root.setAttribute('data-screen-label', `t=${sec}s`);
  }, [sec]);
  return null;
}

// ── Title cards (cold open + close) ─────────────────────────────────────────

function ColdOpenLabel({ sprite }) {
  const t = sprite.localTime;
  const op = clamp((t - 0.5) / 0.6, 0, 1) - clamp((t - 4.0) / 0.5, 0, 1);

  return (
    <div style={{
      position: 'absolute',
      left: 960, top: 1000,
      transform: 'translate(-50%, -50%)',
      opacity: clamp(op, 0, 1),
      fontFamily: 'Inter, sans-serif',
      fontSize: 13,
      letterSpacing: '0.32em',
      color: 'rgba(29,29,31,0.42)',
      textTransform: 'uppercase',
      fontWeight: 500,
    }}>
      Same query · "best protein powder for runners"
    </div>
  );
}

function TitleCard({ sprite }) {
  const t = sprite.localTime;
  // Two-line magazine title: line 1 0..1.4, line 2 1.4..3.0, hold, fade out
  const line1Op = clamp((t - 0.0) / 0.6, 0, 1) - clamp((t - 4.5) / 0.4, 0, 1);
  const line2Op = clamp((t - 1.6) / 0.6, 0, 1) - clamp((t - 4.5) / 0.4, 0, 1);
  const eyebrowOp = clamp((t - 0.0) / 0.5, 0, 1) - clamp((t - 4.5) / 0.4, 0, 1);

  return (
    <div style={{
      position: 'absolute',
      left: 960, top: 660,
      transform: 'translate(-50%, -50%)',
      textAlign: 'center',
      pointerEvents: 'none',
    }}>
      <div style={{
        display: 'inline-flex', alignItems: 'center', gap: 10,
        fontSize: 13,
        letterSpacing: '0.18em',
        textTransform: 'uppercase',
        color: '#6E6E73',
        fontWeight: 500,
        fontFamily: 'Inter, sans-serif',
        opacity: clamp(eyebrowOp, 0, 1),
        marginBottom: 28,
      }}>
        <span style={{ width: 7, height: 7, borderRadius: 4, background: '#30D158' }} />
        SEO · Field guide
      </div>
      <div style={{
        fontFamily: 'Inter, sans-serif',
        fontSize: 88,
        fontWeight: 800,
        letterSpacing: '-0.04em',
        lineHeight: 1.05,
        color: '#1D1D1F',
        opacity: clamp(line1Op, 0, 1),
        transform: `translateY(${(1 - clamp(line1Op, 0, 1)) * 18}px)`,
      }}>
        Two pages. Same topic.
      </div>
      <div style={{
        fontFamily: 'Newsreader, serif',
        fontStyle: 'italic',
        fontWeight: 500,
        fontSize: 96,
        letterSpacing: '-0.04em',
        lineHeight: 1.05,
        color: '#0071E3',
        marginTop: 6,
        opacity: clamp(line2Op, 0, 1),
        transform: `translateY(${(1 - clamp(line2Op, 0, 1)) * 18}px)`,
      }}>
        One ranks.
      </div>
    </div>
  );
}

function ActLabel({ sprite, eyebrow, title, subtitle, color = '#0071E3', side = 'left' }) {
  const t = sprite.localTime;
  const dur = sprite.duration;
  // Long fade in (0.6s), generous hold, smooth fade out (0.8s before end)
  const op = clamp(t / 0.6, 0, 1) - clamp((t - (dur - 0.8)) / 0.8, 0, 1);
  const slideIn = clamp(t / 0.6, 0, 1);
  return (
    <div style={{
      position: 'absolute',
      [side]: 90,
      top: 90,
      opacity: clamp(op, 0, 1),
      transform: `translateY(${(1 - slideIn) * 14}px)`,
      fontFamily: 'Inter, sans-serif',
      maxWidth: 620,
      textAlign: side === 'right' ? 'right' : 'left',
      // Soft backdrop card so the subtitle reads cleanly above busy page mocks
      padding: '32px 38px 36px',
      background: 'rgba(251,251,253,0.82)',
      backdropFilter: 'blur(14px)',
      WebkitBackdropFilter: 'blur(14px)',
      borderRadius: 4,
      boxShadow: '0 1px 0 rgba(0,0,0,0.04)',
    }}>
      <div style={{
        fontSize: 13,
        letterSpacing: '0.24em',
        textTransform: 'uppercase',
        color: color,
        fontWeight: 600,
        marginBottom: 16,
      }}>{eyebrow}</div>
      <div style={{
        fontFamily: 'Newsreader, serif',
        fontStyle: 'italic',
        fontWeight: 500,
        fontSize: 60,
        letterSpacing: '-0.025em',
        lineHeight: 1.05,
        color: '#1D1D1F',
      }}>{title}</div>
      {subtitle && (
        <div style={{
          fontSize: 19,
          color: '#3D3D44',
          marginTop: 18,
          maxWidth: 540,
          lineHeight: 1.45,
          marginLeft: side === 'right' ? 'auto' : 0,
        }}>{subtitle}</div>
      )}
    </div>
  );
}

function CloseCard({ sprite }) {
  const t = sprite.localTime;
  const op = clamp((t - 0.4) / 0.7, 0, 1);
  const sigOp = clamp((t - 2.0) / 0.6, 0, 1);

  return (
    <div style={{
      position: 'absolute',
      left: 960, top: 540,
      transform: 'translate(-50%, -50%)',
      textAlign: 'center',
      pointerEvents: 'none',
    }}>
      <div style={{
        fontFamily: 'Newsreader, serif',
        fontStyle: 'italic',
        fontWeight: 500,
        fontSize: 58,
        letterSpacing: '-0.025em',
        color: '#1D1D1F',
        opacity: op,
        transform: `translateY(${(1 - op) * 12}px)`,
      }}>
        Reputation, made <span style={{ color: '#0071E3' }}>crawlable</span>.
      </div>
      <div style={{
        marginTop: 18,
        display: 'inline-flex',
        alignItems: 'center', gap: 10,
        fontFamily: 'Inter, sans-serif',
        fontSize: 14,
        letterSpacing: '0.22em',
        textTransform: 'uppercase',
        color: '#6E6E73',
        fontWeight: 500,
        opacity: sigOp,
      }}>
        <span style={{
          display: 'inline-block',
          width: 26, height: 26,
          background: `url(${(window.__resources && window.__resources.logoSvg) || 'assets/logo.svg'}) center/contain no-repeat`,
        }} />
        Anthony Ligyat · anthonyligyat.com
      </div>
    </div>
  );
}

// ── The Camera + scene container ────────────────────────────────────────────

function CameraWorld({ children }) {
  const time = useTime();
  const cam = lerpCamera(time);

  // Map world (x,y,scale) so that world-point (cam.x, cam.y) lands at viewport
  // center (STAGE_W/2, STAGE_H/2).
  const tx = STAGE_W / 2 - cam.x * cam.scale;
  const ty = STAGE_H / 2 - cam.y * cam.scale;

  return (
    <div style={{
      position: 'absolute', inset: 0,
      transform: `translate(${tx}px, ${ty}px) scale(${cam.scale}) rotate(${cam.rot}deg)`,
      transformOrigin: '0 0',
      willChange: 'transform',
    }}>
      {children}
    </div>
  );
}

// Scene 5+ (diagram): rendered as overlay above world; pages dim out.
function DiagramOverlay({ sprite }) {
  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: '#FBFBFD',
    }}>
      <EEATDiagram sprite={sprite} />
      <Sprite start={50.5} end={DURATION}>
        {(s) => <CloseCard sprite={s} />}
      </Sprite>
    </div>
  );
}

// Background grid that lives inside the world at the page-stage region
function PageStageBackdrop() {
  return (
    <div style={{
      position: 'absolute',
      left: 0, top: 0,
      width: STAGE_W, height: STAGE_H,
      pointerEvents: 'none',
    }}>
      <svg width={STAGE_W} height={STAGE_H} style={{ display: 'block' }}>
        <defs>
          <pattern id="bg-grid-dots" width="40" height="40" patternUnits="userSpaceOnUse">
            <circle cx="1" cy="1" r="1" fill="#D8D8DE" fillOpacity="0.55"/>
          </pattern>
        </defs>
        <rect width={STAGE_W} height={STAGE_H} fill="url(#bg-grid-dots)" />
      </svg>
    </div>
  );
}

// SERP chip floating above the two pages — gives narrative context
function SerpChip() {
  return (
    <div style={{
      position: 'absolute',
      left: STAGE_W / 2, top: 200,
      transform: 'translate(-50%, -50%)',
      display: 'inline-flex',
      alignItems: 'center', gap: 12,
      padding: '14px 22px',
      borderRadius: 999,
      background: '#fff',
      border: '1px solid #E8E8ED',
      boxShadow: '0 12px 40px rgba(0,0,0,0.06)',
      fontFamily: 'Inter, sans-serif',
      fontSize: 18,
      color: '#1D1D1F',
    }}>
      <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
        <circle cx="10" cy="10" r="6.5" stroke="#86868B" strokeWidth="1.6"/>
        <line x1="14.8" y1="14.8" x2="19" y2="19" stroke="#86868B" strokeWidth="1.6" strokeLinecap="round"/>
      </svg>
      <span style={{ color: '#86868B' }}>best protein powder for runners</span>
      <span style={{
        fontSize: 11,
        fontFamily: 'JetBrains Mono, ui-monospace, monospace',
        color: '#0A7B3E',
        background: 'rgba(16,152,86,0.08)',
        padding: '3px 8px',
        borderRadius: 4,
        letterSpacing: '0.06em',
      }}>SERP</span>
    </div>
  );
}

// ── Main composition ────────────────────────────────────────────────────────

function Composition() {
  return (
    <>
      <TimestampLabel />

      {/* === Pages live inside the camera world === */}
      <Sprite start={0} end={42.0} keepMounted>
        {(sceneSprite) => {
          // Scene-wide fade out of the world before diagram cuts in
          const time = useTime();
          const fade = clamp((42.0 - time) / 1.0, 0, 1);
          return (
            <div style={{ position: 'absolute', inset: 0, opacity: fade }}>
              <CameraWorld>
                <PageStageBackdrop />
                <SerpChip />

                {/* Bad page */}
                <Sprite start={0} end={42.0} keepMounted>
                  {(s) => (
                    <div style={{
                      position: 'absolute',
                      left: WORLD.badPagePos.x, top: WORLD.badPagePos.y,
                      transform: 'rotate(-1.2deg)',
                      filter: time > 24 ? `grayscale(${clamp((time - 24) / 1.5, 0, 0.6)}) brightness(${1 - clamp((time - 24) / 1.5, 0, 0.25)})` : 'none',
                      transition: 'filter 0.4s',
                    }}>
                      <BadPage sprite={s} />
                    </div>
                  )}
                </Sprite>

                {/* Good page */}
                <Sprite start={0} end={42.0} keepMounted>
                  {(s) => (
                    <div style={{
                      position: 'absolute',
                      left: WORLD.goodPagePos.x, top: WORLD.goodPagePos.y,
                      transform: 'rotate(1deg)',
                      filter: time < 24 ? `grayscale(0.4) brightness(0.85)` : 'none',
                      transition: 'filter 0.6s',
                    }}>
                      <GoodPage
                        sprite={s}
                        showSchema={time > 30}
                        showLetterChips={time > 31}
                        letterTime={Math.max(0, time - 31)}
                      />
                    </div>
                  )}
                </Sprite>
              </CameraWorld>
            </div>
          );
        }}
      </Sprite>

      {/* === Title card (lives in viewport, not world) === */}
      <Sprite start={6.5} end={11.5}>
        {(s) => {
          // Dim the world during the title card so type pops
          const t = s.localTime;
          const op = clamp(t / 0.6, 0, 1) - clamp((t - 4.0) / 0.6, 0, 1);
          return (
            <>
              <div style={{
                position: 'absolute', inset: 0,
                background: '#FBFBFD',
                opacity: clamp(op * 0.85, 0, 0.85),
                pointerEvents: 'none',
              }} />
              <TitleCard sprite={s} />
            </>
          );
        }}
      </Sprite>

      {/* Cold open caption */}
      <Sprite start={0.5} end={4.5}>
        {(s) => <ColdOpenLabel sprite={s} />}
      </Sprite>

      {/* Act label: the bad page */}
      <Sprite start={13.5} end={19.5}>
        {(s) => (
          <ActLabel
            sprite={s}
            eyebrow="Page A"
            title={<>Anonymous. <span style={{ color: '#E5484D' }}>Unverifiable.</span></>}
            subtitle="No author, no dates, no sources. Google can't tell who's accountable for this — and neither can the reader."
            color="#E5484D"
          />
        )}
      </Sprite>

      {/* Act label: the good page */}
      <Sprite start={25.5} end={32.0}>
        {(s) => (
          <ActLabel
            sprite={s}
            eyebrow="Page B"
            title={<>Same topic. <span style={{ color: '#0071E3' }}>Different signals.</span></>}
            subtitle="A real person. A real track record. Sources cited. Schema wired up. Now watch where Google's framework lands."
            color="#0071E3"
          />
        )}
      </Sprite>

      {/* Mini caption while letters arrive */}
      <Sprite start={31.5} end={40.5}>
        {(s) => {
          const t = s.localTime;
          const op = clamp(t / 0.5, 0, 1) - clamp((t - 8.5) / 0.5, 0, 1);
          return (
            <div style={{
              position: 'absolute',
              left: 80, bottom: 100,
              opacity: clamp(op, 0, 1),
              fontFamily: 'Inter, sans-serif',
              maxWidth: 480,
            }}>
              <div style={{
                fontSize: 12,
                letterSpacing: '0.22em',
                textTransform: 'uppercase',
                color: '#0071E3',
                fontWeight: 600,
                marginBottom: 10,
              }}>The framework</div>
              <div style={{
                fontFamily: 'Newsreader, serif',
                fontStyle: 'italic',
                fontWeight: 500,
                fontSize: 38,
                letterSpacing: '-0.02em',
                lineHeight: 1.1,
                color: '#1D1D1F',
              }}>Experience. Expertise.<br/>Authoritativeness. Trust.</div>
            </div>
          );
        }}
      </Sprite>

      {/* === Diagram scene === */}
      <Sprite start={42.5} end={DURATION}>
        {(s) => <DiagramOverlay sprite={s} />}
      </Sprite>

      {/* Subtle vignette across the whole stage for editorial feel */}
      <div style={{
        position: 'absolute', inset: 0,
        pointerEvents: 'none',
        background: 'radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,0.08) 100%)',
      }} />
    </>
  );
}

Object.assign(window, { Composition, DURATION, STAGE_W, STAGE_H });
