import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, Button, FormActions, Modal } from '@/ui'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'

/**
 * Результат AOI picker'а:
 * - bbox: `west,south,east,north` (CSV для ?bbox= query)
 * - polygon: GeoJSON Polygon (для ?polygon= query)
 *
 * Match'ит API ordinis-rest-api: `BoundingBox.parse` для bbox CSV,
 * `GeoJsonPolygon.parse` для polygon JSON. Server validates lon/lat
 * range, anti-meridian, polygon ring closure.
 */
export type AoiResult =
  | { kind: 'bbox'; bboxCsv: string; geojson: GeoJSON.Polygon }
  | { kind: 'polygon'; bboxCsv: string; geojson: GeoJSON.Polygon }

type Props = {
  isOpen: boolean
  onClose: () => void
  onApply: (result: AoiResult) => void
  /** Optional initial geometry to render and edit. */
  initial?: GeoJSON.Polygon | null
}

type DrawMode = 'idle' | 'rectangle' | 'polygon'

/**
 * AOI picker на Leaflet с custom draw logic (rectangle + polygon).
 *
 * Используем свою draw-implementation вместо `leaflet-draw` 1.0.4 —
 * последний release lib'ы был в 2017, плохо совместим с Leaflet 1.8+:
 * `_onMouseDown` сразу триггерит `_onMouseUp` на rectangle drag, и
 * прямоугольник схлопывается в точку. Custom logic — ~80 строк, полный
 * контроль, без сломанной зависимости.
 *
 * Rectangle: mousedown → mousemove preview → mouseup finalize.
 * Polygon: click добавляет вершину, dblclick закрывает (≥3 точки).
 */
export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) => {
  const { t } = useTranslation()
  const mapDivRef = useRef<HTMLDivElement | null>(null)
  const mapRef = useRef<L.Map | null>(null)
  const drawnRef = useRef<L.FeatureGroup | null>(null)
  const [shape, setShape] = useState<L.Layer | null>(null)
  const [error, setError] = useState<string | null>(null)
  const [drawMode, setDrawMode] = useState<DrawMode>('idle')
  const drawModeRef = useRef<DrawMode>('idle')
  drawModeRef.current = drawMode

  // Init map после mount + isOpen=true. RAF poll до non-zero size.
  useEffect(() => {
    if (!isOpen) return
    let cancelled = false
    let ro: ResizeObserver | null = null
    let rafId = 0

    const tryInit = () => {
      if (cancelled) return
      if (mapRef.current) return
      const div = mapDivRef.current

      if (!div || div.clientWidth === 0 || div.clientHeight === 0) {
        if (rafId < 100) {
          rafId += 1
          requestAnimationFrame(tryInit)
        }
        return
      }

      const map = L.map(div, { zoomControl: true, attributionControl: false })
        .setView([60, 90], 3)
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 18,
      }).addTo(map)

      const drawn = new L.FeatureGroup().addTo(map)
      drawnRef.current = drawn

      if (initial && initial.coordinates?.[0]?.length >= 4) {
        const ring = initial.coordinates[0].map(([lng, lat]) => L.latLng(lat, lng))
        const poly = L.polygon(ring, { color: '#1d4ed8', weight: 2 })
        drawn.addLayer(poly)
        setShape(poly)
        map.fitBounds(poly.getBounds(), { padding: [20, 20] })
      }

      attachDrawHandlers(map, drawn, drawModeRef, (newShape) => {
        setShape(newShape)
        setError(null)
        setDrawMode('idle')
      })

      mapRef.current = map
      ro = new ResizeObserver(() => {
        if (mapRef.current) mapRef.current.invalidateSize()
      })
      ro.observe(div)
    }

    tryInit()
    return () => {
      cancelled = true
      if (ro) ro.disconnect()
    }
  }, [isOpen, initial])

  // Cursor + map drag state в зависимости от drawMode.
  useEffect(() => {
    const map = mapRef.current
    if (!map) return
    const container = map.getContainer()
    if (drawMode === 'idle') {
      container.style.cursor = ''
      map.dragging.enable()
    } else {
      container.style.cursor = 'crosshair'
      // Disable map drag в rectangle mode чтобы mousedown+drag рисовал, не панил.
      if (drawMode === 'rectangle') map.dragging.disable()
      else map.dragging.enable()
    }
  }, [drawMode])

  // Cleanup при close.
  useEffect(() => {
    if (isOpen) return
    if (mapRef.current) {
      mapRef.current.remove()
      mapRef.current = null
      drawnRef.current = null
      setShape(null)
      setError(null)
      setDrawMode('idle')
    }
  }, [isOpen])

  const handleClear = () => {
    drawnRef.current?.clearLayers()
    setShape(null)
    setError(null)
  }

  const handleApply = () => {
    if (!shape) {
      setError(t('aoi.error.noShape'))
      return
    }
    const result = layerToResult(shape)
    if (!result) {
      setError(t('aoi.error.invalid'))
      return
    }
    onApply(result)
    onClose()
  }

  const toolbarBtn = (mode: DrawMode, label: string) => {
    const active = drawMode === mode
    return (
      <button
        type="button"
        onClick={() => setDrawMode(active ? 'idle' : mode)}
        className={[
          'px-3 py-1.5 text-cell font-display rounded-sm border transition-colors',
          active
            ? 'border-accent bg-accent text-white'
            : 'border-line bg-surface text-ink hover:border-ink/40',
        ].join(' ')}
      >
        {label}
      </button>
    )
  }

  return (
    <Modal
      isOpen={isOpen}
      onClose={onClose}
      title={t('aoi.dialog.title')}
      maxWidth="max-w-5xl"
      panelClassName="max-h-[calc(100vh-2rem)] my-4 flex flex-col"
      bodyClassName="flex-1 flex flex-col gap-3 overflow-hidden"
    >
      <p className="text-body text-ink-2">{t('aoi.dialog.help')}</p>

      <div className="flex flex-wrap items-center gap-2">
        {toolbarBtn('rectangle', t('aoi.dialog.drawRectangle'))}
        {toolbarBtn('polygon', t('aoi.dialog.drawPolygon'))}
        <button
          type="button"
          onClick={handleClear}
          disabled={!shape}
          className="px-3 py-1.5 text-cell font-display rounded-sm border border-line bg-surface text-ink hover:border-ink/40 disabled:opacity-40 disabled:cursor-not-allowed"
        >
          {t('aoi.dialog.clear')}
        </button>
        {drawMode === 'polygon' && (
          <span className="text-cell text-mute">
            {t('aoi.dialog.polygonHint')}
          </span>
        )}
      </div>

      <div
        ref={mapDivRef}
        className="rounded-sm border border-line shrink-0"
        style={{ display: 'block', width: '100%', height: 520, minHeight: 520 }}
        aria-label={t('aoi.dialog.mapAriaLabel')}
      />

      {error && (
        <Alert variant="error" title={t('error.failed')}>
          {error}
        </Alert>
      )}

      <FormActions>
        <Button type="button" variant="ghost" onClick={onClose}>
          {t('form.cancel')}
        </Button>
        <Button type="button" variant="primary" onClick={handleApply} disabled={!shape}>
          {t('aoi.dialog.apply')}
        </Button>
      </FormActions>
    </Modal>
  )
}

/**
 * Custom draw handlers: подписываемся на map events, рисуем в зависимости
 * от текущего drawMode (читаемого через ref чтобы не пересоздавать
 * listeners на каждое изменение state).
 */
const attachDrawHandlers = (
  map: L.Map,
  drawn: L.FeatureGroup,
  modeRef: React.RefObject<DrawMode>,
  onComplete: (layer: L.Layer) => void,
) => {
  const STYLE = { color: '#1d4ed8', weight: 2 }

  // Rectangle: mousedown→start, mousemove→preview, mouseup→finalize.
  let rectStart: L.LatLng | null = null
  let rectPreview: L.Rectangle | null = null

  map.on('mousedown', (e) => {
    if (modeRef.current !== 'rectangle') return
    rectStart = (e as L.LeafletMouseEvent).latlng
  })

  map.on('mousemove', (e) => {
    if (modeRef.current !== 'rectangle' || !rectStart) return
    const cur = (e as L.LeafletMouseEvent).latlng
    if (rectPreview) {
      rectPreview.setBounds(L.latLngBounds(rectStart, cur))
    } else {
      rectPreview = L.rectangle(L.latLngBounds(rectStart, cur), STYLE).addTo(drawn)
    }
  })

  map.on('mouseup', () => {
    if (modeRef.current !== 'rectangle' || !rectStart) return
    if (rectPreview) {
      // Finalize: replace previous shape с новым rectangle.
      const finalRect = rectPreview
      drawn.clearLayers()
      drawn.addLayer(finalRect)
      onComplete(finalRect)
    }
    rectStart = null
    rectPreview = null
  })

  // Polygon: click→add vertex, dblclick→close, mousemove→preview line.
  let polyPoints: L.LatLng[] = []
  let polyPreview: L.Polyline | null = null
  let polyMouseLine: L.Polyline | null = null

  map.on('click', (e) => {
    if (modeRef.current !== 'polygon') return
    const pt = (e as L.LeafletMouseEvent).latlng
    polyPoints.push(pt)
    if (polyPreview) {
      polyPreview.setLatLngs(polyPoints)
    } else {
      polyPreview = L.polyline(polyPoints, STYLE).addTo(drawn)
    }
  })

  map.on('mousemove', (e) => {
    if (modeRef.current !== 'polygon' || polyPoints.length === 0) return
    const cur = (e as L.LeafletMouseEvent).latlng
    const last = polyPoints[polyPoints.length - 1]
    if (polyMouseLine) {
      polyMouseLine.setLatLngs([last, cur])
    } else {
      polyMouseLine = L.polyline([last, cur], { ...STYLE, dashArray: '4 4' }).addTo(drawn)
    }
  })

  map.on('dblclick', () => {
    if (modeRef.current !== 'polygon') return
    if (polyPoints.length < 3) return
    // Finalize: replace с финальным polygon.
    const finalPoly = L.polygon(polyPoints, STYLE)
    drawn.clearLayers()
    drawn.addLayer(finalPoly)
    onComplete(finalPoly)
    polyPoints = []
    polyPreview = null
    polyMouseLine = null
  })

  // Disable Leaflet's default zoom-on-doubleclick — конфликтует с polygon close.
  map.doubleClickZoom.disable()
}

/**
 * Конвертирует Leaflet layer (rectangle / polygon) в bbox CSV +
 * GeoJSON Polygon. Возвращает null если geometry invalid.
 */
const layerToResult = (layer: L.Layer): AoiResult | null => {
  if (layer instanceof L.Rectangle) {
    const b = layer.getBounds()
    const bbox = `${b.getWest()},${b.getSouth()},${b.getEast()},${b.getNorth()}`
    return { kind: 'bbox', bboxCsv: bbox, geojson: rectangleToGeoJSON(b) }
  }
  if (layer instanceof L.Polygon) {
    const latlngsRaw = layer.getLatLngs()
    const ring = (Array.isArray(latlngsRaw[0]) ? latlngsRaw[0] : latlngsRaw) as L.LatLng[]
    if (!ring || ring.length < 3) return null
    const closed = [...ring, ring[0]]
    const coords = closed.map((p) => [p.lng, p.lat] as [number, number])
    const b = layer.getBounds()
    const bbox = `${b.getWest()},${b.getSouth()},${b.getEast()},${b.getNorth()}`
    return {
      kind: 'polygon',
      bboxCsv: bbox,
      geojson: { type: 'Polygon', coordinates: [coords] },
    }
  }
  return null
}

const rectangleToGeoJSON = (b: L.LatLngBounds): GeoJSON.Polygon => {
  const w = b.getWest()
  const s = b.getSouth()
  const e = b.getEast()
  const n = b.getNorth()
  return {
    type: 'Polygon',
    coordinates: [
      [
        [w, s],
        [e, s],
        [e, n],
        [w, n],
        [w, s],
      ],
    ],
  }
}
