'use client'

import { useEffect, useRef } from 'react'
import type L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet-draw/dist/leaflet.draw.css'

export type MapDrawGeoJSON = Record<string, unknown> | null

type MapDrawProps = {
  center?: [number, number]
  zoom?: number
  height?: string | number
  onAreaChange?: (geojson: MapDrawGeoJSON, area?: number) => void
  initialGeoJson?: MapDrawGeoJSON
  singleShape?: boolean
}

export default function MapDraw({
  center = [35.6892, 51.389],
  zoom = 13,
  height = '400px',
  onAreaChange,
  initialGeoJson,
  singleShape = true
}: MapDrawProps) {
  const mapRef = useRef<HTMLDivElement>(null)
  const mapInstanceRef = useRef<L.Map | null>(null)
  const drawnItemsRef = useRef<L.FeatureGroup | null>(null)
  const drawControlRef = useRef<L.Control.Draw | null>(null)

  useEffect(() => {
    if (typeof window === 'undefined' || !mapRef.current) return

    let isMounted = true
    let cleanupFn: (() => void) | null = null

    const initMap = async () => {
      const L = (await import('leaflet')).default
      await import('leaflet-draw')

      if (!isMounted || !mapRef.current || mapInstanceRef.current) return

      const map = L.map(mapRef.current).setView(center, zoom)

      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap'
      }).addTo(map)

      const drawnItems = L.featureGroup().addTo(map)
      drawnItemsRef.current = drawnItems

      if (initialGeoJson && drawnItems) {
        try {
          const layer = L.geoJSON(initialGeoJson as never).eachLayer(l => {
            drawnItems.addLayer(l)
          })
          if (layer.getBounds && layer.getBounds().isValid()) {
            map.fitBounds(layer.getBounds())
          }
        } catch {
          // ignore invalid geojson
        }
      }

      const drawControl = new L.Control.Draw({
        position: 'topright',
        draw: {
          polygon: { shapeOptions: { color: '#3388ff' } },
          rectangle: { shapeOptions: { color: '#3388ff' } },
          circle: false,
          circlemarker: false,
          marker: false,
          polyline: false
        },
        edit: { featureGroup: drawnItems, remove: true }
      })

      map.addControl(drawControl)
      drawControlRef.current = drawControl

      const getGeoJsonFromDrawn = (): MapDrawGeoJSON => {
        const geojson = drawnItems.toGeoJSON()

        if (geojson.type === 'FeatureCollection' && geojson.features.length > 0) {
          return geojson.features[0] as unknown as MapDrawGeoJSON ?? null 
        }

        return null
      }

      const getArea = (layer: L.Layer): number | undefined => {
        const poly = layer as L.Polygon
        if (poly.getLatLngs && typeof L.GeometryUtil?.geodesicArea === 'function') {
          const latlngs = poly.getLatLngs() as L.LatLng[]
          return L.GeometryUtil.geodesicArea(Array.isArray(latlngs[0]) ? latlngs[0] : latlngs)
        }

        return undefined
      }

      const emitChange = () => {
        const geojson = getGeoJsonFromDrawn()
        let area: number | undefined

        drawnItems.eachLayer(layer => {
          const a = getArea(layer)
          if (a !== undefined) area = (area ?? 0) + a
        })

        onAreaChange?.(geojson, area)
      }

      const onCreated = (e: L.LeafletEvent) => {
        const event = e as L.DrawEvents.Created
        const layer = event.layer

        if (singleShape) {
          drawnItems.clearLayers()
        }
        drawnItems.addLayer(layer)
        emitChange()
      }

      const onEdited = () => emitChange()
      const onDeleted = () => emitChange()

      map.on(L.Draw.Event.CREATED, onCreated)
      map.on(L.Draw.Event.EDITED, onEdited)
      map.on(L.Draw.Event.DELETED, onDeleted)

      mapInstanceRef.current = map

      if (initialGeoJson && drawnItems.getLayers().length > 0) {
        emitChange()
      }

      cleanupFn = () => {
        map.off(L.Draw.Event.CREATED, onCreated)
        map.off(L.Draw.Event.EDITED, onEdited)
        map.off(L.Draw.Event.DELETED, onDeleted)
        map.removeControl(drawControl)
        map.remove()
        mapInstanceRef.current = null
        drawnItemsRef.current = null
        drawControlRef.current = null
      }
    }

    initMap()

    return () => {
      isMounted = false
      if (cleanupFn) {
        cleanupFn()
      } else if (mapInstanceRef.current) {
        mapInstanceRef.current.remove()
        mapInstanceRef.current = null
        drawnItemsRef.current = null
        drawControlRef.current = null
      }
    }
  }, [])

  return (
    <div
      ref={mapRef}
      style={{ height: typeof height === 'number' ? `${height}px` : height, width: '100%', borderRadius: 8 }}
      className='overflow-hidden'
    />
  )
}
