/* helpers.jsx — data access, cube aggregation, formatting, icons (exported to window) */
const D = window.REFILL_DATA;
const { useState, useEffect, useMemo, useRef, useCallback } = React;

/* ---------- formatting ---------- */
const fmt = (n) => (n == null || isNaN(n)) ? "—" : Math.round(n).toLocaleString("en-US");
const fmtSign = (n) => (n > 0 ? "+" : n < 0 ? "−" : "") + fmt(Math.abs(n));
const pct = (n, d = 1) => (n == null || isNaN(n)) ? "—" : (n).toFixed(d) + "%";
const monthLabel = (m) => {
  const [y, mo] = m.split("-");
  return ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][+mo - 1] + " " + y.slice(2);
};
const monthLabelShort = (m) => {
  const [y, mo] = m.split("-");
  return ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][+mo - 1];
};
const MONTH_FULL = ["January","February","March","April","May","June","July","August","September","October","November","December"];
const fmtMonthLong = (m) => MONTH_FULL[+m.split("-")[1] - 1] + " " + m.split("-")[0];
const dayLabel = (iso) => { const d = new Date(iso + "T00:00:00"); return (d.getMonth()+1) + "/" + d.getDate(); };

/* ---------- icons (1.5px stroke, lucide-ish) ---------- */
const Ico = ({ d, size = 16, fill, ...p }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill={fill || "none"} stroke="currentColor"
       strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}>{d}</svg>
);
const ICONS = {
  overview: <><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></>,
  mix: <><line x1="4" y1="20" x2="4" y2="10"/><line x1="10" y1="20" x2="10" y2="4"/><line x1="16" y1="20" x2="16" y2="13"/><line x1="22" y1="20" x2="2" y2="20" strokeWidth="1.2"/></>,
  patients: <><circle cx="9" cy="8" r="3.2"/><path d="M3.5 20a5.5 5.5 0 0 1 11 0"/><path d="M16 7.5a3 3 0 0 1 0 5.6"/><path d="M18 14.5a5 5 0 0 1 3 4.5"/></>,
  forecast: <><path d="M3 17l5-5 4 3 7-8"/><path d="M16 7h4v4"/></>,
  chev: <polyline points="6 9 12 15 18 9"/>,
  chevR: <polyline points="9 6 15 12 9 18"/>,
  check: <polyline points="20 6 9 17 4 12"/>,
  x: <><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></>,
  reset: <><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></>,
  copy: <><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></>,
  arrowUp: <><line x1="12" y1="19" x2="12" y2="5"/><polyline points="6 11 12 5 18 11"/></>,
  arrowDn: <><line x1="12" y1="5" x2="12" y2="19"/><polyline points="6 13 12 19 18 13"/></>,
  flame: <path d="M12 3c1 3-2 4-2 7a3 3 0 0 0 6 0c0-1-.3-2-1-3 2 1 4 3.5 4 7a7 7 0 0 1-14 0c0-4 4-6 7-11z"/>,
  layers: <><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></>,
  verify: <><path d="M12 3l7 3v5c0 4.4-3 7.6-7 9-4-1.4-7-4.6-7-9V6z"/><polyline points="9 11.5 11.2 13.7 15.5 9.3"/></>,
  beaker: <><path d="M9 3h6"/><path d="M10 3v6l-5 8a2 2 0 0 0 1.7 3h10.6a2 2 0 0 0 1.7-3l-5-8V3"/><line x1="7" y1="15" x2="17" y2="15"/></>,
  cal: <><rect x="3" y="4.5" width="18" height="16" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="8" y1="2.5" x2="8" y2="6"/><line x1="16" y1="2.5" x2="16" y2="6"/></>,
  funnel: <polygon points="3 4 21 4 14 12.5 14 20 10 18 10 12.5"/>,
  clock: <><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15.5 14"/></>,
  refill: <><path d="M21 12a9 9 0 1 1-2.64-6.36"/><polyline points="21 3 21 9 15 9"/></>,
  truck: <><rect x="1.5" y="6" width="13" height="10" rx="1.5"/><path d="M14.5 9.5h3.6l3.4 3.6V16h-7"/><circle cx="6" cy="18.3" r="1.8"/><circle cx="17.4" cy="18.3" r="1.8"/></>,
  pill: <><rect x="3" y="8.6" width="18" height="6.8" rx="3.4" transform="rotate(-38 12 12)"/><line x1="8.6" y1="14.6" x2="15.4" y2="9.4"/></>,
};
const Icon = ({ name, size = 16 }) => <Ico size={size} d={ICONS[name]} />;

/* ---------- service-line color var ---------- */
const slVar = (line) => "var(--" + (D.lineColors[line] || "sl-acc") + ")";

/* ---------- the cube aggregation engine ----------
   filter = { mFrom, mTo (indices), payers:Set<idx>, lines:Set<name>, pbLens:'all'|'xent'|'xpb' }
   pbLens: xpb excludes all paper-bill payers; xent excludes enteral cells of paper-bill payers. */
function cellOK(di, pi, filter) {
  if (filter.products && !filter.products.has(di)) return false;
  const drug = D.drugs[di], pay = D.payers[pi];
  if (!filter.lines.has(drug.line)) return false;
  if (!filter.payers.has(pi)) return false;
  if (filter.pbLens === "xpb" && pay.pb) return false;
  if (filter.pbLens === "xent" && pay.pb && drug.line === "Enteral") return false;
  return true;
}

function aggregate(filter) {
  const nM = D.months.length, nP = D.payers.length, nD = D.drugs.length;
  const byDrug = new Array(nD).fill(0);
  const byPayer = new Array(nP).fill(0);
  const byMonth = new Array(nM).fill(0);
  const byLine = {};
  D.lines.forEach(l => byLine[l] = 0);
  // byMonth split by line for stacked series
  const monthLine = D.months.map(() => ({}));
  let grand = 0;
  const w = filter.weights || D.months.map((_, m) => (m >= filter.mFrom && m <= filter.mTo) ? 1 : 0);
  for (let di = 0; di < nD; di++) {
    const drug = D.drugs[di], grid = D.cube[di];
    if (!filter.lines.has(drug.line)) continue;
    for (let pi = 0; pi < nP; pi++) {
      if (!cellOK(di, pi, filter)) continue;
      const row = grid[pi];
      for (let m = filter.mFrom; m <= filter.mTo; m++) {
        if (!w[m] || !row[m]) continue;
        const v = row[m] * w[m];
        byDrug[di] += v; byPayer[pi] += v; byMonth[m] += v;
        byLine[drug.line] += v; grand += v;
        monthLine[m][drug.line] = (monthLine[m][drug.line] || 0) + v;
      }
    }
  }
  const R = (a) => a.map(x => Math.round(x));
  D.lines.forEach(l => byLine[l] = Math.round(byLine[l]));
  return { byDrug: R(byDrug), byPayer: R(byPayer), byMonth: R(byMonth), byLine, monthLine, grand: Math.round(grand), filter };
}

/* drug × payer matrix for currently selected months/lens (respecting payer & line filters) */
function matrix(filter) {
  const drugsIdx = D.drugs.map((d, i) => i).filter(i => filter.lines.has(D.drugs[i].line));
  const payersIdx = D.payers.map((p, i) => i).filter(i => filter.payers.has(i) &&
    !(filter.pbLens === "xpb" && p.pb));
  const w = filter.weights || D.months.map((_, m) => (m >= filter.mFrom && m <= filter.mTo) ? 1 : 0);
  const cells = drugsIdx.map(di => payersIdx.map(pi => {
    if (!cellOK(di, pi, filter)) return 0;
    let s = 0; const row = D.cube[di][pi];
    for (let m = filter.mFrom; m <= filter.mTo; m++) s += row[m] * (w[m] || 0);
    return Math.round(s);
  }));
  return { drugsIdx, payersIdx, cells };
}

/* ---------- date utilities ---------- */
const ONE_DAY = 864e5;
const toDate = (iso) => new Date(iso + "T00:00:00");
const toISO = (d) => d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
const addDays = (iso, n) => toISO(new Date(toDate(iso).getTime() + n * ONE_DAY));
const fmtDate = (iso) => { const d = toDate(iso); return ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][d.getMonth()] + " " + d.getDate(); };
const fmtDateY = (iso) => fmtDate(iso) + ", " + toDate(iso).getFullYear();

const AS_OF = D.data_window.last;
function resolvePreset(key) {
  const a = AS_OF, ad = toDate(a);
  const monthFirst = (d) => toISO(new Date(d.getFullYear(), d.getMonth(), 1));
  const monthLast = (y, m) => toISO(new Date(y, m + 1, 0));
  switch (key) {
    case "today": return { from: a, to: a };
    case "yesterday": return { from: addDays(a, -1), to: addDays(a, -1) };
    case "last7": return { from: addDays(a, -6), to: a };
    case "last30": return { from: addDays(a, -29), to: a };
    case "thismonth": return { from: monthFirst(ad), to: a };
    case "lastmonth": { const pm = new Date(ad.getFullYear(), ad.getMonth() - 1, 1); return { from: monthFirst(pm), to: monthLast(pm.getFullYear(), pm.getMonth()) }; }
    case "qtd": { const q = Math.floor(ad.getMonth() / 3) * 3; return { from: toISO(new Date(ad.getFullYear(), q, 1)), to: a }; }
    case "ytd": return { from: toISO(new Date(ad.getFullYear(), 0, 1)), to: a };
    case "lastyear": return { from: (ad.getFullYear() - 1) + "-01-01", to: (ad.getFullYear() - 1) + "-12-31" };
    default: return { from: D.data_window.first, to: D.data_window.last };
  }
}
const PRESETS = [
  ["today", "Today"], ["yesterday", "Yesterday"], ["last7", "Last 7 days"], ["last30", "Last 30 days"],
  ["thismonth", "This month"], ["lastmonth", "Last month"], ["qtd", "This quarter"], ["ytd", "Year to date"],
  ["lastyear", "Last year"], ["all", "All time"],
];

/* per-month coverage weights from a date range, using byDay shares */
function monthWeights(from, to) {
  const w = D.months.map(() => 0);
  const tot = D.months.map(() => 0);
  const sel = D.months.map(() => 0);
  for (const { d, c } of D.byDay) {
    const mi = D.months.indexOf(d.slice(0, 7));
    if (mi < 0) continue;
    tot[mi] += c;
    if (d >= from && d <= to) sel[mi] += c;
  }
  for (let i = 0; i < w.length; i++) w[i] = tot[i] ? sel[i] / tot[i] : 0;
  return w;
}
/* daily series within range */
function byDayRange(from, to) {
  return D.byDay.filter(x => x.d >= from && x.d <= to && x.c >= 0);
}
/* business-day count of fills in range (exact) */
function fillsInRange(from, to) {
  let s = 0; for (const x of D.byDay) if (x.d >= from && x.d <= to) s += x.c; return s;
}
/* ops totals for a date range, from byDay's parallel fields (v=verified,
   t=tracked, pa, ship/verify lag sums). Date-only — downstream-of-intake stats. */
function opsInRange(from, to) {
  let c = 0, v = 0, t = 0, pa = 0, slt = 0, sln = 0, vlt = 0, vln = 0;
  for (const x of D.byDay) if (x.d >= from && x.d <= to) {
    c += x.c; v += x.v || 0; t += x.t || 0; pa += x.pa || 0;
    slt += x.slt || 0; sln += x.sln || 0; vlt += x.vlt || 0; vln += x.vln || 0;
  }
  return { fills: c, verified: v, tracked: t, pa,
           ship_lag: sln ? slt / sln : null, verify_lag: vln ? vlt / vln : null,
           shipped_pct: v ? t / v * 100 : null };
}

/* build a complete filter from raw range + selections */
function buildFilter(raw) {
  const w = monthWeights(raw.from, raw.to);
  let mFrom = w.findIndex(x => x > 0); let mTo = w.length - 1; while (mTo > 0 && !w[mTo]) mTo--;
  if (mFrom < 0) { mFrom = 0; mTo = 0; }
  return {
    from: raw.from, to: raw.to, preset: raw.preset, weights: w, mFrom, mTo,
    payers: new Set(raw.payers), lines: new Set(raw.lines), pbLens: raw.pbLens,
    products: new Set(raw.products && raw.products.length ? raw.products : D.drugs.map((_, i) => i)),
  };
}

window.RX = {
  D, fmt, fmtSign, pct, monthLabel, monthLabelShort, dayLabel, Icon, Ico, ICONS,
  slVar, aggregate, matrix, cellOK, resolvePreset, PRESETS, monthWeights, byDayRange, fillsInRange, buildFilter,
  fmtDate, fmtDateY, addDays, AS_OF, fmtMonthLong, opsInRange,
};
Object.assign(window, { useState, useEffect, useMemo, useRef, useCallback, fmt, fmtSign, pct, monthLabel, monthLabelShort, dayLabel, Icon, Ico, ICONS, slVar });
