// Pages.jsx — Overview, Intake, Playbooks, IntakeFormSlideOver

// ============================================================= INTAKE FORM SLIDE-OVER

function useClosingAnimation(onClose, duration = 260) {
  const [closing, setClosing] = React.useState(false);
  const timerRef = React.useRef(null);

  const triggerClose = React.useCallback(() => {
    if (closing) return;
    setClosing(true);
    timerRef.current = setTimeout(() => {
      onClose();
    }, duration);
  }, [closing, onClose, duration]);

  React.useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current); }, []);

  return { closing, triggerClose };
}

function IntakeFormSlideOver({ onClose, onSubmit }) {
  const [form, setForm] = React.useState({
    title: '',
    problem: '',
    requester: 'sales',
    priority: 'high',
    postToSlack: true,
    jtbd: { when: '', need: '', outcome: '' },
  });
  const [errors, setErrors] = React.useState({});
  const [submitting, setSubmitting] = React.useState(false);

  const { closing, triggerClose } = useClosingAnimation(onClose);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const validate = () => {
    const e = {};
    if (!form.title.trim())   e.title   = 'Required';
    if (!form.problem.trim()) e.problem = 'Required';
    setErrors(e);
    return Object.keys(e).length === 0;
  };

  const handleSubmit = () => {
    if (!validate()) return;
    setSubmitting(true);
    const maxId = Math.max(...TICKETS.map(t => parseInt(t.id.replace('GTM-', ''), 10)));
    const id = 'GTM-' + (maxId + 1);
    const ticket = {
      id,
      title: form.title.trim(),
      status: 'triage',
      phase: 'discovery',
      progress: 3,
      priority: form.priority,
      labels: ['automation'],
      requester: form.requester,
      requesterPeople: ['LR'],
      owner: null,
      team: [],
      reporterPm: 'DK',
      createdAt: 'just now',
      updatedAt: 'just now',
      targetShip: null,
      jtbd: { when: form.jtbd.when.trim(), need: form.jtbd.need.trim(), outcome: form.jtbd.outcome.trim() },
      problem: form.problem.trim(),
      approach: 'TBD in scoping.',
      baselineMetric: null,
      supportMetrics: [],
      impactStatement: null,
      activity: [{ t: 'just now', who: 'LR', what: 'Submitted this request.', icon: 'TrayIcon' }],
      comments: [],
      risks: [],
      milestones: [
        { label: 'Discovery', status: 'doing', date: '—' },
        { label: 'Scoping',   status: 'todo',  date: 'TBD' },
        { label: 'Building',  status: 'todo',  date: 'TBD' },
        { label: 'Pilot',     status: 'todo',  date: 'TBD' },
      ],
    };
    onSubmit(ticket, form.postToSlack);
    setSubmitting(false);
  };

  React.useEffect(() => {
    const handler = (e) => { if (e.key === 'Escape') triggerClose(); };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [triggerClose]);

  const clearErr = (k) => { if (errors[k]) setErrors(x => ({ ...x, [k]: null })); };

  return (
    <>
      <div className={`so-backdrop${closing ? ' closing' : ''}`} onClick={triggerClose} />
      <div className={`so-panel${closing ? ' closing' : ''}`} role="dialog" aria-modal="true">

        <div className="so-head">
          <div>
            <div className="so-head-title">New request</div>
            <div className="so-head-sub">Describe the GTM problem. We'll triage and scope it.</div>
          </div>
          <Button variant="ghost" size="sm" icon="XIcon" onClick={triggerClose} />
        </div>

        <div className="so-body">
          <div className="so-section" style={{ borderBottom: 'none' }}>

            <div className="so-field">
              <label className="so-label">What are you requesting? <span className="so-req">*</span></label>
              <input
                className={'inp' + (errors.title ? ' inp-err' : '')}
                placeholder="e.g. AI-drafted post-demo recap emails"
                value={form.title}
                onChange={e => { set('title', e.target.value); clearErr('title'); }}
                autoFocus
              />
              {errors.title && <div className="so-errmsg">{errors.title}</div>}
            </div>

            <div className="so-field">
              <label className="so-label">What's the problem? <span className="so-req">*</span></label>
              <textarea
                className={'inp so-ta-lg' + (errors.problem ? ' inp-err' : '')}
                placeholder="What's broken or slow? What does it cost in time or revenue?"
                value={form.problem}
                onChange={e => { set('problem', e.target.value); clearErr('problem'); }}
              />
              {errors.problem && <div className="so-errmsg">{errors.problem}</div>}
            </div>

            <div className="so-field">
              <label className="so-label">Frame the ultimate goal <span className="so-opt">(optional — helps us scope faster)</span></label>
              <div className="jtbd-form-block">
                <div className="jtbd-form-row">
                  <div className="jtbd-form-kicker">When</div>
                  <textarea className="inp so-ta-sm" placeholder="a new lead comes in from the website…" value={form.jtbd.when} onChange={e => set('jtbd', { ...form.jtbd, when: e.target.value })} />
                </div>
                <div className="jtbd-form-row">
                  <div className="jtbd-form-kicker">I need</div>
                  <textarea className="inp so-ta-sm" placeholder="the account auto-enriched and routed in under 90 seconds…" value={form.jtbd.need} onChange={e => set('jtbd', { ...form.jtbd, need: e.target.value })} />
                </div>
                <div className="jtbd-form-row">
                  <div className="jtbd-form-kicker">So that</div>
                  <textarea className="inp so-ta-sm" placeholder="SDRs start selling before the lead goes cold" value={form.jtbd.outcome} onChange={e => set('jtbd', { ...form.jtbd, outcome: e.target.value })} />
                </div>
              </div>
            </div>

            <div className="so-row-2">
              <div className="so-field">
                <label className="so-label">Requesting team</label>
                <select className="so-select" value={form.requester} onChange={e => set('requester', e.target.value)}>
                  {TEAMS.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}
                </select>
              </div>
              <div className="so-field">
                <label className="so-label">Priority</label>
                <select className="so-select" value={form.priority} onChange={e => set('priority', e.target.value)}>
                  {Object.entries(PRIORITIES).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
                </select>
              </div>
            </div>

          </div>
        </div>

        <div className="so-foot">
          <label className="slack-opt" onClick={() => set('postToSlack', !form.postToSlack)}>
            <Checkbox checked={form.postToSlack} onChange={v => set('postToSlack', v)} />
            Post to <span className="slack-opt-ch">#gtm-funnel</span>
          </label>
          <Button variant="secondary" onClick={triggerClose}>Cancel</Button>
          <Button variant="primary" icon="PaperPlaneIcon" onClick={handleSubmit} disabled={submitting}>
            {submitting ? 'Submitting…' : 'Submit request'}
          </Button>
        </div>
      </div>
    </>
  );
}

// ============================================================= OVERVIEW

function D3StatusChart({ ticketVer }) {
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (!window.d3 || !ref.current) return;
    const d3 = window.d3;
    const data = STATUSES.map(status => ({
      label: status.label,
      short: status.short,
      color: status.color,
      value: TICKETS.filter(ticket => ticket.status === status.id).length,
    }));
    const svg = d3.select(ref.current);
    const width = 420;
    const height = 200;
    const margin = { top: 18, right: 12, bottom: 28, left: 12 };
    svg.selectAll('*').remove();
    svg.attr('viewBox', `0 0 ${width} ${height}`);

    const x = d3.scaleBand().domain(data.map(d => d.short)).range([margin.left, width - margin.right]).padding(0.18);
    const y = d3.scaleLinear().domain([0, d3.max(data, d => d.value) + 1]).nice().range([height - margin.bottom, margin.top]);

    svg.append('g')
      .attr('transform', `translate(0,${height - margin.bottom})`)
      .call(d3.axisBottom(x).tickSize(0))
      .call(g => g.select('.domain').remove())
      .call(g => g.selectAll('text').attr('fill', 'var(--text-neutral-subtle)').style('font-size', '11px'));

    svg.append('g')
      .attr('transform', `translate(${margin.left},0)`)
      .call(d3.axisLeft(y).ticks(4).tickSize(-(width - margin.left - margin.right)))
      .call(g => g.select('.domain').remove())
      .call(g => g.selectAll('text').attr('fill', 'var(--text-neutral-subtle)').style('font-size', '10px'))
      .call(g => g.selectAll('.tick line').attr('stroke', 'rgba(113,113,122,.16)'));

    const bars = svg.append('g').selectAll('rect').data(data).join('rect')
      .attr('x', d => x(d.short))
      .attr('y', y(0))
      .attr('rx', 10)
      .attr('width', x.bandwidth())
      .attr('height', 0)
      .attr('fill', d => d.color);

    bars.transition().duration(720).ease(d3.easeCubicOut)
      .attr('y', d => y(d.value))
      .attr('height', d => y(0) - y(d.value));

    const labels = svg.append('g').selectAll('text.value').data(data).join('text')
      .attr('class', 'value')
      .attr('x', d => x(d.short) + x.bandwidth() / 2)
      .attr('y', d => y(d.value) - 8)
      .attr('text-anchor', 'middle')
      .attr('fill', 'var(--text-neutral-prominent)')
      .style('font-size', '12px')
      .style('font-weight', '600')
      .style('opacity', 0)
      .text(d => d.value);

    labels.transition().delay(280).duration(300).style('opacity', 1);
  }, [ticketVer]);

  return <svg ref={ref} className="chart" aria-label="Tickets by delivery stage" />;
}

function D3TeamChart({ ticketVer }) {
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (!window.d3 || !ref.current) return;
    const d3 = window.d3;
    const priorityWeight = { urgent: 4, high: 3, med: 2, low: 1 };
    const data = TEAMS.map(team => ({
      label: team.label,
      color: team.color,
      open: TICKETS.filter(ticket => ticket.requester === team.id && ticket.status !== 'done').length,
      weighted: TICKETS.filter(ticket => ticket.requester === team.id && ticket.status !== 'done')
        .reduce((sum, ticket) => sum + (priorityWeight[ticket.priority] || 1), 0),
    }))
      .filter(team => team.open > 0)
      .sort((a, b) => b.weighted - a.weighted)
      .slice(0, 5);

    const svg = d3.select(ref.current);
    const width = 420;
    const height = 200;
    const margin = { top: 14, right: 18, bottom: 10, left: 116 };
    svg.selectAll('*').remove();
    svg.attr('viewBox', `0 0 ${width} ${height}`);

    const x = d3.scaleLinear().domain([0, d3.max(data, d => d.weighted) + 1]).range([margin.left, width - margin.right]);
    const y = d3.scaleBand().domain(data.map(d => d.label)).range([margin.top, height - margin.bottom]).padding(0.2);

    svg.append('g')
      .attr('transform', `translate(0,${margin.top - 6})`)
      .call(d3.axisTop(x).ticks(4).tickSize(0))
      .call(g => g.select('.domain').remove())
      .call(g => g.selectAll('text').attr('fill', 'var(--text-neutral-subtle)').style('font-size', '10px'));

    svg.append('g')
      .attr('transform', `translate(${margin.left},0)`)
      .call(d3.axisLeft(y).tickSize(0))
      .call(g => g.select('.domain').remove())
      .call(g => g.selectAll('text').attr('fill', 'var(--text-neutral-prominent)').style('font-size', '11px'));

    const rows = svg.append('g').selectAll('rect.track').data(data).join('rect')
      .attr('x', margin.left)
      .attr('y', d => y(d.label))
      .attr('width', width - margin.left - margin.right)
      .attr('height', y.bandwidth())
      .attr('rx', 9)
      .attr('fill', 'rgba(113,113,122,.08)');

    rows.lower();

    const bars = svg.append('g').selectAll('rect.fill').data(data).join('rect')
      .attr('x', margin.left)
      .attr('y', d => y(d.label))
      .attr('width', 0)
      .attr('height', y.bandwidth())
      .attr('rx', 9)
      .attr('fill', d => d.color);

    bars.transition().duration(760).ease(d3.easeCubicOut)
      .attr('width', d => x(d.weighted) - margin.left);

    svg.append('g').selectAll('text.meta').data(data).join('text')
      .attr('class', 'meta')
      .attr('x', d => x(d.weighted) + 8)
      .attr('y', d => y(d.label) + y.bandwidth() / 2 + 4)
      .attr('fill', 'var(--text-neutral-subtle)')
      .style('font-size', '10px')
      .text(d => `${d.open} open`);
  }, [ticketVer]);

  return <svg ref={ref} className="chart" aria-label="Open work by requester team" />;
}

function HomePage({ onNavigate, onOpenTicket, onSelectView, onNewRequest, ticketVer }) {
  const inProgress = TICKETS.filter(t => t.status === 'progress');
  const triage = TICKETS.filter(t => t.status === 'triage');
  const shipped = TICKETS.filter(t => t.status === 'shipped' || t.status === 'done');
  const shippedCount = shipped.length;

  // Count-up hooks for impact band (#1)
  const pipeline = useCountUp(4.2, 1000);
  const hours    = useCountUp(380, 1100);
  const cycleDay = useCountUp(14,  900);
  const builds   = useCountUp(shippedCount, 950);

  // Count-up hooks for NORTH_STARS — 4 fixed entries (#7)
  const ns0 = useCountUp(NORTH_STARS[0] ? NORTH_STARS[0].count : 0, 900);
  const ns1 = useCountUp(NORTH_STARS[1] ? NORTH_STARS[1].count : 0, 980);
  const ns2 = useCountUp(NORTH_STARS[2] ? NORTH_STARS[2].count : 0, 1060);
  const ns3 = useCountUp(NORTH_STARS[3] ? NORTH_STARS[3].count : 0, 1140);
  const nsCounters = [ns0, ns1, ns2, ns3];

  React.useEffect(() => {
    // Start all count-ups on mount
    pipeline.start();
    hours.start();
    cycleDay.start();
    builds.start();
    ns0.start(); ns1.start(); ns2.start(); ns3.start();
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const thisWeek = [
    { d: '22', m: 'Apr', title: 'Intent → Slack router pilot expands to EMEA',   ticket: 'GTM-138', who: 'PS + Marketing' },
    { d: '24', m: 'Apr', title: 'Ship dedup model v0.7 to staging',               ticket: 'GTM-142', who: 'LR + JN' },
    { d: '25', m: 'Apr', title: 'Demo env autoprovision — SecEng review',         ticket: 'GTM-131', who: 'PS + SecEng' },
    { d: '28', m: 'Apr', title: 'AI lead enrichment pilot kickoff (5 SDRs)',      ticket: 'GTM-142', who: 'LR + Sales' },
    { d: '21', m: 'Apr', title: 'Churn signal scoping review',                    ticket: 'GTM-149', who: 'MJ + CS' },
  ];

  return (
    <div>
      <div className="home-greeting">
        <div className="home-greeting-left">
          <span className="hg-role">GTM Engineer</span>
          <div className="hg-name">Good afternoon, Joe</div>
          <div className="hg-status">
            <span><b style={{ color: 'var(--text-neutral-prominent)', fontWeight: 600 }}>{inProgress.length}</b> in progress</span>
            <span className="sep">·</span>
            <span><b style={{ color: 'var(--text-neutral-prominent)', fontWeight: 600 }}>{triage.length}</b> in triage</span>
            <span className="sep">·</span>
            <span className="next-review">next review 3p today</span>
          </div>
        </div>
        <div className="home-greeting-actions">
          <Button variant="secondary" icon="DownloadIcon">Weekly digest</Button>
          <Button variant="primary" icon="PlusIcon" onClick={onNewRequest}>New request</Button>
        </div>
      </div>

      <div className="impact-band">
        <div className="ib-cell">
          <div className="ib-label">Pipeline unlocked</div>
          <div className={`ib-value accent${pipeline.counting ? ' counting' : ''}`}>
            ${pipeline.value.toFixed(1)}<span className="ib-unit">M</span>
          </div>
          <div className="ib-sub">Attributed to shipped builds this quarter</div>
        </div>
        <div className="ib-cell">
          <div className="ib-label">Hours reclaimed / wk</div>
          <div className={`ib-value green${hours.counting ? ' counting' : ''}`}>
            {Math.round(hours.value)}<span className="ib-unit">+</span>
          </div>
          <div className="ib-sub">Across Sales, CS, SE, and Marketing orgs</div>
        </div>
        <div className="ib-cell">
          <div className="ib-label">Avg build cycle time</div>
          <div className={`ib-value${cycleDay.counting ? ' counting' : ''}`}>
            {Math.round(cycleDay.value)}<span className="ib-unit">d</span>
          </div>
          <div className="ib-sub">Discovery to pilot — industry avg is 6 weeks</div>
        </div>
        <div className="ib-cell">
          <div className="ib-label">Builds shipped</div>
          <div className={`ib-value${builds.counting ? ' counting' : ''}`}>
            {Math.round(builds.value)}
          </div>
          <div className="ib-sub">Across {[...new Set(TICKETS.filter(t => t.status === 'shipped' || t.status === 'done').map(t => t.requester))].length} requesting teams</div>
        </div>
      </div>

      <div className="hero-band" style={{ marginBottom: 20 }}>
        <div className="hero-cell lead">
          <div className="hlabel">GTM Eng mission</div>
          <div className="hvalue">Solve GTM problems</div>
          <div className="lead-sub">Every ticket is a problem a GTM team is stuck on. Our job is to understand the ultimate outcome they need and ship what gets them there.</div>
        </div>
        {NORTH_STARS.map((n, i) => (
          <div key={n.id} className="hero-cell">
            <div className="hlabel" style={{ color: 'rgba(255,255,255,.55)' }}>{n.label}</div>
            <div className={`hvalue${nsCounters[i].counting ? ' counting' : ''}`} style={{ fontSize: 34 }}>
              {Math.round(nsCounters[i].value)}<span className="hunit">tickets</span>
            </div>
            <div className="lead-sub" style={{ maxWidth: 260, fontSize: 13 }}>{n.desc}</div>
          </div>
        ))}
      </div>

      <div className="overview-chart-grid">
        <Card
          title="Delivery mix"
          description="How work is currently distributed across the operating system."
          action={<span className="chart-callout">Updates after every new request</span>}
        >
          <D3StatusChart ticketVer={ticketVer} />
        </Card>
        <Card
          title="Requester pressure"
          description="Weighted by active priority so the loudest queues are obvious."
          action={<span className="chart-callout">Open work only</span>}
        >
          <D3TeamChart ticketVer={ticketVer} />
        </Card>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1.35fr 1fr', gap: 16 }}>
        <Card title="In progress" description={`${inProgress.length} tickets with GTM Eng right now`}
              action={<Button variant="ghost" size="sm" iconRight="ArrowRightIcon" onClick={() => onNavigate('Inbox')}>Open inbox</Button>}
              bodyClassName="">
          {inProgress.map(t => (
            <div key={t.id} className="overview-ticket" onClick={() => onOpenTicket(t.id)}>
              <div className="ot-head">
                <span className="ot-id">{t.id}</span>
                <PriorityChip priority={t.priority} />
                <div style={{ marginLeft: 'auto' }}>{t.owner && <Person id={t.owner} size={20} />}</div>
              </div>
              <div className="ot-title">{t.title}</div>
              <div className="ot-goal">→ {t.jtbd.outcome}</div>
              <div className="ot-progress">
                <ProgressBar pct={t.progress} color={STATUSES.find(s => s.id === 'progress').color} />
                <div className="ot-meta">
                  <span>{PHASES.find(p => p.id === t.phase)?.label}</span>
                  <span>{t.progress}%</span>
                  {t.targetShip && <span>ship {t.targetShip}</span>}
                </div>
              </div>
            </div>
          ))}
        </Card>

        <div className="stack">
          <Card title="This week"
                action={<Button variant="ghost" size="sm" iconRight="ArrowRightIcon">Calendar</Button>}
                bodyClassName="">
            {thisWeek.map((u, i) => (
              <div key={i} className="up-row" onClick={() => onOpenTicket(u.ticket)}>
                <div className="up-date">
                  <div className="d">{u.d}</div>
                  <div className="m">{u.m}</div>
                </div>
                <div className="up-body">
                  <div className="up-title">{u.title}</div>
                  <div className="up-meta">
                    <span>{u.ticket}</span><span className="sep">/</span><span>{u.who}</span>
                  </div>
                </div>
              </div>
            ))}
          </Card>

          <Card title="Fresh in triage" description={`${triage.length} new requests waiting`}
                action={<Button variant="ghost" size="sm" iconRight="ArrowRightIcon" onClick={() => onNavigate('Intake')}>Review</Button>}
                bodyClassName="">
            {triage.length === 0 ? (
              <div style={{ padding: '20px 18px', textAlign: 'center', color: 'var(--text-neutral-subtle)', fontSize: 13 }}>
                No requests waiting
              </div>
            ) : triage.map(t => (
              <div key={t.id} className="up-row" onClick={() => onOpenTicket(t.id)}>
                <div className="up-body">
                  <div className="up-title">{t.title}</div>
                  <div className="up-meta">
                    <TeamChip id={t.requester} /><span className="sep">/</span>
                    <span>{t.updatedAt || t.createdAt}</span>
                  </div>
                </div>
                <PriorityChip priority={t.priority} />
              </div>
            ))}
          </Card>
        </div>
      </div>

      <SectionHeader title="Recently shipped" desc="Outcomes we delivered and are measuring" right={
        <Button variant="ghost" size="sm" iconRight="ArrowRightIcon"
                onClick={() => { onSelectView && onSelectView('view-shipped'); onNavigate('Inbox'); }}>
          See all
        </Button>
      } />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
        {shipped.slice(0, 4).map(t => (
          <div key={t.id} className="shipped-card" onClick={() => onOpenTicket(t.id)}>
            <div className="sc-h">
              <StatusChip status={t.status} size="sm" />
              <span className="sc-id">{t.id}</span>
              <span className="sc-date">{t.shippedAt}</span>
            </div>
            <div className="sc-title">{t.title}</div>
            <div className="sc-goal">→ {t.jtbd.outcome}</div>
            {t.baselineMetric && (
              <div className="sc-outcome">
                <span className="sc-lab">{t.baselineMetric.label}</span>
                <span className="sc-from">{t.baselineMetric.was}</span>
                <Icon name="ArrowRightIcon" size={11} />
                <span className="sc-to">{t.baselineMetric.now}</span>
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

// ============================================================= INTAKE (triage queue)

function IntakePage({ onOpenTicket, onNewRequest }) {
  const triage = TICKETS.filter(t => t.status === 'triage');
  return (
    <div>
      <div className="page-head">
        <div>
          <div className="title-row">
            <h1>New requests</h1>
            <span className="badge b-brand">{triage.length} in triage</span>
          </div>
          <div className="page-desc">Every GTM problem starts here. We read, ask questions, and decide what earns a build.</div>
        </div>
        <div className="actions">
          <Button variant="secondary" icon="FunnelIcon">Filters</Button>
          <Button variant="primary" icon="PlusIcon" onClick={onNewRequest}>Submit request</Button>
        </div>
      </div>

      {triage.length === 0 ? (
        <div className="intake-empty">
          <Icon name="TrayIcon" size={40} style={{ color: 'var(--neutral-300)', marginBottom: 12 }} />
          <div className="ie-title">Triage queue is clear</div>
          <div className="ie-sub">All caught up. Submit a new request when the next GTM problem arrives.</div>
          <Button variant="primary" icon="PlusIcon" onClick={onNewRequest} style={{ marginTop: 16 }}>Submit request</Button>
        </div>
      ) : (
        <div className="intake-grid">
          {triage.map(t => {
            const req = TEAMS.find(x => x.id === t.requester);
            return (
              <div key={t.id} className="intake-card" onClick={() => onOpenTicket(t.id)}>
                <div className="ic-head">
                  <span className="ic-id">{t.id}</span>
                  <span className="ic-team" style={{ color: req?.color, background: req?.color + '14' }}>
                    <span className="d" style={{ background: req?.color }} />{req?.label}
                  </span>
                  <PriorityChip priority={t.priority} />
                </div>
                <div className="ic-title">{t.title}</div>
                <div className="ic-goal">
                  <div className="ic-label">Ultimate goal</div>
                  <div className="ic-goal-text">
                    {(t.jtbd.when || t.jtbd.need || t.jtbd.outcome)
                      ? <>When {t.jtbd.when || '…'}, {PEOPLE[t.requesterPeople[0]]?.name?.split(' ')[0] || 'requester'} needs {t.jtbd.need || '…'}, so that {t.jtbd.outcome || '…'}.</>
                      : <span style={{ color: 'var(--text-neutral-subtle)', fontStyle: 'italic' }}>No goal statement submitted yet.</span>
                    }
                  </div>
                </div>
                <div className="ic-problem">
                  <div className="ic-label">Problem</div>
                  <p><MD text={t.problem} /></p>
                </div>
                <div className="ic-foot">
                  <div className="ic-reqs">
                    {t.requesterPeople.map(id => <Person key={id} id={id} size={20} />)}
                    <span className="ic-req-name">{PEOPLE[t.requesterPeople[0]]?.name}</span>
                    {t.requesterPeople.length > 1 && <span className="ic-more">+{t.requesterPeople.length - 1}</span>}
                  </div>
                  <div className="ic-actions">
                    <Button variant="ghost" size="sm" icon="CheckIcon" onClick={e => e.stopPropagation()}>Accept</Button>
                    <Button variant="ghost" size="sm" icon="ChatCircleIcon" onClick={e => e.stopPropagation()}>Ask</Button>
                    <Button variant="ghost" size="sm" icon="XIcon" onClick={e => e.stopPropagation()}>Decline</Button>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ============================================================= PLAYBOOKS

const PLAYBOOK_EXAMPLES = [
  {
    id: 'pre-call-intel',
    title: 'Pre-Call Intelligence Pack',
    accentColor: '#2563eb',
    depts: [{ label: 'Sales', color: '#2563eb' }, { label: 'Solutions Eng', color: '#ec4899' }],
    problem: 'Reps pull context from 4+ tools before every call — Gong, SFDC, LinkedIn, email — burning 20+ minutes to show up informed. SEs configure demo environments manually per prospect, blocking late-stage deals.',
    builds: [
      { title: 'Pre-call brief', dept: 'Sales', color: '#2563eb' },
      { title: 'Custom deal risk flags', dept: 'Sales', color: '#2563eb' },
      { title: 'Demo env autoprovision', dept: 'Sales', color: '#2563eb' },
      { title: 'Discovery synthesizer', dept: 'Solutions Eng', color: '#ec4899' },
    ],
    outcome: '20+ min manual prep → fully briefed in <2 min. Demo env ready before the call starts.',
  },
  {
    id: 'signal-to-meeting',
    title: 'Signal-to-Booked Meeting',
    accentColor: '#0ea5e9',
    depts: [{ label: 'BDR', color: '#0ea5e9' }, { label: 'Marketing', color: '#f59e0b' }],
    problem: 'Intent signals — Backstage frustration, new platform hires, post-incident windows — exist but sit unactioned. Reps ignore them and revert to generic "how are you doing?" cold calls that go nowhere.',
    builds: [
      { title: 'Morning account prioritizer', dept: 'BDR', color: '#0ea5e9' },
      { title: 'Signal-to-sequence router', dept: 'BDR', color: '#0ea5e9' },
      { title: 'Personalized opener generator', dept: 'BDR', color: '#0ea5e9' },
      { title: 'Intent digest', dept: 'Marketing', color: '#f59e0b' },
    ],
    outcome: 'Signal detected → right sequence selected → personalized opener generated → meeting booked in <24 hours.',
  },
  {
    id: 'zero-surprise-cs',
    title: 'Zero-Surprise Account Coverage',
    accentColor: '#16a34a',
    depts: [{ label: 'Customer Success', color: '#16a34a' }],
    problem: 'CSMs discover risks on Monday morning calls. QBR prep takes half a day of pulling from Gong, SFDC, and Zendesk. Leadership has no visibility into account health before customer meetings happen.',
    builds: [
      { title: 'Customer call brief', dept: 'CS', color: '#16a34a' },
      { title: 'Account health score', dept: 'CS', color: '#16a34a' },
      { title: 'Churn signal alerts', dept: 'CS', color: '#16a34a' },
      { title: 'QBR autopilot', dept: 'CS', color: '#16a34a' },
    ],
    outcome: '2–4 hr QBR prep → 10 min. Churn warning 30–60 days out, not 7. Leadership briefed before every call.',
  },
  {
    id: 'revenue-intel-loop',
    title: 'Revenue Intelligence Loop',
    accentColor: '#7458db',
    depts: [{ label: 'RevOps', color: '#7458db' }, { label: 'Marketing', color: '#f59e0b' }],
    problem: '"How we lost" data never enters Salesforce — loss reasons live in Gong transcripts nobody reads. Campaigns get optimized on instinct, not reality. The closed-won/lost signal never feeds back into targeting.',
    builds: [
      { title: 'Win/loss capture', dept: 'RevOps', color: '#7458db' },
      { title: 'Revenue reporting automation', dept: 'RevOps', color: '#7458db' },
      { title: 'Attribution pipeline', dept: 'Marketing', color: '#f59e0b' },
      { title: 'ICP & account list refresh', dept: 'Marketing', color: '#f59e0b' },
    ],
    outcome: 'Every closed deal auto-tags loss reasons in SFDC. Monthly: ICP re-derived, campaigns updated, attribution proven.',
  },
  {
    id: 'outbound-machine',
    title: 'Full-Funnel Outbound Machine',
    accentColor: '#0ea5e9',
    depts: [{ label: 'BDR', color: '#0ea5e9' }, { label: 'Marketing', color: '#f59e0b' }, { label: 'Sales', color: '#2563eb' }],
    problem: '1,300+ contacts in flows with 324 unprocessed tasks. Account lists are stale. Email sequences optimized for opens, not replies. There is no consistent handoff between marketing campaign, BDR outreach, and AE follow-up.',
    builds: [
      { title: 'Contact freshness monitor', dept: 'BDR', color: '#0ea5e9' },
      { title: 'Account list automation', dept: 'BDR', color: '#0ea5e9' },
      { title: 'MQL enrichment + routing', dept: 'Marketing', color: '#f59e0b' },
      { title: 'Post-call recap email', dept: 'Sales', color: '#2563eb' },
    ],
    outcome: 'Clean contacts → auto-sequenced → MQL routed in <5 min → AE follow-up drafted in 5 min after call.',
  },
  {
    id: 'competitive-response',
    title: 'Competitive Response System',
    accentColor: '#ec4899',
    depts: [{ label: 'Sales', color: '#2563eb' }, { label: 'Solutions Eng', color: '#ec4899' }],
    problem: 'Battle cards are stale in Confluence — nobody updates them, nobody reads them mid-call. SEs rebuild the same RFP answers and proof points from scratch on every deal. Competitive pressure is answered with improvisation.',
    builds: [
      { title: 'Competitive intel bot', dept: 'Sales', color: '#2563eb' },
      { title: 'RFP first-drafter', dept: 'Sales', color: '#2563eb' },
      { title: 'Proof point library', dept: 'Solutions Eng', color: '#ec4899' },
      { title: 'POC success tracker', dept: 'Solutions Eng', color: '#ec4899' },
    ],
    outcome: '/compete backstage in Slack → instant battle card. RFP first draft in 15 min. No Confluence login required.',
  },
];

const DEPT_CATALOG = [
  {
    id: 'sales',
    label: 'Sales',
    color: '#2563eb',
    icon: 'CurrencyDollarIcon',
    tagline: 'Reps spend more time selling, less time doing research and admin.',
    bottlenecks: [
      'Pre-call research pulls from Gong, SFDC, LinkedIn, and email. Takes 20+ min per call just to show up prepared.',
      'Post-call recaps are slow or skipped. Deals go cold in the handoff gap.',
      'Deal risk scoring is a black box: Gong\'s AI assumes CRM hygiene the team doesn\'t actually have.',
      'No real-time signal when a prospect goes hot: pricing page visit, doc open, or champion change.',
      'New leads hit SFDC with no context, no phone, no enrichment. Reps start cold every time.',
    ],
    builds: [
      { icon: 'FileTextIcon',     title: 'Pre-call brief',           desc: 'Auto-generate talk tracks from Gong history, SFDC activity, and signals — delivered to Slack 1 hour before every meeting.',  complexity: 'Quick'   },
      { icon: 'PaperPlaneIcon',   title: 'Post-call recap email',    desc: 'AI drafts a follow-up email from call notes and sends it to the rep\'s outbox within 5 minutes of hangup.',                    complexity: 'Medium'  },
      { icon: 'BellIcon',         title: 'Custom deal risk flags',   desc: 'Transparent risk scoring: >1 week no contact, champion went dark, deal age vs. avg cycle time — no black-box assumptions.',    complexity: 'Quick'   },
      { icon: 'FunnelIcon',       title: 'Lead enrichment pipeline', desc: 'New lead hits SFDC, auto-enriches with mobile + LinkedIn, verifies email, loads into the right Gong Engage flow.',             complexity: 'Medium'  },
      { icon: 'MonitorIcon',      title: 'Demo env autoprovision',   desc: 'One-click sandbox provisioning per prospect — no more manual SE requests blocking late-stage demos.',                          complexity: 'Platform'},
      { icon: 'ShieldCheckIcon',  title: 'Competitive intel bot',    desc: '/compete backstage in Slack → instant battle card with objection handles, win stories, and TCO talking points. No Confluence login mid-call.', complexity: 'Quick' },
      { icon: 'FileTextIcon',     title: 'RFP first-drafter',        desc: 'Upload any RFP → Claude searches 200+ past answers and returns a formatted first draft in 15 minutes. Targets RFPs we decline due to SE capacity.', complexity: 'Medium' },
    ],
  },
  {
    id: 'bdr',
    label: 'BDR',
    color: '#0ea5e9',
    icon: 'BroadcastIcon',
    tagline: 'More meetings from smarter signals, not higher volume.',
    bottlenecks: [
      'Signal filters too tight: only 3 companies clear the full enrichment funnel on a good day.',
      'No structure to the morning. Account lists, events, retargeting, and inbounds all compete with no sequencing.',
      'Reps revert to generic openers instead of using the intent context they\'re given.',
      'Contacts go stale. People leave companies, lists don\'t update, reps burn time on dead ends.',
      'Response rates lag open rates: sequences are optimized for clicks, not replies.',
      'LinkedIn is the only safe channel in some markets. Email reputation is damaged in others.',
    ],
    builds: [
      { icon: 'LightningIcon',    title: 'Morning account prioritizer', desc: 'Surfaces the top 5–10 accounts to work each day based on signal hits, recency, and pipeline stage — delivered via Slack by 8am.',  complexity: 'Quick'   },
      { icon: 'SparkleIcon',      title: 'Personalized opener generator', desc: 'GitHub activity, job postings, and intent signals become a 2-sentence custom opener per cold call. Replaces "how are you doing?"', complexity: 'Quick'   },
      { icon: 'StackIcon',        title: 'Account list automation',   desc: 'Auto-refreshed account lists twice a week: new signal hits surface, gone-dark accounts drop out, and a clean prioritized list lands in Slack.', complexity: 'Medium'  },
      { icon: 'FunnelSimpleIcon', title: 'Signal-to-sequence router', desc: 'Maps each intent signal (Backstage frustration, new platform hire, post-incident window) to the right sequence variant. One click to launch the correct play.', complexity: 'Medium' },
      { icon: 'DatabaseIcon',     title: 'Contact freshness monitor', desc: 'Continuously validates contact records against job-change signals. Flags stale contacts and suggests replacements before reps waste cycles.', complexity: 'Medium' },
    ],
  },
  {
    id: 'marketing',
    label: 'Marketing',
    color: '#f59e0b',
    icon: 'MegaphoneIcon',
    tagline: 'Every campaign dollar is traceable, every hot account gets actioned same-day.',
    bottlenecks: [
      'Attribution is broken: can\'t answer "which campaigns generated revenue" without a manual multi-day pull.',
      'News monitoring for customers and open opps is manual. Team learns about layoffs on Monday morning calls.',
      'Target account list is outdated. Contacts who\'ve changed companies are still being targeted.',
      'Salesforce is too noisy with stale data, making segment targeting and campaign list quality unreliable.',
      'Intent signals from Reo.dev and 6sense sit unread in dashboards nobody has time to check.',
      'Post-event lead lists take days to turn into active sequences. Warm contacts go cold.',
    ],
    builds: [
      { icon: 'BellIcon',         title: 'Customer news monitor',    desc: 'Daily scan of customers and open opps for news signals (layoffs, reorgs, funding, leadership changes) — delivered every morning so no one gets caught off-guard.', complexity: 'Quick'   },
      { icon: 'LightningIcon',    title: 'MQL enrichment + routing', desc: 'Form fill triggers instant enrichment, score, and SFDC routing — qualified lead reaches AE in under 5 minutes.',    complexity: 'Medium'  },
      { icon: 'ChartLineUpIcon',  title: 'Attribution pipeline',     desc: 'UTM tags flow through Marketo and SFDC to closed-won. Every campaign tied to revenue, every channel justified.',    complexity: 'Platform'},
      { icon: 'CalendarIcon',     title: 'Post-event processor',     desc: 'Upload event list, get back a deduplicated, enriched, verified batch loaded into sequences — same day.',             complexity: 'Quick'   },
      { icon: 'BroadcastIcon',    title: 'Intent digest',            desc: 'Weekly Slack summary of warm accounts by rep territory pulled from Reo.dev — no dashboard login required.',           complexity: 'Quick'   },
    ],
  },
  {
    id: 'cs',
    label: 'Customer Success',
    color: '#16a34a',
    icon: 'LifebuoyIcon',
    tagline: 'CSMs know which accounts need attention before the customer says anything.',
    bottlenecks: [
      'No brief before customer calls. CSMs pull context manually from Gong, SFDC, Zendesk, and email.',
      'Leadership is not receiving customer call briefs, creating blind spots in account strategy.',
      'QBR prep takes 2 to 4 hours per account assembling data from Cortex, Gong, Zendesk, and support tools.',
      'Expansion signals like usage spikes and new team spin-ups go unnoticed until renewal.',
      'Churn is reactive. Escalation starts after it is already too late to change the outcome.',
    ],
    builds: [
      { icon: 'HeartbeatIcon',    title: 'Account health score',     desc: 'Product usage, support tickets, and engagement signals combined into a single score surfaced in SFDC daily.',        complexity: 'Platform'},
      { icon: 'FileTextIcon',     title: 'Customer call brief',      desc: 'Auto-sends a pre-call brief to all attendees (including leadership) 1 hour before every customer meeting: health, risks, renewal timeline, and talking points.', complexity: 'Quick' },
      { icon: 'FilePdfIcon',      title: 'QBR autopilot',            desc: 'Pull SFDC data, usage metrics, and open risks into a formatted deck ready to present — 2 hours becomes 10 minutes.',  complexity: 'Medium'  },
      { icon: 'WarningIcon',      title: 'Churn signal alerts',      desc: 'Usage drop, login cadence decline, or support spike detected — CSM gets a Slack alert with account context 30–60 days out, not 7.', complexity: 'Medium'  },
      { icon: 'TrendUpIcon',      title: 'Expansion signal monitor', desc: 'Account adds headcount, opens new engineering orgs, or raises funding — CSM gets notified with a suggested expansion play.',    complexity: 'Quick'   },
      { icon: 'TableIcon',        title: 'Renewal health heatmap',   desc: 'Every account plotted by health trajectory in one Monday-morning view. Red quadrants jump out. Coaching is proactive, not post-mortem.', complexity: 'Medium' },
    ],
  },
  {
    id: 'solutions',
    label: 'Solutions Engineering',
    color: '#ec4899',
    icon: 'StackIcon',
    tagline: 'SEs spend their time on technical depth, not logistics and environment wrangling.',
    bottlenecks: [
      'Demo environments are manually configured per prospect. Takes hours, and a single SE blocks multiple deals.',
      'Demo environment instability during live calls is a real deal risk. Spinners of death mid-presentation happen.',
      'Technical discovery notes live in notebooks and raw Zoom recordings, never structured or actioned.',
      'No shared proof-point library. Every SE rebuilds the same customer case studies from scratch.',
      'RFP and security questionnaire responses are drafted from scratch every time.',
    ],
    builds: [
      { icon: 'CubeIcon',         title: 'Demo environment builder', desc: 'Pick OS, team size, and use case — pre-configured sandbox with realistic data provisions automatically. No more hours of manual SE setup per deal.',  complexity: 'Platform'},
      { icon: 'BookOpenIcon',     title: 'Proof point library',      desc: 'Structured database of customer wins by industry, use case, and persona — surfaced in Slack when a relevant deal opens.',complexity: 'Medium'},
      { icon: 'ClipboardTextIcon',title: 'RFP response assistant',   desc: 'Incoming security and RFP questions matched to a maintained answer bank — first draft in minutes, not days.',        complexity: 'Medium'  },
      { icon: 'VideoIcon',        title: 'Discovery synthesizer',    desc: 'Upload a Zoom transcript from a technical discovery call → structured doc with pain points, technical environment, and recommended Cortex use cases. 2 hours → 5 minutes.', complexity: 'Quick' },
      { icon: 'ListChecksIcon', title: 'POC success tracker',    desc: 'Live dashboard for every active POC: success criteria, blockers, health score, and SE-to-AE handoff status — updated automatically from SFDC and Slack.', complexity: 'Medium' },
    ],
  },
  {
    id: 'revops',
    label: 'Revenue Operations',
    color: '#7458DB',
    icon: 'ChartLineUpIcon',
    tagline: 'Data, process, and tooling so the revenue engine runs clean, fast, and defensible.',
    bottlenecks: [
      '"How we lost" data never makes it into Salesforce. No structured capture, no feedback loop into campaigns or sequences.',
      '1,300 contacts sitting in active flows with 324 unprocessed tasks. Workflow debt compounds silently.',
      'Reporting requires manual pulls from Gong, Salesforce, and Marketo every single week.',
      'Webinar traffic control and email building falls to ops, crowding out higher-leverage strategic work.',
      'CRM is too noisy. Stale data and uncleaned fields make segmentation, scoring, and forecasting unreliable.',
    ],
    builds: [
      { icon: 'GraphIcon',        title: 'Win/loss capture',          desc: 'Structured win/loss data auto-populated into SFDC from Gong transcripts. Loss reasons categorized, searchable, and fed back into campaigns monthly.', complexity: 'Quick'   },
      { icon: 'ChartLineUpIcon',  title: 'Revenue reporting automation', desc: 'Pulls from Salesforce, Gong, and Marketo into a single weekly board-ready report. No more Monday morning manual reconciliation.', complexity: 'Medium'  },
      { icon: 'GearIcon',         title: 'Flow health monitor',       desc: 'Daily audit of all BDR sequences: flags stale contacts, unprocessed tasks, and broken steps. Auto-pauses dead flows, surfaces top cleanup actions.', complexity: 'Medium'  },
      { icon: 'ChartLineUpIcon',  title: 'Forecast accuracy model',   desc: 'ML model trained on pipeline velocity, stage transitions, and rep attainment history — replaces gut feel with a weekly probability-weighted call.', complexity: 'Platform'},
      { icon: 'TableIcon',        title: 'Commission validator',      desc: 'Pull actuals from SFDC, run against comp plan rules, surface discrepancies before payroll closes. Commission disputes from 2 weeks to same-day.', complexity: 'Medium' },
    ],
  },
];

const COMPLEXITY_STYLE = {
  Quick:    { bg: '#dcfce7', color: '#16a34a' },
  Medium:   { bg: '#fef9c3', color: '#ca8a04' },
  Platform: { bg: '#ede9fe', color: '#7c3aed' },
};

function PlaybooksPage({ onNewRequest }) {
  const gridRef = React.useRef(null);
  const animatedRef = React.useRef(false);

  React.useEffect(() => {
    if (animatedRef.current) return;
    animatedRef.current = true;
    // small delay so layout is painted before we trigger the CSS animation
    const id = setTimeout(() => {
      if (gridRef.current) gridRef.current.classList.add('animate');
    }, 40);
    return () => clearTimeout(id);
  }, []);

  return (
    <div>
      <div className="page-head">
        <div>
          <div className="title-row"><h1>What we build</h1></div>
          <div className="page-desc">GTM Eng builds across every revenue team. Find your department, see what's possible, and request a build.</div>
        </div>
        <div className="actions">
          <Button variant="primary" icon="PlusIcon" onClick={onNewRequest}>New request</Button>
        </div>
      </div>

      <SectionHeader
        title="Playbook examples"
        desc="See how individual builds combine into end-to-end workflows. Each playbook solves a specific GTM bottleneck by wiring multiple automations together."
      />
      <div className="playbook-examples" ref={gridRef}>
        {PLAYBOOK_EXAMPLES.map(pb => (
          <div key={pb.id} className="pex-card">
            <div className="pex-accent" style={{ background: pb.accentColor }} />
            <div className="pex-body">
              <div className="pex-top">
                <div className="pex-pills">
                  {pb.depts.map(d => (
                    <span key={d.label} className="pex-pill" style={{ background: d.color + '18', color: d.color }}>{d.label}</span>
                  ))}
                </div>
              </div>
              <div className="pex-title">{pb.title}</div>
              <div className="pex-problem">{pb.problem}</div>
              <div>
                <div className="pex-builds-label">Builds included</div>
                <div className="pex-build-chain">
                  {pb.builds.map((b, i) => (
                    <div key={i} className="pex-build-row">
                      <div className="pex-build-dot" style={{ background: b.color }} />
                      <span style={{ fontWeight: 500 }}>{b.title}</span>
                      <span style={{ color: 'var(--text-neutral-muted)', fontSize: 12 }}>· {b.dept}</span>
                    </div>
                  ))}
                </div>
              </div>
              <div className="pex-outcome">
                <span className="pex-outcome-arrow">→</span>
                <span>{pb.outcome}</span>
              </div>
            </div>
            <div className="pex-footer">
              <button className="pex-cta" style={{ color: pb.accentColor, borderColor: pb.accentColor + '40' }}
                      onClick={onNewRequest}>
                <Icon name="LightningIcon" size={12} weight="fill" style={{ color: pb.accentColor }} />
                Request this playbook
              </button>
            </div>
          </div>
        ))}
      </div>

      <SectionHeader
        title="What we can build for you"
        desc="GTM Eng builds across every revenue team. Find your department, see what's possible, and request a build."
      />
      <div className="dept-catalog">
        {DEPT_CATALOG.map(dept => (
          <div key={dept.id} className="dept-section" style={{ '--dept-color': dept.color }}>
            <div className="dept-header" style={{ borderLeftColor: dept.color }}>
              <div className="dept-header-left">
                <div className="dept-icon" style={{ background: dept.color + '18', color: dept.color }}>
                  <Icon name={dept.icon} size={18} weight="fill" />
                </div>
                <div>
                  <div className="dept-name">{dept.label}</div>
                  <div className="dept-tagline">{dept.tagline}</div>
                </div>
              </div>
            </div>

            <div className="dept-bottlenecks">
              <div className="dept-bottleneck-label">Where the day breaks down</div>
              <div className="dept-bottleneck-list">
                {dept.bottlenecks.map((b, i) => (
                  <div key={i} className="dept-bottleneck-item">
                    <Icon name="WarningCircleIcon" size={14} weight="fill" style={{ color: '#f59e0b', flexShrink: 0, marginTop: 2 }} />
                    <span>{b}</span>
                  </div>
                ))}
              </div>
            </div>

            <div className="dept-builds">
              {dept.builds.map((build, i) => {
                const cs = COMPLEXITY_STYLE[build.complexity] || COMPLEXITY_STYLE.Medium;
                return (
                  <div key={i} className="dept-build-card">
                    <div className="dbc-top">
                      <div className="dbc-icon" style={{ background: dept.color + '14', color: dept.color }}>
                        <Icon name={build.icon} size={15} weight="fill" />
                      </div>
                      <div className="dbc-complexity" style={{ background: cs.bg, color: cs.color }}>{build.complexity}</div>
                    </div>
                    <div className="dbc-title">{build.title}</div>
                    <div className="dbc-desc">{build.desc}</div>
                    <button className="dbc-cta" style={{ color: dept.color, borderColor: dept.color + '40' }}
                            onClick={onNewRequest}>
                      Request this
                    </button>
                  </div>
                );
              })}
              {Array.from({ length: (3 - (dept.builds.length % 3)) % 3 }).map((_, i) => (
                <div key={`empty-${i}`} className="dept-build-card-empty" />
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Object.assign(window, { HomePage, IntakePage, PlaybooksPage, IntakeFormSlideOver });
