export type SafeChartSpec = {
  title?: string;
  chartType: "line" | "bar";
  xAxisName?: string;
  yAxisName?: string;
};

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

export type SafeChartData = {
  xData: string[];
  series: SafeChartSeries[];
};

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 function parseChartSpec(value: unknown): SafeChartSpec {
  if (!isRecord(value)) {
    return { chartType: "line" };
  }

  return {
    title: stringValue(value.title),
    chartType: value.chart_type === "bar" || value.chartType === "bar" ? "bar" : "line",
    xAxisName: stringValue(value.x_axis_name ?? value.xAxisName),
    yAxisName: stringValue(value.y_axis_name ?? value.yAxisName)
  };
}

export function normalizeSafeChartData(value: unknown, maxPoints = 24): SafeChartData | null {
  if (!isRecord(value)) {
    return null;
  }

  const xData = normalizeXData(value.x_data ?? value.xData);
  const rawSeriesItems = normalizeRawSeriesItems(value.series);
  if (!rawSeriesItems.length) {
    return null;
  }

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

      const labelsFromPoints: string[] = [];
      const data = rawData
        .slice(0, maxPoints)
        .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((item): item is number => item !== null);

      if (!data.length) {
        return null;
      }

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

      return {
        name: stringValue(item.name) ?? `系列 ${seriesIndex + 1}`,
        data,
        type: item.type === "line" || item.type === "bar" ? item.type : undefined
      };
    })
    .filter((item): item is SafeChartSeries => Boolean(item));

  const clippedXData = xData.slice(0, maxPoints);
  const clippedSeries = normalizedSeries.map((series) => ({
    ...series,
    data: series.data.slice(0, clippedXData.length)
  }));

  return clippedXData.length > 0 && clippedSeries.length > 0
    ? {
        xData: clippedXData,
        series: clippedSeries
      }
    : null;
}

export function 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 (isRecord(point)) {
    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;
}

function normalizeXData(rawXData: unknown): string[] {
  return Array.isArray(rawXData) ? rawXData.map((item) => String(item ?? "")).filter(Boolean) : [];
}

function normalizeRawSeriesItems(rawSeries: unknown): unknown[] {
  if (!Array.isArray(rawSeries)) {
    return isRecord(rawSeries) ? [rawSeries] : [];
  }

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

function isRawChartPoint(item: unknown): boolean {
  if (toFiniteNumber(item) !== null) {
    return true;
  }
  if (Array.isArray(item)) {
    return item.length >= 2 && toFiniteNumber(item[1]) !== null;
  }
  if (isRecord(item)) {
    return item.data === undefined && item.points === undefined && item.values === undefined && toFiniteNumber(item.value ?? item.y) !== null;
  }
  return false;
}

function 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;
}

function stringValue(value: unknown) {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
