import { useRef, useEffect, useState } from 'react'

interface PriceCandle {
  time: string
  open: number
  high: number
  low: number
  close: number
  volume: number
}

interface LinePoint {
  time: string
  value: number
}

interface ChartEvent {
  date: string
  title: string
  level: string
  category?: string
  impact_score?: number
  end_date?: string | null
  description?: string
}

interface TheoPoint {
  date: string
  cumulative_pips: number
  contributions: { template_name: string; event_name: string; event_date: string; pips: number; decay_factor: number }[]
}

interface Props {
  priceData:    PriceCandle[]
  indicators:   Record<string, LinePoint[]>
  events?:      ChartEvent[]
  height?:      number
  chartType?:   'candles' | 'line'
  onDateHover?: (date: string | null) => void
  theoryCurve?: TheoPoint[]
  /** Called once the chart is ready with (dateToCoord, canvasPageLeft) — lets external components align with the chart's x-axis */
  onChartReady?: (dateToCoord: (d: string) => number | null, canvasPageLeft: number) => void
}

export type { TheoPoint }

const MA_COLORS: Record<string, string> = {
  ma20:  '#f59e0b',
  ma50:  '#3b82f6',
  ma100: '#22d3ee',
  ma200: '#a855f7',
}

// Every overlay a user can show/hide independently. 'bb' toggles the upper+lower band pair
// together (they're one visual concept); 'volatility' gets its own price scale since
// annualized % lives on a totally different axis than the price series.
type OverlayKey = 'ma20' | 'ma50' | 'ma100' | 'ma200' | 'bb' | 'volatility'
const OVERLAY_KEYS: OverlayKey[] = ['ma20', 'ma50', 'ma100', 'ma200', 'bb', 'volatility']
const OVERLAY_META: Record<OverlayKey, { label: string; color: string; dashed?: boolean; defaultVisible: boolean }> = {
  ma20:       { label: 'MA20',       color: MA_COLORS.ma20,  defaultVisible: true },
  ma50:       { label: 'MA50',       color: MA_COLORS.ma50,  defaultVisible: true },
  ma100:      { label: 'MA100',      color: MA_COLORS.ma100, defaultVisible: true },
  ma200:      { label: 'MA200',      color: MA_COLORS.ma200, defaultVisible: true },
  bb:         { label: 'BB(20,2)',   color: '#94a3b8', dashed: true, defaultVisible: true },
  volatility: { label: 'Volatilité', color: '#ec4899', defaultVisible: false },
}

const CAT_COLORS: Record<string, string> = {
  event_calendar: '#f59e0b',
  geopolitical:   '#ef4444',
  fundamental:    '#10b981',
  report:         '#3b82f6',
  sentiment:      '#8b5cf6',
  technical:      '#06b6d4',
}

const CAT_LABELS: Record<string, string> = {
  event_calendar: 'Cal.',
  geopolitical:   'Géo.',
  fundamental:    'Fund.',
  report:         'Report',
  sentiment:      'Sent.',
  technical:      'Tech.',
}

export default function InstrumentChart({ priceData, indicators, events = [], height = 420, chartType = 'candles', onDateHover, theoryCurve, onChartReady }: Props) {
  const containerRef    = useRef<HTMLDivElement>(null)
  const cleanupRef      = useRef<(() => void) | null>(null)
  const onHoverRef      = useRef(onDateHover)
  const onChartReadyRef = useRef(onChartReady)
  const chartRef        = useRef<any>(null)
  const seriesRefs      = useRef<Partial<Record<OverlayKey, any[]>>>({})

  // Which overlays have any data at all for THIS instrument — only these get a toggle pill.
  const availableOverlays = OVERLAY_KEYS.filter(k =>
    k === 'bb' ? (indicators.bb_upper?.length && indicators.bb_lower?.length) : indicators[k]?.length
  )

  const [visible, setVisible] = useState<Record<OverlayKey, boolean>>(() =>
    Object.fromEntries(OVERLAY_KEYS.map(k => [k, OVERLAY_META[k].defaultVisible])) as Record<OverlayKey, boolean>
  )
  // Read at chart-(re)build time without forcing a rebuild on every toggle — toggling
  // itself is handled cheaply below via each series' own applyOptions({visible}).
  const visibleRef = useRef(visible)
  useEffect(() => { visibleRef.current = visible }, [visible])

  const toggleOverlay = (key: OverlayKey) => {
    setVisible(v => {
      const next = { ...v, [key]: !v[key] }
      seriesRefs.current[key]?.forEach(s => s.applyOptions({ visible: next[key] }))
      if (key === 'volatility') {
        chartRef.current?.priceScale('left').applyOptions({ visible: next[key] })
      }
      return next
    })
  }

  // Keep callback refs fresh without triggering chart rebuild
  useEffect(() => { onHoverRef.current = onDateHover }, [onDateHover])
  useEffect(() => { onChartReadyRef.current = onChartReady }, [onChartReady])

  useEffect(() => {
    cleanupRef.current?.()
    cleanupRef.current = null

    if (!containerRef.current || !priceData.length) return

    let active = true

    import('lightweight-charts').then((lc: any) => {
      if (!active || !containerRef.current) return

      const { createChart, CrosshairMode, ColorType, LineStyle } = lc

      const chart = createChart(containerRef.current, {
        width:  containerRef.current.clientWidth,
        height,
        layout: {
          background: { type: ColorType.Solid, color: 'transparent' },
          textColor: '#64748b',
          fontSize: 11,
        },
        grid: {
          vertLines: { color: 'rgba(255,255,255,0.04)' },
          horzLines: { color: 'rgba(255,255,255,0.04)' },
        },
        crosshair: { mode: CrosshairMode.Normal },
        rightPriceScale: { borderColor: 'rgba(255,255,255,0.1)' },
        timeScale: {
          borderColor: 'rgba(255,255,255,0.1)',
          timeVisible: true,
          secondsVisible: false,
          fixLeftEdge: true,
          fixRightEdge: true,
        },
        handleScroll: true,
        handleScale: true,
      })
      chartRef.current = chart
      seriesRefs.current = {}

      // Volume (bottom 18%)
      const totalVol = priceData.reduce((s, d) => s + (d.volume || 0), 0)
      if (totalVol > 0) {
        const volSeries = chart.addHistogramSeries({
          color: 'rgba(148,163,184,0.2)',
          priceFormat: { type: 'volume' },
          priceScaleId: 'volume',
        })
        chart.priceScale('volume').applyOptions({ scaleMargins: { top: 0.82, bottom: 0 } })
        volSeries.setData(priceData.map(d => ({
          time:  d.time,
          value: d.volume,
          color: d.close >= d.open ? 'rgba(16,185,129,0.25)' : 'rgba(239,68,68,0.25)',
        })))
      }

      // Prix principal — chandeliers ou courbe lisse
      if (chartType === 'line') {
        const areaSeries = chart.addAreaSeries({
          lineColor:              '#3b82f6',
          topColor:               'rgba(59,130,246,0.18)',
          bottomColor:            'rgba(59,130,246,0.01)',
          lineWidth:              2,
          priceLineVisible:       true,
          lastValueVisible:       true,
          crosshairMarkerVisible: true,
          crosshairMarkerRadius:  4,
        })
        areaSeries.setData(priceData.map(d => ({ time: d.time as any, value: d.close })))
      } else {
        const candleSeries = chart.addCandlestickSeries({
          upColor:         '#10b981',
          downColor:       '#ef4444',
          borderUpColor:   '#10b981',
          borderDownColor: '#ef4444',
          wickUpColor:     '#6ee7b7',
          wickDownColor:   '#fca5a5',
        })
        candleSeries.setData(priceData)
      }

      // MA lines — each individually toggleable
      for (const [key, color] of Object.entries(MA_COLORS)) {
        const data = indicators[key]
        if (data?.length) {
          const series = chart.addLineSeries({
            color,
            lineWidth: key === 'ma200' ? 1.5 : 1,
            priceLineVisible: false,
            lastValueVisible: true,
            crosshairMarkerVisible: false,
            visible: visibleRef.current[key as OverlayKey],
          })
          series.setData(data)
          seriesRefs.current[key as OverlayKey] = [series]
        }
      }

      // Bollinger Bands — upper+lower toggle together as one 'bb' overlay
      if (indicators.bb_upper?.length && indicators.bb_lower?.length) {
        const bbOpts = {
          color: 'rgba(148,163,184,0.32)',
          lineWidth: 1,
          lineStyle: LineStyle.Dashed,
          priceLineVisible: false,
          lastValueVisible: false,
          crosshairMarkerVisible: false,
          visible: visibleRef.current.bb,
        }
        const bbUpper = chart.addLineSeries(bbOpts)
        bbUpper.setData(indicators.bb_upper)
        const bbLower = chart.addLineSeries(bbOpts)
        bbLower.setData(indicators.bb_lower)
        seriesRefs.current.bb = [bbUpper, bbLower]
      }

      // Volatilité réalisée (annualisée, %) — échelle dédiée, cachée par défaut. Must use
      // the built-in 'left' scale id, not a custom string: lightweight-charts treats any
      // non-'left'/'right' priceScaleId as an "overlay" scale, which never renders a visible
      // axis and ignores `visible` entirely (confirmed in its own typings) — that silently
      // killed both the axis labels and the hover value tag on this series.
      if (indicators.volatility?.length) {
        chart.priceScale('left').applyOptions({
          visible: visibleRef.current.volatility,
          borderColor: 'rgba(236,72,153,0.25)',
          textColor: '#ec4899',
          // bottom: 0.08, not 0.02 — lightweight-charts renders its mandatory attribution
          // logo fixed in the bottom-left corner (layout.attributionLogo, required by the
          // library's free-tier license unless attributed elsewhere), and a tight margin
          // put this axis's own labels right on top of it.
          scaleMargins: { top: 0.72, bottom: 0.08 },
        })
        const volatilitySeries = chart.addLineSeries({
          color: '#ec4899',
          lineWidth: 1,
          priceScaleId: 'left',
          title: 'Vol. réalisée %',
          priceLineVisible: false,
          lastValueVisible: true,
          crosshairMarkerVisible: true,
          crosshairMarkerRadius: 3,
          visible: visibleRef.current.volatility,
        })
        volatilitySeries.setData(indicators.volatility)
        seriesRefs.current.volatility = [volatilitySeries]
      }

      // ── Theoretical curve — left scale (pips) ─────────────────────────────
      if (theoryCurve?.length) {
        chart.priceScale('left').applyOptions({
          visible: true,
          borderColor: 'rgba(167,139,250,0.25)',
          textColor: '#a78bfa',
          scaleMargins: { top: 0.1, bottom: 0.1 },
        })
        const theorySeries = chart.addLineSeries({
          color: 'rgba(167,139,250,0.75)',
          lineWidth: 1.5,
          priceScaleId: 'left',
          title: 'Δ pips théorique',
          priceLineVisible: false,
          lastValueVisible: true,
          crosshairMarkerVisible: true,
          crosshairMarkerRadius: 3,
        })
        const theoryData = theoryCurve
          .filter(p => p.contributions.length > 0)
          .map(p => ({ time: p.date as any, value: p.cumulative_pips }))
        if (theoryData.length) theorySeries.setData(theoryData)
      }

      chart.timeScale().fitContent()

      // Expose coordinate mapping so siblings can align with the chart x-axis
      if (onChartReadyRef.current && containerRef.current) {
        const canvasLeft = containerRef.current.getBoundingClientRect().left
        onChartReadyRef.current((d: string) => chart.timeScale().timeToCoordinate(d as any), canvasLeft)
      }

      // ── Star overlay — ★ icons positioned via chart coordinate API ────────
      const candleMap: Record<string, number> = {}
      for (const c of priceData) candleMap[c.time] = c.high

      const starsOverlay = document.createElement('div')
      starsOverlay.style.cssText = [
        'position:absolute', 'top:0', 'left:0', 'width:100%', 'height:100%',
        'pointer-events:none', 'overflow:hidden', 'z-index:10',
      ].join(';')
      containerRef.current!.appendChild(starsOverlay)

      const renderStars = () => {
        if (!active) return
        starsOverlay.innerHTML = ''
        const cw = containerRef.current?.clientWidth ?? 0

        // ── Collect visible events with their x coords ──────────────────────
        type Placed = { xCoord: number; halfW: number; ev: typeof events[0] }
        const visible: Placed[] = []
        for (const ev of events) {
          const xCoord = chart.timeScale().timeToCoordinate(ev.date as any)
          if (xCoord === null || xCoord < 0 || xCoord > cw) continue
          const title = ev.title.slice(0, 20)
          const halfW = Math.max(30, title.length * 6 + 12) // approx px half-width
          visible.push({ xCoord, halfW, ev })
        }

        // ── Greedy row placement (up to 4 rows, 20px apart) ─────────────────
        const ROW_H   = 20   // pixels between rows
        const MAX_ROWS = 4
        // rowEnds[r] = rightmost x already occupied in row r
        const rowEnds: number[] = Array(MAX_ROWS).fill(-Infinity)

        // Sort by x so greedy works left-to-right
        visible.sort((a, b) => a.xCoord - b.xCoord)

        for (const { xCoord, halfW, ev } of visible) {
          const left  = xCoord - halfW
          const right = xCoord + halfW + 4

          // Find first row where the label fits (no overlap)
          let row = 0
          for (let r = 0; r < MAX_ROWS; r++) {
            if (rowEnds[r] <= left) { row = r; break }
            if (r === MAX_ROWS - 1) row = r // last resort: use bottom row
          }
          rowEnds[row] = right

          const yBase = 6 + row * ROW_H

          const cat   = ev.category ?? 'fundamental'
          const color = CAT_COLORS[cat] ?? '#94a3b8'
          const score = ev.impact_score ?? 0.5
          const sz    = score >= 0.8 ? 16 : score >= 0.6 ? 13 : 11

          const wrap = document.createElement('div')
          wrap.style.cssText = [
            `position:absolute`, `left:${xCoord}px`, `top:${yBase}px`,
            `transform:translateX(-50%)`,
            `display:flex`, `align-items:center`, `gap:3px`,
            `pointer-events:auto`, `cursor:default`,
          ].join(';')
          wrap.title = `${ev.title}\n${ev.date}${ev.end_date ? ' → ' + ev.end_date : ''}`

          const star = document.createElement('span')
          star.style.cssText = [
            `font-size:${sz}px`, `color:${color}`, `line-height:1`,
            `filter:drop-shadow(0 0 3px ${color}80)`,
          ].join(';')
          star.textContent = '★'

          const lbl = document.createElement('span')
          lbl.style.cssText = [
            `font-size:10px`, `font-family:ui-monospace,monospace`,
            `color:${color}`, `white-space:nowrap`,
            `text-shadow:0 1px 4px #000,0 0 6px #000`,
            `line-height:1`, `max-width:100px`,
            `overflow:hidden`, `text-overflow:ellipsis`,
            `font-weight:600`,
          ].join(';')
          lbl.textContent = ev.title.slice(0, 20)

          wrap.appendChild(star)
          wrap.appendChild(lbl)
          starsOverlay.appendChild(wrap)
        }
      }

      renderStars()
      chart.timeScale().subscribeVisibleLogicalRangeChange(renderStars)

      // Crosshair → date callback
      chart.subscribeCrosshairMove((param: any) => {
        if (param?.time && typeof param.time === 'string') {
          onHoverRef.current?.(param.time)
        } else if (!param?.point) {
          onHoverRef.current?.(null) // mouse left chart
        }
      })

      const ro = new ResizeObserver(() => {
        if (containerRef.current) chart.applyOptions({ width: containerRef.current.clientWidth })
      })
      ro.observe(containerRef.current)

      cleanupRef.current = () => {
        ro.disconnect()
        chart.timeScale().unsubscribeVisibleLogicalRangeChange(renderStars)
        if (starsOverlay.parentNode) starsOverlay.parentNode.removeChild(starsOverlay)
        chart.remove()
        chartRef.current = null
        seriesRefs.current = {}
        onChartReadyRef.current?.(null as any, 0)
      }
    })

    return () => {
      active = false
      cleanupRef.current?.()
      cleanupRef.current = null
    }
    // `visible` intentionally excluded — toggling is handled directly via series refs
    // above, without tearing down and rebuilding the whole chart.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [priceData, indicators, events, height, chartType, theoryCurve])

  return (
    <div className="bg-dark-900/60 rounded-xl border border-slate-700/40 overflow-hidden">
      <div className="flex items-center gap-2 px-4 py-2 border-b border-slate-700/30 text-xs flex-wrap">
        {availableOverlays.map(key => {
          const meta = OVERLAY_META[key]
          const on = visible[key]
          return (
            <button
              key={key}
              onClick={() => toggleOverlay(key)}
              title={on ? `Masquer ${meta.label}` : `Afficher ${meta.label}`}
              className="flex items-center gap-1.5 px-1.5 py-0.5 rounded transition-opacity hover:bg-slate-700/30"
              style={{ opacity: on ? 1 : 0.35 }}
            >
              <span
                className={meta.dashed ? 'inline-block w-5 border-t border-dashed' : 'inline-block w-5 h-0.5 rounded'}
                style={meta.dashed ? { borderColor: meta.color } : { background: meta.color }}
              />
              <span className="text-slate-400">{meta.label}</span>
            </button>
          )
        })}
        {theoryCurve?.some(p => p.contributions.length > 0) && (
          <span className="flex items-center gap-1.5 px-1.5" style={{ color: '#a78bfa' }}>
            <span className="inline-block w-5 h-0.5 rounded" style={{ background: '#a78bfa' }} />
            Théorie (pips)
          </span>
        )}
        <span className="ml-auto flex items-center gap-2.5">
          {Object.entries(CAT_COLORS).map(([cat, c]) => (
            <span key={cat} className="flex items-center gap-0.5" style={{ color: c }}>
              <span style={{ fontSize: 10 }}>★</span>
              <span style={{ fontSize: 9 }}>{CAT_LABELS[cat]}</span>
            </span>
          ))}
        </span>
      </div>
      {!priceData.length ? (
        <div className="flex items-center justify-center" style={{ height }}>
          <span className="text-slate-500 text-sm">Chargement...</span>
        </div>
      ) : (
        <div ref={containerRef} style={{ height: `${height}px`, width: '100%', position: 'relative' }} />
      )}
    </div>
  )
}
