<template>
  <div ref="containerRef" class="w-full h-full">
    <MapboxMap
      :key="mapId"
      :map-id="mapId"
      style="height: 100%; width: 100%;"
      :options="mapOptions"
      @load="onMapCreated"
    >
      <MapboxNavigationControl position="top-right" />
    </MapboxMap>
  </div>
</template>

<script setup lang="ts">
import type { PropType } from 'vue'
import { getCurrentInstance } from 'vue'
import type { Map as MapboxMapType } from 'mapbox-gl'
import { LngLatBounds, Popup } from 'mapbox-gl'
import { GetAutoRouteDocument, GetRailRouteDocument } from '~/composables/graphql/public/geo-generated'

type RouteStage = {
  fromLat?: number | null
  fromLon?: number | null
  fromName?: string | null
  toLat?: number | null
  toLon?: number | null
  toName?: string | null
  transportType?: string | null
}

type OrderRoute = {
  uuid: string
  name: string
  status?: string
  stages: RouteStage[]
}

const props = defineProps({
  routes: {
    type: Array as PropType<OrderRoute[]>,
    default: () => []
  },
  selectedOrderId: {
    type: String as PropType<string | null>,
    default: null
  }
})

const emit = defineEmits<{
  (e: 'select-order', uuid: string): void
}>()

const { execute } = useGraphQL()
const containerRef = ref<HTMLElement | null>(null)
const mapRef = ref<MapboxMapType | null>(null)
const isMapReady = ref(false)
const didFitBounds = ref(false)

const instanceId = getCurrentInstance()?.uid || Math.floor(Math.random() * 100000)
const mapId = computed(() => `orders-map-${instanceId}`)

const statusColors: Record<string, string> = {
  pending: '#f59e0b',
  processing: '#3b82f6',
  in_transit: '#06b6d4',
  delivered: '#22c55e',
  cancelled: '#ef4444'
}

const routeGeometries = ref<Map<string, [number, number][]>>(new Map())

const allCoordinates = computed(() => {
  const coords: [number, number][] = []
  props.routes.forEach(order => {
    order.stages.forEach(stage => {
      if (stage.fromLat && stage.fromLon) {
        coords.push([stage.fromLon, stage.fromLat])
      }
      if (stage.toLat && stage.toLon) {
        coords.push([stage.toLon, stage.toLat])
      }
    })
  })
  return coords
})

const mapCenter = computed<[number, number]>(() => {
  const coords = allCoordinates.value
  if (!coords.length) return [50, 50]
  const avgLng = coords.reduce((sum, c) => sum + c[0], 0) / coords.length
  const avgLat = coords.reduce((sum, c) => sum + c[1], 0) / coords.length
  return [avgLng, avgLat]
})

const mapOptions = computed(() => ({
  style: 'mapbox://styles/mapbox/satellite-streets-v12',
  center: mapCenter.value,
  zoom: 3
}))

const normalizeCoordinates = (geometry: unknown): [number, number][] | null => {
  if (!Array.isArray(geometry)) return null
  const coords = geometry
    .filter(point => Array.isArray(point) && typeof point[0] === 'number' && typeof point[1] === 'number')
    .map(point => [point[0], point[1]] as [number, number])
  return coords.length > 1 ? coords : null
}

const fetchRouteGeometry = async (stage: RouteStage): Promise<[number, number][] | null> => {
  if (!stage.fromLat || !stage.fromLon || !stage.toLat || !stage.toLon) return null

  if (stage.transportType === 'auto' || stage.transportType === 'rail') {
    try {
      const RouteDocument = stage.transportType === 'auto' ? GetAutoRouteDocument : GetRailRouteDocument
      const routeField = stage.transportType === 'auto' ? 'autoRoute' : 'railRoute'

      const routeData = await execute(RouteDocument, {
        fromLat: stage.fromLat,
        fromLon: stage.fromLon,
        toLat: stage.toLat,
        toLon: stage.toLon
      }, 'public', 'geo') as Record<string, any>

      const geometry = routeData?.[routeField]?.geometry
      if (typeof geometry === 'string') {
        return normalizeCoordinates(JSON.parse(geometry))
      }
      return normalizeCoordinates(geometry)
    } catch (error) {
      console.error('Failed to load route geometry:', error)
    }
  }

  return [
    [stage.fromLon, stage.fromLat],
    [stage.toLon, stage.toLat]
  ]
}

let loadCounter = 0
const loadDetailedRoutes = async () => {
  if (!process.client) return

  const requestId = ++loadCounter
  const newGeometries = new Map<string, [number, number][]>()

  for (const order of props.routes) {
    const allCoords: [number, number][] = []

    for (const stage of order.stages) {
      const coords = await fetchRouteGeometry(stage)
      if (coords) {
        allCoords.push(...coords)
      }
    }

    if (allCoords.length > 0) {
      newGeometries.set(order.uuid, allCoords)
    }
  }

  if (requestId !== loadCounter) return

  routeGeometries.value = newGeometries
  updateRoutesSource()
  fitMapToRoutes()
}

const routeLineFeatures = computed<GeoJSON.FeatureCollection<GeoJSON.LineString>>(() => {
  const features: GeoJSON.Feature<GeoJSON.LineString>[] = []

  props.routes.forEach(order => {
    const coords = routeGeometries.value.get(order.uuid)
    if (coords && coords.length > 1) {
      const isSelected = props.selectedOrderId === order.uuid
      features.push({
        type: 'Feature',
        properties: {
          orderId: order.uuid,
          orderName: order.name,
          status: order.status,
          color: statusColors[order.status || ''] || '#6b7280',
          isSelected,
          lineWidth: isSelected ? 6 : 4,
          opacity: isSelected ? 1 : 0.7
        },
        geometry: {
          type: 'LineString',
          coordinates: coords
        }
      })
    }
  })

  // Sort: selected on top
  features.sort((a, b) => {
    if (a.properties?.isSelected) return 1
    if (b.properties?.isSelected) return -1
    return 0
  })

  return { type: 'FeatureCollection', features }
})

const markersFeatureCollection = computed<GeoJSON.FeatureCollection<GeoJSON.Point>>(() => {
  const points: GeoJSON.Feature<GeoJSON.Point>[] = []
  const seen = new Set<string>()

  props.routes.forEach(order => {
    const isSelected = props.selectedOrderId === order.uuid
    order.stages.forEach(stage => {
      if (stage.fromLat && stage.fromLon) {
        const key = `${order.uuid}-from-${stage.fromLon},${stage.fromLat}`
        if (!seen.has(key)) {
          seen.add(key)
          points.push({
            type: 'Feature',
            properties: {
              orderId: order.uuid,
              orderName: order.name,
              name: stage.fromName || 'Start',
              color: statusColors[order.status || ''] || '#6b7280',
              isSelected,
              radius: isSelected ? 10 : 6
            },
            geometry: { type: 'Point', coordinates: [stage.fromLon, stage.fromLat] }
          })
        }
      }
      if (stage.toLat && stage.toLon) {
        const key = `${order.uuid}-to-${stage.toLon},${stage.toLat}`
        if (!seen.has(key)) {
          seen.add(key)
          points.push({
            type: 'Feature',
            properties: {
              orderId: order.uuid,
              orderName: order.name,
              name: stage.toName || 'End',
              color: statusColors[order.status || ''] || '#6b7280',
              isSelected,
              radius: isSelected ? 10 : 6
            },
            geometry: { type: 'Point', coordinates: [stage.toLon, stage.toLat] }
          })
        }
      }
    })
  })

  // Sort: selected on top
  points.sort((a, b) => {
    if (a.properties?.isSelected) return 1
    if (b.properties?.isSelected) return -1
    return 0
  })

  return { type: 'FeatureCollection', features: points }
})

const updateRoutesSource = () => {
  const map = mapRef.value
  if (!map) return

  const source = map.getSource('orders-routes') as mapboxgl.GeoJSONSource | undefined
  if (source) source.setData(routeLineFeatures.value)

  const markersSource = map.getSource('orders-markers') as mapboxgl.GeoJSONSource | undefined
  if (markersSource) markersSource.setData(markersFeatureCollection.value)
}

const fitMapToRoutes = () => {
  const map = mapRef.value
  if (!map || didFitBounds.value) return

  const coords = allCoordinates.value
  if (coords.length === 0) return

  const bounds = coords.reduce(
    (acc, coord) => acc.extend(coord),
    new LngLatBounds(coords[0], coords[0])
  )

  map.fitBounds(bounds, { padding: 50, maxZoom: 8 })
  didFitBounds.value = true
}

const flyToOrder = (orderId: string) => {
  const map = mapRef.value
  if (!map) return

  const order = props.routes.find(o => o.uuid === orderId)
  if (!order) return

  const coords: [number, number][] = []
  order.stages.forEach(stage => {
    if (stage.fromLat && stage.fromLon) coords.push([stage.fromLon, stage.fromLat])
    if (stage.toLat && stage.toLon) coords.push([stage.toLon, stage.toLat])
  })

  if (coords.length === 0) return

  if (coords.length === 1) {
    map.flyTo({ center: coords[0], zoom: 8 })
  } else {
    const bounds = coords.reduce(
      (acc, coord) => acc.extend(coord),
      new LngLatBounds(coords[0], coords[0])
    )
    map.fitBounds(bounds, { padding: 80, maxZoom: 8 })
  }
}

const onMapCreated = (map: MapboxMapType) => {
  mapRef.value = map

  const initMap = () => {
    map.addSource('orders-routes', {
      type: 'geojson',
      data: routeLineFeatures.value
    })

    map.addLayer({
      id: 'orders-routes-lines',
      type: 'line',
      source: 'orders-routes',
      layout: {
        'line-join': 'round',
        'line-cap': 'round'
      },
      paint: {
        'line-color': ['get', 'color'],
        'line-width': ['get', 'lineWidth'],
        'line-opacity': ['get', 'opacity']
      }
    })

    map.addSource('orders-markers', {
      type: 'geojson',
      data: markersFeatureCollection.value
    })

    map.addLayer({
      id: 'orders-markers-circles',
      type: 'circle',
      source: 'orders-markers',
      paint: {
        'circle-radius': ['get', 'radius'],
        'circle-color': ['get', 'color'],
        'circle-stroke-width': 2,
        'circle-stroke-color': '#ffffff'
      }
    })

    // Click on route line
    map.on('click', 'orders-routes-lines', (e) => {
      const orderId = e.features?.[0]?.properties?.orderId
      if (orderId) {
        emit('select-order', orderId)
      }
    })

    // Click on marker
    map.on('click', 'orders-markers-circles', (e) => {
      const feature = e.features?.[0]
      if (!feature) return
      const props = feature.properties
      const orderId = props?.orderId
      if (orderId) {
        emit('select-order', orderId)
      }

      const coordinates = (feature.geometry as GeoJSON.Point).coordinates.slice() as [number, number]
      new Popup()
        .setLngLat(coordinates)
        .setHTML(`<strong>${props?.name || 'Point'}</strong><br/>${props?.orderName || ''}`)
        .addTo(map)
    })

    map.on('mouseenter', 'orders-routes-lines', () => { map.getCanvas().style.cursor = 'pointer' })
    map.on('mouseleave', 'orders-routes-lines', () => { map.getCanvas().style.cursor = '' })
    map.on('mouseenter', 'orders-markers-circles', () => { map.getCanvas().style.cursor = 'pointer' })
    map.on('mouseleave', 'orders-markers-circles', () => { map.getCanvas().style.cursor = '' })

    isMapReady.value = true
    fitMapToRoutes()
  }

  if (map.loaded()) {
    initMap()
  } else {
    map.on('load', initMap)
  }
}

// Watch for selection changes
watch(() => props.selectedOrderId, (newId) => {
  updateRoutesSource()
  if (newId) {
    flyToOrder(newId)
  }
})

// Watch for routes changes
watch(
  () => props.routes,
  () => {
    didFitBounds.value = false
    loadDetailedRoutes()
  },
  { deep: true, immediate: true }
)

// Expose flyTo method
defineExpose({
  flyTo: flyToOrder
})
</script>
