/* === Timeline — firmness lives in the END smear, not the whole bar ===
   Every bar is crisp navy through its locked end-date.
   Past the nominal end, an extension fades / smears further into the future
   depending on how firm the date is:
     locked     → no extension, crisp end
     quoted     → ~½ month fade extension
     estimated  → 2–4 month blurred smear extension
   Bar color stays navy. Red/yellow is reserved for at-risk items only.
*/

const TL_ITEMS = [
// {label, milestone, startMo, endMo, firmness, atRisk?}
{ id: "t1", label: "Wealth Brief #4", milestone: "M-03", startMo: 4, endMo: 6, firmness: "locked" },
{ id: "t2", label: "Saver's Match conv.", milestone: "M-02", startMo: 6, endMo: 7, firmness: "locked" },
{ id: "t3", label: "OR TA — signature", milestone: "M-10", startMo: 5, endMo: 6, firmness: "estimated", atRisk: "yellow" },
{ id: "t4", label: "TN fiscal report", milestone: "M-04", startMo: 5, endMo: 7, firmness: "estimated", atRisk: "yellow" },
{ id: "t5", label: "Saver's Match paper", milestone: "M-09", startMo: 5, endMo: 7, firmness: "quoted" },
{ id: "t6", label: "KYC scoping memo", milestone: "M-11", startMo: 5, endMo: 8, firmness: "estimated" },
{ id: "t7", label: "Survey wave 3", milestone: "M-03", startMo: 4, endMo: 11, firmness: "quoted" },
{ id: "t8", label: "NAST state treasurers", milestone: "M-02", startMo: 9, endMo: 10, firmness: "quoted" },
{ id: "t9", label: "Aspen training pack v1", milestone: "M-05", startMo: 6, endMo: 9, firmness: "estimated" },
{ id: "t10", label: "Asset-limits paper", milestone: "M-08", startMo: 4, endMo: 8, firmness: "estimated", atRisk: "red" }];

const FUZZ_EXTEND = {
  // months to extend past the nominal end, per firmness × fuzziness setting
  subtle: { locked: 0, quoted: 0.4, estimated: 1.4 },
  medium: { locked: 0, quoted: 0.7, estimated: 2.4 },
  bold:   { locked: 0, quoted: 1.2, estimated: 3.6 }
};

/* 12-month window anchored at the first of LAST month, so the chart always
   shows a little recent past and ~11 months ahead. */
const TL_RANGE = 12;
const __tlNow = new Date();
const __tlStart = new Date(__tlNow.getFullYear(), __tlNow.getMonth() - 1, 1);
const TL_BASE_YEAR = __tlStart.getFullYear();
const TL_START_MO = __tlStart.getMonth() + 1;
const TL_TODAY_MO = (__tlNow.getFullYear() - TL_BASE_YEAR) * 12 + (__tlNow.getMonth() + 1) + (__tlNow.getDate() - 1) / 30;

const TL_MONTHS = Array.from({ length: TL_RANGE }, (_, i) =>
  new Date(TL_BASE_YEAR, TL_START_MO - 1 + i, 1).toLocaleDateString("en-US", { month: "short" }));

function tlPct(mo) {
  return ((mo - TL_START_MO) / TL_RANGE) * 100;
}

function FirmnessTimeline({
  fuzziness = "medium",
  title = "Upcoming",
  sub = "Solid through the locked end-date · the extension past it shows how confident the date is.",
  items = TL_ITEMS,
  contracts = null,
  contractsLabel = "Contracts",
}) {
  return (
    <div className="card timeline-card">
      <div className="tl-head">
        <div>
          <div className="section-title" style={{ fontSize: 16 }}>{title}</div>
          <div className="section-sub">{sub}</div>
        </div>
        <div className="row" style={{ gap: 14 }}>
          <FuzzKey kind="locked" label="Locked" />
          <FuzzKey kind="quoted" label="Quoted" />
          <FuzzKey kind="estimated" label="Estimated" />
        </div>
      </div>

      <div className="tl-chart">
        {/* Background: full-height month grid + today line */}
        <div className="tl-gutter"></div>
        <div className="tl-bg">
          {TL_MONTHS.map((m, i) => (
            <div key={m} className="tl-gridline" style={{ left: `${(i / TL_RANGE) * 100}%` }}></div>
          ))}
          <div className="tl-today" style={{ left: `${tlPct(TL_TODAY_MO)}%` }}></div>
        </div>

        {/* Month header */}
        <div className="tl-monthrow">
          {TL_MONTHS.map((m, i) => (
            <div key={m} className="tl-month" style={{ left: `${(i / TL_RANGE) * 100}%` }}>{m}</div>
          ))}
          <div className="tl-today-label" style={{ left: `${tlPct(TL_TODAY_MO)}%` }}>TODAY</div>
        </div>

        {/* Work / milestone rows */}
        {items && items.length > 0 ? (
          <TlSection label="Project work" items={items} fuzziness={fuzziness} />
        ) : null}

        {contracts && contracts.length > 0 ? (
          <TlSection label={contractsLabel} items={contracts} fuzziness={fuzziness} top={items && items.length > 0} />
        ) : null}
      </div>
    </div>
  );
}

function TlSection({ label, items, fuzziness, top }) {
  return (
    <>
      <div className={`tl-section-head ${top ? "tl-section-head--top" : ""}`}>
        <span className="eyebrow">{label}</span>
      </div>
      {items.map((it) => (
        <div key={it.id} className="tl-row">
          <div className="tl-row-label">{it.label}</div>
          <SmearBar item={it} fuzziness={fuzziness} />
        </div>
      ))}
    </>
  );
}

function SmearBar({ item, fuzziness }) {
  const leftPct = tlPct(item.startMo);
  const naturalEndPct = tlPct(item.endMo);
  const chartEndPct = 100;
  const clipped = naturalEndPct > chartEndPct;
  const endPct = clipped ? chartEndPct : naturalEndPct;
  const widthPct = endPct - leftPct;
  const extendMonths = clipped ? 0 : (FUZZ_EXTEND[fuzziness][item.firmness] || 0);
  const extendPct = (extendMonths / TL_RANGE) * 100;
  const color = item.atRisk === "red" ? "var(--red)" :
                item.atRisk === "yellow" ? "var(--yellow)" : "var(--navy)";
  const colorSoft = item.atRisk === "red" ? "var(--red)" :
                    item.atRisk === "yellow" ? "var(--yellow)" : "var(--navy-soft)";
  const tag = item.milestone || (item.kind === "contract" ? "EXP" : "");

  return (
    <>
      {/* Solid core */}
      <div style={{
        position: "absolute",
        left: `${leftPct}%`,
        width: `${widthPct}%`,
        top: 6, height: 14,
        background: color,
        borderRadius: clipped ? "3px 0 0 3px" : (extendPct > 0 ? "3px 0 0 3px" : 3),
        display: "flex", alignItems: "center", padding: "0 6px",
        color: "white",
        font: "500 10px/1 var(--mono)",
        letterSpacing: ".02em",
        zIndex: 2,
        overflow: "hidden",
      }}>
        <span style={{ fontWeight: 600, opacity: .92 }}>{tag}</span>
      </div>

      {/* Continues-past-chart indicator */}
      {clipped ? (
        <div style={{
          position: "absolute",
          left: `calc(${chartEndPct}% - 2px)`,
          top: 6, height: 14,
          width: 14,
          display: "flex", alignItems: "center", justifyContent: "center",
          color: "white",
          font: "600 11px/1 var(--mono)",
          background: color,
          clipPath: "polygon(0 0, 100% 50%, 0 100%)",
          zIndex: 3,
        }}>→</div>
      ) : null}

      {/* Extension smear past the nominal end */}
      {extendPct > 0 ? (
        <div style={{
          position: "absolute",
          left: `${leftPct + widthPct}%`,
          width: `${extendPct}%`,
          top: 6, height: 14,
          borderRadius: "0 3px 3px 0",
          background: item.firmness === "estimated"
            ? `repeating-linear-gradient(135deg, ${colorSoft} 0 3px, color-mix(in oklch, ${colorSoft} 22%, transparent) 3px 7px), linear-gradient(90deg, ${color}, color-mix(in oklch, ${color} 0%, transparent))`
            : `linear-gradient(90deg, ${color} 0%, color-mix(in oklch, ${color} 20%, transparent) 90%, transparent 100%)`,
          filter: item.firmness === "estimated"
            ? (fuzziness === "bold" ? "blur(1.6px)" : fuzziness === "medium" ? "blur(1px)" : "blur(.6px)")
            : (fuzziness === "bold" ? "blur(.6px)" : "none"),
          opacity: item.firmness === "estimated" ? 0.75 : 0.9,
          pointerEvents: "none",
          zIndex: 2,
        }}></div>
      ) : null}

      {/* nominal-end vertical tick (only when not clipped) */}
      {!clipped && extendPct === 0 ? null : null}
    </>
  );
}

function FuzzKey({ kind, label }) {
  const TOTAL = 62;
  const ext = { locked: 0, quoted: 18, estimated: 32 }[kind];
  const solid = TOTAL - ext;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 6, fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".1em", color: "var(--ink-mute)", textTransform: "uppercase" }}>
      <div style={{ position: "relative", width: TOTAL, height: 10 }}>
        <div style={{ position: "absolute", left: 0, width: solid, top: 0, height: 10, background: "var(--navy)", borderRadius: ext > 0 ? "2px 0 0 2px" : 2 }}></div>
        {ext > 0 ?
          <div style={{
            position: "absolute", left: solid, width: ext, top: 0, height: 10,
            borderRadius: "0 2px 2px 0",
            background: kind === "estimated" ?
              "repeating-linear-gradient(135deg, var(--navy-soft) 0 3px, transparent 3px 7px)" :
              "linear-gradient(90deg, var(--navy) 0%, transparent 100%)",
            filter: kind === "estimated" ? "blur(.8px)" : "none",
            opacity: kind === "estimated" ? 0.75 : 0.9
          }}></div> :
        null}
      </div>
      <span>{label}</span>
    </div>
  );
}

// Parse "Mon DD, YYYY" → fractional month index in the 12-mo window (Apr 2026 → Mar 2027)
function dateToMo(dateStr) {
  if (!dateStr || dateStr === "TBD") return null;
  const d = new Date(dateStr);
  if (isNaN(d)) return null;
  return (d.getFullYear() - TL_BASE_YEAR) * 12 + (d.getMonth() + 1) + (d.getDate() - 1) / 30;
}

// Build timeline items from work items (only in-flight) — used by Work tab
function workItemsToTimeline(workItems) {
  return workItems
    .filter(w => !w.completedDate)
    .map(w => {
      const end = dateToMo(w.due);
      if (end == null) return null;
      const start = Math.max(TL_START_MO, end - 3);
      const atRisk = w.stuckSince ? "yellow" : null;
      return {
        id: w.id,
        label: w.title.length > 38 ? w.title.slice(0, 36) + "…" : w.title,
        milestone: w.milestones[0] || "",
        startMo: start,
        endMo: end,
        firmness: w.firmness || "estimated",
        atRisk,
      };
    })
    .filter(Boolean)
    .filter(w => w.endMo >= TL_START_MO - 1 && w.endMo <= TL_START_MO + TL_RANGE + 1);
}

// Build timeline items from contracts — real spans (begin → expire), locked firmness.
// atRisk: red if expired; yellow if contract has known followup-relevant deliverable.
function contractsToTimeline(contracts, { includeExpired = true } = {}) {
  const yellowVendors = new Set(["K&L Gates LLP"]); // tight deliverable before expiry
  return contracts
    .map(c => {
      const start = dateToMo(c.begin);
      const end = dateToMo(c.expire);
      if (end == null) return null;
      const isExpired = new Date(c.expire) < new Date();
      if (isExpired && !includeExpired) return null;
      // Clamp start to chart window so we can see the bar even if it began before
      const clampedStart = Math.max(TL_START_MO, start || TL_START_MO);
      return {
        id: "c-" + c.vendor,
        label: c.vendor.length > 32 ? c.vendor.slice(0, 30) + "…" : c.vendor,
        kind: "contract",
        startMo: clampedStart,
        endMo: end,
        firmness: "locked",
        atRisk: isExpired ? "red" : yellowVendors.has(c.vendor) ? "yellow" : null,
      };
    })
    .filter(Boolean)
    .filter(c => c.endMo >= TL_START_MO - 1 && c.endMo <= TL_START_MO + TL_RANGE + 1);
}

Object.assign(window, {
  FirmnessTimeline, TL_ITEMS, FuzzKey,
  workItemsToTimeline, contractsToTimeline,
});
