"use client";

import React, { useMemo } from "react";
import ReactECharts from "echarts-for-react";
import * as echarts from "echarts";
import { AnimatePresence, motion } from "framer-motion";
import { Box, Paper, Skeleton, Stack, Typography, alpha, useTheme } from "@mui/material";

/* ------------------------------------------------------------------ */
/*  Inline chart rendered inside a chat message bubble.               */
/*  Accepts structured data produced by the AI tool_call.             */
/* ------------------------------------------------------------------ */

export interface ChatChartSeries {
  name: string;
  data: number[];
  type?: "line" | "bar";
}

type RawChartPoint =
  | number
  | string
  | [unknown, unknown]
  | RawChartPointObject;

type RawChartPointObject = {
  x?: unknown;
  y?: unknown;
  time?: unknown;
  timestamp?: unknown;
  label?: unknown;
  name?: unknown;
  value?: unknown;
};

type RawChartSeries = {
  name?: unknown;
  data?: unknown;
  points?: unknown;
  values?: unknown;
  type?: unknown;
};

export interface ChatInlineChartProps {
  title?: string;
  chart_type?: "line" | "bar" | "pie";
  x_data?: unknown;
  series?: unknown;
  y_axis_name?: string;
  x_axis_name?: string;
  isStreaming?: boolean;
}

export const CHART_HEIGHT = 240;
export const CHART_MIN_HEIGHT = 286;

const COLORS = [
  "#5470c6",
  "#91cc75",
  "#fac858",
  "#ee6666",
  "#73c0de",
  "#3ba272",
  "#fc8452",
  "#9a60b4",
  "#ea7ccc",
];

const ChartSkeletonContent = ({ status }: { status?: React.ReactNode }) => (
  <Stack spacing={1.25} sx={{ p: 1.5 }}>
    <Stack direction="row" alignItems="center" justifyContent="space-between">
      <Skeleton variant="text" width="34%" height={20} />
      {status}
    </Stack>
    <Skeleton variant="rounded" height={208} sx={{ borderRadius: 2 }} />
    <Stack direction="row" spacing={1}>
      <Skeleton variant="text" width="24%" height={16} />
      <Skeleton variant="text" width="18%" height={16} />
      <Skeleton variant="text" width="20%" height={16} />
    </Stack>
  </Stack>
);

export const ChartGenerationSkeleton = ({ status }: { status?: React.ReactNode }) => {
  const theme = useTheme();

  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.18 }}
      style={{ width: "100%" }}
    >
      <Paper
        elevation={0}
        sx={{
          mt: 1.5,
          mb: 1,
          minHeight: CHART_MIN_HEIGHT,
          borderRadius: 3,
          border: `1px solid ${alpha(theme.palette.divider, 0.12)}`,
          bgcolor: alpha("#fff", 0.78),
          overflow: "hidden",
        }}
      >
        <ChartSkeletonContent status={status} />
      </Paper>
    </motion.div>
  );
};

const toFiniteNumber = (value: unknown): number | null => {
  if (typeof value === "number") {
    return Number.isFinite(value) ? value : null;
  }
  if (typeof value === "string" && value.trim()) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : null;
  }
  return null;
};

export const pointToLabelValue = (
  point: RawChartPoint,
  fallbackLabel: string,
): { label: string; value: number } | null => {
  const directValue = toFiniteNumber(point);
  if (directValue !== null) {
    return { label: fallbackLabel, value: directValue };
  }

  if (Array.isArray(point)) {
    const value = toFiniteNumber(point[1]);
    if (value === null) return null;
    return { label: String(point[0] ?? fallbackLabel), value };
  }

  if (point && typeof point === "object") {
    const value = toFiniteNumber(point.value ?? point.y);
    if (value === null) return null;
    const label =
      point.x ?? point.time ?? point.timestamp ?? point.label ?? point.name ?? fallbackLabel;
    return { label: String(label), value };
  }

  return null;
};

const normalizeXData = (rawXData: unknown): string[] =>
  Array.isArray(rawXData)
    ? rawXData.map((item) => String(item ?? "")).filter((item) => item.length > 0)
    : [];

const normalizeSeriesType = (type: unknown): "line" | "bar" | undefined =>
  type === "line" || type === "bar" ? type : undefined;

const isRawChartPoint = (item: unknown): boolean => {
  if (toFiniteNumber(item) !== null) return true;
  if (Array.isArray(item)) return item.length >= 2 && toFiniteNumber(item[1]) !== null;
  if (item && typeof item === "object") {
    const rawItem = item as RawChartSeries & RawChartPointObject;
    return (
      rawItem.data === undefined &&
      rawItem.points === undefined &&
      rawItem.values === undefined &&
      toFiniteNumber(rawItem.value ?? rawItem.y) !== null
    );
  }
  return false;
};

const normalizeRawSeriesItems = (rawSeries: unknown): unknown[] => {
  if (!Array.isArray(rawSeries)) {
    return rawSeries && typeof rawSeries === "object" ? [rawSeries] : [];
  }

  return rawSeries.length > 0 && rawSeries.every(isRawChartPoint)
    ? [{ name: "数据", data: rawSeries }]
    : rawSeries;
};

export const normalizeChartData = (
  rawXData: unknown,
  rawSeries: unknown,
): { xData: string[]; series: ChatChartSeries[] } => {
  const xData = normalizeXData(rawXData);
  const rawSeriesItems = normalizeRawSeriesItems(rawSeries);
  if (!rawSeriesItems.length) {
    return { xData, series: [] };
  }

  const normalizedSeries = rawSeriesItems
    .map((rawItem, seriesIndex): ChatChartSeries | null => {
      const item =
        rawItem && typeof rawItem === "object" && !Array.isArray(rawItem)
          ? (rawItem as RawChartSeries)
          : ({ data: rawItem } satisfies RawChartSeries);
      const rawData = item.data ?? item.points ?? item.values;
      if (!Array.isArray(rawData)) return null;

      const labelsFromPoints: string[] = [];
      const data = rawData
        .map((point, index) => {
          const parsed = pointToLabelValue(
            point as RawChartPoint,
            xData[index] ?? `${index + 1}`,
          );
          if (!parsed) return null;
          labelsFromPoints[index] = parsed.label;
          return parsed.value;
        })
        .filter((value): value is number => value !== null);

      if (!data.length) return null;
      if (!xData.length && labelsFromPoints.length) {
        xData.push(...labelsFromPoints);
      }

      return {
        name:
          typeof item.name === "string" && item.name.trim()
            ? item.name
            : `系列 ${seriesIndex + 1}`,
        data,
        type: normalizeSeriesType(item.type),
      };
    })
    .filter((item): item is ChatChartSeries => Boolean(item));

  return { xData, series: normalizedSeries };
};

export const ChatInlineChart: React.FC<ChatInlineChartProps> = ({
  title,
  chart_type: chartType = "line",
  x_data,
  series,
  y_axis_name: yAxisName,
  x_axis_name: xAxisName,
  isStreaming = false,
}) => {
  const theme = useTheme();
  const [showIntroSkeleton, setShowIntroSkeleton] = React.useState(true);
  const { xData, series: chartSeries } = useMemo(
    () => normalizeChartData(x_data, series),
    [x_data, series],
  );

  React.useEffect(() => {
    const timer = window.setTimeout(() => {
      setShowIntroSkeleton(false);
    }, isStreaming ? 360 : 260);

    return () => window.clearTimeout(timer);
  }, [isStreaming]);

  const option = useMemo(() => {
    if (!chartSeries.length) return null;

    /* ---------- Pie chart ---------- */
    if (chartType === "pie") {
      const pieData =
        chartSeries[0]?.data.map((value, i) => ({
          name: xData?.[i] ?? `${i}`,
          value,
        })) ?? [];

      return {
        animation: true,
        animationDuration: isStreaming ? 560 : 420,
        animationDurationUpdate: 240,
        animationEasing: "cubicOut",
        animationEasingUpdate: "cubicOut",
        tooltip: { trigger: "item" },
        legend: { top: "bottom", textStyle: { fontSize: 11 } },
        series: [
          {
            type: "pie",
            radius: ["30%", "60%"],
            data: pieData,
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: "rgba(0, 0, 0, 0.5)",
              },
            },
            label: { fontSize: 11 },
            animationType: "expansion",
            animationDuration: isStreaming ? 560 : 420,
            animationDelay: (idx: number) => idx * 40,
            animationDurationUpdate: 240,
          },
        ],
        color: COLORS,
      };
    }

    /* ---------- Line / Bar chart ---------- */
    return {
      animation: true,
      animationDuration: isStreaming ? 560 : 420,
      animationDurationUpdate: 240,
      animationEasing: "cubicOut",
      animationEasingUpdate: "cubicOut",
      tooltip: { trigger: "axis", confine: true },
      legend: { top: "top", textStyle: { fontSize: 11 } },
      grid: {
        left: "5%",
        right: "5%",
        bottom: "12%",
        top: title ? "18%" : "14%",
        containLabel: true,
      },
      xAxis: {
        type: "category" as const,
        boundaryGap: chartType === "bar",
        data: xData ?? [],
        axisLabel: {
          fontSize: 10,
          rotate: xData && xData.length > 10 ? 30 : 0,
        },
        name: xAxisName,
      },
      yAxis: {
        type: "value" as const,
        scale: true,
        axisLabel: { fontSize: 10 },
        name: yAxisName,
      },
      dataZoom:
        xData && xData.length > 20
          ? [{ type: "inside", start: 0, end: 100 }]
          : undefined,
      series: chartSeries.map((s, i) => {
        const color = COLORS[i % COLORS.length];
        const isLineSeries = chartType === "line";
        return {
          name: s.name,
          type: (s.type ?? chartType) as string,
          data: s.data,
          symbol: isLineSeries ? "none" : undefined,
          smooth: isLineSeries,
          itemStyle: { color },
          animationDuration: isStreaming ? 560 : 420,
          animationDurationUpdate: 240,
          animationDelay:
            chartType === "bar"
              ? (idx: number) => i * 80 + idx * 18
              : i * 80,
          animationDelayUpdate: 0,
          ...(isLineSeries
            ? {
                areaStyle: {
                  color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    { offset: 0, color: alpha(color, 0.3) },
                    { offset: 1, color: alpha(color, 0.05) },
                  ]),
                  opacity: 0.3,
                },
              }
            : {}),
        };
      }),
      color: COLORS,
    };
  }, [chartType, xData, chartSeries, title, yAxisName, xAxisName, isStreaming]);

  if (!option) {
    return (
      <Paper
        elevation={0}
        sx={{
          mt: 1.5,
          mb: 1,
          minHeight: 72,
          display: "flex",
          alignItems: "center",
          px: 2,
          borderRadius: 3,
          border: `1px solid ${alpha(theme.palette.divider, 0.12)}`,
          bgcolor: alpha("#fff", 0.72),
        }}
      >
        <Typography variant="caption" color="text.secondary">
          图表数据为空
        </Typography>
      </Paper>
    );
  }

  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.22, ease: "easeOut" }}
      style={{ width: "100%" }}
    >
      <Paper
        elevation={0}
        sx={{
          mt: 1.5,
          mb: 1,
          minHeight: CHART_MIN_HEIGHT,
          borderRadius: 3,
          border: `1px solid ${alpha(theme.palette.divider, 0.15)}`,
          bgcolor: alpha("#fff", 0.92),
          overflow: "hidden",
          position: "relative",
        }}
      >
        <AnimatePresence initial={false}>
          {showIntroSkeleton ? (
            <Box
              key="chart-intro-skeleton"
              component={motion.div}
              aria-hidden
              initial={{ opacity: 1 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              transition={{ duration: 0.22, ease: "easeOut" }}
              sx={{
                position: "absolute",
                inset: 0,
                zIndex: 2,
                bgcolor: alpha("#fff", 0.92),
                pointerEvents: "none",
              }}
            >
              <ChartSkeletonContent />
            </Box>
          ) : null}
        </AnimatePresence>
        {title && (
          <Typography
            variant="subtitle2"
            sx={{ px: 2, pt: 1.5, fontWeight: 600, color: "text.primary" }}
          >
            {title}
          </Typography>
        )}
        <Box
          component={motion.div}
          initial={{ opacity: 0 }}
          animate={{ opacity: showIntroSkeleton ? 0.35 : 1 }}
          transition={{ duration: 0.24, ease: "easeOut" }}
          sx={{ px: 1, pb: 1, minHeight: CHART_HEIGHT }}
        >
          <ReactECharts
            option={option}
            style={{ height: CHART_HEIGHT, width: "100%" }}
            notMerge
            lazyUpdate
          />
        </Box>
      </Paper>
    </motion.div>
  );
};
