// dashboard-components.jsx: UI pieces mirroring the live client Overview and
// its assistant. Shared with the Document Review demo (../document-review),
// which loads this file by relative path; its scenes use the chat pieces here (pages/Overview.tsx, components/overview/AssistantHero.tsx,
// components/chat/*). Drawn at twice the app's size on the 1920x1080 stage so
// the demo stays legible in the Platform page's half-width iframe.
//
// Copy follows the firm voice used everywhere a client reads: "we", no
// contractions, no em dashes, and no mention of AI. When the screen this
// mirrors changes, change it here too.

/* global Spinner */

// Live light-theme tokens (frontend/dashboard/src/index.css :root).
const OV = {
  bg: '#FAF9F7',
  surface: '#FFFFFF',
  raised: '#FAFAF9',
  ink: '#1C1917',
  ink2: '#57534E',
  muted: '#78716C',
  line: '#E7E5E4',
  lineStrong: '#D6D3D1',
  subtle: '#F5F5F4',
  indigo: '#3B5BDB',
  indigoDeep: '#364FC7',
  indigoWeak: '#EDF2FF',
  success: '#16a34a',
  successWeak: '#f0fdf4',
  amber: '#d97706',
  red: '#dc2626',
  display: "'Plus Jakarta Sans', 'DM Sans', system-ui, sans-serif",
  body: "'DM Sans', system-ui, sans-serif",
};

const INDIGO_GRADIENT = `linear-gradient(135deg, ${OV.indigo} 0%, ${OV.indigoDeep} 100%)`;

// ─── Icons (lucide path data, the set the app uses) ───────────────────────
const ICON_NODES = {
  messageSquarePlus: [
    ['path', { d: 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z' }],
    ['path', { d: 'M12 8v6' }], ['path', { d: 'M9 11h6' }],
  ],
  scrollText: [
    ['path', { d: 'M15 12h-5' }], ['path', { d: 'M15 8h-5' }], ['path', { d: 'M19 17V5a2 2 0 0 0-2-2H4' }],
    ['path', { d: 'M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3' }],
  ],
  graduationCap: [
    ['path', { d: 'M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z' }],
    ['path', { d: 'M22 10v6' }], ['path', { d: 'M6 12.5V16a6 3 0 0 0 12 0v-3.5' }],
  ],
  paperclip: [
    ['path', { d: 'm16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551' }],
  ],
  mic: [
    ['path', { d: 'M12 19v3' }], ['path', { d: 'M19 10v2a7 7 0 0 1-14 0v-2' }],
    ['rect', { x: 9, y: 2, width: 6, height: 13, rx: 3 }],
  ],
  send: [
    ['path', { d: 'M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z' }],
    ['path', { d: 'm21.854 2.147-10.94 10.939' }],
  ],
  rotateCcw: [['path', { d: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }], ['path', { d: 'M3 3v5h5' }]],
  scale: [
    ['path', { d: 'M12 3v18' }], ['path', { d: 'm19 8 3 8a5 5 0 0 1-6 0zV7' }],
    ['path', { d: 'M3 7h1a17 17 0 0 0 8-2 17 17 0 0 0 8 2h1' }], ['path', { d: 'm5 8 3 8a5 5 0 0 1-6 0zV7' }],
    ['path', { d: 'M7 21h10' }],
  ],
  fileText: [
    ['path', { d: 'M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z' }],
    ['path', { d: 'M14 2v5a1 1 0 0 0 1 1h5' }], ['path', { d: 'M10 9H8' }], ['path', { d: 'M16 13H8' }], ['path', { d: 'M16 17H8' }],
  ],
  penLine: [
    ['path', { d: 'M13 21h8' }],
    ['path', { d: 'M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z' }],
  ],
  chevronRight: [['path', { d: 'm9 18 6-6-6-6' }]],
  creditCard: [['rect', { width: 20, height: 14, x: 2, y: 5, rx: 2 }], ['line', { x1: 2, x2: 22, y1: 10, y2: 10 }]],
  phone: [
    ['path', { d: 'M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384' }],
  ],
  check: [['path', { d: 'M20 6 9 17l-5-5' }]],
  globe: [
    ['circle', { cx: 12, cy: 12, r: 10 }],
    ['path', { d: 'M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20' }], ['path', { d: 'M2 12h20' }],
  ],
  building2: [
    ['path', { d: 'M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z' }], ['path', { d: 'M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2' }],
    ['path', { d: 'M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2' }], ['path', { d: 'M10 6h4' }], ['path', { d: 'M10 10h4' }],
    ['path', { d: 'M10 14h4' }], ['path', { d: 'M10 18h4' }],
  ],
  calendarClock: [
    ['path', { d: 'M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5' }], ['path', { d: 'M16 2v4' }],
    ['path', { d: 'M8 2v4' }], ['path', { d: 'M3 10h5' }], ['path', { d: 'M17.5 17.5 16 16.3V14' }], ['circle', { cx: 16, cy: 16, r: 6 }],
  ],
  messageSquare: [
    ['path', { d: 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z' }],
  ],
  bookOpen: [
    ['path', { d: 'M12 7v14' }],
    ['path', { d: 'M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z' }],
  ],
  users: [
    ['path', { d: 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2' }], ['path', { d: 'M16 3.128a4 4 0 0 1 0 7.744' }],
    ['path', { d: 'M22 21v-2a4 4 0 0 0-3-3.87' }], ['circle', { cx: 9, cy: 7, r: 4 }],
  ],
};

function Icon({ name, size = 32, color = 'currentColor', strokeWidth = 2 }) {
  return (
    <svg
      width={size} height={size} viewBox="0 0 24 24" fill="none"
      stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"
      style={{ flexShrink: 0, display: 'block' }}
    >
      {ICON_NODES[name].map(([tag, attrs], i) => React.createElement(tag, { key: i, ...attrs }))}
    </svg>
  );
}

// ─── Live anchor: an element's centre inside a container ──────────────────
// Re-measured after every layout, so the cursor can aim at rows that appear
// mid-scene (a bottom-anchored thread moves everything above it).
function useLiveAnchor(ref, containerRef) {
  const [anchor, setAnchor] = React.useState(null);
  React.useLayoutEffect(() => {
    if (!ref.current || !containerRef.current) return;
    let el = ref.current;
    let x = 0, y = 0;
    while (el && el !== containerRef.current) {
      x += el.offsetLeft;
      y += el.offsetTop;
      el = el.offsetParent;
    }
    const next = { x: x + ref.current.offsetWidth / 2, y: y + ref.current.offsetHeight / 2 };
    if (!anchor || Math.abs(anchor.x - next.x) > 0.5 || Math.abs(anchor.y - next.y) > 0.5) setAnchor(next);
  });
  return anchor;
}

// A row that eases in once its moment arrives (t = seconds into the scene).
function Appear({ at, t, children, rise = 16, style = {} }) {
  if (t < at) return null;
  const k = Easing.easeOutCubic(clamp((t - at) / 0.35, 0, 1));
  return (
    <div style={{ opacity: k, transform: `translateY(${(1 - k) * rise}px)`, ...style }}>
      {children}
    </div>
  );
}

// ─── Overview header: company identity + Legal Coverage ring ──────────────
function Badge({ children, tone = 'neutral' }) {
  const ok = tone === 'success';
  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center', gap: 10,
      padding: '6px 20px',
      borderRadius: 999,
      border: `2px solid ${ok ? '#a7f3d0' : OV.line}`,
      background: ok ? '#ecfdf5' : OV.raised,
      color: ok ? '#047857' : OV.muted,
      fontSize: 24, fontWeight: 500,
      whiteSpace: 'nowrap',
    }}>
      {ok && <div style={{ width: 12, height: 12, borderRadius: 6, background: '#10b981' }}/>}
      {children}
    </div>
  );
}

function CoverageRing({ pct }) {
  const r = 68, stroke = 10, size = 160;
  const circ = 2 * Math.PI * r;
  const color = pct > 0 ? (pct >= 60 ? OV.success : pct >= 25 ? OV.amber : OV.red) : '#a1a1aa';
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
      <div style={{ position: 'relative', width: 128, height: 128 }}>
        <svg width="128" height="128" viewBox={`0 0 ${size} ${size}`}>
          <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={OV.line} strokeWidth={stroke}/>
          <circle
            cx={size / 2} cy={size / 2} r={r} fill="none"
            stroke={color} strokeWidth={stroke} strokeLinecap="round"
            strokeDasharray={circ} strokeDashoffset={circ * (1 - pct / 100)}
            transform={`rotate(-90 ${size / 2} ${size / 2})`}
          />
        </svg>
        <div style={{
          position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 28, fontWeight: 700, color: OV.ink, fontVariantNumeric: 'tabular-nums',
        }}>{Math.round(pct)}%</div>
      </div>
      <div style={{ fontSize: 20, fontWeight: 500, color: OV.muted }}>Legal Coverage</div>
      <div style={{ fontSize: 20, color: OV.muted }}>Next: Work health and safety policy</div>
    </div>
  );
}

function CompanyHeader({ pct = 38 }) {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
      <div>
        <div style={{
          fontFamily: OV.display, fontSize: 60, fontWeight: 700, letterSpacing: '-0.02em', color: OV.ink, lineHeight: 1.15,
        }}>Frontier Capital Pty Ltd</div>
        <div style={{ display: 'flex', gap: 16, marginTop: 16 }}>
          <Badge>PTY LTD</Badge>
          <Badge><span style={{ marginRight: 2 }}>🇦🇺</span> AU</Badge>
          <Badge tone="success">Active</Badge>
        </div>
        <div style={{ fontSize: 24, color: OV.muted, marginTop: 14 }}>ACN: 123 456 789</div>
      </div>
      <CoverageRing pct={pct} />
    </div>
  );
}

// ─── Assistant hero: heading, composer card, three shortcut tiles ─────────
function SpraypaintGlow() {
  const blob = (w, h, tx, ty, bg, blur) => (
    <div style={{
      position: 'absolute', left: '50%', top: '50%', width: w, height: h,
      transform: `translate(${tx}, ${ty})`, background: bg, filter: `blur(${blur}px)`,
    }}/>
  );
  return (
    <div style={{ position: 'absolute', left: 0, right: 0, top: -60, height: 620, pointerEvents: 'none', opacity: 0.55 }}>
      {blob(1700, 700, '-50%', '-50%', 'radial-gradient(50% 60% at 50% 50%, rgba(45,212,191,0.14) 0%, rgba(20,184,166,0.06) 50%, transparent 100%)', 140)}
      {blob(960, 600, '-50%', '-50%', 'radial-gradient(50% 55% at 50% 50%, rgba(77,255,244,0.2) 0%, rgba(45,212,191,0.1) 55%, transparent 100%)', 110)}
      {blob(800, 600, '-65%', '-45%', 'radial-gradient(circle, rgba(16,185,129,0.22) 0%, rgba(45,212,191,0.08) 50%, transparent 75%)', 120)}
      {blob(800, 600, '-10%', '-50%', 'radial-gradient(circle, rgba(99,180,248,0.14) 0%, rgba(45,212,191,0.08) 50%, transparent 75%)', 120)}
    </div>
  );
}

function SendButton({ active = false, sendRef }) {
  return (
    <div ref={sendRef} style={{
      width: 72, height: 72, borderRadius: 36,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: active ? INDIGO_GRADIENT : OV.subtle,
      color: active ? '#FFFFFF' : OV.muted,
      transition: 'background 200ms',
      flexShrink: 0,
    }}>
      <Icon name="send" size={32} />
    </div>
  );
}

function Caret({ height = 36 }) {
  return (
    <span style={{
      display: 'inline-block', width: 3, height, marginLeft: 2, background: OV.indigo,
      verticalAlign: 'middle', animation: 'caretBlink 0.9s steps(2) infinite',
    }}/>
  );
}

// The composer as the empty state shows it: the hero card.
function HeroComposer({ value = '', focused = false, inputRef, sendRef }) {
  return (
    <div style={{
      width: 1536,
      background: OV.surface,
      border: `2px solid ${OV.line}`,
      borderRadius: 32,
      boxShadow: focused ? '0 16px 48px rgba(28,25,23,0.16)' : '0 12px 40px rgba(28,25,23,0.12)',
      transition: 'box-shadow 300ms',
      overflow: 'hidden',
      textAlign: 'left',
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 16, padding: '28px 24px 16px 40px' }}>
        <div ref={inputRef} style={{
          flex: 1, minWidth: 0, padding: '18px 0 30px', fontSize: 30, lineHeight: 1.4,
          color: value ? OV.ink : '#A8A29E',
        }}>
          {value || 'Just ask me anything!'}
          {focused && <Caret />}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <div style={{ width: 72, height: 72, display: 'flex', alignItems: 'center', justifyContent: 'center', color: OV.muted }}>
            <Icon name="mic" size={36} />
          </div>
          <SendButton active={!!value} sendRef={sendRef} />
        </div>
      </div>
      <div style={{
        borderTop: `2px solid ${OV.line}`, padding: '20px 32px',
        display: 'flex', alignItems: 'center', gap: 12, color: OV.ink2, fontSize: 24,
      }}>
        <Icon name="paperclip" size={28} />
        Upload a document
      </div>
    </div>
  );
}

const SHORTCUTS = [
  { id: 'engage', label: 'Engage a Lawyer', icon: 'messageSquarePlus' },
  { id: 'generate', label: 'Generate a Document', icon: 'scrollText' },
  { id: 'learn', label: 'Learn', icon: 'graduationCap' },
];

// Frosted shortcut tile (index.css .lg-tile on the stone page).
function ShortcutTile({ label, icon, selected = false, tileRef }) {
  return (
    <div ref={tileRef} style={{
      display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16,
      padding: '24px 32px',
      borderRadius: 24,
      background: selected ? 'rgba(255,255,255,0.98)' : 'rgba(255,255,255,0.88)',
      border: '2px solid rgba(255,255,255,0.8)',
      boxShadow: [
        'inset 2px 2px 0 rgba(255,255,255,0.85)',
        'inset -2px -2px 0 rgba(28,25,23,0.06)',
        selected ? '0 28px 60px -24px rgba(0,0,0,0.24)' : '0 16px 40px -20px rgba(0,0,0,0.16)',
        '0 4px 12px -4px rgba(0,0,0,0.06)',
      ].join(', '),
      color: selected ? OV.ink : OV.ink2,
      fontSize: 28, fontWeight: selected ? 600 : 500,
      transition: 'background 180ms, box-shadow 180ms',
    }}>
      <Icon name={icon} size={32} color={selected ? OV.indigo : OV.muted} />
      {label}
    </div>
  );
}

function OverviewPage({ pct = 38, entry = 1, composerValue = '', composerFocused = false, inputRef, sendRef, selectedTile = null, tileRefs = {} }) {
  const step = (from) => Easing.easeOutCubic(clamp((entry - from) / 0.35, 0, 1));
  const fade = (from) => ({ opacity: step(from), transform: `translateY(${(1 - step(from)) * 18}px)` });
  return (
    <div style={{ position: 'absolute', inset: 0, background: OV.bg, fontFamily: OV.body, color: OV.ink }}>
      <div style={{ position: 'absolute', inset: 0, padding: '72px 120px 0' }}>
        <div style={fade(0)}>
          <CompanyHeader pct={pct} />
        </div>
        <div style={{ position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: 56 }}>
          <SpraypaintGlow />
          <div style={{ position: 'relative', textAlign: 'center', ...fade(0.15) }}>
            <div style={{ fontFamily: OV.display, fontSize: 76, fontWeight: 700, letterSpacing: '-0.03em', color: OV.ink }}>
              How can we help?
            </div>
            <div style={{ fontSize: 28, color: OV.ink2, marginTop: 12 }}>
              Ask anything about your legal needs, or start with one of the shortcuts below.
            </div>
          </div>
          <div style={{ position: 'relative', marginTop: 28, ...fade(0.3) }}>
            <HeroComposer value={composerValue} focused={composerFocused} inputRef={inputRef} sendRef={sendRef} />
          </div>
          <div style={{
            position: 'relative', width: 1536, marginTop: 32,
            display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 24, ...fade(0.45),
          }}>
            {SHORTCUTS.map((s) => (
              <ShortcutTile key={s.id} label={s.label} icon={s.icon} selected={selectedTile === s.id} tileRef={tileRefs[s.id]} />
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── The conversation: a framed column with one docked composer ───────────
function IdentityRow({ title, subtitle }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24,
      padding: '28px 40px 20px', background: OV.surface, position: 'relative', zIndex: 2,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 20, minWidth: 0 }}>
        <div style={{
          width: 56, height: 56, borderRadius: 16, background: '#000000',
          display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
        }}>
          <img src="/logos/logomark-white.svg" alt="" style={{ width: 32, height: 32 }}/>
        </div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 16, minWidth: 0 }}>
          <div style={{ fontSize: 28, fontWeight: 600, color: OV.ink, whiteSpace: 'nowrap' }}>{title}</div>
          {subtitle && <div style={{ fontSize: 24, color: OV.muted, whiteSpace: 'nowrap' }}>{subtitle}</div>}
        </div>
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 24, fontWeight: 500, color: OV.muted }}>
        <Icon name="rotateCcw" size={28} />
        Start over
      </div>
      <div style={{
        position: 'absolute', left: 0, right: 0, top: '100%', height: 48, pointerEvents: 'none',
        background: `linear-gradient(to bottom, ${OV.surface}, rgba(255,255,255,0))`,
      }}/>
    </div>
  );
}

// The composer docked at the bottom of a conversation: the floating pill.
function DockedComposer({ placeholder = 'Just ask me anything!', value = '', uploadLabel = 'Upload a document', uploadRef, sendRef }) {
  return (
    <div style={{
      background: OV.surface,
      border: `2px solid ${OV.line}`,
      borderRadius: 52,
      boxShadow: '0 4px 28px rgba(28,25,23,0.08)',
    }}>
      <div style={{ padding: '26px 40px 6px', fontSize: 30, lineHeight: 1.4, color: value ? OV.ink : '#A8A29E' }}>
        {value || placeholder}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 20px 14px 20px' }}>
        <div ref={uploadRef} style={{
          display: 'inline-flex', alignItems: 'center', gap: 12,
          padding: '12px 20px 12px 12px', borderRadius: 999, fontSize: 24, color: OV.ink2,
        }}>
          <Icon name="paperclip" size={32} />
          {uploadLabel}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <div style={{ width: 72, height: 72, display: 'flex', alignItems: 'center', justifyContent: 'center', color: OV.muted }}>
            <Icon name="mic" size={36} />
          </div>
          <SendButton active={!!value} sendRef={sendRef} />
        </div>
      </div>
    </div>
  );
}

// Page-style thread: the column frames the turns, newest at the bottom, so a
// growing thread scrolls older turns up and out of view.
function ConversationPanel({ title, subtitle, children, composer }) {
  return (
    <div style={{ position: 'absolute', inset: 0, background: OV.bg, fontFamily: OV.body, color: OV.ink }}>
      {/* Centred by offset, not transform: the cursor aims by offsetLeft/Top. */}
      <div style={{
        position: 'absolute', left: (1920 - 1536) / 2, top: 104, bottom: 28, width: 1536,
        background: OV.surface, border: `2px solid ${OV.line}`, borderRadius: 56,
        boxShadow: '0 8px 48px rgba(28,25,23,0.06)', overflow: 'hidden',
        display: 'flex', flexDirection: 'column',
      }}>
        <IdentityRow title={title} subtitle={subtitle} />
        <div style={{
          flex: 1, minHeight: 0, overflow: 'hidden', padding: '0 64px',
          display: 'flex', flexDirection: 'column', justifyContent: 'flex-end',
        }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 32, paddingBottom: 28 }}>
            {children}
          </div>
        </div>
        <div style={{ padding: '4px 28px 28px', flexShrink: 0 }}>{composer}</div>
      </div>
    </div>
  );
}

function UserBubble({ children }) {
  return (
    <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
      <div style={{
        maxWidth: '78%', padding: '20px 32px', borderRadius: 48, background: OV.subtle,
        fontSize: 30, lineHeight: 1.55, color: OV.ink,
      }}>{children}</div>
    </div>
  );
}

function AgentText({ children, style = {} }) {
  return <div style={{ fontSize: 30, lineHeight: 1.6, color: OV.ink, ...style }}>{children}</div>;
}

function TypingDots() {
  return (
    <div style={{ display: 'flex', gap: 12, padding: '12px 0' }}>
      {[0, 1, 2].map((i) => (
        <div key={i} style={{
          width: 14, height: 14, borderRadius: 7, background: OV.muted,
          animation: `typingBounce 1.1s ease-in-out ${i * 0.15}s infinite`,
        }}/>
      ))}
    </div>
  );
}

function StepLine({ done, running, label }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14, fontSize: 24, color: OV.muted }}>
      {done ? <Icon name="check" size={28} color={OV.success} /> : <Spinner size={24} color={OV.muted} />}
      {done ? label : running}
    </div>
  );
}

function FileChip({ name }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 12 }}>
      <Icon name="fileText" size={30} color={OV.muted} />
      {name}
    </span>
  );
}

// ─── Template suggestion cards (components/chat/SuggestionCards.tsx) ──────
function Tag({ children, tone = 'neutral' }) {
  const ok = tone === 'success';
  return (
    <span style={{
      padding: '4px 16px', borderRadius: 12, fontSize: 22, fontWeight: ok ? 500 : 400,
      background: ok ? OV.successWeak : OV.bg, color: ok ? OV.success : OV.ink2,
      border: ok ? 'none' : `2px solid ${OV.line}`,
    }}>{children}</span>
  );
}

function TemplateCard({ name, description, primary = false, buttonRef }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', minWidth: 0, padding: 28, borderRadius: 24, background: OV.surface,
      border: `2px solid ${primary ? OV.indigo : OV.line}`,
      boxShadow: primary ? `0 0 0 2px ${OV.indigo}` : 'none',
    }}>
      <div style={{
        width: 64, height: 64, borderRadius: 18, background: OV.indigoWeak, color: OV.indigo,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <Icon name="users" size={34} strokeWidth={1.8} />
      </div>
      <div style={{ marginTop: 20, fontFamily: OV.display, fontSize: 30, fontWeight: 600, letterSpacing: '-0.01em', color: OV.ink }}>{name}</div>
      <div style={{
        marginTop: 8, fontSize: 25, lineHeight: 1.4, color: OV.ink2,
        display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
      }}>{description}</div>
      <div style={{ display: 'flex', gap: 12, marginTop: 20 }}>
        <Tag tone="success">Free</Tag>
        <Tag>Hire someone</Tag>
        <Tag>AU</Tag>
      </div>
      <div ref={buttonRef} style={{
        marginTop: 24, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 12,
        padding: '16px 24px', borderRadius: 16, fontSize: 24, fontWeight: 500,
        ...(primary
          ? { background: INDIGO_GRADIENT, color: '#FFFFFF' }
          : { background: OV.surface, color: OV.indigo, border: `2px solid rgba(59,91,219,0.25)` }),
      }}>
        <Icon name="penLine" size={28} />
        Prepare in chat
        <Icon name="chevronRight" size={28} />
      </div>
      <div style={{ marginTop: 10, alignSelf: 'center', fontSize: 22, color: OV.muted }}>or open the form</div>
    </div>
  );
}

// ─── Review brief (components/chat/ReviewSubmitCard.tsx, brief mode) ──────
function BriefField({ label, value }) {
  return (
    <div style={{ minWidth: 0 }}>
      <div style={{ fontSize: 22, fontWeight: 500, color: OV.muted, marginBottom: 8 }}>{label}</div>
      <div style={{
        padding: '14px 20px', borderRadius: 16, border: `2px solid ${OV.line}`, fontSize: 26, color: OV.ink,
        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
      }}>{value}</div>
    </div>
  );
}

function AppButton({ children, variant = 'primary', buttonRef, busy = false }) {
  const primary = variant === 'primary';
  return (
    <div ref={buttonRef} style={{
      display: 'inline-flex', alignItems: 'center', gap: 12,
      padding: '16px 32px', borderRadius: 16, fontSize: 26, fontWeight: 500,
      background: primary ? OV.ink : OV.surface,
      color: primary ? '#FFFFFF' : OV.ink,
      border: primary ? `2px solid ${OV.ink}` : `2px solid ${OV.line}`,
      whiteSpace: 'nowrap',
    }}>
      {busy && <Spinner size={22} color="#FFFFFF" />}
      {children}
    </div>
  );
}

function ReviewBriefCard({ stage = 'brief', submitRef }) {
  const submitted = stage === 'submitted';
  return (
    <div style={{
      borderRadius: 32, border: `2px solid ${OV.line}`, background: OV.surface, overflow: 'hidden',
      boxShadow: '0 2px 6px rgba(28,25,23,0.05)',
    }}>
      <div style={{ display: 'flex', gap: 20, padding: '24px 32px', borderBottom: `2px solid ${OV.line}` }}>
        <div style={{ paddingTop: 4 }}>
          <Icon name={submitted ? 'check' : 'scale'} size={32} color={submitted ? OV.success : OV.indigo} />
        </div>
        <div>
          <div style={{ fontSize: 28, fontWeight: 600, color: OV.ink }}>
            {submitted ? 'Review request submitted' : 'Ready to send to a lawyer'}
          </div>
          <div style={{ fontSize: 22, color: OV.muted, marginTop: 4 }}>
            {submitted
              ? 'Your document has been uploaded and sent for review. You can track the status in My Requests.'
              : 'Check the brief below and edit anything, then send it through.'}
          </div>
        </div>
      </div>
      <div style={{ padding: '24px 32px 28px', display: 'flex', flexDirection: 'column', gap: 20 }}>
        {!submitted && (
          <>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
              <BriefField label="Document type" value="Supply agreement" />
              <BriefField label="Your role" value="Customer" />
            </div>
            <BriefField label="Concerns" value="The liability cap in clause 12, and the 90-day notice in clause 15" />
            <div style={{ borderRadius: 24, border: `2px solid ${OV.line}`, background: OV.subtle, padding: '18px 24px' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 14, fontSize: 26, fontWeight: 500, color: OV.ink }}>
                <Icon name="creditCard" size={30} color={OV.muted} />
                Lawyer review fee
              </div>
              <div style={{ fontSize: 22, color: OV.muted, marginTop: 6 }}>
                Based on 9 pages. Your Visa card ending 4242 will be charged when you submit.
              </div>
            </div>
          </>
        )}
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 20 }}>
          {submitted ? (
            <>
              <AppButton variant="secondary">Go to My Requests</AppButton>
              <AppButton>Go to My Documents</AppButton>
            </>
          ) : (
            <>
              <AppButton variant="secondary">Keep chatting</AppButton>
              <AppButton buttonRef={submitRef} busy={stage === 'submitting'}>
                {stage === 'submitting' ? 'Processing payment…' : 'Submit for review'}
              </AppButton>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Guided intake chips (components/chat/IntakePanel.tsx) ────────────────
function IntakeChips({ options, chosen = null, chipRefs = {} }) {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12 }}>
      {options.map((o) => {
        const on = chosen === o;
        return (
          <div key={o} ref={chipRefs[o]} style={{
            display: 'inline-flex', alignItems: 'center', gap: 10,
            padding: '16px 24px', borderRadius: 16, fontSize: 24,
            border: `2px solid ${on ? 'rgba(59,91,219,0.4)' : OV.line}`,
            background: on ? OV.indigoWeak : OV.surface,
            color: OV.indigo,
            transition: 'background 160ms, border-color 160ms',
          }}>
            {o}
            <span style={{ opacity: 0.4, display: 'flex' }}><Icon name="chevronRight" size={24} /></span>
          </div>
        );
      })}
    </div>
  );
}

// ─── Dispute assessment (the reply format dispute-chat.ts asks for) ───────
function Assessment() {
  const line = { fontSize: 28, lineHeight: 1.5, color: OV.ink };
  const label = { ...line, marginTop: 18 };
  return (
    <div>
      <div style={line}>Viability: Moderate</div>
      <div style={label}>Key factors in your favour:</div>
      <div style={line}>- A signed agreement sets the price and the payment terms</div>
      <div style={line}>- The invoices and delivery records are in writing</div>
      <div style={label}>Key risks:</div>
      <div style={line}>- The client says the work was delivered late</div>
      <div style={label}>Recommended next steps:</div>
      <div style={line}>- Have a lawyer review the agreement and the correspondence</div>
    </div>
  );
}

function OutlineButton({ icon, children, buttonRef }) {
  return (
    <div ref={buttonRef} style={{
      display: 'inline-flex', alignItems: 'center', gap: 12, padding: '18px 32px', borderRadius: 16,
      fontSize: 24, background: OV.surface, border: `2px solid ${OV.line}`, color: OV.ink,
    }}>
      <Icon name={icon} size={28} />
      {children}
    </div>
  );
}

function IndigoButton({ icon, children, buttonRef }) {
  return (
    <div ref={buttonRef} style={{
      display: 'inline-flex', alignItems: 'center', gap: 12, padding: '18px 32px', borderRadius: 16,
      fontSize: 24, background: INDIGO_GRADIENT, color: '#FFFFFF',
    }}>
      <Icon name={icon} size={28} />
      {children}
    </div>
  );
}

// ─── Caption pill ─────────────────────────────────────────────────────────
// Bottom on the Overview (free space under the tiles); top in a conversation,
// where the docked composer owns the bottom edge.
function SceneCaption({ show = true, placement = 'bottom', children }) {
  return (
    <div style={{
      position: 'absolute', left: 0, right: 0, [placement]: placement === 'top' ? 26 : 44,
      display: 'flex', justifyContent: 'center', pointerEvents: 'none', zIndex: 200,
    }}>
      <div style={{
        padding: '14px 32px', background: 'rgba(28,25,23,0.88)', color: '#FAF9F7', borderRadius: 999,
        fontFamily: OV.body, fontSize: 24, fontWeight: 600, letterSpacing: '0.12em',
        opacity: show ? 1 : 0, transition: 'opacity 320ms',
      }}>{children}</div>
    </div>
  );
}

Object.assign(window, {
  OV, Icon, useLiveAnchor, Appear,
  CompanyHeader, CoverageRing, OverviewPage, HeroComposer, ShortcutTile,
  ConversationPanel, DockedComposer, UserBubble, AgentText, TypingDots, StepLine, FileChip,
  TemplateCard, ReviewBriefCard, IntakeChips, Assessment, OutlineButton, IndigoButton,
  SceneCaption, AppButton, BriefField,
});
