/* Sprint Planner — Capacity Lanes paradigm
   The page IS the capacity rail. One lane per person, tickets stack inline
   against the capacity ceiling, PTO is visible where it falls. */

const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ============ DATA ============ */

const PEOPLE = [
  { id: 'maya', name: 'Maya Patel',     role: 'SR · DESIGN', tone: 1, contracted: 40,
    pto: [{ label: 'Mon May 26', hours: 8, dayIdx: 1 }] },
  { id: 'tom',  name: 'Tom Reyes',      role: 'STAFF · ENG', tone: 2, contracted: 40,
    pto: [{ label: 'Thu–Fri May 29–30', hours: 16, dayIdx: 4 }] },
  { id: 'jin',  name: 'Jin Liu',        role: 'SR · ENG',    tone: 3, contracted: 40,
    pto: [] },
  { id: 'aria', name: 'Aria Nakamura',  role: 'DESIGN',      tone: 5, contracted: 40,
    pto: [] },
];

// Real ticket type field — bug / story / task / epic — chosen on creation
// (not derived from priority anymore)
const BACKLOG_SEED = [
  { id: 'SYM-412', type: 'story', title: 'Capacity calculation engine · v2',           hours: 6,  assignee: 'tom',  priority: 'P1', meta: '2 sub-tasks' },
  { id: 'SYM-418', type: 'bug',   title: 'PTO indicator wrong color in dark mode',     hours: 3,  assignee: 'maya', priority: 'P2' },
  { id: 'SYM-419', type: 'story', title: 'Cost-per-ticket display on hover',           hours: 8,  assignee: 'jin',  priority: 'P1', meta: 'design ready' },
  { id: 'SYM-421', type: 'story', title: 'Onboarding flow · Step 4 redesign',          hours: 12, assignee: 'aria', priority: 'P2', meta: 'blocked · waiting on copy' },
  { id: 'SYM-424', type: 'task',  title: 'Wire approval chain to leave model',         hours: 4,  assignee: 'tom',  priority: 'P0' },
  { id: 'SYM-426', type: 'bug',   title: 'Audit log streams missing employee.updated', hours: 5,  assignee: 'jin',  priority: 'P1' },
  { id: 'SYM-427', type: 'task',  title: 'Update SCIM token rotation docs',            hours: 3,  assignee: 'maya', priority: 'P3' },
  { id: 'SYM-429', type: 'bug',   title: 'Cmd+K jumps to wrong panel after refresh',   hours: 2,  assignee: 'jin',  priority: 'P2' },
  { id: 'SYM-431', type: 'story', title: 'Payslip PDF watermark in dark mode',         hours: 4,  assignee: 'aria', priority: 'P2' },
  { id: 'SYM-433', type: 'task',  title: 'Migrate webhook secrets to KMS',             hours: 6,  assignee: 'tom',  priority: 'P1' },
  { id: 'SYM-435', type: 'epic',  title: 'Sprint review export · stakeholder digest',  hours: 16, assignee: 'maya', priority: 'P1', meta: '4 sub-tickets' },
  { id: 'SYM-437', type: 'bug',   title: 'Burndown spline jitters under 5 tickets',    hours: 3,  assignee: 'jin',  priority: 'P3' },
];

const SPRINT_SEED = [
  { id: 'SYM-401', type: 'story', title: 'Burn-down chart export to PDF',                       hours: 6,  assignee: 'tom',  priority: 'P0', meta: 'est 6h' },
  { id: 'SYM-404', type: 'bug',   title: 'Sprint planner drag-drop loses ticket on Safari',     hours: 4,  assignee: 'tom',  priority: 'P0' },
  { id: 'SYM-405', type: 'story', title: 'Capacity rail on project board page',                 hours: 8,  assignee: 'maya', priority: 'P1' },
  { id: 'SYM-406', type: 'epic',  title: 'Project cost dashboard · v1',                         hours: 12, assignee: 'maya', priority: 'P1', meta: '3 sub-tickets' },
  { id: 'SYM-408', type: 'task',  title: 'Wire Slack approval webhook',                         hours: 5,  assignee: 'jin',  priority: 'P1' },
  { id: 'SYM-410', type: 'story', title: 'AI co-pilot · capacity question handler',             hours: 6,  assignee: 'jin',  priority: 'P2' },
  { id: 'SYM-411', type: 'task',  title: 'Polish capacity rail micro-animations',               hours: 6,  assignee: 'aria', priority: 'P2' },
  // Tom over-allocated scenario:
  { id: 'SYM-414', type: 'story', title: 'Webhook retry policy w/ exponential backoff',         hours: 8,  assignee: 'tom',  priority: 'P1' },
  { id: 'SYM-417', type: 'task',  title: 'Refactor sprint close handler · idempotent',          hours: 5,  assignee: 'tom',  priority: 'P1' },
  { id: 'SYM-422', type: 'bug',   title: 'Comments thread loses scroll on new reply',           hours: 3,  assignee: 'tom',  priority: 'P2' },
];

const SPRINTS = [
  { id: 's24', label: 'Sprint 24',  range: 'May 20 → Jun 2',  state: 'planning', goal: 'Ship cost dashboard MVP + AI co-pilot capacity hook', velocityActual: null },
  { id: 's23', label: 'Sprint 23',  range: 'May 6 → May 19',  state: 'closed',   goal: 'Audit log v2',                                          velocityActual: 84 },
  { id: 's22', label: 'Sprint 22',  range: 'Apr 22 → May 5',  state: 'closed',   goal: 'Payroll engine refactor',                               velocityActual: 76 },
  { id: 's21', label: 'Sprint 21',  range: 'Apr 8 → Apr 21',  state: 'closed',   goal: 'Onboarding redesign',                                   velocityActual: 74 },
];

const PROJECTS = [
  { id: 'sym', label: 'Symphora Web',       crumb: 'symphora web',       active: true },
  { id: 'api', label: 'Platform API',       crumb: 'platform api',       active: false },
  { id: 'mob', label: 'Mobile companion',   crumb: 'mobile companion',   active: false },
];

/* ============ HELPERS ============ */

const TYPE_LABEL = { bug: 'B', story: 'S', task: 'T', epic: 'E' };
const TYPE_FULL  = { bug: 'Bug', story: 'Story', task: 'Task', epic: 'Epic' };

function pctOf(n, d) { return Math.round((n / d) * 100); }

function personBucket(p, tickets) {
  const assigned = tickets.filter(t => t.assignee === p.id);
  const usedH    = assigned.reduce((s, t) => s + t.hours, 0);
  const ptoH     = p.pto.reduce((s, x) => s + x.hours, 0);
  const availH   = p.contracted - ptoH;
  const overH    = Math.max(0, usedH - availH);
  const slackH   = Math.max(0, availH - usedH);
  return { assigned, usedH, ptoH, availH, overH, slackH, pct: pctOf(usedH, availH) };
}

function totals(tickets) {
  const usedH  = tickets.reduce((s, t) => s + t.hours, 0);
  const teamAvail = PEOPLE.reduce((s, p) => s + (p.contracted - p.pto.reduce((q, x) => q + x.hours, 0)), 0);
  return { usedH, teamAvail, pct: pctOf(usedH, teamAvail) };
}

/* ============ ICONS ============ */

const Icon = ({ d, size = 14, stroke = 1.75 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
       strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"
       dangerouslySetInnerHTML={{ __html: d }} />
);
const ICONS = {
  chev:   '<path d="M9 6l6 6-6 6"/>',
  chevD:  '<path d="M6 9l6 6 6-6"/>',
  plus:   '<path d="M12 5v14M5 12h14"/>',
  x:      '<path d="M18 6L6 18M6 6l12 12"/>',
  pencil: '<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/>',
  alert:  '<path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4M12 17h.01"/>',
  info:   '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>',
  check:  '<path d="M20 6 9 17l-5-5"/>',
  spark:  '<path d="M12 2v6M12 16v6M2 12h6M16 12h6"/>',
  search: '<circle cx="11" cy="11" r="7"/><path d="m20 20-3-3"/>',
  filter: '<path d="M3 4h18l-7 9v6l-4 2v-8L3 4z"/>',
  undo:   '<path d="M3 7v6h6M21 17a9 9 0 0 0-15-6.7L3 13"/>',
  grip:   '<circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/>',
  lock:   '<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>',
  flag:   '<path d="M4 21V4M4 4h13l-2 5 2 5H4"/>',
  cal:    '<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/>',
  arrowR: '<path d="M5 12h14m-6-6 6 6-6 6"/>',
  arrowL: '<path d="M19 12H5m6 6-6-6 6-6"/>',
  trash:  '<path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/>',
  ai:     '<path d="M12 3l2.5 6.5L21 12l-6.5 2.5L12 21l-2.5-6.5L3 12l6.5-2.5z"/>',
  layout: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/>',
};

/* ============ TICKET CHIP ============ */

function TicketChip({ t, onClick, draggable = true, onDragStart, onDragEnd, compact, person }) {
  const overflow = person && (person.usedH > person.availH) &&
                   (person.usedH - person.availH) >= t.hours;
  return (
    <div
      className={"chip-t" + (compact ? " chip-t--compact" : "")}
      draggable={draggable}
      onDragStart={onDragStart}
      onDragEnd={onDragEnd}
      onClick={onClick}
      data-type={t.type}
      title={`${t.id} · ${TYPE_FULL[t.type]} · ${t.hours}h`}
    >
      <span className="chip-t__type">{TYPE_LABEL[t.type]}</span>
      <span className="chip-t__id">{t.id}</span>
      <span className="chip-t__title">{t.title}</span>
      <span className="chip-t__hours">{t.hours}h</span>
    </div>
  );
}

/* ============ HEADER (switcher + actions) ============ */

function Header({ sprint, project, onSwitcher, onEdit, onClose, state, onLock }) {
  return (
    <header className="sp-head">
      <div className="sp-head__left">
        <button className="sprint-switcher" onClick={onSwitcher} aria-haspopup="dialog">
          <div className="sprint-switcher__crumb">
            <span className="sprint-switcher__project">{project.label}</span>
            <Icon d={ICONS.chev} size={11} />
            <span className="sprint-switcher__sprint">{sprint.label}</span>
            <span className={"sprint-switcher__state state-" + state}>
              {state === 'planning' && 'planning'}
              {state === 'locked'   && 'locked'}
              {state === 'closing'  && 'closing soon'}
              {state === 'closed'   && 'closed'}
            </span>
          </div>
          <Icon d={ICONS.chevD} size={14} />
        </button>
        <h1 className="sp-head__title">{sprint.range}</h1>
        <button className="sp-head__goal" onClick={onEdit} title="Edit sprint">
          <span className="sp-head__goal__lbl">Goal</span>
          <span className="sp-head__goal__txt">{sprint.goal}</span>
          <Icon d={ICONS.pencil} size={12} />
        </button>
      </div>
      <div className="sp-head__right">
        <button className="btn btn--ghost" onClick={onEdit}>
          <Icon d={ICONS.cal} size={13} />Edit sprint
        </button>
        {state === 'planning' && (
          <button className="btn btn--primary" onClick={onLock}>
            <Icon d={ICONS.lock} size={13} />Lock sprint
          </button>
        )}
        {state === 'locked' && (
          <button className="btn btn--accent" onClick={onClose}>
            <Icon d={ICONS.flag} size={13} />Close sprint →
          </button>
        )}
        {state === 'closing' && (
          <button className="btn btn--accent" onClick={onClose}>
            <Icon d={ICONS.flag} size={13} />Review &amp; close →
          </button>
        )}
        {state === 'closed' && (
          <a className="btn btn--ghost" href="#">View sprint review</a>
        )}
      </div>
    </header>
  );
}

/* ============ BANNER (stats) ============ */

function Banner({ tickets, state }) {
  const t = totals(tickets);
  const overPeople = PEOPLE.filter(p => personBucket(p, tickets).overH > 0).length;
  const totalPTO = PEOPLE.reduce((s, p) => s + p.pto.reduce((q, x) => q + x.hours, 0), 0);

  // Typical velocity = mean of last 3 closed sprints
  const velocityTypical = Math.round(
    SPRINTS.filter(s => s.velocityActual).slice(0, 3)
      .reduce((s, x) => s + x.velocityActual, 0) / 3
  );
  const burn = t.usedH * 84;

  return (
    <section className="sp-banner">
      <Stat label="Planned" value={`${t.usedH}h`} sub={`of ${t.teamAvail}h available`} pct={t.pct} tone={t.pct > 100 ? 'bad' : t.pct > 90 ? 'warn' : 'good'} />
      <Stat label="Typical velocity" value={`${velocityTypical}h`} sub="mean of last 3 sprints" hint />
      <Stat label="PTO in sprint" value={`${totalPTO}h`} sub={
        PEOPLE.filter(p => p.pto.length).map(p => `${p.name.split(' ')[0]}: ${p.pto.map(x => x.label).join(', ')}`).join(' · ')
      } />
      <Stat label="Burn cost" value={`$${(burn/1000).toFixed(1)}k`} sub="$84/hr blended · loaded" />
      <Stat label="Over-allocated" value={overPeople ? `${overPeople}` : '0'} sub={overPeople ? 'blocks sprint lock' : 'clear to lock'} tone={overPeople ? 'bad' : 'good'} />
    </section>
  );
}

function Stat({ label, value, sub, pct, tone, hint }) {
  return (
    <div className="sp-stat">
      <div className="sp-stat__label">{label}</div>
      <div className={"sp-stat__val" + (tone ? " is-" + tone : "")}>{value}{pct !== undefined && <span className="sp-stat__pct">·{pct}%</span>}</div>
      <div className={"sp-stat__sub" + (hint ? " is-hint" : "")}>{sub}</div>
    </div>
  );
}

Object.assign(window, { Icon, ICONS, TicketChip, PEOPLE, BACKLOG_SEED, SPRINT_SEED, SPRINTS, PROJECTS, TYPE_LABEL, TYPE_FULL, personBucket, totals, pctOf, Header, Banner, Stat });
