// Screens 4/5 — Product Lifecycle (OAM BOM) + Gantt

const { useState: uS4, useMemo: uM4, useRef: uR4 } = React;

// ─────────────────────────── PRODUCT LIFECYCLE ────────────────────────────
function MiniBar({ v, color }) {
  return (
    <div style={{ display:"flex", alignItems:"center", gap:5, minWidth:0 }}>
      <div style={{ flex:1, height:5, background:"var(--bg-3)", borderRadius:3, overflow:"hidden", minWidth:60 }}>
        <div style={{ width:"100%", height:"100%", background:color, borderRadius:3, transform:`scaleX(${Math.min(1, Math.max(0, v/100))})`, transformOrigin:"left center", transition:"transform .3s" }}/>
      </div>
      <span className="mono tnum" style={{ fontSize: 12, color:"var(--ink-3)", width:26, textAlign:"right", flexShrink:0 }}>{v}%</span>
    </div>
  );
}

function _statusColor(d, m, t) {
  const avg = (d + m + t) / 3;
  if (avg >= 80) return "ok";
  if (avg >= 40) return "warn";
  if (avg === 0) return "err";
  return "info";
}

function BomRow({ item, indent = 0, childMap, open, setOpen, sel, setSel, filterItems }) {
  const children = childMap[item.num] || [];
  const isOpen = open[item.num];
  const isSel  = sel === item.num;
  const st = _statusColor(item.d, item.m, item.t);

  return (
    <>
      <tr onClick={() => setSel(item.num)}
        className={isSel ? "selected" : ""}
        style={{ cursor:"pointer" }}>
        <td style={{ paddingLeft: 10 + indent * 14 }}>
          <div style={{ display:"flex", alignItems:"center", gap:6 }}>
            {children.length > 0 ? (
              <button onClick={e => { e.stopPropagation(); setOpen(o => ({ ...o, [item.num]: !o[item.num] })); }}
                style={{ background:"transparent", border:0, color:"var(--ink-4)", cursor:"pointer", padding:0, lineHeight:1 }}>
                {isOpen ? I("ChevD",{size:11}) : I("ChevR",{size:11})}
              </button>
            ) : <span style={{ width:11 }}/>}
            <span className="mono" style={{ fontSize: 12, color:"var(--ink)", fontWeight: 500, flexShrink:0 }}>{item.num}</span>
          </div>
        </td>
        <td style={{ color:"var(--ink)", maxWidth:220 }}>
          <span style={{ overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap", display:"block" }}>{item.name}</span>
        </td>
        <td className="tnum" style={{ textAlign:"center" }}>{item.qty}</td>
        <td style={{ minWidth:100 }}><MiniBar v={item.d} color="var(--info)"/></td>
        <td style={{ minWidth:100 }}><MiniBar v={item.m} color="var(--accent)"/></td>
        <td style={{ minWidth:100 }}><MiniBar v={item.t} color="var(--ok)"/></td>
        <td><Pill tone={st} dot={false}>{st === "ok" ? "ON TRACK" : st === "warn" ? "IN PROG" : st === "err" ? "NOT STARTED" : "ACTIVE"}</Pill></td>
      </tr>
      {isOpen && filterItems(children).map(c =>
        <BomRow key={c.num} item={c} indent={indent+1}
          childMap={childMap} open={open} setOpen={setOpen}
          sel={sel} setSel={setSel} filterItems={filterItems}/>
      )}
    </>
  );
}

// Derive design/manufacturing/test progress for a part across tenant shapes.
// Tritan parts have cost_inr/lead_time_days; Aetherion has mass_g/power_w/qual.
// OAM (default fallback) already has d/m/t. Each tenant gets a different signal.
function deriveLifecycleMetrics(part, tenantId) {
  if (typeof part.d === "number" && typeof part.m === "number") {
    return { d: part.d, m: part.m, t: part.t || 0 };
  }
  if (tenantId === "aetherion") {
    const q = (part.qual || "").toString();
    if (q === "qual-passed")        return { d: 100, m: 100, t: 95 };
    if (q === "flight-spare")       return { d: 100, m: 100, t: 100 };
    if (q === "qual-pending")       return { d: 80,  m: 60,  t: 20 };
    if (q === "engineering-model")  return { d: 65,  m: 40,  t: 0  };
    if (q === "waiver-N")           return { d: 90,  m: 80,  t: 60 };
    return { d: 50, m: 50, t: 0 };
  }
  if (tenantId === "mittelstand" || tenantId === "tritan") {
    const lt = Number(part.lead_time_days);
    const m = isFinite(lt) ? Math.max(0, Math.min(100, 100 - (lt * 2))) : 60;
    const supplier = (part.supplier || "").toLowerCase();
    const d = supplier.indexOf("in-house") >= 0 ? 90 : 70;
    return { d: d, m: Math.round(m), t: 0 };
  }
  return { d: 0, m: 0, t: 0 };
}

function ScreenProductLifecycle() {
  const tenant = useActiveTenant();
  const tenantId = (tenant && tenant.tenant && tenant.tenant.id) || "tritan";

  const [open, setOpen]   = uS4({ "1":true, "2":false, "6":false });
  const [sel,  setSel]    = uS4(null);
  const [filter, setFilter] = uS4("all"); // all | design | mfg | test | blocked
  const [search, setSearch] = uS4("");

  // Resolve the parts list per active tenant; fall back to OAM_BOM_FLAT.
  const tenantParts = uM4(() => {
    const raw = (tenant && tenant.parts && tenant.parts.length) ? tenant.parts : OAM_BOM_FLAT;
    return raw.map(function(p) {
      const m = deriveLifecycleMetrics(p, tenantId);
      return Object.assign({}, p, { d: m.d, m: m.m, t: m.t });
    });
  }, [tenant, tenantId]);

  const topItems = uM4(() => tenantParts.filter(x => x.p === null || x.p === undefined), [tenantParts]);
  const childMap  = uM4(() => {
    const m = {};
    tenantParts.forEach(x => { if (x.p) { (m[x.p] = m[x.p] || []).push(x); } });
    return m;
  }, [tenantParts]);

  const avgD = uM4(() => Math.round(tenantParts.reduce((a,x)=>a+x.d,0)/Math.max(1,tenantParts.length)), [tenantParts]);
  const avgM = uM4(() => Math.round(tenantParts.reduce((a,x)=>a+x.m,0)/Math.max(1,tenantParts.length)), [tenantParts]);
  const avgT = uM4(() => Math.round(tenantParts.reduce((a,x)=>a+x.t,0)/Math.max(1,tenantParts.length)), [tenantParts]);

  const programLabel = (tenant && tenant.program && (tenant.program.name || tenant.program.code))
    || (tenantId === "aetherion" ? "KESTREL-3 · 12U Cubesat" : tenantId === "mittelstand" ? "MTL-220 · 5-axis bridge mill" : "OAM · Orbital Adjustment Module");
  const qualStages = tenantId === "aetherion"
    ? ["SRR","PDR","CDR","FAB","ASSY","I&T","TVAC","ACC"]
    : tenantId === "mittelstand"
      ? ["Frame","Eng","LongLead","Cell","FAI","PPAP","Ship"]
      : ["DR-1","DR-2","DR-3","Tooling","Pilot","FAT","Release"];

  const selItem = sel ? tenantParts.find(x => x.num === sel) : null;

  function stageForItem(item) {
    if (!item) return 2;
    const avg = (item.d + item.m) / 2;
    if (avg >= 90) return 5;
    if (avg >= 70) return 4;
    if (avg >= 50) return 3;
    if (avg >= 30) return 2;
    return 1;
  }

  function filterItems(items) {
    return items.filter(x => {
      if (search && !x.name.toLowerCase().includes(search.toLowerCase()) && !x.num.includes(search)) return false;
      if (filter === "design")  return x.d < 50;
      if (filter === "mfg")     return x.m < 50;
      if (filter === "blocked") return x.d === 0 && x.m === 0;
      return true;
    });
  }

  const visibleTop = filterItems(topItems);

  return (
    <div style={{ padding:14, display:"grid", gridTemplateColumns:"1fr 280px", gridTemplateRows:"auto 1fr", gap:14, height:"100%", minHeight:0 }}>

      {/* ── SUMMARY BAR ── */}
      <Panel style={{ gridColumn:"1/-1" }} title={programLabel + " · Product Lifecycle"}>
        <div style={{ display:"grid", gridTemplateColumns:"repeat(6,1fr)", gap:0 }}>
          {[
            ["Total Items", String(tenantParts.length), "info"],
            ["Assemblies", String(topItems.length), "info"],
            ["Design Avg",`${avgD}%`,"info"],
            ["Mfg Avg",`${avgM}%`,"accent"],
            ["Test Avg",`${avgT}%`,"ok"],
            ["Not Started", String(tenantParts.filter(x=>x.d===0&&x.m===0).length), "err"],
          ].map(([k,v,t],i) => (
            <div key={k} style={{ padding:"10px 14px", borderRight:"1px solid var(--line-soft)" }}>
              <div className="label" style={{ whiteSpace: "normal", overflow: "visible", textOverflow: "clip", lineHeight: 1.3 }}>{k}</div>
              <div className="mono tnum" style={{ fontSize:20, color:`var(--${t})` }}>{v}</div>
            </div>
          ))}
        </div>
        {/* overall progress bars */}
        <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:0, borderTop:"1px solid var(--line-soft)" }}>
          {[["DESIGN",avgD,"var(--info)"],["MANUFACTURING",avgM,"var(--accent)"],["TESTING",avgT,"var(--ok)"]].map(([label,v,c]) => (
            <div key={label} style={{ padding:"8px 14px", borderRight:"1px solid var(--line-soft)" }}>
              <div style={{ display:"flex", justifyContent:"space-between", marginBottom:4 }}>
                <span className="label">{label}</span>
                <span className="mono tnum" style={{ fontSize: 12 }}>{v}%</span>
              </div>
              <div style={{ height:6, background:"var(--bg-3)", borderRadius:3, overflow:"hidden" }}>
                <div style={{ width:`${v}%`, height:"100%", background:c, borderRadius:3 }}/>
              </div>
            </div>
          ))}
        </div>
      </Panel>

      {/* ── BOM TABLE ── */}
      <Panel title="Bill of Materials" right={
        <>
          <div style={{ position:"relative" }}>
            {(() => {
              const _searchInputStyle = { background:"var(--bg-2)", border:"1px solid var(--line)", borderRadius:4, padding:"3px 8px 3px 24px",
                color:"var(--ink)", fontFamily:"var(--font-mono)", fontSize: 12, width:160 };
              return (
                <input id="plc-search" name="plc-search" aria-label="Search bill of materials" value={search} onChange={e=>setSearch(e.target.value)} placeholder="Search…" style={_searchInputStyle}/>
              );
            })()}
            <span style={{ position:"absolute", left:7, top:"50%", transform:"translateY(-50%)", color:"var(--ink-4)" }}>{I("Search",{size:12})}</span>
          </div>
          {["all","design","mfg","blocked"].map(f => (
            <Btn key={f} size="sm" variant={f===filter?"primary":"ghost"} onClick={()=>setFilter(f)}>
              {f==="all"?"All":f==="design"?"Design <50%":f==="mfg"?"Mfg <50%":"Blocked"}
            </Btn>
          ))}
          <Btn size="sm" icon="ArrowD" onClick={() => window.forgeToast && window.forgeToast("Gantt export queued — wired in v2")}>Export</Btn>
        </>
      }>
        <div style={{ overflow:"auto", height:"100%" }}>
          <table className="dtable" style={{ tableLayout:"fixed", width:"100%" }}>
            <colgroup>
              <col style={{ width:70 }}/><col/><col style={{ width:40 }}/>
              <col style={{ width:120 }}/><col style={{ width:120 }}/><col style={{ width:120 }}/>
              <col style={{ width:100 }}/>
            </colgroup>
            <thead>
              <tr>
                <th>#</th><th>Component</th><th>Qty</th>
                <th style={{ color:"var(--info)" }}>DESIGN</th>
                <th style={{ color:"var(--accent)" }}>MFG</th>
                <th style={{ color:"var(--ok)" }}>TEST</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              {visibleTop.map(item => <BomRow key={item.num} item={item}
                childMap={childMap} open={open} setOpen={setOpen}
                sel={sel} setSel={setSel} filterItems={filterItems}/>)}
            </tbody>
          </table>
        </div>
      </Panel>

      {/* ── DETAIL / QUAL PANEL ── */}
      <div style={{ display:"flex", flexDirection:"column", gap:14, minHeight:0 }}>
        {selItem ? (
          <>
            <Panel title={`${selItem.num} · Detail`}>
              <div style={{ padding:12 }}>
                <div className="serif" style={{ fontSize:18, lineHeight:1.2 }}>{selItem.name}</div>
                <div className="muted mono" style={{ fontSize: 12, marginTop:2 }}>Qty · {selItem.qty}</div>
                <div className="hr"/>
                <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:8, marginTop:4 }}>
                  {[["Design",selItem.d,"var(--info)"],["Mfg",selItem.m,"var(--accent)"],["Testing",selItem.t,"var(--ok)"]].map(([k,v,c]) => (
                    <div key={k} style={{ padding:8, border:"1px solid var(--line-soft)", borderRadius:5 }}>
                      <div className="label">{k}</div>
                      <div style={{ marginTop:4, height:8, background:"var(--bg-3)", borderRadius:4, overflow:"hidden" }}>
                        <div style={{ width:`${v}%`, height:"100%", background:c }}/>
                      </div>
                      <div className="mono tnum" style={{ fontSize:16, marginTop:4 }}>{v}%</div>
                    </div>
                  ))}
                </div>
                <div className="hr"/>
                <KV4 k="Parent"  v={selItem.p || (tenantId === "aetherion" ? "KESTREL-3 Root" : tenantId === "mittelstand" ? "MTL-220 Root" : "Program Root")} />
                <KV4 k="Type"    v={childMap[selItem.num] ? "Assembly" : "Part"} />
                <KV4 k="Owner"   v={["Dev K.","Priya N.","Rohan J.","Aditi S."][parseInt(selItem.num) % 4]} />
              </div>
            </Panel>

            <Panel title="Qualification Stage">
              <div style={{ padding:"10px 12px" }}>
                <div style={{ display:"flex", gap:0, flexWrap:"wrap" }}>
                  {qualStages.map((s, i) => {
                    const cur = stageForItem(selItem);
                    const done = i < cur, active = i === cur;
                    return (
                      <div key={s} style={{ flex:1, minWidth:50, padding:"6px 4px", textAlign:"center",
                        borderTop:`3px solid ${done?"var(--ok)":active?"var(--accent)":"var(--line-strong)"}`,
                        background: active ? "color-mix(in oklch,var(--accent) 8%,transparent)" : "transparent" }}>
                        <div className="mono" style={{ fontSize: 12, color: done?"var(--ok)":active?"var(--accent)":"var(--ink-3)", textTransform:"uppercase" }}>{s}</div>
                        {done && <div style={{ fontSize: 12, color:"var(--ok)", marginTop:2 }}>✓</div>}
                        {active && <div style={{ fontSize: 12, color:"var(--accent)", marginTop:2 }}>●</div>}
                      </div>
                    );
                  })}
                </div>
              </div>
            </Panel>

            <Panel title="Linked Actions">
              <div style={{ padding:10 }}>
                {[
                  ["Open ECO","ECO-"+String(2600+parseInt(selItem.num||1)),"warn"],
                  ["Drawing","DWG-OAM-"+selItem.num,"info"],
                  ["Supplier PO","PO-OAM-"+String(100+parseInt(selItem.num||1)),"ok"],
                ].map(([a,b,t]) => (
                  <div key={a} style={{ display:"grid", gridTemplateColumns:"90px 1fr", gap:8, padding:"5px 0", borderBottom:"1px solid var(--line-soft)" }}>
                    <div className="label">{a}</div>
                    <Pill tone={t} dot={false}>{b}</Pill>
                  </div>
                ))}
              </div>
            </Panel>
          </>
        ) : (
          <Panel title="Select a component">
            <div style={{ padding:24, textAlign:"center", color:"var(--ink-3)" }}>
              <div style={{ marginBottom:8 }}>{I("Satellite",{size:28})}</div>
              <div className="mono" style={{ fontSize: 12 }}>Click any BOM row to inspect</div>
            </div>
          </Panel>
        )}
      </div>
    </div>
  );
}

function KV4({ k, v }) {
  return (
    <div style={{ display:"grid", gridTemplateColumns:"80px 1fr", gap:8, padding:"3px 0" }}>
      <span className="label">{k}</span>
      <span className="mono" style={{ fontSize: 12, color:"var(--ink-2)" }}>{v}</span>
    </div>
  );
}

// ───────────────────────────────── GANTT ──────────────────────────────────
function ScreenGantt() {
  const tenant = useActiveTenant();
  const [zoom, setZoom]       = uS4("month");  // month | quarter
  const [laneFilter, setLaneFilter] = uS4("all");

  // Locale-aware short month names — fall back to English when forgeI18n
  // isn't yet initialised (early bootstrap). Tritan reads en-IN, mittelstand
  // de-DE, etc. — matches the rest of the date axis system.
  const MONTHS  = (function () {
    var loc = (window.forgeI18n && window.forgeI18n.locale && window.forgeI18n.locale()) || "en-US";
    try {
      var fmt = new Intl.DateTimeFormat(loc, { month: "short" });
      var out = [];
      for (var i = 0; i < 12; i++) {
        out.push(fmt.format(new Date(Date.UTC(2026, i, 1))));
      }
      return out;
    } catch (e) {
      return ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
    }
  })();
  // Today indexed into the 2026 month axis. Single source of truth — uses
  // window.__forgeNowMs override (test/demo fixtures) else real wall-clock.
  // Prior bug: hardcoded 3.8 (late April) drifted from real today and from the
  // sibling gantts (space/, mittelstand/). With the rect width clamp below,
  // out-of-window stations render as empty bars rather than negative-width SVG.
  const NOW_IDX = (function() {
    const ms = (typeof window !== "undefined" && window.__forgeNowMs) || Date.now();
    const d = new Date(ms);
    if (d.getUTCFullYear() !== 2026) return 3.8; // axis is fixed at 2026; off-axis dates fall back to demo position
    return d.getUTCMonth() + (d.getUTCDate() / 30);
  })();

  const LANES = [
    { id:"eng",  label:"Engineering Teams",           color:"var(--info)"   },
    { id:"proc", label:"Procurement & Supply Chain",  color:"var(--accent)" },
    { id:"mfg",  label:"Manufacturing & Plant Ops",   color:"var(--ok)"     },
  ];

  // Build tenant-derived gantt tasks if work_orders + routing are available.
  // Each WO contributes 7 station bars (one per routing station) using station_progress.
  const tenantTasks = (function() {
    if (!tenant || !tenant.work_orders || !tenant.work_orders.length || !tenant.routing || !tenant.routing.length) return null;
    const out = [];
    const dateMonthIdx = function(d) {
      if (!d) return 4;
      const m = new Date(d);
      return isNaN(m) ? 4 : (m.getUTCMonth() + (m.getUTCDate() / 30));
    };
    tenant.work_orders.forEach(function(wo) {
      const start = dateMonthIdx(wo.scheduled_start);
      const end   = dateMonthIdx(wo.scheduled_end);
      const span  = Math.max(end - start, 0.4);
      const sp = wo.station_progress || {};
      tenant.routing.forEach(function(stn, i) {
        const seg = span / tenant.routing.length;
        const s = start + i * seg;
        const e = s + seg;
        const prog = sp[stn.id] != null ? sp[stn.id] : 0;
        const status = prog >= 1 ? "done" : prog > 0 ? "active" : "pending";
        // Map station_type to lane.
        const lane = (stn.station_type === "machining" || stn.station_type === "assembly")
          ? "mfg"
          : (stn.station_type === "qa-inbound" || stn.station_type === "test" || stn.station_type === "final")
            ? "eng"
            : "proc";
        out.push({
          id: wo.id + "·" + stn.id,
          name: wo.id + " · " + stn.name,
          start: s, end: e, lane: lane, status: status,
          ms: false,
        });
      });
    });
    return out;
  })();

  const tasks = tenantTasks || GANTT_TASKS;

  const W   = 960; // svg inner width
  const COL = W / 12;
  const ROW = 28;
  const HDR = 36;
  const LANE_HDR = 22;

  // SVG <text> has no native ellipsis — clip long tenant-derived task names
  // (e.g. "WO-44219 · Motor sub-assembly winding") so they don't run into the bars.
  const truncName = (s, n) => (s && s.length > n) ? s.slice(0, n - 1) + "…" : s;

  const statusColor = (s) => s === "done" ? "var(--ok)" : s === "active" ? "var(--accent)" : "var(--line-strong)";
  const statusFill  = (s) => s === "done"
    ? "color-mix(in oklch,var(--ok) 22%,transparent)"
    : s === "active"
    ? "color-mix(in oklch,var(--accent) 22%,transparent)"
    : "color-mix(in oklch,var(--line) 30%,transparent)";

  const visLanes = LANES.filter(l => laneFilter === "all" || l.id === laneFilter);

  // compute y-offsets for each lane
  let yOff = HDR;
  const laneY = {};
  visLanes.forEach(l => {
    laneY[l.id] = yOff + LANE_HDR;
    const lt = tasks.filter(t => t.lane === l.id);
    yOff += LANE_HDR + lt.length * ROW + 12;
  });
  const totalH = yOff + 12;

  return (
    <div style={{ padding:14, display:"grid", gridTemplateRows:"auto 1fr", gap:14, height:"100%", minHeight:0 }}>

      {/* ── MILESTONES STRIP ── */}
      <Panel title={((tenant && tenant.program && tenant.program.code) || "OAM") + " Program · Key Milestones"}>
        <div style={{ display:"flex", gap:0, overflow:"auto" }}>
          {tasks.reduce((acc, t) => { if (t.ms) acc.push(t); return acc; }, []).map(t => {
            const done = t.end < NOW_IDX, active = t.start <= NOW_IDX && t.end >= NOW_IDX;
            return (
              <div key={t.id} style={{ padding:"8px 16px", borderRight:"1px solid var(--line-soft)", flexShrink:0 }}>
                <Pill tone={done?"ok":active?"accent":""} dot={false}>{t.id}</Pill>
                <div style={{ fontSize:12, color:"var(--ink)", marginTop:4, maxWidth:160 }}>{t.name}</div>
                <div className="mono" style={{ fontSize: 12, color:"var(--ink-3)", marginTop:2 }}>
                  {MONTHS[Math.floor(t.start)]} – {MONTHS[Math.min(11,Math.floor(t.end))]} 2026
                </div>
              </div>
            );
          })}
        </div>
      </Panel>

      {/* ── CHART ── */}
      <Panel title="Schedule · 2026" right={
        <>
          {["all","eng","proc","mfg"].map(f=>(
            <Btn key={f} size="sm" variant={f===laneFilter?"primary":"ghost"} onClick={()=>setLaneFilter(f)}>
              {f==="all"?"All":f==="eng"?"Engineering":f==="proc"?"Procurement":"Manufacturing"}
            </Btn>
          ))}
          <Btn size="sm" icon="ArrowD" onClick={() => window.forgeToast && window.forgeToast("Gantt export queued — wired in v2")}>Export</Btn>
        </>
      }>
        <div style={{ overflow:"auto", height:"100%" }}>
          <svg width={W + 200} height={totalH} style={{ display:"block", minWidth: W + 200 }}>
            {/* month header bg */}
            <rect x="200" y="0" width={W} height={HDR} fill="var(--bg-2)"/>
            {/* month columns */}
            {MONTHS.map((m, i) => (
              <g key={m}>
                <rect x={200 + i * COL} y="0" width={COL} height={totalH}
                  fill={i % 2 === 0 ? "transparent" : "color-mix(in oklch,var(--bg-2) 50%,transparent)"}/>
                <text x={200 + i * COL + COL/2} y="22" textAnchor="middle"
                  fontSize="11" fontFamily="var(--font-mono)" fill="var(--ink-3)">{m}</text>
                <line x1={200 + i * COL} y1={HDR} x2={200 + i * COL} y2={totalH}
                  stroke="var(--line-soft)" strokeWidth="0.5"/>
              </g>
            ))}

            {/* NOW line */}
            <line x1={200 + NOW_IDX * COL} y1="0" x2={200 + NOW_IDX * COL} y2={totalH}
              stroke="var(--accent)" strokeWidth="1.5" strokeDasharray="4,3"/>
            <text x={200 + NOW_IDX * COL + 4} y="14" fontSize="10" fontFamily="var(--font-mono)"
              fill="var(--accent)">TODAY</text>

            {/* lanes */}
            {visLanes.map(lane => {
              const laneTasks = tasks.filter(t => t.lane === lane.id);
              const ly = laneY[lane.id];
              return (
                <g key={lane.id}>
                  {/* lane header */}
                  <rect x="0" y={ly - LANE_HDR} width={200 + W} height={LANE_HDR}
                    fill="color-mix(in oklch,var(--bg-2) 80%,transparent)"/>
                  <text x="10" y={ly - LANE_HDR + 15} fontSize="11"
                    fontFamily="var(--font-ui)" fontWeight="600" fill={lane.color}>{lane.label}</text>

                  {/* task rows */}
                  {laneTasks.map((t, idx) => {
                    const ty = ly + idx * ROW;
                    const x  = 200 + t.start * COL;
                    const tw = (t.end - t.start) * COL;
                    return (
                      <g key={t.id}>
                        {/* row bg */}
                        <rect x="0" y={ty} width={200 + W} height={ROW}
                          fill={idx % 2 === 0 ? "transparent" : "color-mix(in oklch,var(--bg-1) 40%,transparent)"}/>
                        {/* label */}
                        <text x="10" y={ty + ROW/2 + 4} fontSize="11"
                          fontFamily="var(--font-mono)" fill="var(--ink-3)">{truncName(t.name, 27)}<title>{t.name}</title></text>
                        {/* bar */}
                        <rect x={x} y={ty + 6} width={Math.max(tw,4)} height={ROW-12}
                          rx="3" fill={statusFill(t.status)} stroke={statusColor(t.status)} strokeWidth="1"/>
                        {/* milestone diamond */}
                        {t.ms && (
                          <polygon
                            points={`${x+tw/2},${ty+4} ${x+tw/2+6},${ty+ROW/2} ${x+tw/2},${ty+ROW-4} ${x+tw/2-6},${ty+ROW/2}`}
                            fill={statusColor(t.status)} opacity="0.8"/>
                        )}
                        {/* progress fill — clamp [0, tw]; NOW may be before t.start (future active stn) or after t.end (overdue active stn) */}
                        {t.status === "active" && (
                          <rect x={x} y={ty + 6} width={Math.max(0, Math.min((NOW_IDX - t.start)/(t.end - t.start) * tw, tw))} height={ROW-12}
                            rx="3" fill="color-mix(in oklch,var(--accent) 35%,transparent)"/>
                        )}
                      </g>
                    );
                  })}
                  {/* lane bottom border */}
                  <line x1="0" y1={ly + laneTasks.length * ROW + 6}
                    x2={200 + W} y2={ly + laneTasks.length * ROW + 6}
                    stroke="var(--line-soft)" strokeWidth="1"/>
                </g>
              );
            })}

            {/* legend */}
            {[["done","Complete"],["active","In Progress"],["pending","Planned"]].map(([s,l], i) => (
              <g key={s} transform={`translate(${200 + 10 + i * 130}, ${totalH - 18})`}>
                <rect x="0" y="2" width="14" height="10" rx="2"
                  fill={statusFill(s)} stroke={statusColor(s)} strokeWidth="1"/>
                <text x="18" y="12" fontSize="10" fontFamily="var(--font-mono)" fill="var(--ink-3)">{l}</text>
              </g>
            ))}
          </svg>
        </div>
      </Panel>
    </div>
  );
}

Object.assign(window, { ScreenProductLifecycle, ScreenGantt });
