"use client";

import { useEffect, useRef, type CSSProperties } from "react";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import type { ApexOptions } from "apexcharts";
import type { Props as ApexChartProps } from "react-apexcharts";

export type SafeApexChartsProps = ApexChartProps & {
  /** Bumps internal remount when set — JSON.stringify(options) drops `chart.events`, so use this when handlers/data must stay in sync. */
  chartRevision?: string | number;
};

/**
 * Donut/pie series is number[] (possibly string numbers from JSON).
 * Do not use `typeof series[0] === "number"` only — first slice can be 0 or a string.
 */
function tryCoerceOneDimensionalNumericSeries(raw: unknown[]): number[] | null {
  if (raw.length === 0) return null;
  const out: number[] = [];
  for (const x of raw) {
    if (typeof x === "number" && Number.isFinite(x)) {
      out.push(x);
      continue;
    }
    if (typeof x === "string" && x.trim() !== "") {
      const n = Number(x);
      if (Number.isFinite(n)) {
        out.push(n);
        continue;
      }
    }
    return null;
  }
  return out;
}

function sanitizeSeries(series: ApexChartProps["series"]): ApexChartProps["series"] {
  if (series == null) return [];
  if (Array.isArray(series)) {
    const asNums = tryCoerceOneDimensionalNumericSeries(series as unknown[]);
    if (asNums != null) {
      return asNums.map((v) => (Number.isFinite(v) ? v : 0));
    }
    return (series as { name?: string; data?: unknown[] }[]).map((s) => ({
      name: s?.name ?? "",
      data: Array.isArray(s?.data)
        ? s.data.map((v) => {
            const n = Number(v);
            return Number.isFinite(n) ? n : 0;
          })
        : [],
    }));
  }
  return [];
}

function isAxisChart(type: ApexChartProps["type"]): boolean {
  return type === "line" || type === "area" || type === "bar";
}

function isRadialChart(type: ApexChartProps["type"]): boolean {
  return type === "donut" || type === "pie" || type === "radialBar";
}

function isNumberArray(sanitized: ApexChartProps["series"]): sanitized is number[] {
  return Array.isArray(sanitized) && sanitized.length > 0 && sanitized.every((v) => typeof v === "number");
}

/** Stacked/category charts: every series row must have data.length === categories.length or Apex can throw. */
function alignSeriesToCategoryCount(
  series: ApexChartProps["series"],
  categoryCount: number,
): ApexChartProps["series"] {
  if (!Array.isArray(series) || categoryCount <= 0 || isNumberArray(series)) return series;
  return (series as { name?: string; data?: unknown[] }[]).map((row) => {
    const raw = Array.isArray(row.data) ? row.data : [];
    const data = raw.map((v) => {
      const n = Number(v);
      return Number.isFinite(n) ? n : 0;
    });
    const next = data.slice(0, categoryCount);
    while (next.length < categoryCount) next.push(0);
    return { ...row, data: next };
  });
}

/** Sum numeric series (donut) or all y values in multi-series bar/line. */
function seriesNumericTotal(sanitized: ApexChartProps["series"]): number {
  if (!Array.isArray(sanitized) || sanitized.length === 0) return 0;
  if (isNumberArray(sanitized)) {
    return sanitized.reduce((a, v) => a + (Number.isFinite(v) ? v : 0), 0);
  }
  return (sanitized as { data?: number[] }[]).reduce((sum, row) => {
    const d = row.data ?? [];
    return sum + d.reduce((a, v) => a + (Number.isFinite(v) ? v : 0), 0);
  }, 0);
}

/** ApexCharts throws (e.g. .toString of undefined) on empty categories, empty multi-series, donut sum 0, etc. */
function shouldShowPlaceholder(
  type: ApexChartProps["type"],
  options: ApexChartProps["options"],
  sanitized: ApexChartProps["series"],
): boolean {
  if (!Array.isArray(sanitized) || sanitized.length === 0) return true;

  if (isRadialChart(type)) {
    if (!isNumberArray(sanitized)) return true;
    const sum = sanitized.reduce((a, v) => a + (Number.isFinite(v) && v > 0 ? v : 0), 0);
    if (sum <= 0) return true;
    return false;
  }

  if (!isAxisChart(type)) return false;

  const cats = options?.xaxis?.categories;
  if (!Array.isArray(cats) || cats.length === 0) return true;
  if (isNumberArray(sanitized)) return false;
  const rows = sanitized as { data?: number[] }[];
  const anyPoint = rows.some((r) => (r.data?.length ?? 0) > 0);
  if (!anyPoint) return true;
  if (type === "bar" && seriesNumericTotal(sanitized) <= 0) return true;
  return false;
}

function buildApexConfig(
  base: ApexOptions | undefined,
  chartType: string,
  h: ApexChartProps["height"],
  w: ApexChartProps["width"],
  s: ApexChartProps["series"],
): ApexOptions {
  const o = base ?? {};
  const prev = o.chart && typeof o.chart === "object" ? o.chart : {};
  return {
    ...o,
    chart: {
      ...prev,
      type: chartType,
      height: h ?? (prev as { height?: ApexChartProps["height"] }).height ?? "auto",
      width: w ?? (prev as { width?: string | number }).width ?? "100%",
    } as ApexOptions["chart"],
    series: s as ApexOptions["series"],
  };
}

const EMPTY_MESSAGE = "暫無圖表資料（後端無法連線或此區間無資料）。";

export default function SafeApexCharts(props: SafeApexChartsProps) {
  const { type, series, options, height, width, chartRevision, ...rest } = props;
  const containerRef = useRef<HTMLDivElement>(null);
  const chartRef = useRef<{ destroy: () => void } | null>(null);

  let sanitized = sanitizeSeries(series);

  const cats = options?.xaxis?.categories;
  if (isAxisChart(type) && Array.isArray(cats) && cats.length > 0) {
    sanitized = alignSeriesToCategoryCount(sanitized, cats.length);
  }

  if (shouldShowPlaceholder(type, options, sanitized)) {
    return (
      <Typography color="text.secondary" sx={{ py: 3 }}>
        {EMPTY_MESSAGE}
      </Typography>
    );
  }

  let chartOptions = options;
  let renderSeries: ApexChartProps["series"] = sanitized;

  if (isRadialChart(type) && isNumberArray(sanitized)) {
    const prev = Array.isArray(options?.labels) ? (options!.labels as unknown[]) : [];
    const pairs = sanitized.map((v, i) => {
      const n = Number.isFinite(v) ? v : 0;
      const raw = prev[i];
      const label = raw != null && String(raw).trim() !== "" ? String(raw) : `項目 ${i + 1}`;
      return { v: Math.max(0, n), label };
    });
    const nonzero = pairs.filter((p) => p.v > 0);
    if (nonzero.length === 0) {
      return (
        <Typography color="text.secondary" sx={{ py: 3 }}>
          {EMPTY_MESSAGE}
        </Typography>
      );
    }
    renderSeries = nonzero.map((p) => p.v);
    chartOptions = { ...options, labels: nonzero.map((p) => p.label) };
  }

  const chartType = String(type ?? "line");
  const configSnapshot = `${String(chartRevision ?? "")}|${chartType}|${JSON.stringify(renderSeries ?? null)}|${JSON.stringify(chartOptions ?? {})}|${String(height ?? "")}|${String(width ?? "")}`;

  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;

    let disposed = false;

    void import("apexcharts").then((mod) => {
      if (disposed || containerRef.current !== el) return;

      const ApexChartsCtor = mod.default;
      const config = buildApexConfig(chartOptions as ApexOptions, chartType, height, width, renderSeries);

      let instance: { destroy: () => void; render: () => Promise<void> };
      try {
        instance = new ApexChartsCtor(el, config);
      } catch {
        return;
      }

      chartRef.current = instance;

      if (disposed || containerRef.current !== el) {
        if (chartRef.current === instance) {
          try {
            instance.destroy();
          } catch {
            /* ignore */
          }
          chartRef.current = null;
        }
        return;
      }

      void instance.render().catch(() => {
        /* ignore */
      });
    });

    return () => {
      disposed = true;
      const c = chartRef.current;
      chartRef.current = null;
      if (c) {
        try {
          c.destroy();
        } catch {
          /* ignore */
        }
      }
    };
  }, [configSnapshot]);

  const minH = typeof height === "number" ? height : typeof height === "string" ? height : 240;
  const dom = rest as { className?: string; id?: string; style?: CSSProperties; sx?: object };

  return (
    <Box
      ref={containerRef}
      className={dom.className}
      id={dom.id}
      style={dom.style}
      sx={{
        width: "100%",
        minHeight: minH,
        position: "relative",
        ...dom.sx,
      }}
    />
  );
}
