// InventoryPage / Products & Services — window.InventoryPage
//
// Inventory intelligence section with sub-navigation:
//   Overview · SKU Performance (sales aging buckets) · Channel Analysis (with a
//   one-time channel setup wizard) · Vendors · New & Old Items.
//
// Honest, data-backed. The platform has NO live on-hand quantity feed, so
// on-hand value / aging / reorder come only from an uploaded CSV (never
// fabricated). Sales aging buckets, trends and new/old detection are derived
// from real sales line items (sku, units sold, revenue, cost, gross margin).
// Channel splits come from GL transactions matched to a company's configured
// sales channels (company_channels table, seeded via the setup wizard).
//
// Mode is driven by the company profile (flags / membership lists), never by
// hardcoded single-archetype equality checks.

(function () {
  const h = React.createElement;
  const { useState, useEffect } = React;
  const num = (v) => Number(v) || 0;
  const cents = (v) => num(v) / 100;
  const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

  // Whole months between two dates (d1 earlier → positive when d2 later).
  function monthsDiff(d1, d2) {
    return (d2.getFullYear() - d1.getFullYear()) * 12 + (d2.getMonth() - d1.getMonth());
  }

  // ── Sub-navigation tabs ─────────────────────────────────────────────────────
  const INV_TABS = [
    { id: "overview", label: "Overview", icon: "📊" },
    { id: "sku", label: "SKU Performance", icon: "📦" },
    { id: "channels", label: "Channel Analysis", icon: "🛒" },
    { id: "vendors", label: "Vendors", icon: "🏭" },
    { id: "movements", label: "New & Old Items", icon: "🔄" },
  ];

  // ── Channel setup wizard options + colors ───────────────────────────────────
  const CHANNEL_OPTIONS = [
    { key: "wholesale", label: "Wholesale / B2B", desc: "Selling to retailers, distributors, or other businesses", icon: "🏢" },
    { key: "dtc", label: "Direct to Consumer", desc: "Your own website, store, or direct sales", icon: "🛍️" },
    { key: "marketplace", label: "Online Marketplace", desc: "Amazon, eBay, Etsy, or similar platforms", icon: "🛒" },
    { key: "retail", label: "Retail / Physical Stores", desc: "Own retail locations or consignment", icon: "🏪" },
    { key: "export", label: "Export / International", desc: "Sales to international customers", icon: "🌐" },
    { key: "other", label: "Other Channel", desc: "Any other sales channel", icon: "📋" },
  ];
  const CHANNEL_COLORS = { wholesale: "#1C4ED8", dtc: "#18a867", marketplace: "#d97706", retail: "#9333ea", export: "#0ea5e9", other: "#6475a0" };
  const channelColor = (key) => CHANNEL_COLORS[key] || "#6475a0";

  // Mini 4-dot sparkline: oldest → newest bucket revenue, newest darkest.
  function MiniTrend({ buckets }) {
    const vals = ["0_3", "3_6", "6_9", "9_12"].reverse().map((k) => (buckets[k] ? buckets[k].revenue : 0) || 0);
    const max = Math.max.apply(null, vals.concat([1]));
    return h("div", { style: { display: "flex", alignItems: "flex-end", gap: 3, height: 20 } },
      vals.map((v, i) => h("div", { key: i, style: {
        width: 8, height: Math.max(2, Math.round(v / max * 18)) + "px",
        background: i === vals.length - 1 ? "#0d2040" : "rgba(13,32,64," + (0.2 + i * 0.2) + ")", borderRadius: 2,
      } })));
  }

  // Sales aging buckets from a {ym:{units,revenue}} map, relative to refDate.
  function getSkuAgingBuckets(monthsObj, refDate) {
    const buckets = {
      "0_3": { label: "0–3 months", qty: 0, revenue: 0 },
      "3_6": { label: "3–6 months", qty: 0, revenue: 0 },
      "6_9": { label: "6–9 months", qty: 0, revenue: 0 },
      "9_12": { label: "9–12 months", qty: 0, revenue: 0 },
    };
    Object.keys(monthsObj || {}).forEach((ym) => {
      const m = monthsObj[ym] || {};
      const monthsAgo = monthsDiff(new Date(ym + "-01"), refDate);
      let k = null;
      if (monthsAgo <= 3) k = "0_3";
      else if (monthsAgo <= 6) k = "3_6";
      else if (monthsAgo <= 9) k = "6_9";
      else if (monthsAgo <= 12) k = "9_12";
      if (k) { buckets[k].qty += num(m.units); buckets[k].revenue += num(m.revenue); }
    });
    return buckets;
  }

  // Sales-momentum status from aging buckets (recency, not on-hand).
  function agingStatus(b) {
    const recent = b["0_3"].revenue, older = b["3_6"].revenue, oldest = b["6_9"].revenue + b["9_12"].revenue;
    if (recent === 0 && older === 0) return { badge: "🔴 Dead Stock", color: "#d94f47", key: "dead" };
    if (recent < older * 0.5) return { badge: "🟡 Slowing", color: "#d97706", key: "slowing" };
    if (recent > older * 1.2) return { badge: "⭐ Accelerating", color: "#18a867", key: "accelerating" };
    if (oldest === 0 && older === 0) return { badge: "🆕 New Item", color: "#1C4ED8", key: "new" };
    return { badge: "🟢 Steady", color: "#18a867", key: "steady" };
  }

  // Trailing-3M vs prior-3M arrow.
  function trendArrow(last3, prev3) {
    if (prev3 <= 0) return last3 > 0 ? { s: "↑↑", c: "#18a867" } : { s: "→", c: "#6475a0" };
    const ch = (last3 - prev3) / prev3 * 100;
    if (ch > 10) return { s: "↑↑", c: "#18a867" };
    if (ch > 3) return { s: "↑", c: "#18a867" };
    if (ch < -3) return { s: "↓", c: "#d94f47" };
    return { s: "→", c: "#6475a0" };
  }

  // ── Sample CSV downloads ────────────────────────────────────────────────────
  const ITEMS_CSV = [
    "sku_code,description,vendor,category,on_hand_qty,cost_per_unit,selling_price,reorder_point,lead_time_days",
    "SKU001,Blue Widget 500ml,Acme Supply Co,Widgets,150,12.50,24.99,50,14",
    "SKU002,Red Gadget Pro,Best Vendor Ltd,Gadgets,80,28.00,54.99,30,21",
    "SKU003,Green Tool Basic,Acme Supply Co,Tools,220,8.00,16.99,100,7",
  ].join("\n");
  const SALES_CSV = [
    "sku_code,period_month,qty_sold,revenue,cogs,channel",
    "SKU001,2025-01,45,1124.55,562.50,wholesale",
    "SKU001,2025-02,52,1299.48,650.00,wholesale",
    "SKU001,2025-01,12,299.88,150.00,dtc",
    "SKU002,2025-01,18,989.82,504.00,marketplace",
  ].join("\n");
  function downloadCsv(name, text) {
    const a = document.createElement("a");
    a.href = "data:text/csv;charset=utf-8," + encodeURIComponent(text);
    a.download = name; a.click();
  }

  function Page(props) {
    const K = window.PerduraPageKit;
    if (!K) return h("div", { className: "pc-page" }, "Loading…");
    const { data, companyProfile, scopedCompanyId } = props;
    const M = (v) => K.moneyStr(v, { compact: true });

    // Mode — inventory archetypes vs everyone else (membership lists / flags only).
    const profileT = String((companyProfile && (companyProfile.industry || companyProfile.business_type)) || "").toLowerCase();
    const holdsInv = !!(companyProfile && companyProfile.holds_inventory) || ["retail", "wholesale", "manufacturing", "ecommerce", "product", "distribution"].indexOf(profileT) >= 0;
    const noun = holdsInv ? "Inventory & Products" : "Products & Services";

    // Sub-tab / drill / table state.
    const [invTab, setInvTab] = useState("overview");
    const [tab, setTab] = useState("category");          // overview breakdown dimension
    const [selSku, setSelSku] = useState(null);
    const [page, setPage] = useState(0);                 // overview listing pagination
    const [sortKey, setSortKey] = useState("revenue");
    const [sortDir, setSortDir] = useState("desc");
    const [skuRows, setSkuRows] = useState([]);          // uploaded on-hand CSV rows
    const PER_PAGE = 50;

    // SKU Performance tab state.
    const [skuPage, setSkuPage] = useState(0);
    const [skuSortKey, setSkuSortKey] = useState("total12");
    const [skuSortDir, setSkuSortDir] = useState("desc");
    const [skuStatusFilter, setSkuStatusFilter] = useState("all");
    const [skuVendorFilter, setSkuVendorFilter] = useState("all");
    const [skuCatFilter, setSkuCatFilter] = useState("all");
    const [skuSearch, setSkuSearch] = useState("");
    const [skuSort, setSkuSort] = useState({ col: "total12", dir: "desc" });

    // Channel state + wizard.
    const [channels, setChannels] = useState(null);      // null=loading, []=none
    const [chReload, setChReload] = useState(0);
    const [wizardStep, setWizardStep] = useState(1);
    const [selectedChannels, setSelectedChannels] = useState([]);
    const [channelMappings, setChannelMappings] = useState({});
    const [savingCh, setSavingCh] = useState(false);
    const [chErr, setChErr] = useState(null);
    const [editing, setEditing] = useState(false);

    const [selVendor, setSelVendor] = useState(null);

    const dims = (data && data.dimensions) || {};
    const sli = dims.salesLineItems || (data && data.salesLineItems) || [];
    const catalog = dims.products || (data && data.products) || [];

    // Period window (LTM default) — drives the Overview aggregations.
    const plH = (data && (data.plHistory || data.pl)) || { labels: [], years: [] };
    const anchor = K.anchorFromPlH(plH);
    const ps = K.usePeriodState("inventory", "ltm");
    const range = K.resolvePeriod(ps.mode, anchor, ps.custom);
    const rs = +range.start, re = +range.end;
    const inWindow = (iso) => { const d = iso ? +new Date(iso) : 0; return d >= rs && d <= re; };

    // ── Fetch configured channels ─────────────────────────────────────────────
    useEffect(() => {
      let cancelled = false;
      const db = window.supabaseClient;
      if (!db || !scopedCompanyId) { setChannels([]); return; }
      setChannels(null);
      db.from("company_channels").select("*").eq("company_id", scopedCompanyId).order("sort_order")
        .then(({ data: d, error }) => { if (cancelled) return; setChannels(error ? [] : (d || [])); },
              () => { if (!cancelled) setChannels([]); });
      return () => { cancelled = true; };
    }, [scopedCompanyId, chReload]);

    // ── aggregate sold line items by SKU (period-windowed, for Overview) ───────
    const bySku = {};
    const firstSaleBySku = {};
    for (const it of sli) {
      if (!inWindow(it.order_date)) continue;
      const sku = (it.sku || it.product_name || "—").toString();
      const r = bySku[sku] || (bySku[sku] = { sku, name: it.product_name || sku, category: it.category || "Uncategorized", channels: {}, units: 0, revenue: 0, cost: 0, gp: 0, months: {} });
      const q = num(it.quantity);
      const rev = cents(it.line_total_cents);
      const cst = cents(it.line_cost_cents);
      const gp = it.line_gross_profit_cents != null ? cents(it.line_gross_profit_cents) : (rev - cst);
      r.units += q; r.revenue += rev; r.cost += cst; r.gp += gp;
      if (it.channel) r.channels[it.channel] = (r.channels[it.channel] || 0) + rev;
      const ym = (it.order_date || "").slice(0, 7);
      if (ym) { const m = r.months[ym] || (r.months[ym] = { units: 0, revenue: 0, cost: 0, gp: 0 }); m.units += q; m.revenue += rev; m.cost += cst; m.gp += gp; }
      if (it.order_date && (!firstSaleBySku[sku] || it.order_date < firstSaleBySku[sku])) firstSaleBySku[sku] = it.order_date;
    }
    const catBySku = {};
    for (const p of catalog) { if (p.sku) catBySku[p.sku] = p; }
    for (const p of catalog) { if (p.sku && !bySku[p.sku]) bySku[p.sku] = { sku: p.sku, name: p.name || p.sku, category: p.category || "Uncategorized", channels: {}, units: 0, revenue: 0, cost: 0, gp: 0, months: {} }; }

    const recentCut = re - 60 * 86400000;
    const statusOf = (r) => {
      const p = catBySku[r.sku];
      if (p && p.is_active === false) return { label: "Discontinued", icon: "🚫", color: "#94a3b8" };
      const first = firstSaleBySku[r.sku];
      if (first && +new Date(first) >= recentCut) return { label: "New", icon: "⭐", color: "#1C4ED8" };
      if (r.units <= 0) return { label: "No sales in period", icon: "🔴", color: "#d94f47" };
      return { label: "Active", icon: "🟢", color: "#18a867" };
    };
    const allRows = Object.values(bySku).map((r) => Object.assign({}, r, {
      gmPct: r.revenue ? r.gp / r.revenue * 100 : null,
      avgPrice: r.units ? r.revenue / r.units : (catBySku[r.sku] ? cents(catBySku[r.sku].unit_price_cents) : null),
      status: statusOf(r),
    }));
    const totRevenue = allRows.reduce((s, r) => s + r.revenue, 0);
    const totCost = allRows.reduce((s, r) => s + r.cost, 0);
    const totGp = allRows.reduce((s, r) => s + r.gp, 0);
    const totUnits = allRows.reduce((s, r) => s + r.units, 0);
    const skuCount = allRows.length;
    const soldSkuCount = allRows.filter((r) => r.units > 0).length;
    const avgGm = totRevenue ? totGp / totRevenue * 100 : null;
    const hasData = sli.length > 0 || catalog.length > 0;

    // ── Trailing-12-month SKU aggregation (period-independent) for SKU
    // Performance / aging / New & Old. firstSale/lastSale span ALL history. ─────
    let maxSaleMs = 0;
    for (const it of sli) { const d = it.order_date ? +new Date(it.order_date) : 0; if (d > maxSaleMs) maxSaleMs = d; }
    const refDate = maxSaleMs ? new Date(maxSaleMs) : new Date();
    const win12Start = +new Date(refDate.getFullYear(), refDate.getMonth() - 11, 1);
    const firstSaleAll = {}, lastSaleAll = {};
    const sku12 = {};
    for (const it of sli) {
      const sku = (it.sku || it.product_name || "—").toString();
      if (it.order_date) {
        if (!firstSaleAll[sku] || it.order_date < firstSaleAll[sku]) firstSaleAll[sku] = it.order_date;
        if (!lastSaleAll[sku] || it.order_date > lastSaleAll[sku]) lastSaleAll[sku] = it.order_date;
      }
      const d = it.order_date ? +new Date(it.order_date) : 0;
      if (!d || d < win12Start || d > maxSaleMs) continue;
      const r = sku12[sku] || (sku12[sku] = { sku, name: it.product_name || sku, category: it.category || "Uncategorized", units: 0, revenue: 0, cost: 0, gp: 0, months: {}, channels: {} });
      const q = num(it.quantity), rev = cents(it.line_total_cents), cst = cents(it.line_cost_cents);
      const gp = it.line_gross_profit_cents != null ? cents(it.line_gross_profit_cents) : (rev - cst);
      r.units += q; r.revenue += rev; r.cost += cst; r.gp += gp;
      const ym = (it.order_date || "").slice(0, 7);
      if (ym) { const m = r.months[ym] || (r.months[ym] = { units: 0, revenue: 0, cost: 0, gp: 0 }); m.units += q; m.revenue += rev; m.cost += cst; m.gp += gp; }
      if (it.channel) r.channels[it.channel] = (r.channels[it.channel] || 0) + rev;
    }
    // Uploaded on-hand meta by SKU (the ONLY honest source of on-hand qty/value).
    const uploadMeta = {};
    for (const r of skuRows) { const k = (r.sku_code || r.sku || "").toString(); if (k) uploadMeta[k] = r; }
    const vendorOf = (sku) => { const u = uploadMeta[sku]; if (u && u.vendor) return u.vendor; const p = catBySku[sku]; return (p && p.vendor) || "—"; };
    const onHandOf = (sku) => { const u = uploadMeta[sku]; return u ? (parseFloat(u.on_hand_qty || 0) || 0) : null; };
    const onHandValOf = (sku) => { const u = uploadMeta[sku]; if (!u) return null; const q = parseFloat(u.on_hand_qty || 0) || 0; const c = parseFloat(u.cost_per_unit || 0) || 0; return q * c; };

    // On-hand cost/price/GM% come ONLY from the uploaded CSV (honest — no feed).
    const costOf = (sku) => { const u = uploadMeta[sku]; const c = u ? parseFloat(u.cost_per_unit || 0) : NaN; return Number.isFinite(c) && c > 0 ? c : null; };
    const priceOf = (sku) => { const u = uploadMeta[sku]; const p = u ? parseFloat(u.selling_price || 0) : NaN; return Number.isFinite(p) && p > 0 ? p : null; };
    const gmFromPrice = (sku) => { const c = costOf(sku), p = priceOf(sku); return (p && p > 0) ? (p - (c || 0)) / p * 100 : null; };
    const enrich = (r, sku) => {
      const aging = getSkuAgingBuckets(r.months || {}, refDate);
      return Object.assign({}, r, {
        sku, name: r.name || (uploadMeta[sku] && uploadMeta[sku].description) || sku,
        category: r.category || (uploadMeta[sku] && uploadMeta[sku].category) || "Uncategorized",
        aging,
        total12: r.revenue || 0,
        gmPct: r.revenue ? r.gp / r.revenue * 100 : null,
        vendor: vendorOf(sku),
        onHand: onHandOf(sku),
        onHandVal: onHandValOf(sku),
        cost: costOf(sku),
        price: priceOf(sku),
        gmPrice: gmFromPrice(sku),
        firstSale: firstSaleAll[sku] || null,
        lastSale: lastSaleAll[sku] || null,
        aStatus: agingStatus(aging),
      });
    };
    const skuPerfRows = Object.values(sku12).map((r) => enrich(r, r.sku));
    // Complete listing = sales SKUs ∪ uploaded-only SKUs (so on-hand-only items
    // without sales still appear).
    const perfBySku = {}; skuPerfRows.forEach((r) => { perfBySku[r.sku] = r; });
    const invListing = skuPerfRows.slice();
    for (const u of skuRows) { const sku = (u.sku_code || u.sku || "").toString(); if (sku && !perfBySku[sku]) invListing.push(enrich({ months: {}, revenue: 0, gp: 0, units: 0, name: u.description, category: u.category }, sku)); }

    // ── Inventory ↔ Balance Sheet reconciliation ──────────────────────────────
    const invBsBalance = (data && data.bs && Number(data.bs.inventory)) || 0;
    const invSkuTotal = skuRows.reduce((s, r) => s + (parseFloat(r.on_hand_qty || 0) * parseFloat(r.cost_per_unit || 0)), 0);
    const reconcileDiff = invSkuTotal - invBsBalance;
    const isReconciled = Math.abs(reconcileDiff) < 1;

    function SortableHeader(props) {
      const isActive = props.currentSort.col === props.col;
      return h("th", { onClick: () => props.onSort({ col: props.col, dir: isActive && props.currentSort.dir === "desc" ? "asc" : "desc" }),
        style: { textAlign: props.align || "right", padding: "9px 10px", background: "#0d2040", color: "white", fontWeight: 700, fontSize: 10, textTransform: "uppercase", letterSpacing: ".06em", cursor: "pointer", whiteSpace: "nowrap", userSelect: "none" } },
        props.label + " " + (isActive ? (props.currentSort.dir === "desc" ? "↓" : "↑") : ""));
    }

    // ── GL-derived inventory layer + purchase analysis (from prior build) ──────
    const txns = (data && data.txns) || [];
    const looksInv = (s) => { const t = String(s || "").toLowerCase(); return t.includes("inventor") || t.includes("cost of goods") || t.includes("cogs") || t.includes("purchases"); };
    const hasGLInventory = txns.some((t) => looksInv(t.canonical_category) || looksInv(t.account_name) || looksInv(t.account_type));
    const invBalance = (data && data.bs && Number(data.bs.inventory)) || 0;
    const cogsLtm = (plH.cogs || []).slice(-12).reduce((s, v) => s + (Number(v) || 0), 0);
    const invTurns = invBalance > 0 && cogsLtm > 0 ? cogsLtm / invBalance : null;
    const invDays = cogsLtm > 0 ? invBalance / (cogsLtm / 365) : null;
    const purchaseMonths = (() => {
      const set = new Set();
      for (const t of txns) { if (looksInv(t.canonical_category) || looksInv(t.account_name)) { const m = (t.posted_date || "").slice(0, 7); if (m) set.add(m); } }
      return [...set].sort().slice(-6);
    })();
    const purchasesByVendor = {};
    for (const t of txns) {
      if (!(looksInv(t.canonical_category) || looksInv(t.account_name))) continue;
      const vendor = t.memo || t.reference || "Unspecified vendor";
      const amt = Math.abs(num(t.amount));
      const mon = (t.posted_date || "").slice(0, 7);
      if (!amt) continue;
      const v = purchasesByVendor[vendor] || (purchasesByVendor[vendor] = { vendor, total: 0, months: {} });
      v.total += amt;
      if (mon) v.months[mon] = (v.months[mon] || 0) + amt;
    }
    const topPurchaseVendors = Object.values(purchasesByVendor).sort((a, b) => b.total - a.total).slice(0, 10);
    const purchaseTotal = topPurchaseVendors.reduce((s, v) => s + v.total, 0);
    let maxPurchCell = 0;
    for (const v of topPurchaseVendors) for (const mo of purchaseMonths) { const x = v.months[mo] || 0; if (x > maxPurchCell) maxPurchCell = x; }
    const pCellShade = (x) => { if (!x || !maxPurchCell) return "transparent"; const t = Math.min(1, x / maxPurchCell); return "rgba(217,79,71," + (0.06 + t * 0.34).toFixed(3) + ")"; };
    const pMoLabel = (ym) => MONTHS[(+ym.slice(5, 7)) - 1] + " '" + ym.slice(2, 4);

    // CSV upload → on-hand SKU rows (client-side; no server round-trip).
    const handleInventoryCSV = (file) => {
      if (!file) return;
      const reader = new FileReader();
      reader.onload = (e) => {
        const lines = String(e.target.result || "").split(/\r?\n/).filter((l) => l.trim());
        if (!lines.length) return;
        const headers = lines[0].split(",").map((hd) => hd.trim().toLowerCase().replace(/\s+/g, "_"));
        const rows = lines.slice(1).map((line) => { const vals = line.split(","); const o = {}; headers.forEach((hd, i) => { o[hd] = (vals[i] || "").trim(); }); return o; }).filter((r) => r.sku_code || r.sku);
        setSkuRows(rows);
      };
      reader.readAsText(file);
    };
    const getSkuStatus = (row) => {
      const onHand = parseInt(row.on_hand_qty || 0, 10) || 0;
      const reorder = parseInt(row.reorder_point || 0, 10) || 0;
      if (onHand === 0) return { badge: "🔴 Out of Stock", color: "#d94f47" };
      if (reorder > 0 && onHand <= reorder) return { badge: "⚠️ Reorder Now", color: "#d97706" };
      if (reorder > 0 && onHand > reorder * 3) return { badge: "🟡 Overstocked", color: "#d97706" };
      return { badge: "🟢 Healthy", color: "#18a867" };
    };
    const csvTemplate = ["sku_code,description,vendor,on_hand_qty,cost_per_unit,selling_price,category,reorder_point,lead_time_days",
      "SKU001,Example Widget,Acme Supply Co,150,12.50,24.99,Widgets,50,14",
      "SKU002,Sample Product,Best Vendor Ltd,80,8.00,19.99,Products,30,7"].join("\n");
    const downloadCsvTemplate = () => downloadCsv("inventory_upload_template.csv", csvTemplate);

    // ── 12-month revenue + gross-margin trend (Overview) ──────────────────────
    const ymList = (() => {
      const out = []; const a = range.end instanceof Date ? range.end : new Date();
      let y = a.getFullYear(), m = a.getMonth();
      for (let i = 0; i < 12; i++) { out.unshift(y + "-" + String(m + 1).padStart(2, "0")); m--; if (m < 0) { m = 11; y--; } }
      return out;
    })();
    const monthAgg = ymList.map((ym) => { let rev = 0, gp = 0, units = 0; for (const r of allRows) { const mm = r.months[ym]; if (mm) { rev += mm.revenue; gp += mm.gp; units += mm.units; } } return { ym, rev, gp, units, gmPct: rev ? gp / rev * 100 : null }; });
    const trendLabels = ymList.map((ym) => MONTHS[(+ym.slice(5, 7)) - 1] + " '" + ym.slice(2, 4));
    const trendSeries = [
      { type: "bar", color: "#1C4ED8", data: monthAgg.map((m) => m.rev) },
      { type: "line", color: "#18a867", data: monthAgg.map((m) => m.gmPct), secondary: true },
    ];

    // ── breakdown by category / channel (Overview) ────────────────────────────
    const groupBy = (keyFn) => {
      const g = {};
      for (const r of allRows) {
        if (tab === "channel") {
          const chs = Object.keys(r.channels);
          if (!chs.length) { const e = g["(unspecified)"] || (g["(unspecified)"] = { name: "(unspecified)", revenue: 0, cost: 0, gp: 0, units: 0, skus: new Set() }); e.revenue += r.revenue; e.cost += r.cost; e.gp += r.gp; e.units += r.units; e.skus.add(r.sku); continue; }
          for (const ch of chs) { const e = g[ch] || (g[ch] = { name: ch, revenue: 0, cost: 0, gp: 0, units: 0, skus: new Set() }); const share = r.revenue ? r.channels[ch] / r.revenue : 0; e.revenue += r.channels[ch]; e.cost += r.cost * share; e.gp += r.gp * share; e.units += r.units * share; e.skus.add(r.sku); }
        } else {
          const k = keyFn(r); const e = g[k] || (g[k] = { name: k, revenue: 0, cost: 0, gp: 0, units: 0, skus: new Set() });
          e.revenue += r.revenue; e.cost += r.cost; e.gp += r.gp; e.units += r.units; e.skus.add(r.sku);
        }
      }
      return Object.values(g).map((e) => Object.assign(e, { skuCount: e.skus.size, gmPct: e.revenue ? e.gp / e.revenue * 100 : null })).sort((a, b) => b.revenue - a.revenue);
    };
    const breakdown = groupBy((r) => r.category);

    // ── Overview SKU table (sorted + paginated) ───────────────────────────────
    const sorted = allRows.slice().sort((a, b) => {
      const av = a[sortKey], bv = b[sortKey];
      const an = av == null ? -Infinity : (typeof av === "number" ? av : String(av).toLowerCase());
      const bn = bv == null ? -Infinity : (typeof bv === "number" ? bv : String(bv).toLowerCase());
      if (an < bn) return sortDir === "asc" ? -1 : 1;
      if (an > bn) return sortDir === "asc" ? 1 : -1;
      return 0;
    });
    const pages = Math.max(1, Math.ceil(sorted.length / PER_PAGE));
    const curPage = Math.min(page, pages - 1);
    const pageRows = sorted.slice(curPage * PER_PAGE, curPage * PER_PAGE + PER_PAGE);
    const setSort = (k) => { if (sortKey === k) setSortDir(sortDir === "asc" ? "desc" : "asc"); else { setSortKey(k); setSortDir(k === "name" || k === "sku" || k === "category" ? "asc" : "desc"); } };

    // ── shared table cell helpers ─────────────────────────────────────────────
    const HEADBG = "#0d2040";
    const thc = (txt, align) => h("th", { style: { textAlign: align || "right", padding: "8px 12px", fontSize: 9.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.5, color: "rgba(255,255,255,.85)", whiteSpace: "nowrap" } }, txt);
    const mono = { fontFamily: "'JetBrains Mono',monospace" };
    const card = (title, sub, body, right) => h(K.Card, { title, sub, padding: 0, right }, body);
    const fmtMoney = (n) => { const v = Math.abs(Number(n) || 0); return v >= 1e6 ? "$" + (v / 1e6).toFixed(1) + "M" : v >= 1e3 ? "$" + Math.round(v / 1e3) + "K" : "$" + Math.round(v); };
    const fmtDate = (iso) => iso ? iso.slice(0, 10) : "—";
    const daysSince = (iso) => iso ? Math.round((+refDate - +new Date(iso)) / 86400000) : null;

    // KPI tiles + GL-derived layer (shared).
    const glKpis = [
      { label: "Inventory Balance", value: invBalance ? M(invBalance) : "—", valueColor: "navy", sub: "from balance sheet" },
      { label: "COGS (LTM)", value: cogsLtm ? M(cogsLtm) : "—", valueColor: "red", sub: "trailing 12 months" },
      { label: "Inventory Turns", value: invTurns == null ? "—" : invTurns.toFixed(1) + "×", valueColor: invTurns == null ? "navy" : invTurns >= 6 ? "green" : invTurns >= 3 ? "amber" : "red", sub: "COGS ÷ avg inventory" },
      { label: "Days on Hand", value: invDays == null ? "—" : Math.round(invDays) + "d", valueColor: "teal", sub: "inventory ÷ daily COGS" },
    ];
    const renderGlDerived = () => h(K.Card, { title: "INVENTORY FROM YOUR LEDGER", sub: "Balance-sheet inventory + P&L COGS · always available, no SKU feed required" },
      h("div", { className: "pa-kpi-strip pa-kpi-strip-4", style: { marginBottom: 0 } }, glKpis.map((k, i) => h(K.Kpi, Object.assign({ key: i, animDelay: i * 0.04, onClick: () => window.__perduraSetPage && window.__perduraSetPage("trial_balance") }, k)))));
    const renderPurchaseMatrix = (withTrend) => h(K.Card, { title: "Inventory purchases — by vendor & month", sub: "Top vendors · last " + purchaseMonths.length + " months of inventory/COGS GL postings · darker = higher spend", padding: 0 },
      (topPurchaseVendors.length && purchaseMonths.length) ? h("div", { style: { overflowX: "auto" } },
        h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 720 } },
          h("thead", null, h("tr", { style: { background: HEADBG } },
            h("th", { style: { textAlign: "left", padding: "8px 12px", fontSize: 9.5, fontWeight: 700, letterSpacing: 0.5, textTransform: "uppercase", color: "rgba(255,255,255,.85)", position: "sticky", left: 0, background: HEADBG, whiteSpace: "nowrap" } }, "Vendor"),
            purchaseMonths.map((mo) => thc(pMoLabel(mo))), thc("Total"), withTrend ? thc("Trend") : thc("% Purch"))),
          h("tbody", null, topPurchaseVendors.map((v, i) => {
            const lab = withTrend ? (() => { const l3 = purchaseMonths.slice(-3).reduce((s, m) => s + (v.months[m] || 0), 0); const p3 = purchaseMonths.slice(-6, -3).reduce((s, m) => s + (v.months[m] || 0), 0); return trendArrow(l3, p3); })() : null;
            return h("tr", { key: i, onClick: withTrend ? () => setSelVendor(v.vendor) : undefined, style: { borderTop: "1px solid #eef0f4", cursor: withTrend ? "pointer" : "default" } },
              h("td", { style: { textAlign: "left", padding: "7px 12px", fontWeight: 600, color: "#1a2540", position: "sticky", left: 0, background: "#fff", whiteSpace: "nowrap" } }, v.vendor),
              purchaseMonths.map((mo) => { const x = v.months[mo] || 0; return h("td", { key: mo, style: Object.assign({ textAlign: "right", padding: "7px 12px", background: pCellShade(x), whiteSpace: "nowrap" }, mono) }, x ? M(x) : "—"); }),
              h("td", { style: Object.assign({ textAlign: "right", padding: "7px 12px", fontWeight: 700, color: "#0d2040" }, mono) }, M(v.total)),
              withTrend ? h("td", { style: { textAlign: "center", padding: "7px 12px", fontWeight: 800, color: lab.c } }, lab.s) : h("td", { style: { textAlign: "right", padding: "7px 12px", color: "#6475a0" } }, (purchaseTotal ? v.total / purchaseTotal * 100 : 0).toFixed(1) + "%"));
          }))))
        : h("div", { className: "pa-card-body", style: { color: "#6475a0", fontSize: 13 } }, "No inventory/COGS postings with vendor detail in the ledger yet."));
    const SOURCES = [
      { icon: "🛒", name: "Shopify", detail: "Products, inventory levels, sales by SKU" },
      { icon: "📦", name: "Cin7", detail: "Full inventory management + POs" },
      { icon: "📋", name: "DEAR / Cin7 Omni", detail: "Manufacturing + inventory" },
      { icon: "🔗", name: "Xero Items", detail: "If inventory is tracked in Xero" },
      { icon: "📊", name: "WooCommerce", detail: "Product catalog + stock levels" },
      { icon: "📤", name: "CSV Upload", detail: "Upload on-hand quantities manually" },
    ];
    const renderMultiSource = () => h("div", { style: { background: "#fff", borderRadius: 12, border: "1px solid rgba(13,32,64,.10)", padding: 28, marginBottom: 16 } },
      h("div", { style: { fontSize: 14, fontWeight: 800, color: "#0d2040", marginBottom: 6 } }, "📦 SKU-level inventory"),
      h("div", { style: { fontSize: 12, color: "#6475a0", marginBottom: 20, lineHeight: 1.6 } }, "Your ledger shows inventory balance and purchase totals above. For SKU-level detail (on-hand per item, margins, reorder points), connect one of these sources or upload a CSV:"),
      h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(200px,1fr))", gap: 12, marginBottom: 20 } },
        SOURCES.map((s, i) => h("div", { key: i, style: { border: "1px solid rgba(13,32,64,.12)", borderRadius: 8, padding: 14, background: "rgba(13,32,64,.02)" } },
          h("div", { style: { fontSize: 18, marginBottom: 6 } }, s.icon),
          h("div", { style: { fontSize: 12, fontWeight: 700, color: "#0d2040", marginBottom: 3 } }, s.name),
          h("div", { style: { fontSize: 10.5, color: "#6475a0" } }, s.detail)))),
      h("div", { style: { display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center" } },
        h("button", { onClick: downloadCsvTemplate, style: { padding: "9px 18px", background: "#0d2040", color: "#fff", border: "none", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "⬇ Download CSV template"),
        h("label", { style: { padding: "9px 18px", background: "rgba(24,168,103,.10)", color: "#18a867", border: "1px solid rgba(24,168,103,.3)", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "⬆ Upload on-hand CSV",
          h("input", { type: "file", accept: ".csv,text/csv", style: { display: "none" }, onChange: (e) => handleInventoryCSV(e.target.files && e.target.files[0]) })),
        h("button", { onClick: () => props.setPage && props.setPage("settings_config"), style: { padding: "9px 18px", background: "rgba(13,32,64,.07)", color: "#0d2040", border: "1px solid rgba(13,32,64,.15)", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "→ Connect data source")));
    const renderUploadedSkus = () => h(K.Card, { title: "On-hand SKUs (uploaded)", sub: skuRows.length + " SKUs from your CSV · on-hand qty, value, margin & reorder status", padding: 0 },
      h("div", { style: { overflowX: "auto" } },
        h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 820 } },
          h("thead", null, h("tr", { style: { background: HEADBG } },
            ["SKU", "Description", "Vendor", "On Hand", "On-Hand Value", "Cost", "Price", "Margin %", "Status"].map((c, i) => h("th", { key: i, style: { textAlign: i <= 2 || i === 8 ? "left" : "right", padding: "8px 12px", fontSize: 9.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.5, color: "rgba(255,255,255,.85)", whiteSpace: "nowrap" } }, c)))),
          h("tbody", null, skuRows.map((r, i) => {
            const onHand = parseFloat(r.on_hand_qty || 0) || 0, cost = parseFloat(r.cost_per_unit || 0) || 0, price = parseFloat(r.selling_price || 0) || 0;
            const margin = price > 0 ? (price - cost) / price * 100 : null; const st = getSkuStatus(r);
            return h("tr", { key: i, style: { borderTop: "1px solid #eef0f4" } },
              h("td", { style: Object.assign({ textAlign: "left", padding: "6px 12px", color: "#475569", fontSize: 10.5 }, mono) }, r.sku_code || r.sku),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontWeight: 600, color: "#1a2540" } }, r.description || "—"),
              h("td", { style: { textAlign: "left", padding: "6px 12px", color: "#6475a0", fontSize: 11 } }, r.vendor || "—"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, onHand.toLocaleString()),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", fontWeight: 600 }, mono) }, fmtMoney(onHand * cost)),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", color: "#6475a0" }, mono) }, cost ? fmtMoney(cost) : "—"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", color: "#6475a0" }, mono) }, price ? fmtMoney(price) : "—"),
              h("td", { style: { textAlign: "right", padding: "6px 12px", color: margin == null ? "#94a3b8" : margin >= 40 ? "#18a867" : margin >= 25 ? "#d97706" : "#d94f47", fontWeight: 700 } }, margin == null ? "—" : margin.toFixed(0) + "%"),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontSize: 11, color: st.color, fontWeight: 600 } }, st.badge));
          })))));

    const onHandNotice = h("div", { style: { background: "rgba(217,119,6,.06)", border: "1px dashed rgba(217,119,6,.35)", borderRadius: 10, padding: "14px 16px", marginBottom: 16 } },
      h("div", { style: { fontSize: 12.5, fontWeight: 700, color: "#b45309", marginBottom: 4 } }, "⏳ On-hand quantity, stock value & reorder points — awaiting an inventory feed"),
      h("div", { style: { fontSize: 11.5, color: "#6475a0", lineHeight: 1.5 } }, "Sales aging, trends and new/old detection below are derived from your real sales line items. On-hand value & reorder need a live stock source or a CSV upload (New & Old Items tab) — never fabricated."));

    // ════════════ TAB: Overview (existing rich body) ════════════
    const tabBtn = (key, label) => h("button", { onClick: () => setTab(key), style: { padding: "5px 14px", fontSize: 12, fontWeight: 700, background: tab === key ? "#0d2040" : "rgba(13,32,64,.06)", color: tab === key ? "#fff" : "#6475a0", border: "1px solid rgba(13,32,64,.12)", borderRadius: 7, cursor: "pointer" } }, label);
    const sortTh = (key, label, align) => h("th", { onClick: () => setSort(key), style: { textAlign: align || "right", padding: "8px 12px", fontSize: 9.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.5, color: "rgba(255,255,255,.85)", cursor: "pointer", whiteSpace: "nowrap" } }, label + (sortKey === key ? (sortDir === "asc" ? " ▲" : " ▼") : ""));
    const takeaway = "Across <b>" + skuCount + "</b> " + (holdsInv ? "products" : "services") + ", <b>" + soldSkuCount + "</b> sold in " + range.label.toLowerCase() +
      " generating <b>" + M(totRevenue) + "</b> at a <b>" + (avgGm == null ? "—" : avgGm.toFixed(1) + "%") + "</b> blended gross margin." +
      (breakdown.length ? " Top " + (tab === "channel" ? "channel" : "category") + " is <b>" + breakdown[0].name + "</b> at " + (totRevenue ? (breakdown[0].revenue / totRevenue * 100).toFixed(0) : "0") + "% of revenue." : "");

    // Inventory ↔ Balance-Sheet reconciliation banner (Overview + SKU tabs).
    function reconcileBanner() {
      if (!holdsInv) return null;
      const ok = isReconciled, has = invSkuTotal > 0;
      return h("div", { style: { background: ok ? "rgba(24,168,103,.08)" : has ? "rgba(217,79,71,.08)" : "rgba(13,32,64,.04)", border: "1px solid " + (ok ? "rgba(24,168,103,.3)" : has ? "rgba(217,79,71,.3)" : "rgba(13,32,64,.12)"), borderRadius: 10, padding: "14px 18px", marginBottom: 16, display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" } },
        h("span", { style: { fontSize: 20 } }, ok ? "✅" : has ? "⚠️" : "ℹ️"),
        h("div", null,
          h("div", { style: { fontSize: 12.5, fontWeight: 700, color: "#0d2040" } },
            !has ? "Upload SKU on-hand data to reconcile to the Balance Sheet"
              : ok ? "Inventory reconciles to Balance Sheet"
              : "Inventory out of balance — " + (reconcileDiff > 0 ? "SKU value exceeds" : "SKU value below") + " Balance Sheet by " + M(Math.abs(reconcileDiff))),
          h("div", { style: { fontSize: 11, color: "#6475a0", marginTop: 2 } },
            "Balance Sheet inventory: " + M(invBsBalance) + " · " + (has ? "SKU upload total: " + M(invSkuTotal) + " · Difference: " + (reconcileDiff >= 0 ? "+" : "−") + M(Math.abs(reconcileDiff)) : "Upload SKU data to reconcile"))),
        (!ok && has) ? h("div", { style: { marginLeft: "auto", fontSize: 11, color: "#6475a0", fontStyle: "italic" } }, "Tip: differences may be in-transit stock, write-downs, or timing") : null);
    }

    // FIX 3C — inventory KPI tiles (SKU data when present, else GL).
    const invOnHandValue = skuRows.length > 0 ? invSkuTotal : invBsBalance;
    const invOnHandQty = skuRows.reduce((s, r) => s + (parseFloat(r.on_hand_qty || 0) || 0), 0);
    const uploadedSkuCount = skuRows.length;
    const avgSkuValue = uploadedSkuCount > 0 ? invOnHandValue / uploadedSkuCount : 0;
    const pricedRows = skuRows.filter((r) => parseFloat(r.selling_price || 0) > 0);
    const avgGMov = pricedRows.length ? pricedRows.reduce((s, r) => s + (parseFloat(r.selling_price) - parseFloat(r.cost_per_unit || 0)) / parseFloat(r.selling_price), 0) / pricedRows.length * 100 : null;
    const invKpiTiles = [
      { label: "On-Hand Value", value: invOnHandValue ? M(invOnHandValue) : "—", valueColor: "navy", sub: skuRows.length ? "from SKU upload" : "from balance sheet" },
      { label: "On-Hand Qty", value: invOnHandQty ? Math.round(invOnHandQty).toLocaleString() : "—", valueColor: "blue", sub: "units in stock" },
      { label: "# SKUs", value: uploadedSkuCount ? String(uploadedSkuCount) : "—", valueColor: "navy", sub: "uploaded on-hand" },
      { label: "Avg SKU Value", value: avgSkuValue ? M(avgSkuValue) : "—", valueColor: "teal", sub: "value ÷ SKUs" },
      { label: "Avg GM %", value: avgGMov == null ? "—" : avgGMov.toFixed(1) + "%", valueColor: avgGMov == null ? "navy" : avgGMov >= 40 ? "green" : avgGMov >= 25 ? "amber" : "red", sub: "price vs cost" },
      { label: "Inventory Turns", value: invTurns == null ? "—" : invTurns.toFixed(1) + "×", valueColor: invTurns == null ? "navy" : invTurns >= 6 ? "green" : invTurns >= 3 ? "amber" : "red", sub: "COGS ÷ avg inventory" },
    ];

    function renderOverview() {
      if (!hasData) {
        if (holdsInv) return h(React.Fragment, null, hasGLInventory ? renderGlDerived() : null, hasGLInventory ? renderPurchaseMatrix(false) : null, skuRows.length ? renderUploadedSkus() : null, renderMultiSource());
        return h("div", { style: { background: "#fff", borderRadius: 12, border: "1px solid rgba(13,32,64,.10)", padding: "40px 32px", textAlign: "center", maxWidth: 560, margin: "12px auto" } },
          h("div", { style: { fontSize: 32, marginBottom: 12 } }, "📦"),
          h("div", { style: { fontSize: 16, fontWeight: 800, color: "#0d2040", marginBottom: 8 } }, "Product data not connected"),
          h("div", { style: { fontSize: 13, color: "#6475a0", lineHeight: 1.7, marginBottom: 20 } }, "This page builds from sales line items and your product catalog. Connect a billing/order system or upload a CSV."),
          h("button", { onClick: () => downloadCsv("products_services_template.csv", "sku_code,description,category,unit_cost,selling_price\nSKU001,Example Product,Category A,25.00,45.00"), style: { padding: "10px 20px", background: "#0d2040", color: "#fff", border: "none", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "⬇ Download CSV template"));
      }
      return h(React.Fragment, null,
        holdsInv ? reconcileBanner() : null,
        holdsInv ? h("div", { className: "pa-kpi-strip pa-kpi-strip-6" }, invKpiTiles.map((k, i) => h(K.Kpi, Object.assign({ key: "inv" + i, animDelay: i * 0.03, onClick: () => window.__perduraSetPage && window.__perduraSetPage("trial_balance") }, k)))) : null,
        holdsInv ? onHandNotice : null,
        (holdsInv && hasGLInventory) ? renderGlDerived() : null,
        (holdsInv && hasGLInventory) ? renderPurchaseMatrix(false) : null,
        h("div", { className: "pa-kpi-strip pa-kpi-strip-6" }, [
          { label: holdsInv ? "Products / SKUs" : "Services / SKUs", value: skuCount ? String(skuCount) : "—", valueColor: "navy", sub: soldSkuCount + " with sales in period" },
          { label: "Units Sold", value: totUnits ? Math.round(totUnits).toLocaleString() : "—", valueColor: "blue", sub: range.label },
          { label: "Revenue", value: totRevenue ? M(totRevenue) : "—", valueColor: "navy", sub: range.label },
          { label: "COGS", value: totCost ? M(totCost) : "—", valueColor: "red", sub: "cost of goods sold" },
          { label: "Avg Gross Margin", value: avgGm == null ? "—" : avgGm.toFixed(1) + "%", valueColor: avgGm == null ? "navy" : avgGm >= 40 ? "green" : avgGm >= 25 ? "amber" : "red", sub: "weighted across SKUs" },
          { label: "Avg Revenue / SKU", value: soldSkuCount ? M(totRevenue / soldSkuCount) : "—", valueColor: "teal", sub: "selling SKUs only" },
        ].map((k, i) => h(K.Kpi, Object.assign({ key: i, animDelay: i * 0.04, onClick: () => window.__perduraSetPage && window.__perduraSetPage("trial_balance") }, k)))),
        h(K.Card, { title: "REVENUE & GROSS MARGIN — 12-MONTH TREND", sub: "Revenue bars · gross-margin % line (secondary)" },
          h(K.MultiSeriesBarChart, { months: trendLabels, series: trendSeries, height: 190 }),
          h(K.KeyTakeaway, { text: takeaway })),
        h(K.Card, { title: (holdsInv ? "Inventory" : "Revenue") + " by " + (tab === "channel" ? "channel" : "category"), sub: "Revenue, units & gross margin · click a tab to switch dimension", padding: 0, right: h("div", { style: { display: "flex", gap: 6 } }, tabBtn("category", "Category"), tabBtn("channel", "Channel")) },
          breakdown.length ? h("table", { className: "pa-table" },
            h("thead", null, h("tr", null, h("th", null, tab === "channel" ? "Channel" : "Category"), h("th", { className: "num" }, "SKUs"), h("th", { className: "num" }, "Units"), h("th", { className: "num" }, "Revenue"), h("th", { className: "num" }, "% of Rev"), h("th", { className: "num" }, "GM %"))),
            h("tbody", null, breakdown.map((r, i) => h("tr", { key: i },
              h("td", { style: { fontWeight: 600 } }, r.name), h("td", { className: "num muted" }, r.skuCount), h("td", { className: "num" }, Math.round(r.units).toLocaleString()),
              h("td", { className: "num" }, M(r.revenue)), h("td", { className: "num muted" }, (totRevenue ? r.revenue / totRevenue * 100 : 0).toFixed(1) + "%"),
              h("td", { className: "num", style: { color: r.gmPct == null ? "#94a3b8" : r.gmPct >= 40 ? "#18a867" : r.gmPct >= 25 ? "#d97706" : "#d94f47", fontWeight: 700 } }, r.gmPct == null ? "—" : r.gmPct.toFixed(0) + "%")))))
            : h("div", { className: "pa-card-body", style: { color: "#6475a0", fontSize: 13 } }, "No classified sales in the selected window.")),
        h(K.Card, { title: holdsInv ? "Product / SKU listing" : "Service / SKU listing", sub: sorted.length + " SKUs · click a column to sort · click a row for the SKU detail · page " + (curPage + 1) + " of " + pages, padding: 0 },
          h("div", { style: { overflowX: "auto" } }, h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 820 } },
            h("thead", null, h("tr", { style: { background: HEADBG } }, sortTh("sku", "SKU", "left"), sortTh("name", "Description", "left"), sortTh("category", "Category", "left"), sortTh("units", "Units"), sortTh("revenue", "Revenue"), sortTh("cost", "COGS"), sortTh("gmPct", "GM %"), sortTh("avgPrice", "Avg Price"), h("th", { style: { textAlign: "left", padding: "8px 12px", fontSize: 9.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.5, color: "rgba(255,255,255,.85)" } }, "Status"))),
            h("tbody", null, pageRows.map((r, i) => h("tr", { key: i, onClick: () => setSelSku(r.sku), style: { cursor: "pointer", borderTop: "1px solid #eef0f4" } },
              h("td", { style: Object.assign({ textAlign: "left", padding: "6px 12px", color: "#475569", fontSize: 10.5 }, mono) }, r.sku),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontWeight: 600, color: "#1a2540" } }, r.name),
              h("td", { style: { textAlign: "left", padding: "6px 12px", color: "#6475a0", fontSize: 11 } }, r.category),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, r.units ? Math.round(r.units).toLocaleString() : "—"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", fontWeight: 600 }, mono) }, r.revenue ? M(r.revenue) : "—"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", color: "#6475a0" }, mono) }, r.cost ? M(r.cost) : "—"),
              h("td", { style: { textAlign: "right", padding: "6px 12px", color: r.gmPct == null ? "#94a3b8" : r.gmPct >= 40 ? "#18a867" : r.gmPct >= 25 ? "#d97706" : "#d94f47", fontWeight: 700 } }, r.gmPct == null ? "—" : r.gmPct.toFixed(0) + "%"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", color: "#475569" }, mono) }, r.avgPrice == null ? "—" : M(r.avgPrice)),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontSize: 11, color: r.status.color, fontWeight: 600 } }, r.status.icon + " " + r.status.label))))) ),
          pages > 1 ? h("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", gap: 10, padding: "10px 0" } },
            h("button", { onClick: () => setPage(Math.max(0, curPage - 1)), disabled: curPage === 0, style: { padding: "5px 12px", fontSize: 12, fontWeight: 600, background: curPage === 0 ? "#f1f3f7" : "#fff", color: curPage === 0 ? "#94a3b8" : "#1C4ED8", border: "1px solid rgba(13,32,64,.14)", borderRadius: 6, cursor: curPage === 0 ? "default" : "pointer" } }, "‹ Prev"),
            h("span", { style: { fontSize: 12, color: "#6475a0" } }, "Page " + (curPage + 1) + " of " + pages),
            h("button", { onClick: () => setPage(Math.min(pages - 1, curPage + 1)), disabled: curPage >= pages - 1, style: { padding: "5px 12px", fontSize: 12, fontWeight: 600, background: curPage >= pages - 1 ? "#f1f3f7" : "#fff", color: curPage >= pages - 1 ? "#94a3b8" : "#1C4ED8", border: "1px solid rgba(13,32,64,.14)", borderRadius: 6, cursor: curPage >= pages - 1 ? "default" : "pointer" } }, "Next ›")) : null),
        holdsInv ? renderMultiSource() : null,
        skuRows.length ? renderUploadedSkus() : null);
    }

    // ════════════ TAB: SKU Performance (aging buckets) ════════════
    function skuEmpty(msg) {
      return h(K.Card, { title: "SKU PERFORMANCE", sub: "Sales aging buckets — needs sales line items" },
        h("div", { style: { padding: "28px 24px", textAlign: "center" } },
          h("div", { style: { fontSize: 13, color: "#6475a0", lineHeight: 1.7, marginBottom: 18, maxWidth: 540, margin: "0 auto 18px" } }, msg),
          h("div", { style: { display: "flex", gap: 10, justifyContent: "center", flexWrap: "wrap" } },
            h("button", { onClick: () => downloadCsv("inventory_items_sample.csv", ITEMS_CSV), style: { padding: "9px 16px", background: "#0d2040", color: "#fff", border: "none", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "⬇ Sample inventory_items CSV"),
            h("button", { onClick: () => downloadCsv("inventory_sales_history_sample.csv", SALES_CSV), style: { padding: "9px 16px", background: "rgba(13,32,64,.07)", color: "#0d2040", border: "1px solid rgba(13,32,64,.15)", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "⬇ Sample sales_history CSV"))));
    }
    function renderSkuPerf() {
      if (!invListing.length) return h(React.Fragment, null, reconcileBanner(), skuEmpty("No SKU data yet. The complete listing shows on-hand qty/value (from an uploaded CSV) alongside sales aging (0–3 / 3–6 / 6–9 / 9–12 month). Connect a sales feed or upload the sample CSVs below."));
      const DAY = 86400000;
      // Summary strip counts (over the full listing).
      const totalSkus = invListing.length;
      const deadCt = invListing.filter((r) => r.aStatus.key === "dead").length;
      const slowCt = invListing.filter((r) => r.lastSale && (+refDate - +new Date(r.lastSale)) > 90 * DAY).length;
      const newCt = invListing.filter((r) => r.firstSale && (+refDate - +new Date(r.firstSale)) <= 90 * DAY).length;
      const activeCt = invListing.filter((r) => r.lastSale && (+refDate - +new Date(r.lastSale)) <= 90 * DAY).length;
      const totalOnHandVal = invListing.reduce((s, r) => s + (r.onHandVal || 0), 0);
      const pricedL = invListing.filter((r) => r.gmPrice != null);
      const avgGmL = pricedL.length ? pricedL.reduce((s, r) => s + r.gmPrice, 0) / pricedL.length : null;

      // Filter (search) + sort.
      const q = (skuSearch || "").toLowerCase();
      const getVal = (r, col) => {
        switch (col) {
          case "sku": return (r.sku || "").toLowerCase();
          case "name": return (r.name || "").toLowerCase();
          case "vendor": return (r.vendor || "").toLowerCase();
          case "category": return (r.category || "").toLowerCase();
          case "onHand": return r.onHand == null ? -Infinity : r.onHand;
          case "onHandVal": return r.onHandVal == null ? -Infinity : r.onHandVal;
          case "cost": return r.cost == null ? -Infinity : r.cost;
          case "price": return r.price == null ? -Infinity : r.price;
          case "gmPrice": return r.gmPrice == null ? -Infinity : r.gmPrice;
          case "0_3": return r.aging["0_3"].revenue;
          case "3_6": return r.aging["3_6"].revenue;
          case "6_9": return r.aging["6_9"].revenue;
          case "9_12": return r.aging["9_12"].revenue;
          case "status": return r.aStatus.key;
          default: return r.total12 || 0;
        }
      };
      const filteredSkus = invListing.filter((r) =>
        !q || (r.sku || "").toLowerCase().includes(q) || (r.name || "").toLowerCase().includes(q) || (r.vendor || "").toLowerCase().includes(q) || (r.category || "").toLowerCase().includes(q)
      ).slice().sort((a, b) => {
        const av = getVal(a, skuSort.col), bv = getVal(b, skuSort.col);
        const m = skuSort.dir === "desc" ? -1 : 1;
        if (typeof av === "string" || typeof bv === "string") return String(av).localeCompare(String(bv)) * m;
        return (av - bv) * m;
      });
      const SKU_PAGE_SIZE = 50;
      const sp = Math.max(1, Math.ceil(filteredSkus.length / SKU_PAGE_SIZE));
      const cp = Math.min(skuPage, sp - 1);
      const pageRows = filteredSkus.slice(cp * SKU_PAGE_SIZE, (cp + 1) * SKU_PAGE_SIZE);

      const SH = (col, label, align) => h(SortableHeader, { col, label, align, currentSort: skuSort, onSort: (s) => { setSkuSort(s); setSkuPage(0); } });
      const numTd = (v, color) => h("td", { style: Object.assign({ textAlign: "right", padding: "6px 10px", fontSize: 11 }, mono, color ? { color } : {}) }, v);
      const bk = (b, cat) => { const v = b.revenue; return h("td", { style: Object.assign({ textAlign: "right", padding: "6px 10px", fontSize: 11, color: v > 0 ? "#1a2540" : "#ccd", background: v > 0 ? "rgba(220,60,60," + Math.min(0.12, (v / (cat || 1)) * 0.12) + ")" : "transparent" }, mono) }, v ? M(v) : "—"); };
      // Page sums for the total row.
      const sum = (fn) => pageRows.reduce((s, r) => s + (fn(r) || 0), 0);
      const sumQty = sum((r) => r.onHand), sumVal = sum((r) => r.onHandVal);
      const sb03 = sum((r) => r.aging["0_3"].revenue), sb36 = sum((r) => r.aging["3_6"].revenue), sb69 = sum((r) => r.aging["6_9"].revenue), sb912 = sum((r) => r.aging["9_12"].revenue);
      const pill = (label, val, color) => h("span", { style: { fontSize: 11, color: "#6475a0" } }, label + ": ", h("b", { style: { color: color || "#0d2040" } }, val));

      return h(React.Fragment, null,
        reconcileBanner(),
        h("div", { style: { display: "flex", flexWrap: "wrap", gap: 14, alignItems: "center", background: "#fff", border: "1px solid rgba(13,32,64,.1)", borderRadius: 10, padding: "10px 16px", marginBottom: 14 } },
          pill("Total SKUs", String(totalSkus)), pill("Active", String(activeCt), "#18a867"), pill("Dead Stock", String(deadCt), "#d94f47"), pill("Slow Moving", String(slowCt), "#d97706"), pill("New", String(newCt), "#1C4ED8"),
          h("span", { style: { color: "rgba(13,32,64,.15)" } }, "|"),
          pill("Total On-Hand Value", totalOnHandVal ? M(totalOnHandVal) : "—"), pill("Avg GM %", avgGmL == null ? "—" : avgGmL.toFixed(1) + "%")),
        h(K.Card, { title: "COMPLETE SKU LISTING", sub: filteredSkus.length + " SKUs · on-hand (uploaded) + sales aging · page " + (cp + 1) + " of " + sp, padding: 0,
          right: h("input", { type: "text", value: skuSearch, onChange: (e) => { setSkuSearch(e.target.value); setSkuPage(0); }, placeholder: "Search SKU, description, vendor, category…", style: { padding: "6px 10px", fontSize: 11.5, border: "1px solid rgba(13,32,64,.15)", borderRadius: 7, width: 240, outline: "none" } }) },
          h("div", { style: { overflowX: "auto" } }, h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 1240 } },
            h("thead", null, h("tr", null,
              SH("sku", "SKU Code", "left"), SH("name", "Description", "left"), SH("vendor", "Vendor", "left"), SH("category", "Category", "left"),
              SH("onHand", "On Hand Qty"), SH("onHandVal", "On Hand Value"), SH("cost", "Cost"), SH("price", "Price"), SH("gmPrice", "GM %"),
              SH("0_3", "0–3M"), SH("3_6", "3–6M"), SH("6_9", "6–9M"), SH("9_12", "9–12M"),
              h("th", { style: { textAlign: "center", padding: "9px 10px", background: "#0d2040", color: "white", fontWeight: 700, fontSize: 10, textTransform: "uppercase", letterSpacing: ".06em" } }, "Trend"),
              SH("status", "Status", "left"))),
            h("tbody", null, pageRows.map((r, i) => h("tr", { key: i, onClick: () => setSelSku(r.sku), style: { cursor: "pointer", borderTop: "1px solid #eef0f4" } },
              h("td", { style: Object.assign({ textAlign: "left", padding: "6px 10px", color: "#475569", fontSize: 10.5 }, mono) }, r.sku),
              h("td", { style: { textAlign: "left", padding: "6px 10px", fontWeight: 600, color: "#1a2540" } }, r.name),
              h("td", { style: { textAlign: "left", padding: "6px 10px", color: "#6475a0", fontSize: 11 } }, r.vendor),
              h("td", { style: { textAlign: "left", padding: "6px 10px", color: "#6475a0", fontSize: 11 } }, r.category),
              numTd(r.onHand == null ? "—" : Math.round(r.onHand).toLocaleString()),
              numTd(r.onHandVal == null ? "—" : M(r.onHandVal), "#0d2040"),
              numTd(r.cost == null ? "—" : M(r.cost), "#6475a0"),
              numTd(r.price == null ? "—" : M(r.price), "#6475a0"),
              h("td", { style: { textAlign: "right", padding: "6px 10px", fontSize: 11, fontWeight: 700, color: r.gmPrice == null ? "#94a3b8" : r.gmPrice >= 40 ? "#18a867" : r.gmPrice >= 25 ? "#d97706" : "#d94f47" } }, r.gmPrice == null ? "—" : r.gmPrice.toFixed(0) + "%"),
              bk(r.aging["0_3"], r.total12), bk(r.aging["3_6"], r.total12), bk(r.aging["6_9"], r.total12), bk(r.aging["9_12"], r.total12),
              h("td", { style: { padding: "6px 10px", display: "flex", justifyContent: "center" } }, h(MiniTrend, { buckets: r.aging })),
              h("td", { style: { textAlign: "left", padding: "6px 10px", fontSize: 11, color: r.aStatus.color, fontWeight: 600, whiteSpace: "nowrap" } }, r.aStatus.badge))),
              h("tr", { style: { borderTop: "2px solid #0d2040", background: "rgba(13,32,64,.05)", fontWeight: 800 } },
                h("td", { style: { padding: "8px 10px", fontWeight: 800, color: "#0d2040", fontSize: 11 } }, "TOTAL (showing " + pageRows.length + " of " + filteredSkus.length + ")"),
                h("td", null), h("td", null), h("td", null),
                numTd(Math.round(sumQty).toLocaleString(), "#0d2040"), numTd(M(sumVal), "#0d2040"),
                h("td", null), h("td", null), h("td", null),
                numTd(M(sb03), "#0d2040"), numTd(M(sb36), "#0d2040"), numTd(M(sb69), "#0d2040"), numTd(M(sb912), "#0d2040"),
                h("td", null), h("td", null))))),
          sp > 1 ? h("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", gap: 10, padding: "10px 0" } },
            h("button", { onClick: () => setSkuPage(Math.max(0, cp - 1)), disabled: cp === 0, style: { padding: "5px 12px", fontSize: 12, fontWeight: 600, background: cp === 0 ? "#f1f3f7" : "#fff", color: cp === 0 ? "#94a3b8" : "#1C4ED8", border: "1px solid rgba(13,32,64,.14)", borderRadius: 6, cursor: cp === 0 ? "default" : "pointer" } }, "‹ Prev"),
            h("span", { style: { fontSize: 12, color: "#6475a0" } }, "Page " + (cp + 1) + " of " + sp),
            h("button", { onClick: () => setSkuPage(Math.min(sp - 1, cp + 1)), disabled: cp >= sp - 1, style: { padding: "5px 12px", fontSize: 12, fontWeight: 600, background: cp >= sp - 1 ? "#f1f3f7" : "#fff", color: cp >= sp - 1 ? "#94a3b8" : "#1C4ED8", border: "1px solid rgba(13,32,64,.14)", borderRadius: 6, cursor: cp >= sp - 1 ? "default" : "pointer" } }, "Next ›")) : null));
    }

    // ════════════ TAB: Channel Analysis (+ wizard) ════════════
    function saveChannels() {
      const db = window.supabaseClient;
      if (!db || !scopedCompanyId) { setChErr("Not connected — cannot save."); return; }
      const rows = selectedChannels.map((key, i) => { const o = CHANNEL_OPTIONS.find((c) => c.key === key); return { company_id: scopedCompanyId, channel_key: key, channel_label: o ? o.label : key, channel_description: o ? o.desc : null, gl_identifier: channelMappings[key] || "", sort_order: i }; });
      setSavingCh(true); setChErr(null);
      // Idempotent replace (supports removing channels during edits); the unique
      // index on (company_id, channel_key) also makes a direct upsert valid.
      db.from("company_channels").delete().eq("company_id", scopedCompanyId)
        .then(({ error }) => { if (error) throw new Error(error.message); return rows.length ? db.from("company_channels").insert(rows) : { error: null }; })
        .then(({ error }) => { setSavingCh(false); if (error) { setChErr("Save failed: " + (error.message || "")); return; } setEditing(false); setWizardStep(1); setChReload((t) => t + 1); })
        .catch((e) => { setSavingCh(false); setChErr("Save failed: " + ((e && e.message) || "")); });
    }
    function renderWizard() {
      const optCard = (ch) => h("div", { key: ch.key, onClick: () => setSelectedChannels((prev) => prev.includes(ch.key) ? prev.filter((c) => c !== ch.key) : prev.concat([ch.key])), style: { border: selectedChannels.includes(ch.key) ? "2px solid #0d2040" : "2px solid rgba(13,32,64,.12)", borderRadius: 10, padding: 16, cursor: "pointer", background: selectedChannels.includes(ch.key) ? "rgba(13,32,64,.05)" : "#fff", transition: "all .15s" } },
        h("div", { style: { fontSize: 24, marginBottom: 8 } }, ch.icon),
        h("div", { style: { fontSize: 13, fontWeight: 700, color: "#0d2040", marginBottom: 4 } }, ch.label),
        h("div", { style: { fontSize: 11, color: "#6475a0", lineHeight: 1.5 } }, ch.desc),
        selectedChannels.includes(ch.key) ? h("div", { style: { marginTop: 8, fontSize: 11, fontWeight: 700, color: "#18a867" } }, "✓ Selected") : null);
      const stepBadge = h("div", { style: { display: "flex", gap: 8, marginBottom: 18 } }, [1, 2, 3].map((n) => h("div", { key: n, style: { flex: 1, height: 4, borderRadius: 3, background: wizardStep >= n ? "#0d2040" : "rgba(13,32,64,.12)" } })));
      const navBtns = (canNext, nextLabel, onNext) => h("div", { style: { display: "flex", gap: 10, marginTop: 18, alignItems: "center" } },
        wizardStep > 1 ? h("button", { onClick: () => setWizardStep(wizardStep - 1), style: { padding: "9px 16px", background: "#fff", color: "#0d2040", border: "1px solid rgba(13,32,64,.15)", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "‹ Back") : null,
        h("button", { onClick: onNext, disabled: !canNext || savingCh, style: { padding: "9px 18px", background: canNext ? "#0d2040" : "#94a3b8", color: "#fff", border: "none", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: canNext ? "pointer" : "default" } }, nextLabel),
        chErr ? h("span", { style: { color: "#d94f47", fontSize: 12, fontWeight: 600 } }, chErr) : null);
      let body;
      if (wizardStep === 1) {
        body = h(React.Fragment, null,
          h("div", { style: { fontSize: 14, fontWeight: 800, color: "#0d2040", marginBottom: 4 } }, "Which channels do you sell through?"),
          h("div", { style: { fontSize: 12, color: "#6475a0", marginBottom: 16 } }, "Select all that apply — this shapes your revenue mix analysis."),
          h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(200px,1fr))", gap: 12 } }, CHANNEL_OPTIONS.map(optCard)),
          navBtns(selectedChannels.length > 0, "Next: map to GL →", () => setWizardStep(2)));
      } else if (wizardStep === 2) {
        body = h(React.Fragment, null,
          h("div", { style: { fontSize: 14, fontWeight: 800, color: "#0d2040", marginBottom: 4 } }, "Map each channel to your GL"),
          h("div", { style: { fontSize: 12, color: "#6475a0", marginBottom: 16 } }, "Which GL account or revenue category maps to each channel? (e.g. \"Wholesale Revenue\", \"Shopify Sales\", \"Amazon Marketplace\"). Optional — leave blank to configure later."),
          h("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, selectedChannels.map((key) => { const o = CHANNEL_OPTIONS.find((c) => c.key === key); return h("div", { key }, h("div", { style: { fontSize: 12, fontWeight: 700, color: "#0d2040", marginBottom: 5 } }, (o ? o.icon + " " + o.label : key)), h("input", { value: channelMappings[key] || "", onChange: (e) => setChannelMappings(Object.assign({}, channelMappings, { [key]: e.target.value })), placeholder: "GL account or category that maps to " + (o ? o.label : key), style: { width: "100%", padding: "8px 10px", fontSize: 12.5, border: "1px solid rgba(13,32,64,.15)", borderRadius: 8, color: "#1a2540", boxSizing: "border-box" } })); })),
          navBtns(true, "Next: review →", () => setWizardStep(3)));
      } else {
        body = h(React.Fragment, null,
          h("div", { style: { fontSize: 14, fontWeight: 800, color: "#0d2040", marginBottom: 4 } }, "Confirm your channels"),
          h("div", { style: { fontSize: 12, color: "#6475a0", marginBottom: 16 } }, selectedChannels.length + " channel(s) — saved to your company profile and editable anytime."),
          h("div", { style: { display: "flex", flexDirection: "column", gap: 8 } }, selectedChannels.map((key) => { const o = CHANNEL_OPTIONS.find((c) => c.key === key); return h("div", { key, style: { display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", border: "1px solid rgba(13,32,64,.1)", borderRadius: 8, borderLeft: "4px solid " + channelColor(key) } }, h("span", { style: { fontSize: 18 } }, o ? o.icon : "•"), h("div", null, h("div", { style: { fontSize: 12.5, fontWeight: 700, color: "#0d2040" } }, o ? o.label : key), h("div", { style: { fontSize: 11, color: "#6475a0" } }, channelMappings[key] ? "GL: " + channelMappings[key] : "No GL mapping yet"))); })),
          navBtns(true, savingCh ? "Saving…" : "✓ Save channels", saveChannels));
      }
      return h(K.Card, { title: "SALES CHANNEL SETUP", sub: "Step " + wizardStep + " of 3" }, stepBadge, body);
    }
    function renderChannels() {
      if (channels === null) return h(K.Card, { title: "Channel Analysis" }, h("div", { style: { padding: 18, color: "#6475a0", fontSize: 13 } }, "Loading channels…"));
      if (!channels.length || editing) return renderWizard();

      // Build channel revenue from GL (revenue = credits → amount < 0).
      const cr = {};
      channels.forEach((ch) => { cr[ch.channel_key] = { key: ch.channel_key, label: ch.channel_label, gl: (ch.gl_identifier || "").toLowerCase(), total: 0, months: {} }; });
      (data && data.txns || []).forEach((t) => {
        if (parseFloat(t.amount || 0) >= 0) return;
        const cat = (t.canonical_category || "").toLowerCase(), acct = (t.account_name || "").toLowerCase();
        const mon = (t.posted_date || "").slice(0, 7), amt = Math.abs(parseFloat(t.amount || 0));
        channels.forEach((ch) => { const id = (ch.gl_identifier || "").toLowerCase(); if (id && (cat.includes(id) || acct.includes(id))) { cr[ch.channel_key].total += amt; if (mon) cr[ch.channel_key].months[mon] = (cr[ch.channel_key].months[mon] || 0) + amt; } });
      });
      const chList = channels.map((ch) => Object.assign({ color: channelColor(ch.channel_key) }, cr[ch.channel_key]));
      const grand = chList.reduce((s, c) => s + c.total, 0);
      const anyMapped = channels.some((ch) => (ch.gl_identifier || "").trim());
      const anyMatched = grand > 0;
      const last6 = ymList.slice(-6);

      const header = h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 10, marginBottom: 14 } },
        h("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, h("span", { style: { fontSize: 12.5, fontWeight: 700, color: "#18a867" } }, "✓ " + channels.length + " channel(s) configured"), h("span", { style: { fontSize: 11.5, color: "#6475a0" } }, "— edit anytime")),
        h("button", { onClick: () => { setSelectedChannels(channels.map((c) => c.channel_key)); const mp = {}; channels.forEach((c) => { mp[c.channel_key] = c.gl_identifier || ""; }); setChannelMappings(mp); setWizardStep(1); setEditing(true); }, style: { padding: "7px 14px", background: "rgba(13,32,64,.07)", color: "#0d2040", border: "1px solid rgba(13,32,64,.15)", borderRadius: 8, fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "✎ Edit channels"));

      if (!anyMapped) return h(React.Fragment, null, header, h(K.Card, { title: "Channel Analysis" }, h("div", { style: { padding: 18, color: "#6475a0", fontSize: 13, lineHeight: 1.6 } }, "Your channels are set up but none are mapped to a GL account/category yet. Click ", h("b", null, "Edit channels"), " → step 2 to add a GL identifier (e.g. \"Wholesale Revenue\") so revenue can be split by channel.")));

      const kpis = chList.map((c) => { const mk = Object.keys(c.months).sort(); const cur = mk.length ? c.months[mk[mk.length - 1]] : 0; const prv = mk.length > 1 ? c.months[mk[mk.length - 2]] : 0; const arr = trendArrow(cur, prv); return { label: c.label, value: M(c.total), valueColor: "navy", sub: (grand ? (c.total / grand * 100).toFixed(0) : "0") + "% of revenue · MoM " + arr.s }; });

      // Stacked bar (12 months × channels).
      const W = 860, H = 230, padL = 44, padR = 12, padT = 14, padB = 30, cw = (W - padL - padR) / ymList.length, chH = H - padT - padB;
      const monthTot = ymList.map((ym) => chList.reduce((s, c) => s + (c.months[ym] || 0), 0));
      const maxT = Math.max.apply(null, monthTot.concat([1]));
      const bars = [];
      ymList.forEach((ym, i) => {
        let yc = padT + chH; const bx = padL + i * cw + cw * 0.18, bw = cw * 0.64;
        chList.forEach((c) => { const v = c.months[ym] || 0; if (v <= 0) return; const hh = v / maxT * chH; yc -= hh; bars.push(h("rect", { key: c.key + i, x: bx, y: yc, width: bw, height: Math.max(hh, 0.5), fill: c.color, opacity: 0.92 })); });
        bars.push(h("text", { key: "xl" + i, x: padL + i * cw + cw / 2, y: H - 10, textAnchor: "middle", fontSize: 8.5, fill: "#94a3b8" }, MONTHS[(+ym.slice(5, 7)) - 1]));
      });
      const legend = h("div", { style: { display: "flex", gap: 14, flexWrap: "wrap", marginTop: 8 } }, chList.map((c) => h("span", { key: c.key, style: { display: "inline-flex", alignItems: "center", gap: 5, fontSize: 10.5, color: "#475569", fontWeight: 600 } }, h("span", { style: { width: 11, height: 11, borderRadius: 2, background: c.color } }), c.label)));

      return h(React.Fragment, null, header,
        h("div", { className: "pa-kpi-strip pa-kpi-strip-" + Math.min(6, Math.max(3, chList.length)) }, kpis.map((k, i) => h(K.Kpi, Object.assign({ key: i, animDelay: i * 0.04, onClick: () => window.__perduraSetPage && window.__perduraSetPage("trial_balance") }, k)))),
        h(K.Card, { title: "REVENUE MIX BY CHANNEL — 12-MONTH STACKED", sub: "Each bar = one month, stacked by channel" }, anyMatched ? h(React.Fragment, null, h("div", { style: { overflowX: "auto" } }, h("svg", { viewBox: "0 0 " + W + " " + H, style: { width: "100%", height: "auto", maxHeight: H, display: "block", minWidth: 560 } }, bars)), legend) : h("div", { style: { padding: 14, color: "#6475a0", fontSize: 13 } }, "No GL revenue matched your channel identifiers yet — check the GL mapping text matches your account/category names.")),
        h(K.Card, { title: "CHANNEL COMPARISON", sub: "Last 6 months · share of total · trailing-3M vs prior-3M trend", padding: 0 },
          h("div", { style: { overflowX: "auto" } }, h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 760 } },
            h("thead", null, h("tr", { style: { background: HEADBG } }, thc("Channel", "left"), last6.map((ym) => thc(MONTHS[(+ym.slice(5, 7)) - 1])), thc("Total"), thc("% Share"), thc("Trend", "center"))),
            h("tbody", null, chList.slice().sort((a, b) => b.total - a.total).map((c, i) => { const l3 = ymList.slice(-3).reduce((s, m) => s + (c.months[m] || 0), 0); const p3 = ymList.slice(-6, -3).reduce((s, m) => s + (c.months[m] || 0), 0); const arr = trendArrow(l3, p3); return h("tr", { key: i, style: { borderTop: "1px solid #eef0f4" } },
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontWeight: 600, color: "#1a2540" } }, h("span", { style: { display: "inline-block", width: 9, height: 9, borderRadius: 2, background: c.color, marginRight: 7 } }), c.label),
              last6.map((ym) => h("td", { key: ym, style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, c.months[ym] ? M(c.months[ym]) : "—")),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", fontWeight: 700, color: "#0d2040" }, mono) }, M(c.total)),
              h("td", { style: { textAlign: "right", padding: "6px 12px", color: "#6475a0" } }, (grand ? c.total / grand * 100 : 0).toFixed(0) + "%"),
              h("td", { style: { textAlign: "center", padding: "6px 12px", fontWeight: 800, color: arr.c } }, arr.s)); })))),
          h("div", { style: { padding: "10px 16px", fontSize: 11.5, color: "#6475a0", borderTop: "1px solid #eef0f4" } }, "💡 Channel margin: map COGS to channels (inventory_sales_history.channel) to unlock GM% per channel. Currently showing revenue only.")));
    }

    // ════════════ TAB: Vendors ════════════
    function renderVendorDetail() {
      const v = purchasesByVendor[selVendor]; if (!v) return null;
      const vSkus = skuPerfRows.filter((r) => r.vendor === selVendor);
      const months = Object.keys(v.months).sort();
      const maxV = Math.max.apply(null, months.map((m) => v.months[m]).concat([1]));
      return h(K.Shell, { hero: { eyebrow: noun.toUpperCase(), title: selVendor, subtitle: "Vendor detail · " + M(v.total) + " purchased · " + vSkus.length + " mapped SKU(s)" } },
        h(K.BackButton, { label: "Vendors", onClick: () => setSelVendor(null) }),
        h(K.Card, { title: "PURCHASE HISTORY", sub: "Monthly inventory/COGS GL spend with this vendor" },
          months.length ? h("div", { style: { display: "flex", alignItems: "flex-end", gap: 6, height: 140, padding: "8px 4px" } }, months.map((m, i) => h("div", { key: i, style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-end" }, title: pMoLabel(m) + ": " + M(v.months[m]) }, h("div", { style: { width: "70%", height: Math.max(2, v.months[m] / maxV * 110) + "px", background: "#1C4ED8", borderRadius: "3px 3px 0 0" } }), h("div", { style: { fontSize: 8.5, color: "#94a3b8", marginTop: 4 } }, MONTHS[(+m.slice(5, 7)) - 1])))) : h("div", { style: { padding: 12, color: "#6475a0", fontSize: 13 } }, "No monthly detail.")),
        vSkus.length ? h(K.Card, { title: "SKUS SUPPLIED", sub: vSkus.length + " SKU(s) mapped to this vendor (from uploaded item data)", padding: 0 },
          h("div", { style: { overflowX: "auto" } }, h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 620 } },
            h("thead", null, h("tr", { style: { background: HEADBG } }, thc("SKU", "left"), thc("Description", "left"), thc("12M Sales"), thc("GM %"), thc("On Hand"), thc("Status", "left"))),
            h("tbody", null, vSkus.sort((a, b) => b.total12 - a.total12).map((r, i) => h("tr", { key: i, style: { borderTop: "1px solid #eef0f4" } },
              h("td", { style: Object.assign({ textAlign: "left", padding: "6px 12px", color: "#475569", fontSize: 10.5 }, mono) }, r.sku),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontWeight: 600, color: "#1a2540" } }, r.name),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, M(r.total12)),
              h("td", { style: { textAlign: "right", padding: "6px 12px", color: r.gmPct == null ? "#94a3b8" : "#0d2040", fontWeight: 700 } }, r.gmPct == null ? "—" : r.gmPct.toFixed(0) + "%"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, r.onHand == null ? "—" : r.onHand.toLocaleString()),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontSize: 11, color: r.aStatus.color, fontWeight: 600 } }, r.aStatus.badge))))))) : h(K.Card, { title: "SKUS SUPPLIED" }, h("div", { style: { padding: 16, color: "#6475a0", fontSize: 13 } }, "No SKUs mapped to this vendor. Upload inventory items with a vendor column (New & Old Items tab) to link SKUs to vendors.")),
        h(K.Card, { title: "PAYMENT TERMS" }, h("div", { style: { padding: 16, color: "#6475a0", fontSize: 13, lineHeight: 1.6 } }, "Vendor payment terms come from AP aging / bill data. Connect AP detail to surface terms, average days-to-pay and early-payment discounts here.")));
    }
    function renderVendors() {
      if (selVendor) return null; // detail rendered at shell level
      if (!topPurchaseVendors.length) return h(K.Card, { title: "VENDORS", sub: "Purchase analysis from your ledger" }, h("div", { style: { padding: 24, color: "#6475a0", fontSize: 13, lineHeight: 1.7 } }, "No inventory/COGS vendor postings in the ledger yet. Vendor spend is derived from inventory & COGS GL transactions. Upload inventory items (New & Old Items tab) to also map SKUs to vendors."));
      const top6 = topPurchaseVendors.slice(0, 6);
      const skuCountByVendor = (vn) => skuPerfRows.filter((r) => r.vendor === vn).length;
      const cards = h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(220px,1fr))", gap: 12, marginBottom: 16 } }, top6.map((v) => {
        const l3 = purchaseMonths.slice(-3).reduce((s, m) => s + (v.months[m] || 0), 0), p3 = purchaseMonths.slice(-6, -3).reduce((s, m) => s + (v.months[m] || 0), 0), arr = trendArrow(l3, p3);
        const last = Object.keys(v.months).sort().pop();
        return h("div", { key: v.vendor, onClick: () => setSelVendor(v.vendor), style: { background: "#fff", border: "1px solid rgba(13,32,64,.1)", borderRadius: 10, padding: "14px 16px", cursor: "pointer", borderLeft: "4px solid #1C4ED8" } },
          h("div", { style: { fontSize: 13, fontWeight: 700, color: "#0d2040", marginBottom: 8, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, v.vendor),
          h("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 11.5, color: "#475569", marginBottom: 3 } }, h("span", null, "Purchases (12M)"), h("b", { style: Object.assign({ color: "#0d2040" }, mono) }, M(v.total))),
          h("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 11.5, color: "#475569", marginBottom: 3 } }, h("span", null, "SKUs supplied"), h("b", { style: { color: "#0d2040" } }, skuCountByVendor(v.vendor) || "—")),
          h("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 11.5, color: "#475569", marginBottom: 3 } }, h("span", null, "Concentration"), h("b", { style: { color: "#0d2040" } }, (purchaseTotal ? v.total / purchaseTotal * 100 : 0).toFixed(0) + "%")),
          h("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 11.5, color: "#475569" } }, h("span", null, "Last PO · Trend"), h("b", { style: { color: arr.c } }, (last ? pMoLabel(last) : "—") + " " + arr.s)));
      }));
      return h(React.Fragment, null,
        h("div", { style: { fontSize: 12.5, color: "#6475a0", marginBottom: 4, fontWeight: 600 } }, "Top vendors by 12-month inventory/COGS spend · click any vendor for detail"),
        cards, renderPurchaseMatrix(true));
    }

    // ════════════ TAB: New & Old Items ════════════
    function renderNewOld() {
      if (!skuPerfRows.length) return skuEmpty("New & old item detection needs sales history. New = first sale within 90 days; Slow/Discontinued = no sale in 90+ days. Upload the sample CSVs to try it.");
      const DAY = 86400000;
      const newItems = skuPerfRows.filter((r) => r.firstSale && (+refDate - +new Date(r.firstSale)) <= 90 * DAY);
      const slowItems = skuPerfRows.filter((r) => r.lastSale && (+refDate - +new Date(r.lastSale)) > 90 * DAY);
      const deadCount = skuPerfRows.filter((r) => r.aStatus.key === "dead").length;
      const deadValue = skuPerfRows.filter((r) => r.aStatus.key === "dead").reduce((s, r) => s + (r.onHandVal || 0), 0);
      const slowValue = slowItems.reduce((s, r) => s + (r.onHandVal || 0), 0);
      const newWeekly = newItems.length ? newItems.reduce((s, r) => s + r.aging["0_3"].revenue / 13, 0) / newItems.length : 0;
      const newUp = newItems.filter((r) => { const b = r.aging; return b["0_3"].revenue > b["3_6"].revenue; }).length;

      const summary = h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(240px,1fr))", gap: 12, marginBottom: 16 } }, [
        { t: "Dead Stock", n: deadCount, sub: deadValue ? "Value: " + fmtMoney(deadValue) + " · clearance pricing" : "No on-hand value uploaded", c: "#d94f47" },
        { t: "Slow Moving", n: slowItems.length, sub: slowValue ? "Value: " + fmtMoney(slowValue) + " · promotional push" : "No sale in 90+ days", c: "#d97706" },
        { t: "New Items", n: newItems.length, sub: newItems.length ? "Avg " + fmtMoney(newWeekly) + "/wk · " + newUp + " of " + newItems.length + " trending up" : "None in last 90 days", c: "#1C4ED8" },
      ].map((x, i) => h("div", { key: i, style: { background: "#fff", border: "1px solid rgba(13,32,64,.1)", borderRadius: 10, padding: "14px 16px", borderLeft: "4px solid " + x.c } },
        h("div", { style: { fontSize: 11, fontWeight: 700, color: "#6475a0", textTransform: "uppercase", letterSpacing: 0.4 } }, x.t),
        h("div", { style: { fontSize: 24, fontWeight: 800, color: x.c, margin: "2px 0" } }, x.n + " SKUs"),
        h("div", { style: { fontSize: 11.5, color: "#6475a0" } }, x.sub))));

      const newTable = h(K.Card, { title: "🚀 NEW ITEMS", sub: "First sale within the last 90 days", padding: 0 },
        newItems.length ? h("div", { style: { overflowX: "auto" } }, h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 880 } },
          h("thead", null, h("tr", { style: { background: HEADBG } }, thc("SKU", "left"), thc("Description", "left"), thc("First Sale", "left"), thc("Days Active"), thc("0–3M Sales"), thc("Sales/Wk"), thc("Trend", "center"), thc("GM %"), thc("Verdict", "left"))),
          h("tbody", null, newItems.sort((a, b) => b.aging["0_3"].revenue - a.aging["0_3"].revenue).map((r, i) => {
            const days = daysSince(r.firstSale) || 0; const perWk = r.aging["0_3"].revenue / 13; const up = r.aging["0_3"].revenue > r.aging["3_6"].revenue;
            const verdict = days < 30 ? { t: "🆕 Too early", c: "#6475a0" } : up ? { t: "🚀 Strong launch", c: "#18a867" } : { t: "⚠️ Slow start", c: "#d97706" };
            return h("tr", { key: i, onClick: () => setSelSku(r.sku), style: { cursor: "pointer", borderTop: "1px solid #eef0f4" } },
              h("td", { style: Object.assign({ textAlign: "left", padding: "6px 12px", color: "#475569", fontSize: 10.5 }, mono) }, r.sku),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontWeight: 600, color: "#1a2540" } }, r.name),
              h("td", { style: { textAlign: "left", padding: "6px 12px", color: "#6475a0" } }, fmtDate(r.firstSale)),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, days + "d"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", fontWeight: 600 }, mono) }, M(r.aging["0_3"].revenue)),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, fmtMoney(perWk)),
              h("td", { style: { padding: "6px 12px", display: "flex", justifyContent: "center" } }, h(MiniTrend, { buckets: r.aging })),
              h("td", { style: { textAlign: "right", padding: "6px 12px", color: r.gmPct == null ? "#94a3b8" : "#0d2040", fontWeight: 700 } }, r.gmPct == null ? "—" : r.gmPct.toFixed(0) + "%"),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontSize: 11, color: verdict.c, fontWeight: 600, whiteSpace: "nowrap" } }, verdict.t));
          }))) ) : h("div", { className: "pa-card-body", style: { color: "#6475a0", fontSize: 13 } }, "No new items with a first sale in the last 90 days."));

      const slowTable = h(K.Card, { title: "🐌 SLOW MOVING / DISCONTINUED", sub: "No sale in 90+ days · on-hand from uploaded item data", padding: 0 },
        slowItems.length ? h("div", { style: { overflowX: "auto" } }, h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 880 } },
          h("thead", null, h("tr", { style: { background: HEADBG } }, thc("SKU", "left"), thc("Description", "left"), thc("Last Sale", "left"), thc("Days Since"), thc("On Hand"), thc("On-Hand Value"), thc("Action", "left"))),
          h("tbody", null, slowItems.sort((a, b) => (daysSince(b.lastSale) || 0) - (daysSince(a.lastSale) || 0)).map((r, i) => {
            const ds = daysSince(r.lastSale) || 0; const oh = r.onHand; const ohv = r.onHandVal;
            let action; if (oh == null) action = { t: "ℹ️ Upload on-hand to assess", c: "#6475a0" };
            else if (oh === 0) action = { t: "✅ Naturally discontinued", c: "#18a867" };
            else if (ds > 180) action = { t: "🔴 Consider clearance — " + fmtMoney(ohv || 0) + " tied up", c: "#d94f47" };
            else action = { t: "🟡 Monitor — reorder only if needed", c: "#d97706" };
            return h("tr", { key: i, onClick: () => setSelSku(r.sku), style: { cursor: "pointer", borderTop: "1px solid #eef0f4" } },
              h("td", { style: Object.assign({ textAlign: "left", padding: "6px 12px", color: "#475569", fontSize: 10.5 }, mono) }, r.sku),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontWeight: 600, color: "#1a2540" } }, r.name),
              h("td", { style: { textAlign: "left", padding: "6px 12px", color: "#6475a0" } }, fmtDate(r.lastSale)),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, ds + "d"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, oh == null ? "—" : oh.toLocaleString()),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", fontWeight: 600 }, mono) }, ohv == null ? "—" : fmtMoney(ohv)),
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontSize: 11, color: action.c, fontWeight: 600 } }, action.t));
          }))) ) : h("div", { className: "pa-card-body", style: { color: "#6475a0", fontSize: 13 } }, "No SKUs without a sale in the last 90 days."));

      return h(React.Fragment, null,
        (!skuRows.length) ? h("div", { style: { background: "rgba(28,78,216,.05)", border: "1px solid rgba(28,78,216,.2)", borderRadius: 10, padding: "12px 16px", marginBottom: 16, fontSize: 12, color: "#1a2540", display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" } },
          h("span", null, "💡 Upload on-hand quantities to populate the value / clearance figures below."),
          h("button", { onClick: () => downloadCsv("inventory_items_sample.csv", ITEMS_CSV), style: { padding: "6px 12px", background: "#0d2040", color: "#fff", border: "none", borderRadius: 6, fontSize: 11.5, fontWeight: 700, cursor: "pointer" } }, "⬇ Sample items CSV"),
          h("label", { style: { padding: "6px 12px", background: "rgba(24,168,103,.12)", color: "#18a867", border: "1px solid rgba(24,168,103,.3)", borderRadius: 6, fontSize: 11.5, fontWeight: 700, cursor: "pointer" } }, "⬆ Upload on-hand CSV", h("input", { type: "file", accept: ".csv,text/csv", style: { display: "none" }, onChange: (e) => handleInventoryCSV(e.target.files && e.target.files[0]) }))) : null,
        summary, newTable, slowTable);
    }

    // ── SKU detail drill (shared across tabs) ─────────────────────────────────
    function renderSkuDetail() {
      const r = allRows.find((x) => x.sku === selSku) || skuPerfRows.find((x) => x.sku === selSku);
      if (!r) { setSelSku(null); return null; }
      const months = r.months || {};
      const monthly = ymList.map((ym) => Object.assign({ ym }, months[ym] || { units: 0, revenue: 0, cost: 0, gp: 0 })).map((m) => Object.assign(m, { gmPct: m.revenue ? m.gp / m.revenue * 100 : null }));
      const gmSeries = monthly.map((m) => m.gmPct);
      const last3 = monthly.slice(-3).reduce((s, m) => s + m.revenue, 0), prev3 = monthly.slice(-6, -3).reduce((s, m) => s + m.revenue, 0);
      const trendPct = prev3 > 0 ? (last3 - prev3) / prev3 * 100 : null;
      const gm = r.gmPct;
      const dKpis = [
        { label: "Units Sold", value: r.units ? Math.round(r.units).toLocaleString() : "—", valueColor: "blue", sub: "12-month" },
        { label: "Revenue", value: r.revenue ? M(r.revenue) : "—", valueColor: "navy", sub: "12-month" },
        { label: "Gross Margin", value: gm == null ? "—" : gm.toFixed(1) + "%", valueColor: gm == null ? "navy" : gm >= 40 ? "green" : gm >= 25 ? "amber" : "red", sub: avgGm == null ? "" : "vs " + avgGm.toFixed(0) + "% co. avg" },
        { label: "First Sale", value: firstSaleAll[r.sku] ? firstSaleAll[r.sku].slice(0, 10) : "—", valueColor: "navy", sub: "earliest" },
        { label: "Last Sale", value: lastSaleAll[r.sku] ? lastSaleAll[r.sku].slice(0, 10) : "—", valueColor: "teal", sub: "most recent" },
      ];
      return h(K.Shell, { hero: { eyebrow: noun.toUpperCase(), title: r.name, subtitle: (r.sku ? "SKU " + r.sku + " · " : "") + r.category } },
        h(K.BackButton, { label: noun, onClick: () => setSelSku(null) }),
        h("div", { className: "pa-kpi-strip pa-kpi-strip-5" }, dKpis.map((k, i) => h(K.Kpi, Object.assign({ key: i, animDelay: i * 0.04, onClick: () => window.__perduraSetPage && window.__perduraSetPage("trial_balance") }, k)))),
        h(K.Card, { title: "MONTHLY SALES FLOW", sub: "Units, revenue, COGS & gross margin by month · last 12 months", padding: 0 },
          h("div", { style: { overflowX: "auto" } }, h("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 11.5, minWidth: 560 } },
            h("thead", null, h("tr", { style: { background: HEADBG } }, ["Month", "Units", "Revenue", "COGS", "Gross Profit", "GM %"].map((c, i) => h("th", { key: i, style: { textAlign: i === 0 ? "left" : "right", padding: "8px 12px", fontSize: 9.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.5, color: "rgba(255,255,255,.85)" } }, c)))),
            h("tbody", null, monthly.map((m, i) => h("tr", { key: i, style: { borderTop: "1px solid #eef0f4" } },
              h("td", { style: { textAlign: "left", padding: "6px 12px", fontWeight: 600, color: "#1a2540" } }, MONTHS[(+m.ym.slice(5, 7)) - 1] + " '" + m.ym.slice(2, 4)),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, m.units ? Math.round(m.units).toLocaleString() : "—"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px" }, mono) }, m.revenue ? M(m.revenue) : "—"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", color: "#6475a0" }, mono) }, m.cost ? M(m.cost) : "—"),
              h("td", { style: Object.assign({ textAlign: "right", padding: "6px 12px", fontWeight: 600 }, mono) }, m.gp ? M(m.gp) : "—"),
              h("td", { style: { textAlign: "right", padding: "6px 12px", color: m.gmPct == null ? "#94a3b8" : m.gmPct >= 40 ? "#18a867" : m.gmPct >= 25 ? "#d97706" : "#d94f47", fontWeight: 700 } }, m.gmPct == null ? "—" : m.gmPct.toFixed(0) + "%")))))) ),
        h("div", { className: "pa-grid-2" },
          h(K.Card, { title: "Gross margin trend", sub: "Monthly GM % · last 12 months" }, gmSeries.filter((v) => v != null).length >= 2 ? h(K.Line, { values: gmSeries, labels: trendLabels, color: "#18a867", suffix: "%", height: 180 }) : h("div", { style: { fontSize: 12.5, color: "#6475a0", padding: 12 } }, "Not enough monthly sales history for a trend.")),
          h(K.Commentary, { title: "CFO commentary", items: [
            { icon: "▣", text: "<b>" + r.name + "</b>" + (r.category ? " (" + r.category + ")" : "") + " sold <b>" + Math.round(r.units).toLocaleString() + "</b> units for <b>" + M(r.revenue) + "</b> over 12 months." },
            gm != null ? { icon: "◆", text: "Gross margin is <b>" + gm.toFixed(1) + "%</b>" + (avgGm != null ? ", " + (gm >= avgGm ? "above" : "below") + " the company average of " + avgGm.toFixed(1) + "%" : "") + "." } : null,
            trendPct != null ? { icon: trendPct >= 0 ? "▲" : "▼", text: "Revenue is <b>" + (trendPct >= 0 ? "up" : "down") + " " + Math.abs(trendPct).toFixed(0) + "%</b> over the last 3 months vs the prior 3." } : null,
            r.aStatus ? { icon: "◷", text: "Sales-aging status: <b>" + r.aStatus.badge + "</b>." } : null,
          ].filter(Boolean) })));
    }

    // ── Render ────────────────────────────────────────────────────────────────
    if (selSku) return renderSkuDetail();
    if (selVendor && invTab === "vendors") return renderVendorDetail();

    // Pure empty (no data AND not an inventory business) → simple onboarding only.
    if (!hasData && !holdsInv) {
      return h(K.Shell, { hero: { eyebrow: noun.toUpperCase(), title: noun, subtitle: "Awaiting product / sales-line-item data" } }, renderOverview());
    }

    const tabBar = h("div", { style: { display: "flex", gap: 4, borderBottom: "2px solid rgba(13,32,64,.08)", marginBottom: 24, overflowX: "auto" } },
      INV_TABS.map((t) => h("button", { key: t.id, onClick: () => setInvTab(t.id), style: { padding: "10px 18px", fontSize: 12, fontWeight: 700, cursor: "pointer", border: "none", borderBottom: invTab === t.id ? "3px solid #0d2040" : "3px solid transparent", background: "transparent", color: invTab === t.id ? "#0d2040" : "#6475a0", whiteSpace: "nowrap", transition: "all .15s" } }, t.icon + " " + t.label)));

    return h(K.Shell, { hero: {
      eyebrow: noun.toUpperCase(), title: noun,
      subtitle: skuCount + " SKUs · units, revenue & gross margin from sales line items",
      controls: invTab === "overview" ? h(K.PeriodControls, Object.assign({}, ps, { showCompare: false })) : null,
    } },
      tabBar,
      invTab === "overview" ? renderOverview() : null,
      invTab === "sku" ? renderSkuPerf() : null,
      invTab === "channels" ? renderChannels() : null,
      invTab === "vendors" ? renderVendors() : null,
      invTab === "movements" ? renderNewOld() : null);
  }
  window.InventoryPage = Page;
})();
