import type { PptxElement } from "./pptxTypes"
import { getCacheKey, getNumber } from "./pptxUtils"

type ChartBar = { x: number; y: number; w: number; h: number; fill: string; opacity?: number }
type ChartPath = { d: string; stroke: string; strokeWidth: number }
type ChartPoint = { cx: number; cy: number; r: number; fill: string; opacity?: number }
type ChartSlice = { d: string; fill: string; opacity?: number }

export type ChartModel = {
  kind: "bar" | "line" | "pie" | "scatter"
  bars: ChartBar[]
  paths: ChartPath[]
  points: ChartPoint[]
  slices: ChartSlice[]
  cx: number
  cy: number
  holeR: number
}

const chartCache = new Map<string, ChartModel>()

export function getChartModel(el: PptxElement, slideIndex: number, cacheId: string): ChartModel {
  const key = `c-${getCacheKey(slideIndex, cacheId)}`
  const cached = chartCache.get(key)
  if (cached) return cached

  const w = Math.max(1, getNumber(el.width))
  const h = Math.max(1, getNumber(el.height))
  const chartType = String((el as any)?.chartType || "").toLowerCase()
  const colorsRaw = Array.isArray((el as any)?.colors) ? ((el as any).colors as string[]) : []
  const colors = colorsRaw.length ? colorsRaw.map((c) => String(c || "").trim()).filter(Boolean) : []
  const getColor = (i: number) => colors[i % Math.max(1, colors.length)] || "#4e79a7"

  const opacity = el.opacity == null ? 1 : Math.max(0, Math.min(1, getNumber(el.opacity)))
  const padding = Math.max(4, Math.min(24, Math.round(Math.min(w, h) * 0.08)))

  const isPie = chartType.includes("pie") || chartType.includes("doughnut")
  const isBar = chartType.includes("bar") || chartType.includes("col")
  const isScatter = chartType.includes("scatter") || chartType.includes("bubble")

  if (isScatter) {
    const data = (el as any)?.data
    const xs = Array.isArray(data?.[0]) ? (data[0] as number[]) : []
    const ys = Array.isArray(data?.[1]) ? (data[1] as number[]) : []
    const n = Math.min(xs.length, ys.length)
    const minX = n ? Math.min(...xs.slice(0, n)) : 0
    const maxX = n ? Math.max(...xs.slice(0, n)) : 1
    const minY = n ? Math.min(...ys.slice(0, n)) : 0
    const maxY = n ? Math.max(...ys.slice(0, n)) : 1
    const spanX = maxX - minX || 1
    const spanY = maxY - minY || 1
    const innerW = Math.max(1, w - padding * 2)
    const innerH = Math.max(1, h - padding * 2)

    const points: ChartPoint[] = []
    for (let i = 0; i < n; i++) {
      const x = padding + ((xs[i] - minX) / spanX) * innerW
      const y = padding + (1 - (ys[i] - minY) / spanY) * innerH
      points.push({ cx: x, cy: y, r: 3, fill: getColor(0), opacity })
    }
    const model: ChartModel = { kind: "scatter", bars: [], paths: [], points, slices: [], cx: 0, cy: 0, holeR: 0 }
    chartCache.set(key, model)
    return model
  }

  const rawData = (el as any)?.data

  if (isPie) {
    const series = Array.isArray(rawData) ? (rawData as any[]) : []
    const valuesRaw = Array.isArray(series?.[0]?.values) ? (series[0].values as any[]) : []
    const values = valuesRaw
      .map((v) => ({ y: getNumber(v?.y), x: String(v?.x ?? "") }))
      .filter((v) => Number.isFinite(v.y) && v.y > 0)
    const sum = values.reduce((acc, v) => acc + v.y, 0) || 1

    const cx = w / 2
    const cy = h / 2
    const r = Math.max(2, Math.min(w, h) / 2 - padding)
    const holeR = chartType.includes("doughnut") ? r * 0.55 : 0

    let start = -Math.PI / 2
    const slices: ChartSlice[] = []
    values.forEach((v, i) => {
      const angle = (v.y / sum) * Math.PI * 2
      const end = start + angle

      const x1 = cx + r * Math.cos(start)
      const y1 = cy + r * Math.sin(start)
      const x2 = cx + r * Math.cos(end)
      const y2 = cy + r * Math.sin(end)
      const large = angle > Math.PI ? 1 : 0

      const d = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`
      slices.push({ d, fill: getColor(i), opacity })
      start = end
    })

    const model: ChartModel = { kind: "pie", bars: [], paths: [], points: [], slices, cx, cy, holeR }
    chartCache.set(key, model)
    return model
  }

  const series = Array.isArray(rawData) ? (rawData as any[]) : []
  const items = series
    .map((s: any) => {
      const valuesRaw = Array.isArray(s?.values) ? (s.values as any[]) : []
      const values = valuesRaw
        .map((v) => ({ y: getNumber(v?.y), x: String(v?.x ?? "") }))
        .filter((v) => Number.isFinite(v.y))
      return {
        name: String(s?.name ?? ""),
        values,
      }
    })
    .filter((s: any) => s.values.length)

  const categories = Array.from(new Set(items.flatMap((s: any) => s.values.map((v: any) => v.x)).filter((x: any) => x != null).map((x: any) => String(x))))
  const catCount = Math.max(1, categories.length)
  const seriesCount = Math.max(1, items.length)

  const maxY = Math.max(1, ...items.flatMap((s: any) => s.values.map((v: any) => v.y)).filter((v: any) => Number.isFinite(v)))

  const innerW = Math.max(1, w - padding * 2)
  const innerH = Math.max(1, h - padding * 2)

  const barDir = String((el as any)?.barDir || "col").toLowerCase()

  if (isBar) {
    const bars: ChartBar[] = []

    if (barDir === "bar") {
      const bandH = innerH / catCount
      const barH = bandH * 0.8
      const gapY = (bandH - barH) / 2
      const eachH = barH / seriesCount

      for (let ci = 0; ci < catCount; ci++) {
        for (let si = 0; si < items.length; si++) {
          const v = items[si].values.find((vv: any) => vv.x === categories[ci])
          const value = v ? v.y : 0
          const bw = (value / maxY) * innerW
          const x = padding
          const y = padding + ci * bandH + gapY + si * eachH
          bars.push({ x, y, w: Math.max(0, bw), h: Math.max(0, eachH * 0.9), fill: getColor(si), opacity })
        }
      }
    } else {
      const bandW = innerW / catCount
      const barW = bandW * 0.8
      const gapX = (bandW - barW) / 2
      const eachW = barW / seriesCount

      for (let ci = 0; ci < catCount; ci++) {
        for (let si = 0; si < items.length; si++) {
          const v = items[si].values.find((vv: any) => vv.x === categories[ci])
          const value = v ? v.y : 0
          const bh = (value / maxY) * innerH
          const x = padding + ci * bandW + gapX + si * eachW
          const y = padding + (innerH - bh)
          bars.push({ x, y, w: Math.max(0, eachW * 0.9), h: Math.max(0, bh), fill: getColor(si), opacity })
        }
      }
    }

    const model: ChartModel = { kind: "bar", bars, paths: [], points: [], slices: [], cx: 0, cy: 0, holeR: 0 }
    chartCache.set(key, model)
    return model
  }

  const paths: ChartPath[] = []
  const points: ChartPoint[] = []
  const strokeWidth = Math.max(1, Math.round(Math.min(w, h) * 0.01))
  const marker = !!(el as any)?.marker

  const xAt = (i: number) => padding + (catCount === 1 ? innerW / 2 : (i / (catCount - 1)) * innerW)
  const yAt = (value: number) => padding + (1 - value / maxY) * innerH

  items.forEach((s: any, si: number) => {
    let d = ""
    categories.forEach((cat: string, ci: number) => {
      const v = s.values.find((vv: any) => vv.x === cat)
      const value = v ? v.y : 0
      const x = xAt(ci)
      const y = yAt(value)
      d += ci === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`
      if (marker) points.push({ cx: x, cy: y, r: strokeWidth * 0.9 + 1, fill: getColor(si), opacity })
    })
    paths.push({ d, stroke: getColor(si), strokeWidth })
  })

  const model: ChartModel = { kind: "line", bars: [], paths, points, slices: [], cx: 0, cy: 0, holeR: 0 }
  chartCache.set(key, model)
  return model
}

