<template>
  <div class="w-full">
    <ClientOnly>
      <MapboxMap
        :key="mapId"
        :map-id="mapId"
        :style="`height: ${heightValue}px; width: 100%;`"
        class="rounded-lg border border-base-300"
        :options="mapOptions"
        @load="onMapCreated"
      >
        <MapboxNavigationControl position="top-right" />
      </MapboxMap>

      <template #fallback>
        <div class="h-96 w-full bg-base-200 rounded-lg flex items-center justify-center">
          <p class="text-base-content/60">{{ t('routeMap.states.loading') }}</p>
        </div>
      </template>
    </ClientOnly>
  </div>
</template>

<script setup lang="ts">
import type { Map as MapboxMapType } from 'mapbox-gl'
import { LngLatBounds, Popup } from 'mapbox-gl'
import { getCurrentInstance } from 'vue'

interface StageCompany {
  uuid?: string | null
  name?: string | null
}

interface StageTrip {
  uuid?: string | null
  company?: StageCompany | null
}

interface RouteStage {
  uuid?: string | null
  stageType?: string | null
  sourceLatitude?: number | null
  sourceLongitude?: number | null
  sourceLocationName?: string | null
  destinationLatitude?: number | null
  destinationLongitude?: number | null
  destinationLocationName?: string | null
  locationLatitude?: number | null
  locationLongitude?: number | null
  locationName?: string | null
  selectedCompany?: StageCompany | null
  trips?: StageTrip[] | null
}

interface RoutePoint {
  id: string
  name: string
  lat: number
  lng: number
  companies: StageCompany[]
}

const props = defineProps<{
  stages?: RouteStage[]
  height?: number
}>()

const defaultHeight = 400

const { t } = useI18n()
const mapRef = ref<MapboxMapType | null>(null)
const didFitBounds = ref(false)

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

const heightValue = computed(() => props.height ?? defaultHeight)

const routePoints = computed(() => {
  const points: RoutePoint[] = []

  props.stages?.forEach((stage: RouteStage) => {
    if (stage.stageType === 'transport') {
      if (stage.sourceLatitude && stage.sourceLongitude) {
        const existingPoint = points.find(p => p.lat === stage.sourceLatitude && p.lng === stage.sourceLongitude)
        if (!existingPoint) {
          points.push({
            id: `source-${stage.uuid}`,
            name: stage.sourceLocationName || t('routeMap.points.source'),
            lat: stage.sourceLatitude,
            lng: stage.sourceLongitude,
            companies: getStageCompanies(stage)
          })
        }
      }

      if (stage.destinationLatitude && stage.destinationLongitude) {
        const existingPoint = points.find(p => p.lat === stage.destinationLatitude && p.lng === stage.destinationLongitude)
        if (!existingPoint) {
          points.push({
            id: `dest-${stage.uuid}`,
            name: stage.destinationLocationName || t('routeMap.points.destination'),
            lat: stage.destinationLatitude,
            lng: stage.destinationLongitude,
            companies: getStageCompanies(stage)
          })
        }
      }
    } else if (stage.locationLatitude && stage.locationLongitude) {
      const existingPoint = points.find(p => p.lat === stage.locationLatitude && p.lng === stage.locationLongitude)
      if (!existingPoint) {
        points.push({
          id: `service-${stage.uuid}`,
          name: stage.locationName || t('routeMap.points.service'),
          lat: stage.locationLatitude,
          lng: stage.locationLongitude,
          companies: getStageCompanies(stage)
        })
      }
    }
  })

  return points
})

const mapCenter = computed<[number, number]>(() => {
  if (!routePoints.value.length) return [0, 0]

  const lats = routePoints.value.map(p => p.lat).filter(Boolean)
  const lngs = routePoints.value.map(p => p.lng).filter(Boolean)

  if (!lats.length || !lngs.length) return [0, 0]

  const avgLat = lats.reduce((a, b) => a + b, 0) / lats.length
  const avgLng = lngs.reduce((a, b) => a + b, 0) / lngs.length

  return [avgLng, avgLat]
})

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

const routeCoordinates = computed(() => {
  return routePoints.value.map(point => [point.lng, point.lat])
})

const pointsFeatureCollection = computed(() => ({
  type: 'FeatureCollection' as const,
  features: routePoints.value.map(point => ({
    type: 'Feature' as const,
    properties: {
      id: point.id,
      name: point.name,
      companies: point.companies
    },
    geometry: {
      type: 'Point' as const,
      coordinates: [point.lng, point.lat]
    }
  }))
}))

const lineFeatureCollection = computed(() => ({
  type: 'FeatureCollection' as const,
  features: routeCoordinates.value.length > 1
    ? [
        {
          type: 'Feature' as const,
          properties: {},
          geometry: {
            type: 'LineString' as const,
            coordinates: routeCoordinates.value
          }
        }
      ]
    : []
}))

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

  const lineSource = map.getSource('route-line') as mapboxgl.GeoJSONSource | undefined
  if (lineSource) {
    lineSource.setData(lineFeatureCollection.value)
  }

  const pointSource = map.getSource('route-points') as mapboxgl.GeoJSONSource | undefined
  if (pointSource) {
    pointSource.setData(pointsFeatureCollection.value)
  }
}

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

  const coordinates = routeCoordinates.value
  if (!coordinates.length) return

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

  map.fitBounds(bounds, {
    padding: 40,
    maxZoom: 8
  })

  didFitBounds.value = true
}

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

  const initMap = () => {
    map.addSource('route-line', {
      type: 'geojson',
      data: lineFeatureCollection.value
    })

    map.addLayer({
      id: 'route-line-layer',
      type: 'line',
      source: 'route-line',
      layout: {
        'line-join': 'round',
        'line-cap': 'round'
      },
      paint: {
        'line-color': 'var(--color-primary, #3b82f6)',
        'line-width': 4,
        'line-opacity': 0.75
      }
    })

    map.addSource('route-points', {
      type: 'geojson',
      data: pointsFeatureCollection.value
    })

    map.addLayer({
      id: 'route-points-layer',
      type: 'circle',
      source: 'route-points',
      paint: {
        'circle-radius': 7,
        'circle-color': '#f97316',
        'circle-stroke-width': 2,
        'circle-stroke-color': '#ffffff'
      }
    })

    map.on('click', 'route-points-layer', (e) => {
      const feature = e.features?.[0]
      if (!feature) return
      const coordinates = (feature.geometry as GeoJSON.Point).coordinates.slice() as [number, number]
      const props = feature.properties
      const name = props?.name || t('routeMap.points.service')

      new Popup()
        .setLngLat(coordinates)
        .setHTML(`<strong>${name}</strong>`)
        .addTo(map)
    })

    map.on('mouseenter', 'route-points-layer', () => { map.getCanvas().style.cursor = 'pointer' })
    map.on('mouseleave', 'route-points-layer', () => { map.getCanvas().style.cursor = '' })

    updateSources()
    fitMapToData()
  }

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

watch(
  () => [routePoints.value, routeCoordinates.value],
  () => {
    didFitBounds.value = false
    updateSources()
    fitMapToData()
  },
  { deep: true }
)

const getStageCompanies = (stage: RouteStage): StageCompany[] => {
  const companies: StageCompany[] = []

  if (stage.selectedCompany) {
    companies.push(stage.selectedCompany)
  }

  const uniqueCompanies = new Set<string>()
  stage.trips?.forEach((trip: StageTrip) => {
    if (trip.company && trip.company.uuid && !uniqueCompanies.has(trip.company.uuid)) {
      uniqueCompanies.add(trip.company.uuid)
      companies.push(trip.company)
    }
  })

  return companies
}
</script>
