// ─────────────────────────────────────────────────────────────
// Client Hours by Rolling 12-Month Periods — grouped + stacked
// Filters: Agency, Start Date, End Date
// Rules:
//   - include only sessions with hours > 0.5
// Graph:
//   - Period 1 = last 12 months to today
//   - Period 2 = 12 months immediately before that
//   - for each month: two bars (comparison vs current), each stacked by agency
// Tables:
//   - monthly totals by period
//   - period totals by agency
// ─────────────────────────────────────────────────────────────
 
const MIN_HOURS = 0.5;
 
const getFM = (k) => dv.current()?.[k];
 
const FILTER_AGENCY = (getFM("Agency Filter") ?? getFM("agency filter") ?? "").toString().trim();
const rawStart = getFM("Start Date") ?? getFM("start date") ?? "";
const rawEnd   = getFM("End Date")   ?? getFM("end date")   ?? "";
 
const SVGNS = "http://www.w3.org/2000/svg";
 
// Helpers
const firstOf = (v) => Array.isArray(v) ? v[0] : v;
 
const toDate = (v) => {
  v = firstOf(v);
  if (!v) return null;
  const d = new Date(v);
  return Number.isNaN(d.getTime()) ? null : d;
};
 
const toNumber = (v) => {
  v = firstOf(v);
  if (v == null || v === "") return 0;
  const n = parseFloat(String(v).trim());
  return Number.isNaN(n) ? 0 : 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;
};
 
const fmtHours = (n) => Number(n || 0).toFixed(2);
 
const fmtISODate = (d) => {
  if (!d) return "—";
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
};
 
const fmtMonthYear = (d) =>
  d.toLocaleString("en-GB", { month: "short", year: "numeric" });
 
const startOfDay = (d) => {
  const x = new Date(d);
  x.setHours(0, 0, 0, 0);
  return x;
};
 
const endOfDay = (d) => {
  const x = new Date(d);
  x.setHours(23, 59, 59, 999);
  return x;
};
 
const startOfMonth = (d) => new Date(d.getFullYear(), d.getMonth(), 1);
const endOfMonth = (d) => new Date(d.getFullYear(), d.getMonth() + 1, 0, 23, 59, 59, 999);
const addMonths = (d, n) => new Date(d.getFullYear(), d.getMonth() + n, d.getDate());
const monthKey = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
 
// Optional outer filter window from frontmatter
let START_DATE = toDate(rawStart);
let END_DATE   = toDate(rawEnd);
 
if (START_DATE && END_DATE && START_DATE > END_DATE) {
  const tmp = START_DATE;
  START_DATE = END_DATE;
  END_DATE = tmp;
}
 
// ------------------------------------------------------------------
// Rolling 12-month periods
// ------------------------------------------------------------------
const today = new Date();
const todayStart = startOfDay(today);
const todayEnd = endOfDay(today);
 
const currentPeriodEnd = todayEnd;
const currentPeriodStart = startOfMonth(addMonths(today, -11));
 
const previousPeriodStart = startOfMonth(addMonths(currentPeriodStart, -12));
const previousPeriodEnd = endOfMonth(addMonths(currentPeriodStart, -1));
 
const currentMonths = Array.from({ length: 12 }, (_, i) => startOfMonth(addMonths(currentPeriodStart, i)));
const previousMonths = currentMonths.map(d => startOfMonth(addMonths(d, -12)));
 
// 1) Load + normalise records
const src = dv.pages("#clienthours").where(p => p.clientref && p.date);
 
const records = Array.from(src).map(p => ({
  clientref: firstOf(p.clientref),
  agency: (firstOf(p.agency) ?? "Unknown").toString().trim() || "Unknown",
  date: toDate(p.date),
  hours: toNumber(p.hours)
})).filter(r => r.date !== null);
 
// 2) Apply outer filters and minimum-hours rule
const filtered = records.filter(r => {
  const agencyOk = !FILTER_AGENCY || r.agency === FILTER_AGENCY;
  const startOk  = !START_DATE || r.date >= START_DATE;
  const endOk    = !END_DATE   || r.date <= END_DATE;
  const hoursOk  = r.hours > MIN_HOURS;
  return agencyOk && startOk && endOk && hoursOk;
});
 
// 3) Gather agencies
const agencies = Array.from(new Set(filtered.map(r => r.agency))).sort((a, b) => a.localeCompare(b));
 
// Ensure we still work if no data
if (agencies.length === 0) agencies.push("Unknown");
 
// 4) Aggregate by month + agency
// monthAgencyTotals["YYYY-MM"]["Agency"] = hours
const monthAgencyTotals = new Map();
 
for (const r of filtered) {
  const mk = monthKey(r.date);
  if (!monthAgencyTotals.has(mk)) monthAgencyTotals.set(mk, new Map());
  const byAgency = monthAgencyTotals.get(mk);
  byAgency.set(r.agency, (byAgency.get(r.agency) ?? 0) + r.hours);
}
 
// 5) Build comparison rows
const monthlyComparison = currentMonths.map((currMonthStart, i) => {
  const prevMonthStart = previousMonths[i];
  const currKey = monthKey(currMonthStart);
  const prevKey = monthKey(prevMonthStart);
 
  const currMap = monthAgencyTotals.get(currKey) ?? new Map();
  const prevMap = monthAgencyTotals.get(prevKey) ?? new Map();
 
  const currentByAgency = Object.fromEntries(
    agencies.map(a => [a, currMap.get(a) ?? 0])
  );
  const previousByAgency = Object.fromEntries(
    agencies.map(a => [a, prevMap.get(a) ?? 0])
  );
 
  const currentTotal = agencies.reduce((acc, a) => acc + (currentByAgency[a] ?? 0), 0);
  const previousTotal = agencies.reduce((acc, a) => acc + (previousByAgency[a] ?? 0), 0);
 
  return {
    monthLabel: currMonthStart.toLocaleString("en-GB", { month: "short" }),
    currentMonthLabel: fmtMonthYear(currMonthStart),
    previousMonthLabel: fmtMonthYear(prevMonthStart),
    currentByAgency,
    previousByAgency,
    currentTotal,
    previousTotal
  };
});
 
// 6) Period totals by agency
const currentTotalsByAgency = Object.fromEntries(agencies.map(a => [a, 0]));
const previousTotalsByAgency = Object.fromEntries(agencies.map(a => [a, 0]));
 
monthlyComparison.forEach(row => {
  agencies.forEach(a => {
    currentTotalsByAgency[a] += row.currentByAgency[a] ?? 0;
    previousTotalsByAgency[a] += row.previousByAgency[a] ?? 0;
  });
});
 
const currentTotal = agencies.reduce((acc, a) => acc + currentTotalsByAgency[a], 0);
const previousTotal = agencies.reduce((acc, a) => acc + previousTotalsByAgency[a], 0);
 
// 7) Summary text
const bits = [];
if (FILTER_AGENCY) bits.push(`Agency: **${FILTER_AGENCY}**`);
if (START_DATE || END_DATE) bits.push(`Outer Window: **${fmtISODate(START_DATE)} → ${fmtISODate(END_DATE)}**`);
bits.push(`Min Session Hours: **>${MIN_HOURS}**`);
bits.push(`Current Period: **${fmtISODate(currentPeriodStart)} → ${fmtISODate(currentPeriodEnd)}**`);
bits.push(`Comparison Period: **${fmtISODate(previousPeriodStart)} → ${fmtISODate(previousPeriodEnd)}**`);
 
dv.paragraph(`Client hours by rolling 12-month periods (${bits.join(" · ")}):`);
 
// 8) Monthly totals table
dv.header(3, "Monthly client hours comparison");
dv.table(
  ["Month", "Comparison Period", "Last 12 Months"],
  monthlyComparison.map(r => [
    `${r.monthLabel} (${r.previousMonthLabel} / ${r.currentMonthLabel})`,
    fmtHours(r.previousTotal),
    fmtHours(r.currentTotal)
  ])
);
 
// 9) Period totals by agency table
dv.header(3, "Period totals by agency");
dv.table(
  ["Agency", "Comparison Period", "Last 12 Months"],
  [
    ...agencies.map(a => [a, fmtHours(previousTotalsByAgency[a]), fmtHours(currentTotalsByAgency[a])]),
    ["Total", fmtHours(previousTotal), fmtHours(currentTotal)]
  ]
);
 
// 10) SVG chart (render only in live Obsidian preview context)
if (typeof document !== "undefined" && this?.container?.createEl) {
const container = this.container.createEl("div", { cls: "client-hours-chart" });
container.style.width = "100%";
container.style.maxWidth = "1100px";
 
const width = 1100;
const baseHeight = 360;
const legendRowH = 18;
const legendTitleH = 16;
const legendPadding = 10;
const legendH = (2 + agencies.length) * legendRowH + legendTitleH + legendPadding;
const height = baseHeight + legendH;
 
const margin = { top: 28, right: 20, bottom: 56, left: 56 };
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);
 
// Definitions / patterns
const defs = svgEl("defs");
svg.appendChild(defs);
 
const palette = [
  "#4C78A8", "#F58518", "#54A24B", "#E45756", "#72B7B2",
  "#B279A2", "#FF9DA6", "#9D755D", "#BAB0AC"
];
 
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);
}
 
// One style per agency
const agencyStyle = new Map();
 
agencies.forEach((agency, idx) => {
  const colour = palette[idx % palette.length];
  const angle = (idx % 4) * 45;
  const spacing = 6 + (idx % 3) * 2;
  const strokeW = 1 + ((idx % 2) * 0.4);
  const patId = `agency-hatch-${idx}`;
 
  addHatchPattern(patId, colour, angle, spacing, strokeW);
 
  agencyStyle.set(agency, {
    fill: colour,
    fillOpacity: 0.35,
    patternUrl: `url(#${patId})`
  });
});
 
// Period outline style
const periodOutline = new Map([
  ["previous", { strokeDasharray: "4 2", strokeOpacity: 0.45 }],
  ["current",  { strokeDasharray: "",    strokeOpacity: 0.25 }]
]);
 
// Scales
const maxY = Math.max(
  1,
  ...monthlyComparison.flatMap(d => [d.previousTotal, d.currentTotal])
);
 
const xStep = innerW / monthlyComparison.length;
const groupW = xStep * 0.76;
const barGap = 8;
const barW = Math.max(8, (groupW - barGap) / 2);
 
const groupX = i => margin.left + i * xStep + (xStep - groupW) / 2;
const y = v => margin.top + innerH - (v / maxY) * innerH;
 
// Axes
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"
}));
 
const yTicks = Math.min(6, Math.max(1, Math.ceil(maxY)));
for (let t = 0; t <= yTicks; t++) {
  const val = (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 = fmtHours(val);
  axisG.appendChild(lbl);
}
 
// Bars
const barsG = svg.appendChild(svgEl("g"));
 
monthlyComparison.forEach((d, i) => {
  const gx = groupX(i);
 
  const seriesValues = [
    { key: "previous", byAgency: d.previousByAgency, total: d.previousTotal, x: gx },
    { key: "current",  byAgency: d.currentByAgency,  total: d.currentTotal,  x: gx + barW + barGap }
  ];
 
  seriesValues.forEach(({ key, byAgency, total, x }) => {
    let cumulative = 0;
 
    agencies.forEach(agency => {
      const value = byAgency[agency] ?? 0;
      if (!value) return;
 
      const y0 = y(cumulative);
      const y1 = y(cumulative + value);
      const h = Math.max(1, y0 - y1);
 
      const st = agencyStyle.get(agency);
 
      barsG.appendChild(svgEl("rect", {
        x, y: y1, width: barW, height: h,
        fill: st.fill,
        "fill-opacity": String(st.fillOpacity),
        stroke: "none"
      }));
 
      barsG.appendChild(svgEl("rect", {
        x, y: y1, width: barW, height: h,
        fill: st.patternUrl,
        "fill-opacity": "1",
        stroke: "currentColor",
        "stroke-opacity": "0.10"
      }));
 
      cumulative += value;
    });
 
    // Period outline around the whole bar
    if (total > 0) {
      const yy = y(total);
      const h = Math.max(1, margin.top + innerH - yy);
      const po = periodOutline.get(key);
 
      barsG.appendChild(svgEl("rect", {
        x, y: yy, width: barW, height: h,
        fill: "none",
        stroke: "currentColor",
        "stroke-opacity": String(po.strokeOpacity),
        "stroke-dasharray": po.strokeDasharray
      }));
    } else {
      barsG.appendChild(svgEl("rect", {
        x,
        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.monthLabel;
  barsG.appendChild(xl);
 
  const sub = svgEl("text", {
    x: margin.left + i * xStep + xStep / 2,
    y: margin.top + innerH + 32,
    "text-anchor": "middle",
    fill: "currentColor"
  });
  sub.style.fontSize = "10px";
  sub.style.opacity = "0.7";
  sub.textContent = d.currentMonthLabel.split(" ")[1];
  barsG.appendChild(sub);
});
 
// Title
const title = svgEl("text", {
  x: margin.left,
  y: 18,
  fill: "currentColor"
});
title.style.fontSize = "14px";
title.style.fontWeight = "600";
title.textContent = "Client Hours by Month (Last 12 Months vs Previous 12 Months; stacked by agency)";
svg.appendChild(title);
 
// Legend below chart
const legendG = svg.appendChild(svgEl("g"));
const legendX = margin.left;
const legendTop = baseHeight + 14;
const boxW = 14;
const boxH = 10;
 
// Legend title
const legendTitle = svgEl("text", {
  x: legendX,
  y: legendTop,
  fill: "currentColor"
});
legendTitle.style.fontSize = "12px";
legendTitle.style.fontWeight = "600";
legendTitle.textContent = "Key";
legendG.appendChild(legendTitle);
 
// Period styles
[
  ["Comparison Period", "previous"],
  ["Last 12 Months", "current"]
].forEach(([label, key], idx) => {
  const yRow = legendTop + legendTitleH + idx * legendRowH;
  const po = periodOutline.get(key);
 
  legendG.appendChild(svgEl("rect", {
    x: legendX,
    y: yRow - boxH + 2,
    width: boxW,
    height: boxH,
    fill: "none",
    stroke: "currentColor",
    "stroke-opacity": String(po.strokeOpacity),
    "stroke-dasharray": po.strokeDasharray
  }));
 
  const t = svgEl("text", {
    x: legendX + boxW + 8,
    y: yRow + 2,
    fill: "currentColor"
  });
  t.style.fontSize = "12px";
  t.textContent = label;
  legendG.appendChild(t);
});
 
// Agency styles
agencies.forEach((agency, idx) => {
  const st = agencyStyle.get(agency);
  const yRow = legendTop + legendTitleH + (2 + idx) * legendRowH;
 
  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 = agency;
  legendG.appendChild(t);
});
} else {
  dv.paragraph("_Chart omitted in markdown export context._");
}