"use client";

import { useEffect, useRef } from "react";
import {
  ArcElement,
  BarController,
  BarElement,
  CategoryScale,
  Chart,
  LinearScale,
  type ChartData,
  type ChartOptions,
  type Plugin,
  type ScriptableContext,
} from "chart.js";
import { Bar, Doughnut } from "react-chartjs-2";
import { Legend, Tooltip } from "chart.js";

/* ------------------------------------------------------------------ */
/* EMS chart kit — every chart in the app draws from here so donuts,   */
/* bars and tooltips share one look: Montserrat, brand teal, soft      */
/* shadows, rounded bars, center-text donuts, k/L/Cr currency axes.    */
/* Powered by Chart.js (canvas) — crisp on any DPI, print-friendly.    */
/* ------------------------------------------------------------------ */

Chart.register(ArcElement, BarController, BarElement, CategoryScale, LinearScale, Tooltip, Legend);

/** the app's own design tokens (globals.css / Viho theme) */
export const CHART_TOKENS = {
  teal: "#24695c",
  tealSoft: "#5ec8b4",
  red: "#e84a5b",
  amber: "#f79a1b",
  unmarked: "#e6edef",
  blue: "#2e6b8f",
  ink: "#1c2b27",
  muted: "#717171",
  border: "#e6edef",
  font: "Montserrat, sans-serif",
};

Chart.defaults.font.family = CHART_TOKENS.font;
Chart.defaults.font.size = 11;
Chart.defaults.color = CHART_TOKENS.muted;

/* ------------------------------------------------------------------ */
/* canvas plugins (tiny, inline — no extra npm packages)               */
/* ------------------------------------------------------------------ */

/** soft drop shadow behind the first dataset (donut arcs / bars) */
const softShadow: Plugin = {
  id: "emsShadow",
  beforeDatasetDraw(chart, args) {
    if (args.meta.index !== 0) return;
    const { ctx } = chart;
    ctx.save();
    ctx.shadowColor = "rgba(28, 43, 39, 0.14)";
    ctx.shadowBlur = 8;
    ctx.shadowOffsetY = 3;
  },
  afterDatasetDraw(chart, args) {
    if (args.meta.index !== 0) return;
    chart.ctx.restore();
  },
};

interface CenterTextConfig {
  label: string;
  value: string;
}

/** donut center: the passed label/value at rest, the hovered segment
 *  (name · count · share) while hovering — like the app's old donuts */
const centerText: Plugin = {
  id: "emsCenterText",
  afterDraw(chart) {
    const plugins = chart.config.options?.plugins as
      | { emsCenterText?: CenterTextConfig }
      | undefined;
    const center = plugins?.emsCenterText;
    if (!center) return;
    const meta = chart.getDatasetMeta(0);
    const first = meta.data[0] as unknown as { x: number; y: number; innerRadius: number } | undefined;
    if (!first || first.innerRadius < 22) return;
    const { x, y, innerRadius } = first;

    const data = (chart.data.datasets[0].data ?? []) as number[];
    const sum = data.reduce((a, b) => a + (Number(b) || 0), 0);
    const active = chart.getActiveElements()[0];

    let top = center.label;
    let main = center.value;
    let sub = "";
    let mainColor = CHART_TOKENS.ink;
    if (active && sum > 0) {
      const value = Number(data[active.index] ?? 0);
      const labels = (chart.data.labels ?? []) as string[];
      top = String(labels[active.index] ?? "");
      main = String(value);
      sub = `${Math.round((value / sum) * 100)}%`;
      const colors = chart.data.datasets[0].backgroundColor;
      mainColor = Array.isArray(colors) ? String(colors[active.index] ?? "") : String(colors ?? "");
    }

    const { ctx } = chart;
    ctx.save();
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = `800 ${Math.max(8.5, innerRadius * 0.155)}px ${CHART_TOKENS.font}`;
    ctx.fillStyle = "#9aa7a3";
    ctx.fillText(top.toUpperCase(), x, y - innerRadius * 0.42);
    ctx.font = `800 ${Math.max(15, innerRadius * 0.3)}px ${CHART_TOKENS.font}`;
    ctx.fillStyle = mainColor || CHART_TOKENS.ink;
    ctx.fillText(main, x, y + (sub ? innerRadius * 0.02 : innerRadius * 0.14));
    if (sub) {
      ctx.font = `700 ${Math.max(10, innerRadius * 0.18)}px ${CHART_TOKENS.font}`;
      ctx.fillStyle = CHART_TOKENS.muted;
      ctx.fillText(sub, x, y + innerRadius * 0.34);
    }
    ctx.restore();
  },
};

/** draw a formatted value above each bar (zeros/blanks skipped) */
const barValueLabels = (format: (n: number) => string): Plugin => ({
  id: "emsBarLabels",
  afterDatasetsDraw(chart) {
    const { ctx } = chart;
    ctx.save();
    ctx.textAlign = "center";
    ctx.textBaseline = "bottom";
    ctx.font = `700 10.5px ${CHART_TOKENS.font}`;
    ctx.fillStyle = CHART_TOKENS.muted;
    for (let d = 0; d < chart.data.datasets.length; d++) {
      const meta = chart.getDatasetMeta(d);
      if (meta.type !== "bar" || meta.hidden) continue;
      const values = (chart.data.datasets[d].data ?? []) as number[];
      meta.data.forEach((bar, i) => {
        const value = Number(values[i] ?? 0);
        if (!value) return;
        const { x, y } = bar as unknown as { x: number; y: number };
        ctx.fillText(format(value), x, y - 4);
      });
    }
    ctx.restore();
  },
});

/** dashed target line (e.g. the 75% attendance goal) with a small label */
const targetLine = (value: number, label: string): Plugin => ({
  id: "emsTargetLine",
  afterDatasetsDraw(chart) {
    const y = chart.scales.y?.getPixelForValue(value);
    if (y == null || Number.isNaN(y)) return;
    const { ctx, chartArea } = chart;
    ctx.save();
    ctx.setLineDash([5, 5]);
    ctx.strokeStyle = "#b8c2bf";
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(chartArea.left, y);
    ctx.lineTo(chartArea.right, y);
    ctx.stroke();
    ctx.setLineDash([]);
    ctx.font = `700 10px ${CHART_TOKENS.font}`;
    ctx.fillStyle = CHART_TOKENS.muted;
    ctx.textAlign = "right";
    ctx.textBaseline = "bottom";
    ctx.fillText(label, chartArea.right, y - 3);
    ctx.restore();
  },
});

/* ------------------------------------------------------------------ */
/* shared option fragments                                             */
/* ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ */
/* external DOM tooltips                                               */
/*                                                                     */
/* Chart.js's built-in tooltip draws INSIDE the canvas — on small      */
/* charts (the 148px attendance donuts) it gets squeezed, clipped and  */
/* its box/text alignment drifts. Rendering the tooltip as an HTML     */
/* card instead keeps full control: crisp 12px rows aligned as         */
/* "dot · label …… value", never clipped, free to overflow the canvas. */
/* ------------------------------------------------------------------ */

export interface TipRow {
  /** colored square dot; omit for plain rows */
  color?: string;
  label: string;
  value: string;
}

export interface TipContent {
  title?: string;
  rows: TipRow[];
}

/** the parts of Chart.js's tooltip model the builders need */
interface TipModel {
  opacity: number;
  caretX: number;
  caretY: number;
  title: string[];
  dataPoints: Array<{ dataIndex: number; datasetIndex: number }>;
}

function ensureTooltipEl(wrap: HTMLElement): HTMLDivElement {
  let el = wrap.querySelector<HTMLDivElement>(".ems-chart-tooltip");
  if (!el) {
    el = document.createElement("div");
    el.className = "ems-chart-tooltip";
    wrap.appendChild(el);
  }
  return el;
}

/** Position the card beside the caret (flipping near edges) and keep it
 *  vertically clamped inside the wrapper. caret coords are CSS pixels
 *  relative to the canvas, matching the wrapper's offset coordinates. */
function placeTooltip(el: HTMLDivElement, canvas: HTMLCanvasElement, model: TipModel) {
  const wrap = el.parentElement as HTMLElement;
  const tipW = el.offsetWidth;
  const tipH = el.offsetHeight;
  const gap = 12;

  let left = canvas.offsetLeft + model.caretX + gap;
  if (left + tipW > wrap.clientWidth) {
    left = canvas.offsetLeft + model.caretX - tipW - gap;
  }
  if (left + tipW > wrap.clientWidth) {
    left = Math.max(0, wrap.clientWidth - tipW);
  }

  let top = canvas.offsetTop + model.caretY - tipH / 2;
  top = Math.min(Math.max(0, top), Math.max(0, wrap.clientHeight - tipH));

  el.style.left = `${Math.round(left)}px`;
  el.style.top = `${Math.round(top)}px`;
}

/** Factory: returns a Chart.js `tooltip.external` handler. `build` turns
 *  the hovered data points into a title + aligned rows. */
function externalTooltip(build: (model: TipModel) => TipContent | null) {
  return (context: { chart: Chart }) => {
    const chart = context.chart;
    const canvas = chart.canvas;
    const wrap = canvas?.parentElement as HTMLElement | null;
    if (!canvas || !wrap) return;

    const model =
      (chart.tooltip as unknown as TipModel | null) ?? {
        opacity: 0,
        caretX: 0,
        caretY: 0,
        title: [],
        dataPoints: [],
      };
    const el = ensureTooltipEl(wrap);

    const content = model.opacity > 0 ? build(model) : null;
    if (!content || content.rows.length === 0) {
      el.style.opacity = "0";
      return;
    }

    el.innerHTML = "";
    if (content.title) {
      const title = document.createElement("div");
      title.className = "ems-tip-title";
      title.textContent = content.title;
      el.appendChild(title);
    }
    for (const row of content.rows) {
      const rowEl = document.createElement("div");
      rowEl.className = "ems-tip-row";
      if (row.color) {
        const dot = document.createElement("span");
        dot.className = "ems-tip-dot";
        dot.style.background = row.color;
        rowEl.appendChild(dot);
      }
      const label = document.createElement("span");
      label.className = "ems-tip-label";
      label.textContent = row.label;
      rowEl.appendChild(label);
      const value = document.createElement("b");
      value.textContent = row.value;
      rowEl.appendChild(value);
      el.appendChild(rowEl);
    }

    el.style.opacity = "1";
    placeTooltip(el, canvas, model);
  };
}

/** redraw once the webfont arrives so canvas metrics match the app */
function useChartFontRefresh() {
  const ref = useRef<Chart | null>(null);
  useEffect(() => {
    let cancelled = false;
    document.fonts?.ready.then(() => {
      if (!cancelled && ref.current) ref.current.update("none");
    });
    return () => {
      cancelled = true;
    };
  }, []);
  return ref;
}

/* ------------------------------------------------------------------ */
/* AttendanceDonut — present/absent/leave/unmarked ring                */
/* ------------------------------------------------------------------ */

export interface DonutSegment {
  label: string;
  value: number;
  color: string;
}

function donutOptions(plugins: NonNullable<ChartOptions<"doughnut">["plugins"]>): ChartOptions<"doughnut"> {
  return {
    responsive: true,
    maintainAspectRatio: false,
    cutout: "72%",
    layout: { padding: 6 },
    animation: { duration: 500 },
    plugins,
  };
}

function donutData(segments: DonutSegment[], hoverOffset: number): ChartData<"doughnut"> {
  return {
    labels: segments.map((s) => s.label),
    datasets: [
      {
        data: segments.map((s) => s.value),
        backgroundColor: segments.map((s) => s.color),
        borderWidth: 3,
        borderColor: "#ffffff",
        hoverOffset,
        hoverBorderWidth: 3,
        spacing: 1,
      },
    ],
  };
}

export function AttendanceDonut({
  segments,
  total,
  height = 158,
}: {
  segments: DonutSegment[];
  total: number;
  height?: number;
}) {
  const chartRef = useChartFontRefresh();
  const options = donutOptions({
    legend: { display: false },
    tooltip: {
      enabled: false,
      external: externalTooltip((m) => {
        const dp = m.dataPoints[0];
        const seg = dp ? segments[dp.dataIndex] : undefined;
        if (!seg) return null;
        const sum = segments.reduce((a, s) => a + s.value, 0);
        const pct = sum > 0 ? Math.round((seg.value / sum) * 100) : 0;
        return {
          rows: [{ color: seg.color, label: seg.label, value: `${seg.value} · ${pct}%` }],
        };
      }),
    },
    // consumed by the centerText plugin
    emsCenterText: { label: "TOTAL", value: String(total) },
  } as NonNullable<ChartOptions<"doughnut">["plugins"]>);

  return (
    <div className="ems-chart-wrap" style={{ width: "100%", height }}>
      <Doughnut
        ref={chartRef as never}
        data={donutData(segments, 6)}
        options={options}
        plugins={[softShadow, centerText] as Plugin<"doughnut">[]}
      />
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* DistributionDonut — accounts income/expense split with a synced     */
/* legend (hover the chart or a row — both highlight together)         */
/* ------------------------------------------------------------------ */

export function DistributionDonut({
  slices,
  centerLabel,
  centerValue,
  formatValue,
  height = 190,
}: {
  slices: DonutSegment[];
  centerLabel: string;
  centerValue: string;
  formatValue: (n: number) => string;
  height?: number;
}) {
  const chartRef = useChartFontRefresh();
  const options = donutOptions({
    legend: { display: false },
    tooltip: {
      enabled: false,
      external: externalTooltip((m) => {
        const dp = m.dataPoints[0];
        const slice = dp ? slices[dp.dataIndex] : undefined;
        if (!slice) return null;
        const sum = slices.reduce((a, s) => a + s.value, 0);
        const pct = sum > 0 ? Math.round((slice.value / sum) * 100) : 0;
        return {
          title: centerLabel,
          rows: [
            { color: slice.color, label: slice.label, value: `${formatValue(slice.value)} · ${pct}%` },
          ],
        };
      }),
    },
    emsCenterText: { label: centerLabel, value: centerValue },
  } as NonNullable<ChartOptions<"doughnut">["plugins"]>);

  const highlight = (index: number | null) => {
    const chart = chartRef.current;
    if (!chart) return;
    if (index === null) {
      chart.setActiveElements([]);
    } else if (chart.getDatasetMeta(0).data[index]) {
      chart.setActiveElements([{ datasetIndex: 0, index }]);
    }
    chart.update("none");
  };

  const total = slices.reduce((a, s) => a + s.value, 0);

  return (
    <div className="d-flex flex-wrap align-items-center justify-content-center gap-4">
      <div style={{ position: "relative", width: 170, height, flex: "0 0 auto" }}>
        <Doughnut
          ref={chartRef as never}
          data={donutData(slices, 8)}
          options={options}
          plugins={[softShadow, centerText] as Plugin<"doughnut">[]}
        />
      </div>
      <div className="d-flex flex-column gap-1" style={{ minWidth: 190 }}>
        {slices.map((s, i) => (
          <div
            key={`${s.label}-${i}`}
            className="d-flex align-items-center justify-content-between gap-3 ems-fs-12"
            onMouseEnter={() => highlight(i)}
            onMouseLeave={() => highlight(null)}
            style={{ padding: "3px 8px", borderRadius: 8, transition: "background 0.15s ease" }}
          >
            <span className="d-inline-flex align-items-center gap-2 text-muted">
              <span
                className="rounded-circle d-inline-block flex-shrink-0"
                style={{ width: 9, height: 9, background: s.color }}
              />
              <span className="text-truncate" style={{ maxWidth: 130 }}>
                {s.label}
              </span>
            </span>
            <span className="f-w-700" style={{ color: CHART_TOKENS.ink }}>
              {formatValue(s.value)}
              {total > 0 && (
                <span className="text-muted f-w-500"> · {Math.round((s.value / total) * 100)}%</span>
              )}
            </span>
          </div>
        ))}
        {slices.length === 0 && <p className="mb-0 text-muted ems-fs-13">—</p>}
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* RateTrendChart — rounded bars colored per value (goal states)       */
/* ------------------------------------------------------------------ */

/** ok-color when at/above target, amber while below-but-present, gray for none */
export function rateBarColor(rate: number | null, target = 75, ok = CHART_TOKENS.teal): string {
  if (rate == null || rate <= 0) return CHART_TOKENS.unmarked;
  return rate >= target ? ok : CHART_TOKENS.amber;
}

export function RateTrendChart({
  labels,
  titles,
  values,
  colors,
  seriesName,
  target,
  targetLabel,
  suffix = "%",
  height = 200,
}: {
  labels: string[];
  /** richer tooltip titles (full dates) — falls back to labels */
  titles?: string[];
  values: (number | null)[];
  colors: string[];
  /** row label inside the tooltip (e.g. localized "Attendance Rate") */
  seriesName?: string;
  target?: number;
  targetLabel?: string;
  suffix?: string;
  height?: number;
}) {
  const chartRef = useChartFontRefresh();
  const data: ChartData<"bar"> = {
    labels,
    datasets: [
      {
        data: values as number[],
        backgroundColor: colors,
        hoverBackgroundColor: colors.map((c) =>
          c === CHART_TOKENS.unmarked ? "#d5dde0" : `${c}dd`
        ),
        borderRadius: { topLeft: 7, topRight: 7, bottomLeft: 0, bottomRight: 0 },
        borderSkipped: false,
        maxBarThickness: 34,
        barPercentage: 0.58,
      },
    ],
  };
  const options: ChartOptions<"bar"> = {
    responsive: true,
    maintainAspectRatio: false,
    animation: { duration: 500 },
    layout: { padding: { top: 18 } },
    plugins: {
      legend: { display: false },
      tooltip: {
        enabled: false,
        external: externalTooltip((m) => {
          const dp = m.dataPoints[0];
          if (!dp) return null;
          const rate = values[dp.dataIndex];
          return {
            title: titles?.[dp.dataIndex] || labels[dp.dataIndex] || "",
            rows: [
              {
                color: colors[dp.dataIndex] === CHART_TOKENS.unmarked ? "#c3cfd0" : colors[dp.dataIndex],
                label: seriesName ?? "",
                value: rate == null ? "—" : `${rate}${suffix}`,
              },
            ],
          };
        }),
      },
    },
    scales: {
      x: {
        grid: { display: false },
        border: { display: false },
        ticks: { font: { weight: 600, size: 11 }, color: CHART_TOKENS.muted },
      },
      y: { display: false, beginAtZero: true, max: Math.max(110, (target ?? 0) + 35) },
    },
  };

  const plugins: Plugin<"bar">[] = [barValueLabels((n) => `${n}${suffix}`)];
  if (target != null && targetLabel) plugins.push(targetLine(target, targetLabel));

  return (
    <div className="ems-chart-wrap" style={{ width: "100%", height }}>
      <Bar ref={chartRef as never} data={data} options={options} plugins={plugins} />
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* MoneyBarsChart — grouped currency bars (accounts, fees)             */
/* ------------------------------------------------------------------ */

/** short money axis: 1.2k / 45k / 3.5L (BDT lakh) / 1.2Cr */
function moneyAxisLabel(n: number): string {
  if (n >= 1e7) return `${(n / 1e7).toFixed(n % 1e7 === 0 ? 0 : 1)}Cr`;
  if (n >= 1e5) return `${(n / 1e5).toFixed(n % 1e5 === 0 ? 0 : 1)}L`;
  if (n >= 1000) return `${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}k`;
  return String(Math.round(n));
}

export interface MoneySeries {
  name: string;
  data: number[];
  /** [gradientFrom, gradientTo] — top to bottom */
  colors: [string, string];
}

export function MoneyBarsChart({
  labels,
  series,
  formatValue,
  height = 240,
  valueLabels = false,
}: {
  labels: string[];
  series: MoneySeries[];
  formatValue: (n: number) => string;
  height?: number;
  /** draw the amount above each bar (small datasets only) */
  valueLabels?: boolean;
}) {
  const chartRef = useChartFontRefresh();

  const gradient =
    (from: string, to: string) =>
    (ctx: ScriptableContext<"bar">): string | CanvasGradient => {
      const { chartArea, ctx: c } = ctx.chart;
      if (!chartArea) return to;
      const g = c.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
      g.addColorStop(0, from);
      g.addColorStop(1, to);
      return g;
    };

  const data: ChartData<"bar"> = {
    labels,
    datasets: series.map((s) => ({
      label: s.name,
      data: s.data,
      backgroundColor: gradient(s.colors[0], s.colors[1]),
      hoverBackgroundColor: gradient(s.colors[0], s.colors[1]),
      borderRadius: 6,
      borderSkipped: "bottom" as const,
      maxBarThickness: 26,
      barPercentage: 0.7,
      categoryPercentage: 0.72,
    })),
  };

  const options: ChartOptions<"bar"> = {
    responsive: true,
    maintainAspectRatio: false,
    animation: { duration: 500 },
    interaction: { mode: "index", intersect: false },
    plugins: {
      legend: {
        display: series.length > 1,
        position: "top",
        align: "center",
        labels: {
          usePointStyle: true,
          pointStyleWidth: 10,
          boxHeight: 7,
          font: { family: CHART_TOKENS.font, size: 11.5, weight: 600 },
          color: CHART_TOKENS.muted,
          padding: 16,
        },
      },
      tooltip: {
        enabled: false,
        external: externalTooltip((m) => {
          if (m.dataPoints.length === 0) return null;
          const rows = m.dataPoints
            .map((dp) => {
              const s = series[dp.datasetIndex];
              if (!s) return null;
              const value = s.data[dp.dataIndex] ?? 0;
              return { color: s.colors[1], label: s.name, value: formatValue(value) } as TipRow;
            })
            .filter((r): r is TipRow => r !== null);
          if (rows.length === 0) return null;
          return { title: labels[m.dataPoints[0].dataIndex] ?? "", rows };
        }),
      },
    },
    scales: {
      x: {
        grid: { display: false },
        border: { display: false },
        ticks: { font: { weight: 600, size: 11 }, color: CHART_TOKENS.muted },
      },
      y: {
        beginAtZero: true,
        border: { display: false },
        grid: { color: "#f0f3f5", drawTicks: false },
        ticks: {
          font: { size: 10 },
          color: "#9aa5b1",
          padding: 8,
          callback: (v) => moneyAxisLabel(Number(v)),
        },
      },
    },
  };

  const plugins: Plugin<"bar">[] = [softShadow as Plugin<"bar">];
  if (valueLabels) plugins.push(barValueLabels(formatValue));

  return (
    <div className="ems-chart-wrap" style={{ width: "100%", height }}>
      <Bar ref={chartRef as never} data={data} options={options} plugins={plugins} />
    </div>
  );
}
