/* project-lifeline.jsx — new visual primitives for the redesigned Project
   Detail (Direction B). Pure presentation: reads the live snapshot `db`
   + `project` and renders the multi-lane lifeline and a progress ring.
   Reuses window.MODEL (aggregation) and window.AC (formatting).
   Exports: window.LifelineLanes, window.ProgressRing. */
(function () {
  const { money, fmtDate } = window.AC;
  const MODEL = window.MODEL;

  const iso = (v) => String(v).slice(0, 10);
  const parse = (v) => { const [y, m, d] = iso(v).split('-').map(Number); return Date.UTC(y, m - 1, d); };
  const toIso = (ms) => new Date(ms).toISOString().slice(0, 10);
  const addMonths = (ms, n) => { const d = new Date(ms); return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + n, d.getUTCDate()); };
  const addDays = (ms, n) => ms + n * 86400000;
  const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

  function billingDates(del, horizonMs) {
    const start = parse(del.billingStartDate || MODEL.TODAY);
    const freq = del.recurrenceFrequency || 'None';
    const end = del.recurrenceEndDate ? Math.min(parse(del.recurrenceEndDate), horizonMs) : horizonMs;
    if (freq === 'None') return [del.billingStartDate || MODEL.TODAY];
    const out = []; let cur = start, guard = 0;
    while (cur <= end && guard < 60) {
      out.push(toIso(cur));
      cur = freq === 'Weekly' ? addDays(cur, 7) : freq === 'Yearly' ? addMonths(cur, 12) : addMonths(cur, 1);
      guard++;
    }
    return out;
  }

  // Blended event model: project start + milestones + deliverable billing + invoices.
  function buildEvents(project, db) {
    const ev = [];
    const todayMs = parse(MODEL.TODAY);
    const projectStartDate = project.startDate || MODEL.TODAY;
    ev.push({ date: projectStartDate, kind: 'start', shape: 'circle', color: 'var(--muted)', title: 'Project start', sub: project.kind });

    (project.milestones || []).forEach((m) => {
      const overdue = !m.completed && m.dueDate && parse(m.dueDate) < todayMs;
      ev.push({ date: m.dueDate || MODEL.TODAY, kind: 'milestone', shape: 'circle', color: m.completed ? 'var(--pos)' : overdue ? 'var(--neg)' : 'var(--primary)', title: m.title, amount: 0 });
    });

    let maxMs = todayMs;
    (project.milestones || []).forEach((m) => { if (m.dueDate) maxMs = Math.max(maxMs, parse(m.dueDate)); });
    (db.invoices || []).forEach((iv) => { maxMs = Math.max(maxMs, parse(iv.issueDate)); });
    (project.projectDeliverables || []).forEach((d) => { if ((d.recurrenceFrequency || 'None') === 'None') maxMs = Math.max(maxMs, parse(d.billingStartDate || MODEL.TODAY)); });
    const horizon = addDays(maxMs, 45);

    (project.projectDeliverables || []).forEach((d) => {
      billingDates(d, horizon).forEach((date) => ev.push({ date, kind: 'billing', shape: 'square', color: 'var(--info)', title: d.name, sub: (d.recurrenceFrequency && d.recurrenceFrequency !== 'None') ? d.recurrenceFrequency + ' billing' : 'One-off billing', amount: d.unitPrice }));
    });

    const colorMap = { Paid: 'var(--pos)', Open: 'var(--warn)', Overdue: 'var(--neg)', Partial: 'var(--warn)', Draft: 'var(--faint)', Cancelled: 'var(--faint)' };
    (db.invoices || []).forEach((iv) => {
      const lines = iv.lines || [];
      const linked = lines.some((l) => l.projectId === project.id || (project.projectDeliverables || []).some((d) => d.id === l.projectDeliverableId));
      if (!linked) return;
      const e = MODEL.enrich(db, iv);
      ev.push({ date: iv.issueDate, kind: 'invoice', shape: 'circle', color: colorMap[e.display] || 'var(--primary)', title: iv.number, sub: 'Invoice · ' + e.display, status: e.display, amount: e.totals.total });
    });

    ev.forEach((e) => { e.future = parse(e.date) > todayMs; });

    let lo = todayMs, hi = todayMs;
    ev.forEach((e) => { lo = Math.min(lo, parse(e.date)); hi = Math.max(hi, parse(e.date)); });
    lo = Math.min(lo, parse(projectStartDate));
    hi = Math.max(hi, todayMs);
    const span = Math.max(1, hi - lo);
    const pad = 0.035;
    const x = (date) => pad * 100 + (1 - 2 * pad) * 100 * (parse(date) - lo) / span;

    const grid = [];
    let cur = Date.UTC(new Date(lo).getUTCFullYear(), new Date(lo).getUTCMonth(), 1);
    cur = addMonths(cur, 1);
    while (cur < hi) { const d = new Date(cur); grid.push({ x: x(toIso(cur)), label: MONTHS[d.getUTCMonth()], qtr: d.getUTCMonth() % 3 === 0 }); cur = addMonths(cur, 1); }

    ev.sort((a, b) => parse(a.date) - parse(b.date));
    return { events: ev, x, grid, todayX: x(MODEL.TODAY) };
  }

  const nodeTip = (e) => `${e.title} — ${fmtDate(e.date)}${e.amount ? ' · ' + money(e.amount) : ''}${e.sub ? '\n' + e.sub : ''}`;

  function LifelineLanes({ project, db }) {
    const L = buildEvents(project, db);
    const lanes = [
      { key: 'milestone', label: 'Milestones', icon: 'check', bg: 'var(--primary-soft)', fg: 'var(--primary-strong)' },
      { key: 'billing', label: 'Billing', icon: 'refresh', bg: '#e2f1f4', fg: 'var(--info)' },
      { key: 'invoice', label: 'Invoices', icon: 'invoices', bg: 'var(--warn-soft)', fg: 'var(--warn)' },
    ];
    const projectStartDate = project.startDate || MODEL.TODAY;
    const startX = L.x(projectStartDate);
    const lastMs = L.events.filter((e) => e.kind === 'milestone').reduce((a, e) => Math.max(a, L.x(e.date)), startX);
    const milestoneSpanWidth = Math.max(0, lastMs - startX);

    return (
      <div>
        <div className="ll-months" style={{ marginLeft: 116 }}>
          {L.grid.map((g, i) => <span key={i} className="ll-month" style={{ left: g.x + '%' }}>{g.label}</span>)}
        </div>
        <div className="ll-lanes" style={{ position: 'relative' }}>
          <div className="ll-today" style={{ '--ll-today-x': L.todayX + '%', top: 0, bottom: 0, zIndex: 5 }} />
          {lanes.map((lane) => {
            const items = L.events.filter((e) => e.kind === lane.key || (lane.key === 'milestone' && e.kind === 'start'));
            return (
              <div className="ll-lane" key={lane.key}>
                <div className="ll-lane-label">
                  <span className="lico" style={{ background: lane.bg, color: lane.fg }}><Icon name={lane.icon} size={13} /></span>{lane.label}
                </div>
                <div className="ll-lane-track">
                  {L.grid.map((g, i) => <span key={i} className={'ll-grid' + (g.qtr ? ' qtr' : '')} style={{ left: g.x + '%', top: 0, bottom: 0 }} />)}
                  {lane.key === 'milestone' && <span className="ll-span" style={{ left: startX + '%', width: milestoneSpanWidth + '%', top: '50%', transform: 'translateY(-50%)' }} />}
                  {items.map((e, i) => (
                    <span key={i} className={'ll-node' + (e.shape === 'square' ? ' sq' : '') + (e.future ? ' future' : '')} title={nodeTip(e)} style={{ left: L.x(e.date) + '%', top: '50%', background: e.color }} />
                  ))}
                </div>
              </div>
            );
          })}
        </div>
        <div className="ll-legend" style={{ marginTop: 12, marginLeft: 116 }}>
          <span className="lg"><span className="sw" style={{ background: 'var(--pos)' }} />Done</span>
          <span className="lg"><span className="sw" style={{ background: 'var(--primary)' }} />Upcoming</span>
          <span className="lg"><span className="sw" style={{ background: 'var(--neg)' }} />Overdue</span>
          <span className="lg"><span className="sw sq" style={{ background: 'var(--info)' }} />Billing event</span>
          <span className="lg"><span className="sw" style={{ background: 'var(--warn)' }} />Invoice open</span>
          <span className="lg"><span className="sw" style={{ background: 'var(--neg)', boxShadow: 'none', width: 2, borderRadius: 0, height: 12 }} />Today</span>
        </div>
      </div>
    );
  }

  function ProgressRing({ value, size = 96, stroke = 9, label, color = 'var(--pos)' }) {
    const r = (size - stroke) / 2;
    const c = 2 * Math.PI * r;
    const pct = Math.max(0, Math.min(1, value));
    return (
      <div className="ring" style={{ width: size, height: size }}>
        <svg width={size} height={size}>
          <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--surface-3)" strokeWidth={stroke} />
          <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={color} strokeWidth={stroke} strokeLinecap="round" strokeDasharray={c} strokeDashoffset={c * (1 - pct)} transform={`rotate(-90 ${size / 2} ${size / 2})`} style={{ transition: 'stroke-dashoffset .4s' }} />
        </svg>
        <div className="ring-center">
          <span className="ring-pct" style={{ fontSize: size * 0.26 }}>{Math.round(pct * 100)}%</span>
          {label && <span className="ring-sub">{label}</span>}
        </div>
      </div>
    );
  }

  Object.assign(window, { LifelineLanes, ProgressRing });
})();
