import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { apiUrl, getAuthToken } from '../api'
import LeafletMapFrame from './LeafletMapFrame'

function hashHtml(value) {
  let hash = 0
  const step = Math.max(1, Math.floor(value.length / 64))
  for (let i = 0; i < value.length; i += step) {
    hash = ((hash << 5) - hash) + value.charCodeAt(i)
    hash |= 0
  }
  return `${value.length}-${Math.abs(hash)}`
}

const MapFrame = forwardRef(function MapFrame({ html, payload = null, sessionId = '' }, ref) {
  const shellRef = useRef(null)
  const iframeRef = useRef(null)
  const leafletRef = useRef(null)
  const timersRef = useRef([])
  const [isFullscreen, setIsFullscreen] = useState(false)
  const frameKey = useMemo(() => hashHtml(html || ''), [html])

  const clearTimers = useCallback(() => {
    timersRef.current.forEach((id) => window.clearTimeout(id))
    timersRef.current = []
  }, [])

  const recenterFromLayers = useCallback(() => {
    const iframe = iframeRef.current
    if (!iframe) return

    try {
      const win = iframe.contentWindow
      const doc = iframe.contentDocument || win?.document
      if (!win || !doc || !win.L) return

      const maps = Object.values(win).filter(
        (item) => item
          && typeof item.fitBounds === 'function'
          && typeof item.eachLayer === 'function'
          && typeof item.invalidateSize === 'function',
      )

      const map = maps[0]
      if (!map) return

      map.invalidateSize(true)
      const dataBounds = win.L.latLngBounds([])

      const visitLayerTree = (layer, visited, fn) => {
        if (!layer || !layer._leaflet_id || visited.has(layer._leaflet_id)) return
        visited.add(layer._leaflet_id)
        fn(layer)
        if (typeof layer.eachLayer === 'function') {
          layer.eachLayer((child) => {
            visitLayerTree(child, visited, fn)
          })
        }
      }

      const isBairroLayer = (layer) => {
        try {
          const nome = String(layer?.options?.name || layer?.layerName || '').toLowerCase()
          if (nome.includes('bairro')) return true
          const featureName = String(layer?.feature?.properties?.NOME || layer?.feature?.properties?.BAIRRO || '').toLowerCase()
          return featureName.length > 0
        } catch {
          return false
        }
      }

      const shouldIgnoreForBounds = (layer) => {
        try {
          return Boolean(layer?.options?.mesaIgnoreBounds)
        } catch {
          return false
        }
      }

      const visited = new Set()
      map.eachLayer((layer) => {
        visitLayerTree(layer, visited, (item) => {
          try {
            if (item instanceof win.L.TileLayer) return
            if (isBairroLayer(item)) return
            if (shouldIgnoreForBounds(item)) return

            if (item instanceof win.L.CircleMarker || item instanceof win.L.Marker) {
              const latlng = item.getLatLng()
              if (latlng && Number.isFinite(latlng.lat) && Number.isFinite(latlng.lng)) {
                dataBounds.extend(latlng)
              }
              return
            }

            const isContainerLayer = typeof item.eachLayer === 'function'
            if (!isContainerLayer && typeof item.getBounds === 'function') {
              const itemBounds = item.getBounds()
              if (itemBounds && typeof itemBounds.isValid === 'function' && itemBounds.isValid()) {
                dataBounds.extend(itemBounds)
              }
            }
          } catch {
            // no-op
          }
        })
      })

      if (dataBounds.isValid()) {
        const size = map.getSize ? map.getSize() : null
        const basePadding = size
          ? Math.max(34, Math.min(84, Math.round(Math.min(size.x, size.y) * 0.085)))
          : 48
        map.fitBounds(dataBounds, { padding: [basePadding, basePadding], maxZoom: 18, animate: false })
      }
    } catch {
      // no-op
    }
  }, [])

  const scheduleRecenter = useCallback(() => {
    clearTimers()
    ;[40, 180, 520, 1100].forEach((delay) => {
      const timerId = window.setTimeout(() => {
        recenterFromLayers()
      }, delay)
      timersRef.current.push(timerId)
    })
  }, [clearTimers, recenterFromLayers])

  const refreshMapSize = useCallback(() => {
    if (leafletRef.current?.fitToPayloadBounds) {
      window.setTimeout(() => {
        leafletRef.current?.fitToPayloadBounds?.(48)
      }, 120)
      return
    }
    scheduleRecenter()
  }, [scheduleRecenter])

  const compartilharContextoComIframe = useCallback(() => {
    const frameWindow = iframeRef.current?.contentWindow
    if (!frameWindow) return
    try {
      frameWindow.__MESA_MAP_CONTEXT__ = {
        apiBase: apiUrl(''),
        authToken: getAuthToken(),
      }
    } catch {
      // O iframe ainda pode usar o endpoint relativo na versão publicada.
    }
  }, [])

  const toggleFullscreen = useCallback(async (event) => {
    event.preventDefault()
    event.stopPropagation()
    const shell = shellRef.current
    if (!shell) return
    try {
      if (document.fullscreenElement === shell) {
        await document.exitFullscreen()
      } else if (typeof shell.requestFullscreen === 'function') {
        await shell.requestFullscreen()
      }
    } catch {
      // Navegadores podem bloquear fullscreen fora de interação direta.
    }
  }, [])

  useEffect(() => {
    return () => {
      clearTimers()
    }
  }, [clearTimers])

  useEffect(() => {
    function onFullscreenChange() {
      const active = document.fullscreenElement === shellRef.current
      setIsFullscreen(active)
      refreshMapSize()
    }

    document.addEventListener('fullscreenchange', onFullscreenChange)
    return () => document.removeEventListener('fullscreenchange', onFullscreenChange)
  }, [refreshMapSize])

  useImperativeHandle(ref, () => ({
    fitToPayloadBounds(padding = 48) {
      if (leafletRef.current?.fitToPayloadBounds) {
        return leafletRef.current.fitToPayloadBounds(padding)
      }
      return false
    },
    async downloadSelectionPng(selectionRect, fileName = 'mapa-recorte.png') {
      if (leafletRef.current?.downloadSelectionPng) {
        return leafletRef.current.downloadSelectionPng(selectionRect, fileName)
      }
      throw new Error('A exportacao em PNG esta disponivel apenas para mapas interativos.')
    },
    async downloadVisiblePng(fileName = 'mapa.png') {
      if (leafletRef.current?.downloadVisiblePng) {
        return leafletRef.current.downloadVisiblePng(fileName)
      }
      throw new Error('A exportacao em PNG esta disponivel apenas para mapas interativos.')
    },
  }), [])

  if (!(payload && payload.type === 'mesa_leaflet_payload') && !html) {
    return <div className="empty-box">Mapa indisponivel.</div>
  }

  const mapContent = payload && payload.type === 'mesa_leaflet_payload'
    ? (
      <LeafletMapFrame
        ref={leafletRef}
        payload={payload}
        sessionId={sessionId}
        onToggleFullscreen={toggleFullscreen}
      />
    )
    : (
      <iframe
        key={frameKey}
        ref={iframeRef}
        title="mapa"
        className="map-frame"
        srcDoc={html}
        sandbox="allow-scripts allow-same-origin allow-popups"
        allow="fullscreen"
        allowFullScreen
        onLoad={() => {
          compartilharContextoComIframe()
          scheduleRecenter()
        }}
      />
    )

  return (
    <div ref={shellRef} className={`map-frame-shell${isFullscreen ? ' is-fullscreen' : ''}`}>
      {mapContent}
    </div>
  )
})

export default MapFrame
