// TenorMD training — director: camera rig, section scripts, titles, Stage.
const React = window.React;
const { Stage, useTime, interpolate, Easing, clamp } = window;
const { Backdrop, WindowFrame, Cursor, CaptionBar, ChapterStrip, CornerStep, Icon, WIN } = window.TM;
const S = window.TM.screens;

/* ---- typing helper ------------------------------------------------------- */
function typed(text, t, t0, per) {
  const n = clamp(Math.floor((t - t0) / per) + 1, 0, text.length);
  return t < t0 ? '' : text.slice(0, n);
}
const dots = (text, t, t0, per) => '•'.repeat(typed(text, t, t0, per).length);

/* ---- camera scene (refs → measured targets → camera + cursor) ------------ */
const TargetCtx = React.createContext(null);
function CameraScene({ config, localTime }) {
  const els = React.useRef({});
  const aRef = React.useRef(), bRef = React.useRef(), cRef = React.useRef();
  const [pos, setPos] = React.useState(null);
  const reg = React.useCallback((name) => (el) => { if (el) els.current[name] = el; }, []);

  const state = config.state ? config.state(localTime) : {};
  const layoutKey = config.layoutKey ? config.layoutKey(state) : '';

  React.useLayoutEffect(() => {
    if (!aRef.current || !bRef.current || !cRef.current) return;
    const A = aRef.current.getBoundingClientRect();
    const B = bRef.current.getBoundingClientRect();
    const C = cRef.current.getBoundingClientRect();
    const sx = (B.left - A.left) / 1000 || 1, sy = (C.top - A.top) / 1000 || 1;
    const m = {};
    Object.entries(els.current).forEach(([n, el]) => {
      if (!el || !el.getBoundingClientRect) return;
      const r = el.getBoundingClientRect();
      const left = (r.left - A.left) / sx, top = (r.top - A.top) / sy;
      const w = r.width / sx, h = r.height / sy;
      m[n] = { left, top, w, h, x: left + w / 2, y: top + h / 2 };
    });
    setPos((prev) => ({ ...(prev || {}), ...m }));
  }, [config.id, layoutKey]);

  const resolve = (target) => {
    if (!target) return null;
    if (Array.isArray(target)) return { x: target[0], y: target[1] };
    if (typeof target === 'object') return target;
    const p = pos && pos[target];
    return p ? { x: p.x, y: p.y } : null;
  };

  const def = { x: 960, y: 360, k: 1 };
  let fx = def.x, fy = def.y, k = def.k, cx = 960, cy = 560, cVis = false;
  const cam = config.camera || [];
  if (cam.length) {
    const ts = cam.map((b) => b.t);
    fx = interpolate(ts, cam.map((b) => { const r = resolve(b.focus); return r ? r.x : def.x; }), Easing.easeInOutCubic)(localTime);
    fy = interpolate(ts, cam.map((b) => { const r = resolve(b.focus); return r ? r.y : def.y; }), Easing.easeInOutCubic)(localTime);
    k = interpolate(ts, cam.map((b) => (b.k != null ? b.k : 1)), Easing.easeInOutCubic)(localTime);
  }
  const cur = config.cursor || [];
  let clicks = [];
  if (cur.length) {
    const ts = cur.map((b) => b.t);
    cx = interpolate(ts, cur.map((b) => { const r = resolve(b.at); return r ? r.x : 960; }), Easing.easeInOutCubic)(localTime);
    cy = interpolate(ts, cur.map((b) => { const r = resolve(b.at); return r ? r.y : 560; }), Easing.easeInOutCubic)(localTime);
    cVis = localTime >= ts[0] - 0.05;
    clicks = cur.filter((b) => b.click).map((b) => b.t);
  }
  // gentle idle breathing so a held frame never reads as frozen
  k *= 1 + 0.005 * Math.sin(localTime * 0.45);
  fy += 2.5 * Math.sin(localTime * 0.3);

  const tx = 960 - fx * k, ty = 540 - fy * k;
  const mk = (x, y) => ({ position: 'absolute', left: x, top: y, width: 1, height: 1, pointerEvents: 'none' });
  const ScreenComp = config.screen;

  return React.createElement('div', { style: { position: 'absolute', inset: 0, overflow: 'hidden' } },
    React.createElement('div', { style: { position: 'absolute', left: 0, top: 0, width: 1920, height: 1080, transformOrigin: '0 0', transform: `translate(${tx}px, ${ty}px) scale(${k})`, willChange: 'transform' } },
      React.createElement('div', { ref: aRef, style: mk(0, 0) }),
      React.createElement('div', { ref: bRef, style: mk(1000, 0) }),
      React.createElement('div', { ref: cRef, style: mk(0, 1000) }),
      React.createElement(Backdrop),
      React.createElement(WindowFrame, null, React.createElement(ScreenComp, { reg, state, local: localTime })),
      React.createElement(Cursor, { x: cx, y: cy, k, localTime, clicks, visible: cVis }),
    ),
  );
}

/* ---- title + intro + outro ----------------------------------------------- */
function fadeHold(localTime, dur, inT = 0.6, outT = 0.6) {
  const fi = clamp(localTime / inT, 0, 1);
  const fo = clamp((dur - localTime) / outT, 0, 1);
  return { op: Math.min(fi, fo), rise: (1 - Easing.easeOutCubic(fi)) * 22 };
}
function emph(text) {
  return String(text).split(/(\*[^*]+\*)/g).filter(Boolean).map((seg, i) =>
    seg.startsWith('*') ? React.createElement('em', { key: i, style: { fontStyle: 'italic', color: 'var(--harbor-500)' } }, seg.slice(1, -1)) : React.createElement('span', { key: i }, seg));
}
function TitleScene({ config, localTime }) {
  const dur = config.end - config.start;
  const { op, rise } = fadeHold(localTime, dur);
  return React.createElement('div', { style: { position: 'absolute', inset: 0, overflow: 'hidden' } },
    React.createElement('div', { style: { position: 'absolute', left: 0, top: 0, width: 1920, height: 1080 } }, React.createElement(Backdrop)),
    React.createElement('div', { style: { position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 40, opacity: op, transform: `translateY(${rise}px)` } },
      React.createElement('div', { style: { font: 'var(--type-eyebrow)', letterSpacing: 'var(--tracking-caps)', textTransform: 'uppercase', color: 'var(--clay-600)', fontSize: 18 } }, config.kicker),
      React.createElement('div', { style: { fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 80, lineHeight: 1.08, color: 'var(--text-primary)', letterSpacing: 'var(--tracking-tight)', textAlign: 'center', maxWidth: 1300 } }, emph(config.line)),
      React.createElement('div', { style: { marginTop: 18 } }, React.createElement(ChapterStrip, { current: config.step })),
    ),
  );
}
function IntroScene({ config, localTime }) {
  const dur = config.end - config.start;
  const { op, rise } = fadeHold(localTime, dur, 0.8, 0.6);
  return React.createElement('div', { style: { position: 'absolute', inset: 0, overflow: 'hidden' } },
    React.createElement('div', { style: { position: 'absolute', left: 0, top: 0, width: 1920, height: 1080 } }, React.createElement(Backdrop)),
    React.createElement('div', { style: { position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 30, opacity: op, transform: `translateY(${rise}px)` } },
      React.createElement('img', { src: 'assets/logo-wordmark.svg', alt: 'TenorMD', style: { height: 58, marginBottom: 6 } }),
      React.createElement('div', { style: { font: 'var(--type-eyebrow)', letterSpacing: 'var(--tracking-caps)', textTransform: 'uppercase', color: 'var(--clay-600)', fontSize: 18 } }, 'Getting started · admin walkthrough'),
      React.createElement('div', { style: { fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 76, lineHeight: 1.08, color: 'var(--text-primary)', letterSpacing: 'var(--tracking-tight)', textAlign: 'center', maxWidth: 1280 } }, emph('Set up candid, anonymous feedback — *built for coaching.*')),
      React.createElement('div', { style: { marginTop: 22 } }, React.createElement(ChapterStrip, { current: 0 })),
    ),
  );
}
function OutroScene({ config, localTime }) {
  const dur = config.end - config.start;
  const { op, rise } = fadeHold(localTime, dur, 0.8, 0.8);
  return React.createElement('div', { style: { position: 'absolute', inset: 0, overflow: 'hidden' } },
    React.createElement('div', { style: { position: 'absolute', left: 0, top: 0, width: 1920, height: 1080 } }, React.createElement(Backdrop)),
    React.createElement('div', { style: { position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 34, opacity: op, transform: `translateY(${rise}px)` } },
      React.createElement('div', { style: { fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 74, lineHeight: 1.08, color: 'var(--text-primary)', letterSpacing: 'var(--tracking-tight)', textAlign: 'center', maxWidth: 1200 } }, emph('Gather the feedback. *Coach from it.*')),
      React.createElement('div', { style: { marginTop: 8 } }, React.createElement(ChapterStrip, { current: 5 })),
      React.createElement('img', { src: 'assets/logo-wordmark.svg', alt: 'TenorMD', style: { height: 40, marginTop: 26 } }),
      React.createElement('div', { style: { font: 'var(--type-mono)', fontSize: 14, color: 'var(--text-muted)', letterSpacing: '0.04em' } }, 'app.tenormd.com'),
    ),
  );
}

/* ---- section scripts ====================================================== */
const SECTIONS = [
  { id: 'intro', kind: 'intro', start: 0, end: 4.5 },

  { id: 't1', kind: 'title', start: 4.5, end: 7.5, step: 1, kicker: 'Step 1', line: 'Sign in' },
  {
    id: 'signin', kind: 'action', start: 7.5, end: 15.5, step: 1, screen: S.SignInScreen,
    state: (t) => ({
      email: typed('morgan.hale@barkley.org', t, 0.8, 0.07),
      pw: dots('demo1234x', t, 3.4, 0.12),
      focus: t >= 0.8 && t < 2.8 ? 'email' : t >= 3.3 && t < 4.7 ? 'pw' : null,
    }),
    camera: [{ t: 0, focus: 'signin.card', k: 0.92 }, { t: 1.0, focus: 'signin.email', k: 1.16 }, { t: 3.2, focus: 'signin.pw', k: 1.16 }, { t: 5.0, focus: 'signin.btn', k: 1.2 }, { t: 6.2, focus: 'signin.card', k: 0.98 }, { t: 8, focus: 'signin.card', k: 0.98 }],
    cursor: [{ t: 0.3, at: [1180, 320] }, { t: 1.0, at: 'signin.email' }, { t: 3.2, at: 'signin.pw' }, { t: 5.0, at: 'signin.btn' }, { t: 5.5, at: 'signin.btn', click: true }, { t: 8, at: 'signin.btn' }],
    captions: [{ start: 0.3, end: 4.9, kicker: 'Step 1 · Sign in', text: 'Sign in with your work email.' }, { start: 5.5, end: 7.8, kicker: 'Step 1 · Sign in', text: 'You land on your organization’s dashboard.' }],
  },

  { id: 't2', kind: 'title', start: 15.5, end: 18.5, step: 2, kicker: 'Step 2', line: 'Add your providers' },
  {
    id: 'dash', kind: 'action', start: 18.5, end: 32.5, step: 2, screen: S.DashboardScreen,
    layoutKey: (s) => `${s.results ? 1 : 0}${s.added ? 1 : 0}`,
    state: (t) => ({
      fn: typed('Jane', t, 1.6, 0.11),
      ln: typed('Okafor', t, 2.7, 0.1),
      st: typed('OH', t, 4.0, 0.13),
      searching: t > 4.7 && t <= 5.2,
      results: t > 5.2,
      added: t > 7.9,
      focus: t >= 1.6 && t < 2.5 ? 'fn' : t >= 2.7 && t < 3.7 ? 'ln' : t >= 4.0 && t < 4.5 ? 'st' : null,
    }),
    camera: [{ t: 0, focus: 'dash.head', k: 0.84 }, { t: 1.8, focus: 'dash.lookup', k: 1.04 }, { t: 8.2, focus: 'dash.lookup', k: 1.06 }, { t: 9.6, focus: 'dash.newRow', k: 1.05 }, { t: 11.8, focus: 'dash.table', k: 0.92 }, { t: 14, focus: 'dash.table', k: 0.92 }],
    cursor: [{ t: 0.3, at: [1320, 360] }, { t: 1.6, at: 'dash.fn' }, { t: 2.7, at: 'dash.ln' }, { t: 4.0, at: 'dash.st' }, { t: 4.6, at: 'dash.searchBtn' }, { t: 4.9, at: 'dash.searchBtn', click: true }, { t: 5.8, at: 'dash.result' }, { t: 7.7, at: 'dash.result' }, { t: 8.0, at: 'dash.result', click: true }, { t: 8.7, at: 'dash.newRow' }, { t: 14, at: 'dash.newRow' }],
    captions: [{ start: 0.3, end: 4.6, kicker: 'Step 2 · Add providers', text: 'Look a clinician up by name and state — or by *NPI*.' }, { start: 5.1, end: 7.7, kicker: 'Step 2 · Add providers', text: 'TenorMD pulls the verified name, credential and specialty from *NPPES*.' }, { start: 8.2, end: 13.4, kicker: 'Step 2 · Add providers', text: 'Select the match and they join the registry — ready for a review cycle.' }],
  },

  { id: 't3', kind: 'title', start: 32.5, end: 35.5, step: 3, kicker: 'Step 3', line: 'Run a cycle, *invite reviewers*' },
  {
    id: 'prov', kind: 'action', start: 35.5, end: 58.5, step: 3, screen: S.ReviewCycleScreen,
    layoutKey: (s) => `${s.peerQueued ? 1 : 0}${s.refQueued ? 1 : 0}${s.selfSent ? 1 : 0}`,
    state: (t) => ({
      peerEmails: typed('k.rao@cardiospec.org\nm.chen@heartcare.org', t, 4.0, 0.045),
      peerQueued: t > 8.0,
      refEmails: typed('p.shaw@northside.health\nj.lee@primarycare.org', t, 11.2, 0.045),
      refQueued: t > 15.0,
      selfEmail: typed('jordan.avery@barkley.org', t, 17.4, 0.06),
      selfSent: t > 20.0,
      focus: t >= 4.0 && t < 7.4 ? 'peer' : t >= 11.2 && t < 14.6 ? 'ref' : t >= 17.4 && t < 19.4 ? 'self' : null,
    }),
    camera: [{ t: 0, focus: 'cyc.head', k: 0.8 }, { t: 2.6, focus: 'cyc.head', k: 0.94 }, { t: 3.4, focus: 'cyc.peer', k: 1.0 }, { t: 4.4, focus: 'cyc.peerEmails', k: 1.15 }, { t: 7.6, focus: 'cyc.peerInvite', k: 1.18 }, { t: 9.2, focus: 'cyc.ref', k: 1.0 }, { t: 11.6, focus: 'cyc.refEmails', k: 1.15 }, { t: 14.6, focus: 'cyc.refInvite', k: 1.18 }, { t: 16.4, focus: 'cyc.self', k: 1.02 }, { t: 17.8, focus: 'cyc.selfEmail', k: 1.16 }, { t: 20.4, focus: 'cyc.self', k: 0.95 }, { t: 23, focus: 'cyc.self', k: 0.95 }],
    cursor: [{ t: 0.4, at: [1100, 360] }, { t: 4.0, at: 'cyc.peerEmails' }, { t: 7.4, at: 'cyc.peerInvite' }, { t: 7.8, at: 'cyc.peerInvite', click: true }, { t: 11.0, at: 'cyc.refEmails' }, { t: 14.4, at: 'cyc.refInvite' }, { t: 14.8, at: 'cyc.refInvite', click: true }, { t: 17.4, at: 'cyc.selfEmail' }, { t: 19.6, at: 'cyc.selfSend' }, { t: 20.0, at: 'cyc.selfSend', click: true }, { t: 23, at: 'cyc.self' }],
    captions: [{ start: 0.4, end: 3.2, kicker: 'Step 3 · Review cycle', text: 'Each reviewer group is *anonymous* and unlocks on its own.' }, { start: 3.6, end: 8.0, kicker: 'Step 3 · Add reviewers', text: 'Drop the peer reviewers’ emails in and queue their invites.' }, { start: 9.2, end: 14.8, kicker: 'Step 3 · Add reviewers', text: 'Do the same for referring providers — one group at a time.' }, { start: 16.2, end: 22.6, kicker: 'Step 3 · Add reviewers', text: 'Send the provider a *self-assessment* to compare with how others see them.' }],
  },
  {
    id: 'outbox', kind: 'action', start: 58.5, end: 66.5, step: 3, screen: S.OutboxScreen,
    layoutKey: (s) => `${s.sent ? 1 : 0}`,
    state: (t) => ({ sent: t > 4.2 }),
    camera: [{ t: 0, focus: 'ob.head', k: 0.84 }, { t: 1.6, focus: 'ob.emailCard', k: 1.0 }, { t: 3.4, focus: 'ob.sendBtn', k: 1.2 }, { t: 4.7, focus: 'ob.row0', k: 1.16 }, { t: 6.6, focus: 'ob.emailCard', k: 0.96 }, { t: 8, focus: 'ob.emailCard', k: 0.96 }],
    cursor: [{ t: 0.4, at: [1100, 360] }, { t: 2.0, at: 'ob.emailCard' }, { t: 3.6, at: 'ob.sendBtn' }, { t: 4.1, at: 'ob.sendBtn', click: true }, { t: 5.2, at: 'ob.row0' }, { t: 8, at: 'ob.row0' }],
    captions: [{ start: 0.3, end: 3.7, kicker: 'Step 3 · Send invites', text: 'Queued invites wait in your *Outbox.*' }, { start: 4.3, end: 7.8, kicker: 'Step 3 · Send invites', text: 'Hit *Send all queued* and the invitations actually go out.' }],
  },
  {
    id: 'rev', kind: 'action', start: 66.5, end: 75, step: 3, screen: S.ReviewerScreen,
    state: (t) => ({ pick: t > 2.5 ? 4 : 0 }),
    camera: [{ t: 0, focus: 'rev.card', k: 0.82 }, { t: 1.2, focus: 'rev.q', k: 1.12 }, { t: 2.4, focus: 'rev.q', k: 1.16 }, { t: 4.4, focus: 'rev.submit', k: 1.18 }, { t: 6.0, focus: 'rev.card', k: 0.9 }, { t: 8.5, focus: 'rev.card', k: 0.9 }],
    cursor: [{ t: 0.4, at: [980, 540] }, { t: 1.6, at: 'rev.q' }, { t: 2.5, at: 'rev.q', click: true }, { t: 4.5, at: 'rev.submit' }, { t: 5.1, at: 'rev.submit', click: true }, { t: 8.5, at: 'rev.submit' }],
    captions: [{ start: 0.3, end: 2.6, kicker: 'What reviewers see', text: 'Reviewers rate the provider against peers — no grades, no numbers.' }, { start: 3.0, end: 8.0, kicker: 'Built for trust', text: 'Answers are anonymous and reported *only in aggregate.*' }],
  },

  { id: 't4', kind: 'title', start: 75, end: 78, step: 4, kicker: 'Step 4', line: 'Coach from the *report*' },
  {
    id: 'rep', kind: 'action', start: 78, end: 99, step: 4, screen: S.ReportScreen,
    camera: [{ t: 0, focus: 'rep.standing', k: 0.8 }, { t: 1.4, focus: 'rep.standing', k: 1.1 }, { t: 4.6, focus: 'rep.trend', k: 1.04 }, { t: 8.6, focus: 'rep.questions', k: 0.96 }, { t: 11.0, focus: 'rep.lowRow', k: 1.24 }, { t: 14.4, focus: 'rep.comments', k: 0.98 }, { t: 17.4, focus: 'rep.comments', k: 1.04 }, { t: 21, focus: 'rep.comments', k: 1.04 }],
    cursor: [{ t: 0.5, at: [1120, 360] }, { t: 1.6, at: 'rep.standing' }, { t: 4.8, at: 'rep.trend' }, { t: 9.0, at: 'rep.questions' }, { t: 11.2, at: 'rep.lowRow' }, { t: 14.6, at: 'rep.comments' }, { t: 21, at: 'rep.comments' }],
    captions: [{ start: 0.4, end: 4.4, kicker: 'Step 4 · Coaching', text: 'The report opens with one plain-spoken standing — not a grade.' }, { start: 4.8, end: 8.4, kicker: 'Step 4 · Coaching', text: 'The trend answers the real question: *is this provider improving?*' }, { start: 8.8, end: 13.8, kicker: 'Step 4 · Coaching', text: 'Per-question bands surface the one thing worth a conversation.' }, { start: 14.4, end: 20.6, kicker: 'Step 4 · Coaching', text: 'Anonymous comments give you the words to *coach from.*' }],
  },

  { id: 'outro', kind: 'outro', start: 99, end: 106 },
];
const DURATION = SECTIONS[SECTIONS.length - 1].end;

/* ---- director ------------------------------------------------------------ */
function Director() {
  const t = useTime();
  const tl = window.useTimeline();
  window.__seek = (x) => { tl.setPlaying(false); tl.setTime(x); };
  const active = SECTIONS.find((s) => t >= s.start && t < s.end) || SECTIONS[SECTIONS.length - 1];
  const local = t - active.start;
  React.useEffect(() => {
    const root = document.getElementById('root');
    if (!root) return;
    const sec = Math.floor(t);
    const mm = String(Math.floor(sec / 60)).padStart(2, '0');
    const ss = String(sec % 60).padStart(2, '0');
    const labels = { intro: 'Intro', t1: 'Step 1', signin: 'Step 1 · Sign in', t2: 'Step 2', dash: 'Step 2 · Add providers', t3: 'Step 3', prov: 'Step 3 · Review cycle', outbox: 'Step 3 · Outbox', rev: 'Step 3 · Reviewer view', t4: 'Step 4', rep: 'Step 4 · Coaching', outro: 'Outro' };
    root.setAttribute('data-screen-label', `${mm}:${ss} · ${labels[active.id] || ''}`);
  }, [Math.floor(t), active.id]);
  let scene;
  if (active.kind === 'intro') scene = React.createElement(IntroScene, { key: active.id, config: active, localTime: local });
  else if (active.kind === 'outro') scene = React.createElement(OutroScene, { key: active.id, config: active, localTime: local });
  else if (active.kind === 'title') scene = React.createElement(TitleScene, { key: active.id, config: active, localTime: local });
  else scene = React.createElement(CameraScene, { key: active.id, config: active, localTime: local });

  return React.createElement(React.Fragment, null,
    scene,
    active.kind === 'action' && React.createElement(CaptionBar, { captions: active.captions, t: local }),
    active.kind === 'action' && React.createElement(CornerStep, { current: active.step }),
  );
}

function App() {
  return React.createElement(Stage, { width: 1920, height: 1080, duration: DURATION, background: '#0d1f25', persistKey: 'tenormd-training' },
    React.createElement(Director));
}
const __rootEl = document.getElementById('root');
window.__tmRoot = window.__tmRoot || ReactDOM.createRoot(__rootEl);
window.__tmRoot.render(React.createElement(App));
