import Chart from "../vendor/chart.js"

const M2FT = 3.28084
const KM2MI = 0.621371

interface ProfilePoint {
  dist_km: number
  elev: number
  beam: number
  r1: number
}

interface DuctRaw {
  base_m_msl: number
  top_m_msl: number
  strength: number
  source?: DuctSource
  likely?: boolean
}

type DuctSource = "hrrr" | "sounding" | "inferred"

interface DuctBand {
  base_ft: number
  top_ft: number
  agl_base_ft: number
  agl_top_ft: number
  strength: number
  source: DuctSource
  likely: boolean
}

interface ProfileData {
  points: ProfilePoint[]
  freq_mhz: number
  k_factor?: number
  dist_km?: number
  ducts?: DuctRaw[]
}

interface EarthCurveParams {
  dTotal: number
  k: number
  R: number
  minElev: number
}

interface ChartWithAreas {
  options: {
    plugins: {
      ductBands?: {
        ducts: DuctBand[]
        earthCurve: EarthCurveParams
      }
    }
  }
  ctx: CanvasRenderingContext2D
  chartArea: { left: number; right: number; top: number; bottom: number }
  scales: { y: { getPixelForValue(value: number): number } }
}

interface ElevationProfileHook extends ViewHook {
  chart: Chart | null
  renderChart(this: ElevationProfileHook, data: ProfileData): void
}

function earthSurfaceFt(f: number, params: EarthCurveParams): number {
  const bulge = (f * params.dTotal * (1 - f) * params.dTotal) / (2 * params.k * params.R)
  return (params.minElev + bulge) * M2FT
}

// Chart.js plugin to draw curved duct bands that follow earth curvature
const ductPlugin = {
  id: "ductBands",
  beforeDraw(chart: ChartWithAreas): void {
    const opts = chart.options.plugins.ductBands
    if (!opts) return
    const { ducts, earthCurve } = opts
    if (!ducts || ducts.length === 0 || !earthCurve) return

    const { ctx, chartArea: { left, right, top, bottom }, scales: { y } } = chart
    const chartWidth = right - left

    ctx.save()
    for (const duct of ducts) {
      const likely = duct.likely
      const fillAlpha = likely ? 0.18 : 0.08
      const strokeAlpha = likely ? 0.8 : 0.3
      const lineWidth = likely ? 1.5 : 1

      ctx.fillStyle = `rgba(34, 197, 94, ${fillAlpha})`
      ctx.strokeStyle = `rgba(34, 197, 94, ${strokeAlpha})`
      ctx.lineWidth = lineWidth
      ctx.setLineDash(likely ? [] : [4, 3])

      // Draw filled curved band by scanning x-pixels
      ctx.beginPath()
      let firstTop = 0
      let firstBase = 0

      // Top edge (left to right)
      for (let px = left; px <= right; px++) {
        const f = (px - left) / chartWidth
        const earthFt = earthSurfaceFt(f, earthCurve)
        const ductTopFt = earthFt + duct.agl_top_ft
        const yPx = Math.max(y.getPixelForValue(ductTopFt), top)
        if (px === left) { ctx.moveTo(px, yPx); firstTop = yPx }
        else ctx.lineTo(px, yPx)
      }
      // Base edge (right to left)
      for (let px = right; px >= left; px--) {
        const f = (px - left) / chartWidth
        const earthFt = earthSurfaceFt(f, earthCurve)
        const ductBaseFt = earthFt + duct.agl_base_ft
        const yPx = Math.min(y.getPixelForValue(ductBaseFt), bottom)
        ctx.lineTo(px, yPx)
        if (px === left) firstBase = yPx
      }
      ctx.closePath()
      ctx.fill()

      // Draw top and base edge lines
      ctx.beginPath()
      for (let px = left; px <= right; px += 2) {
        const f = (px - left) / chartWidth
        const earthFt = earthSurfaceFt(f, earthCurve)
        if (px === left) ctx.moveTo(px, Math.max(y.getPixelForValue(earthFt + duct.agl_top_ft), top))
        else ctx.lineTo(px, Math.max(y.getPixelForValue(earthFt + duct.agl_top_ft), top))
      }
      ctx.stroke()
      ctx.beginPath()
      for (let px = left; px <= right; px += 2) {
        const f = (px - left) / chartWidth
        const earthFt = earthSurfaceFt(f, earthCurve)
        if (px === left) ctx.moveTo(px, Math.min(y.getPixelForValue(earthFt + duct.agl_base_ft), bottom))
        else ctx.lineTo(px, Math.min(y.getPixelForValue(earthFt + duct.agl_base_ft), bottom))
      }
      ctx.stroke()
      ctx.setLineDash([])

      // Label at left edge
      const labelY = Math.max(firstTop + 12, top + 12)
      const textAlpha = likely ? 1.0 : 0.5
      ctx.fillStyle = `rgba(34, 197, 94, ${textAlpha})`
      ctx.font = likely ? "bold 10px sans-serif" : "9px sans-serif"
      const labels: Record<DuctSource, string> = { hrrr: "Duct", sounding: "Duct (sounding)", inferred: "Est. duct" }
      const units: Record<DuctSource, string> = { hrrr: "M-units", sounding: "M-units", inferred: "dN/dh÷10" }
      const prefix = labels[duct.source] || "Duct"
      const unit = units[duct.source] || "M-units"
      const tag = likely ? " — likely path" : ""
      const baseFt = Math.round(duct.base_ft)
      const topFt = Math.round(duct.top_ft)
      ctx.fillText(`${prefix} ${baseFt}–${topFt} ft (${duct.strength} ${unit})${tag}`, left + 4, labelY)
    }
    ctx.restore()
  }
}

export const ElevationProfile: ElevationProfileHook = {
  mounted(this: ElevationProfileHook) {
    const data: ProfileData = JSON.parse(this.el.dataset.profile!)
    if (!data || !data.points || data.points.length === 0) return
    this.renderChart(data)
  },

  renderChart(this: ElevationProfileHook, data: ProfileData) {
    const points = data.points
    const n = points.length - 1
    const distances = points.map(p => p.dist_km * KM2MI)
    const showFresnel = data.freq_mhz >= 1000
    const station1 = this.el.dataset.station1 || ""
    const station2 = this.el.dataset.station2 || ""

    const k = data.k_factor || (4 / 3)
    const R = 6371000
    const dTotal = (data.dist_km || 0) * 1000
    const minElev = Math.min(...points.map(p => p.elev))

    const earthCurve: EarthCurveParams = { dTotal, k, R, minElev }

    // Earth surface: smooth parabolic curve (earth curvature without terrain features)
    const earthSurface = points.map((_p, i) => {
      const f = n > 0 ? i / n : 0
      return earthSurfaceFt(f, earthCurve)
    })

    // Terrain: actual elevation + earth curvature bulge (sits on top of earth surface)
    const elevations = points.map((p, i) => {
      const f = n > 0 ? i / n : 0
      const bulge = (f * dTotal * (1 - f) * dTotal) / (2 * k * R)
      return (p.elev + bulge) * M2FT
    })

    // LOS beam: straight line from antenna A to antenna B
    const losLine = points.map(p => p.beam * M2FT)
    const fresnelLower = points.map(p => (p.beam - p.r1) * M2FT)

    // Ducts: convert to AGL relative to earth surface at endpoints
    const refElevFt = ((points[0].elev + points[n].elev) / 2) * M2FT
    const ducts: DuctBand[] = (data.ducts || []).map(d => ({
      base_ft: d.base_m_msl * M2FT,
      top_ft: d.top_m_msl * M2FT,
      agl_base_ft: d.base_m_msl * M2FT - refElevFt,
      agl_top_ft: d.top_m_msl * M2FT - refElevFt,
      strength: d.strength,
      source: d.source || "hrrr",
      likely: d.likely || false
    }))

    const allElevs = [...elevations, ...losLine, ...earthSurface]
    let minY = Math.min(...allElevs) - 40
    let maxY = Math.max(...allElevs) + 120

    for (const d of ducts) {
      const topAtMid = earthSurfaceFt(0.5, earthCurve) + d.agl_top_ft
      if (topAtMid + 40 > maxY) maxY = topAtMid + 40
    }

    const canvas = this.el.querySelector("canvas") as HTMLCanvasElement | null
    if (!canvas) return
    const ctx = canvas.getContext("2d")!

    this.chart = new Chart(ctx, {
      type: "line",
      plugins: [ductPlugin],
      data: {
        labels: distances,
        datasets: [
          {
            label: "Earth",
            data: earthSurface,
            borderColor: "#5C4033",
            backgroundColor: "rgba(60, 30, 10, 0.5)",
            borderWidth: 1,
            fill: "origin",
            pointRadius: 0,
            tension: 0.4,
            order: 5
          },
          {
            label: "Terrain",
            data: elevations,
            borderColor: "#8B6914",
            backgroundColor: "rgba(139, 105, 20, 0.4)",
            fill: "-1",
            pointRadius: 0,
            borderWidth: 1.5,
            tension: 0.1,
            order: 4
          },
          {
            label: "Line of Sight",
            data: losLine,
            borderColor: "#2563eb",
            borderWidth: 2,
            borderDash: [6, 3],
            pointRadius: 0,
            fill: false,
            order: 1
          },
          ...(showFresnel ? [
            {
              label: "Fresnel Zone",
              data: fresnelLower,
              borderColor: "rgba(239, 68, 68, 0.4)",
              backgroundColor: "rgba(239, 68, 68, 0.08)",
              borderWidth: 1,
              borderDash: [3, 3],
              pointRadius: 0,
              fill: "+1",
              order: 2
            },
            {
              label: "_fresnelBase",
              data: losLine,
              borderWidth: 0,
              pointRadius: 0,
              fill: false,
              order: 2
            }
          ] : []),
          ...(ducts.length > 0 ? [{
            label: "Duct Layer",
            data: [] as number[],
            borderColor: "rgba(34, 197, 94, 0.5)",
            backgroundColor: "rgba(34, 197, 94, 0.12)",
            borderWidth: 2,
            borderDash: [4, 3],
            pointRadius: 0,
            fill: false
          }] : [])
        ]
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        animation: { duration: 300 },
        plugins: {
          ductBands: { ducts, earthCurve },
          legend: {
            display: true,
            position: "top",
            labels: {
              boxWidth: 12,
              font: { size: 10 },
              filter: (item: { text: string }) => !item.text.startsWith("_")
            }
          },
          tooltip: {
            callbacks: {
              title: (items: { label: string }[]) => items[0].label + " mi",
              label: (item: { dataset: { label: string }; raw: number }) => {
                if (item.dataset.label.startsWith("_")) return null
                if (item.dataset.label === "Earth") return null
                return item.dataset.label + ": " + Math.round(item.raw) + " ft"
              }
            }
          }
        },
        scales: {
          x: {
            title: { display: true, text: "Distance (mi)", font: { size: 10 } },
            afterBuildTicks: (axis: { ticks: { value: number }[] }) => {
              const last = distances.length - 1
              const totalMi = distances[last]
              const step = Math.ceil(totalMi / 7) || 1
              const ticks: { value: number }[] = []
              for (let mi = 0; mi < totalMi; mi += step) {
                const idx = distances.findIndex(d => d >= mi)
                if (idx >= 0) ticks.push({ value: idx })
              }
              ticks.push({ value: last })
              axis.ticks = ticks
            },
            ticks: {
              font: { size: 9 },
              autoSkip: false,
              // Chart.js renders string[] tick labels as stacked lines, so we
              // tuck the station callsign onto the second line at the 0-mi
              // and max-mi endpoints — gives each end of the profile a
              // station identity without needing a separate axis.
              callback: (val: number) => {
                const mi = distances[val]
                if (mi === undefined) return ""
                const mileage = Math.round(mi)
                if (val === 0 && station1) return [`${mileage}`, station1]
                if (val === distances.length - 1 && station2) return [`${mileage}`, station2]
                return `${mileage}`
              }
            }
          },
          y: {
            title: { display: true, text: "Elevation (ft MSL)", font: { size: 10 } },
            ticks: { font: { size: 9 } },
            min: Math.max(0, minY),
            max: maxY
          }
        }
      }
    })
  },

  destroyed(this: ElevationProfileHook) {
    if (this.chart) {
      this.chart.destroy()
      this.chart = null
    }
  }
} as ElevationProfileHook
