// ─────────────────────────────────────────────────────────────
// First Episodes by Month — DVJS + SVG (namespace-safe)
// Filters: Agency, Year, Start Date, End Date
// Rule: first appointment must be ≥ MIN_FIRST_HOURS
// Chart: stacked bars by calendar year + legend (colour/hatch per year)
// ─────────────────────────────────────────────────────────────
 
const MIN_FIRST_HOURS = 0.5;
 
// Front-matter driven filters (use any of these keys; case-insensitive fallbacks)
const getFM = (k) => dv.current()?.[k];
const FILTER_AGENCY = (getFM("Agency Filter") ?? getFM("agency filter") ?? "").toString().trim();
const FILTER_YEAR   = (getFM("Year Filter")   ?? getFM("year filter")   ?? "").toString().trim();
 
const rawStart = getFM("Start Date") ?? getFM("start date") ?? "";
const rawEnd   = getFM("End Date")   ?? getFM("end date")   ?? "";
 
const monthOrder = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
const SVGNS = "http://www.w3.org/2000/svg";
 
// Helpers
const toDate = (v) => {
  // Accepts scalar or array; returns { date, key } or null.
  if (Array.isArray(v)) v = v[0];
  if (!v) return null;
 
  // Preserve date-only YAML/Dataview values as local calendar dates.
  // Avoid Date -> toISOString() for display, because BST can shift midnight
  // back to the previous UTC day.
  if (typeof v?.toFormat === "function") {
    const key = v.toFormat("yyyy-MM-dd");
    return { date: new Date(`${key}T00:00:00`), key };
  }
 
  if (typeof v?.toISODate === "function") {
    const key = v.toISODate();
    return { date: new Date(`${key}T00:00:00`), key };
  }
 
  const match = String(v).match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (match) {
    const key = `${match[1]}-${match[2]}-${match[3]}`;
    return { date: new Date(`${key}T00:00:00`), key };
  }
 
  const d = new Date(v);
  if (Number.isNaN(d.getTime())) return null;
  const yyyy = d.getFullYear();
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return { date: d, key: `${yyyy}-${mm}-${dd}` };
};
const toNumber = (v) => {
  if (Array.isArray(v)) v = v[0];
  if (v == null || v === "") return null;
  const n = parseFloat(String(v).trim());
  return Number.isNaN(n) ? null : n;
};
const svgEl = (name, attrs = {}) => {
  const el = document.createElementNS(SVGNS, name);
  for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));
  return el;
};
 
// Parse date-window (inclusive). If only one bound provided, use that side only.
let START_INFO = toDate(rawStart);
let END_INFO   = toDate(rawEnd);
let START_DATE = START_INFO?.date ?? null;
let END_DATE   = END_INFO?.date ?? null;
 
// If start > end, swap (helps avoid accidental inversions)
if (START_DATE && END_DATE && START_DATE > END_DATE) {
  const tmpDate = START_DATE;
  START_DATE = END_DATE;
  END_DATE = tmpDate;
 
  const tmpInfo = START_INFO;
  START_INFO = END_INFO;
  END_INFO = tmpInfo;
}
 
// 1) Load + normalise records from tagged pages
const src = dv.pages("#clienthours").where(p => p.clientref && p.date);
const records = Array.from(src).map(p => {
  const dateInfo = toDate(p.date);
  return {
    clientref: p.clientref,
    agency: (p.agency ?? "").toString(),
    date: dateInfo?.date ?? null,
    dateKey: dateInfo?.key ?? "",
    hours: toNumber(p.hours) ?? 0
  };
}).filter(p => p.date !== null);
 
// 2) Group by client and pick earliest record with hours ≥ MIN_FIRST_HOURS
records.sort((a,b) => a.date - b.date);
const byClient = new Map();
for (const r of records) {
  if (!byClient.has(r.clientref)) byClient.set(r.clientref, []);
  byClient.get(r.clientref).push(r);
}
const firstEpisodes = [];
for (const [clientref, rows] of byClient.entries()) {
  rows.sort((a,b) => a.date - b.date);
  const firstQual = rows.find(rr => (rr.hours ?? 0) >= MIN_FIRST_HOURS);
  if (!firstQual) continue;
  firstEpisodes.push({ clientref, agency: firstQual.agency, date: firstQual.date });
}
 
// 3) Apply filters (agency/year + date window inclusive)
const filtered = firstEpisodes.filter(ep => {
  const agencyOk = !FILTER_AGENCY || ep.agency === FILTER_AGENCY;
  const yearOk   = !FILTER_YEAR   || ep.date.getFullYear().toString() === FILTER_YEAR;
 
  const startOk  = !START_DATE || ep.date >= START_DATE;  // inclusive
  const endOk    = !END_DATE   || ep.date <= END_DATE;    // inclusive
 
  return agencyOk && yearOk && startOk && endOk;
});
 
// 4) Count by month AND year
const yearSet = new Set();
for (const ep of filtered) yearSet.add(ep.date.getFullYear());
const years = Array.from(yearSet).sort((a,b) => a - b); // ascending
 
// countsByMonthYear[month][year] = count
const countsByMonthYear = Object.fromEntries(monthOrder.map(m => [m, Object.create(null)]));
for (const ep of filtered) {
  const m = ep.date.toLocaleString("en-GB", { month: "short" });
  const y = ep.date.getFullYear();
  if (!(m in countsByMonthYear)) continue;
  countsByMonthYear[m][y] = (countsByMonthYear[m][y] ?? 0) + 1;
}
 
// chartData: one entry per month with a year->count map and total
const chartData = monthOrder.map(m => {
  const byY = countsByMonthYear[m];
  const total = years.reduce((acc, y) => acc + (byY[y] ?? 0), 0);
  return { month: m, byYear: byY, total };
});
 
// 5) Summary + table (keep your original table: totals per month)
const bits = [];
if (FILTER_AGENCY) bits.push(`Agency: **${FILTER_AGENCY}**`);
if (FILTER_YEAR)   bits.push(`Year: **${FILTER_YEAR}**`);
bits.push(`Min First Hours: **${MIN_FIRST_HOURS}**`);
if (START_DATE || END_DATE) {
  const fmt = info => info?.key ?? "—";
  bits.push(`Window: **${fmt(START_INFO)} → ${fmt(END_INFO)}**`);
}
dv.paragraph(`First episodes by month ${bits.length ? "(" + bits.join(" · ") + ")" : ""}:`);
dv.table(["Month", "First Episodes"], chartData.map(d => [d.month, d.total]));
 
// 6) SVG bar chart (stacked by year + legend BELOW chart)
// NOTE: this block derives its own year list from chartData to avoid "years is not defined".
 
const container = this.container.createEl("div", { cls: "first-episodes-chart" });
container.style.width = "100%";
container.style.maxWidth = "900px";
 
// Derive years locally from chartData (safe even if you didn't paste earlier code)
const yearsLocal = Array.from(
  new Set(
    chartData.flatMap(d => Object.keys(d.byYear ?? {}).map(k => Number(k))).filter(n => Number.isFinite(n))
  )
).sort((a,b) => a - b);
 
const width = 900;
const legendRowH = 16;
const legendTitleH = 16;
const legendPadding = 10;
const legendH = yearsLocal.length ? (legendTitleH + yearsLocal.length * legendRowH + legendPadding) : 0;
 
// Give the chart more vertical room: base height + legend band
const baseHeight = 320;
const height = baseHeight + legendH;
 
// Margins for the plotting area (legend is outside this)
const margin = { top: 28, right: 20, bottom: 44, left: 44 };
const innerW = width - margin.left - margin.right;
const innerH = baseHeight - margin.top - margin.bottom;
 
const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "xMidYMid meet" });
svg.style.width = "100%";
svg.style.height = "auto";
container.appendChild(svg);
 
// ----- Patterns / styles per year -----
const defs = svgEl("defs");
svg.appendChild(defs);
 
const palette = [
  "#4C78A8", "#F58518", "#54A24B", "#E45756", "#72B7B2",
  "#B279A2", "#FF9DA6", "#9D755D", "#BAB0AC"
];
 
const useColour = true;         // set false for hatch-only
const hatchOnlyStroke = "currentColor";
 
function addHatchPattern(id, stroke, angleDeg, spacing, strokeW) {
  const p = svgEl("pattern", {
    id,
    patternUnits: "userSpaceOnUse",
    width: spacing,
    height: spacing,
    patternTransform: `rotate(${angleDeg})`
  });
  p.appendChild(svgEl("line", {
    x1: 0, y1: 0,
    x2: 0, y2: spacing,
    stroke,
    "stroke-width": strokeW,
    "stroke-linecap": "square",
    "stroke-opacity": "0.9"
  }));
  defs.appendChild(p);
}
 
const yearStyle = new Map();
yearsLocal.forEach((yr, idx) => {
  const colour = palette[idx % palette.length];
  const angle = (idx % 4) * 45;
  const spacing = 6 + (idx % 3) * 2;
  const strokeW = 1 + (idx % 2) * 0.5;
  const patId = `fe-hatch-${yr}`;
 
  addHatchPattern(patId, useColour ? colour : hatchOnlyStroke, angle, spacing, strokeW);
 
  yearStyle.set(yr, {
    fill: useColour ? colour : "none",
    fillOpacity: useColour ? 0.35 : 0,
    patternUrl: `url(#${patId})`
  });
});
 
// Scales
const maxY = Math.max(1, ...chartData.map(d => d.total));
const xStep = innerW / chartData.length;
const barW = xStep * 0.7;
const x = i => margin.left + i * xStep + (xStep - barW) / 2;
const y = v => margin.top + innerH - (v / maxY) * innerH;
 
// Axes group
const axisG = svg.appendChild(svgEl("g"));
axisG.appendChild(svgEl("line", {
  x1: margin.left, y1: margin.top, x2: margin.left, y2: margin.top + innerH, stroke: "currentColor"
}));
axisG.appendChild(svgEl("line", {
  x1: margin.left, y1: margin.top + innerH, x2: margin.left + innerW, y2: margin.top + innerH, stroke: "currentColor"
}));
 
// Y ticks
const yTicks = Math.min(6, maxY);
for (let t = 0; t <= yTicks; t++) {
  const val = Math.round((t / yTicks) * maxY);
  const yy = y(val);
  axisG.appendChild(svgEl("line", {
    x1: margin.left, y1: yy, x2: margin.left + innerW, y2: yy,
    stroke: "currentColor", "stroke-opacity": "0.15"
  }));
  const lbl = svgEl("text", { x: margin.left - 8, y: yy + 4, "text-anchor": "end", fill: "currentColor" });
  lbl.style.fontSize = "12px";
  lbl.textContent = String(val);
  axisG.appendChild(lbl);
}
 
// Bars + X labels (STACKED by year)
const barsG = svg.appendChild(svgEl("g"));
 
chartData.forEach((d, i) => {
  const baseX = x(i);
  let cumulative = 0;
 
  yearsLocal.forEach(yr => {
    const c = (d.byYear?.[yr] ?? d.byYear?.[String(yr)] ?? 0);
    if (!c) return;
 
    const y0 = y(cumulative);
    const y1 = y(cumulative + c);
    const segH = Math.max(1, y0 - y1);
 
    const st = yearStyle.get(yr);
 
    // optional tinted base
    if (st.fill !== "none" && st.fillOpacity > 0) {
      barsG.appendChild(svgEl("rect", {
        x: baseX, y: y1, width: barW, height: segH,
        fill: st.fill, "fill-opacity": String(st.fillOpacity), stroke: "none"
      }));
    }
 
    // hatch overlay (always)
    barsG.appendChild(svgEl("rect", {
      x: baseX, y: y1, width: barW, height: segH,
      fill: st.patternUrl, "fill-opacity": "1",
      stroke: "currentColor", "stroke-opacity": "0.15"
    }));
 
    cumulative += c;
  });
 
  if (d.total === 0) {
    barsG.appendChild(svgEl("rect", {
      x: baseX,
      y: margin.top + innerH - 1,
      width: barW,
      height: 1,
      fill: "currentColor",
      "fill-opacity": "0.15"
    }));
  }
 
  const xl = svgEl("text", {
    x: margin.left + i * xStep + xStep / 2,
    y: margin.top + innerH + 18,
    "text-anchor": "middle",
    fill: "currentColor"
  });
  xl.style.fontSize = "12px";
  xl.textContent = d.month;
  barsG.appendChild(xl);
});
 
// Title
const title = svgEl("text", { x: margin.left, y: 18, fill: "currentColor" });
title.style.fontSize = "14px";
title.style.fontWeight = "600";
title.textContent = "First Episodes by Month (stacked by year)";
svg.appendChild(title);
 
// Legend BELOW chart (in reserved band)
if (yearsLocal.length) {
  const legendG = svg.appendChild(svgEl("g"));
  const boxW = 14, boxH = 10;
 
  const legendX = margin.left;
  const legendTop = baseHeight + 14;
 
  const lt = svgEl("text", { x: legendX, y: legendTop, fill: "currentColor" });
  lt.style.fontSize = "12px";
  lt.style.fontWeight = "600";
  lt.textContent = "Year key";
  legendG.appendChild(lt);
 
  yearsLocal.forEach((yr, idx) => {
    const yRow = legendTop + legendTitleH + idx * legendRowH;
    const st = yearStyle.get(yr);
 
    if (st.fill !== "none" && st.fillOpacity > 0) {
      legendG.appendChild(svgEl("rect", {
        x: legendX,
        y: yRow - boxH + 2,
        width: boxW,
        height: boxH,
        fill: st.fill,
        "fill-opacity": String(st.fillOpacity),
        stroke: "none"
      }));
    }
 
    legendG.appendChild(svgEl("rect", {
      x: legendX,
      y: yRow - boxH + 2,
      width: boxW,
      height: boxH,
      fill: st.patternUrl,
      stroke: "currentColor",
      "stroke-opacity": "0.35"
    }));
 
    const t = svgEl("text", { x: legendX + boxW + 8, y: yRow + 2, fill: "currentColor" });
    t.style.fontSize = "12px";
    t.textContent = String(yr);
    legendG.appendChild(t);
  });
}