// 图表 spec 与「从模型输出里抽取 ```chart 代码块」的解析。
// 约定：chart 工具返回图表 JSON，agent 在答复里用 ```chart 围栏原样包裹；前端据此渲染 SVG。

export interface ChartSeries {
  name?: string;
  data: number[];
}

export interface ChartSpec {
  type: "bar" | "line" | "pie";
  title?: string;
  labels: string[];
  series: ChartSeries[];
}

// Segment：把一段输出拆成「普通文本」与「图表」交错的有序片段，供输出区分别渲染。
export type Segment = { kind: "text"; text: string } | { kind: "chart"; spec: ChartSpec };

const chartFence = /```chart\s*\n([\s\S]*?)```/g;

// isChartSpec 校验解析出的对象是否是合法图表 spec（防脏数据炸渲染）。
export function isChartSpec(v: unknown): v is ChartSpec {
  if (!v || typeof v !== "object") return false;
  const o = v as Record<string, unknown>;
  if (o.type !== "bar" && o.type !== "line" && o.type !== "pie") return false;
  if (!Array.isArray(o.labels) || o.labels.length === 0) return false;
  if (!Array.isArray(o.series) || o.series.length === 0) return false;
  return (o.series as unknown[]).every(
    (s) => s && typeof s === "object" && Array.isArray((s as ChartSeries).data),
  );
}

// extractChartBlocks 把输出文本拆为 text / chart 片段（保持原顺序）。
// ```chart 块解析失败或非法 → 回退为普通文本，绝不丢内容。
export function extractChartBlocks(input: string): Segment[] {
  if (!input) return [];
  const segs: Segment[] = [];
  let last = 0;
  for (let m = chartFence.exec(input); m; m = chartFence.exec(input)) {
    if (m.index > last) segs.push({ kind: "text", text: input.slice(last, m.index) });
    let parsed: unknown;
    try {
      parsed = JSON.parse(m[1].trim());
    } catch {
      parsed = null;
    }
    if (isChartSpec(parsed)) segs.push({ kind: "chart", spec: parsed });
    else segs.push({ kind: "text", text: m[0] }); // 解析失败：原样当文本，不丢
    last = m.index + m[0].length;
  }
  chartFence.lastIndex = 0;
  if (last < input.length) segs.push({ kind: "text", text: input.slice(last) });
  return segs;
}

// hasChart 快速判断输出里是否含图表块（决定是否走分段渲染）。
export function hasChart(input: string): boolean {
  const has = /```chart\s*\n/.test(input);
  return has;
}
