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

      <template #fallback>
        <div class="h-72 w-full bg-base-200 rounded-lg flex items-center justify-center">
          <p class="text-base-content/60">Загрузка карты...</p>
        </div>
      </template>
    </ClientOnly>
  </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 RouteWithStages = {
  stages?: Array<RouteStage | null> | null
}

type RouteMarker = {
  lng: number
  lat: number
  name: string
  label: string
}

type StageGeometry = {
  routeIndex: number
  stageIndex: number
  transportType?: string | null
  coordinates: [number, number][]
}

const props = defineProps({
  routes: {
    type: Array as PropType<RouteWithStages[]>,
    default: () => []
  },
  height: {
    type: Number,
    default: 360
  }
})

const { execute } = useGraphQL()
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(() => `request-routes-${instanceId}`)

const routeColors = [
  '#2563eb',
  '#0f766e',
  '#7c3aed',
  '#ea580c',
  '#16a34a'
]

const routeMarkers = computed(() => {
  const markers: RouteMarker[] = []
  props.routes.forEach((route, routeIndex) => {
    const stages = (route?.stages || []).filter(Boolean) as RouteStage[]
    if (!stages.length) return
    const first = stages[0]
    const last = stages[stages.length - 1]
    if (!first || !last) return

    if (typeof first.fromLat === 'number' && typeof first.fromLon === 'number') {
      markers.push({
        lat: first.fromLat,
        lng: first.fromLon,
        name: first.fromName || 'Старт',
        label: `Маршрут ${routeIndex + 1} — старт`
      })
    }
    if (typeof last.toLat === 'number' && typeof last.toLon === 'number') {
      markers.push({
        lat: last.toLat,
        lng: last.toLon,
        name: last.toName || 'Финиш',
        label: `Маршрут ${routeIndex + 1} — финиш`
      })
    }
  })
  return markers
})

const mapCenter = computed<[number, number]>(() => {
  const points: [number, number][] = []
  routeMarkers.value.forEach(marker => {
    points.push([marker.lng, marker.lat])
  })

  if (!points.length) return [0, 0]

  const avgLng = points.reduce((sum, coord) => sum + coord[0], 0) / points.length
  const avgLat = points.reduce((sum, coord) => sum + coord[1], 0) / points.length

  return [avgLng, avgLat]
})

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

const routeLineFeatures = ref<GeoJSON.FeatureCollection<GeoJSON.LineString>>({
  type: 'FeatureCollection',
  features: []
})

const markersFeatureCollection = computed(() => ({
  type: 'FeatureCollection' as const,
  features: routeMarkers.value.map(marker => ({
    type: 'Feature' as const,
    properties: {
      name: marker.name,
      label: marker.label
    },
    geometry: {
      type: 'Point' as const,
      coordinates: [marker.lng, marker.lat]
    }
  }))
}))

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 fetchStageGeometry = async (stage: RouteStage, routeIndex: number, stageIndex: number): Promise<StageGeometry | null> => {
  if (
    typeof stage.fromLat !== 'number' ||
    typeof stage.fromLon !== 'number' ||
    typeof stage.toLat !== 'number' ||
    typeof stage.toLon !== 'number'
  ) {
    return null
  }

  const fromLat = stage.fromLat
  const fromLon = stage.fromLon
  const toLat = stage.toLat
  const toLon = stage.toLon

  let coordinates: [number, number][] | null = null

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

      const routeData = await execute(RouteDocument, { fromLat, fromLon, toLat, toLon }, 'public', 'geo') as Record<string, any>
      const geometry = routeData?.[routeField]?.geometry

      if (typeof geometry === 'string') {
        coordinates = normalizeCoordinates(JSON.parse(geometry))
      } else {
        coordinates = normalizeCoordinates(geometry)
      }
    } catch (error) {
      console.error('Failed to load detailed route geometry:', error)
    }
  }

  if (!coordinates) {
    coordinates = [
      [fromLon, fromLat],
      [toLon, toLat]
    ]
  }

  return {
    routeIndex,
    stageIndex,
    transportType: stage.transportType,
    coordinates
  }
}

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

  const requestId = ++loadCounter

  const stageTasks: Array<Promise<StageGeometry | null>> = []
  props.routes.forEach((route, routeIndex) => {
    const stages = (route?.stages || []).filter(Boolean) as RouteStage[]
    stages.forEach((stage, stageIndex) => {
      stageTasks.push(fetchStageGeometry(stage, routeIndex, stageIndex))
    })
  })

  const results = await Promise.all(stageTasks)
  if (requestId !== loadCounter) return

  const features = results
    .filter((result): result is StageGeometry => Boolean(result))
    .map((result) => ({
      type: 'Feature' as const,
      properties: {
        routeIndex: result.routeIndex,
        stageIndex: result.stageIndex,
        transportType: result.transportType,
        color: routeColors[result.routeIndex % routeColors.length]
      },
      geometry: {
        type: 'LineString' as const,
        coordinates: result.coordinates
      }
    }))

  routeLineFeatures.value = {
    type: 'FeatureCollection',
    features
  }

  updateRoutesSource()
  fitMapToRoutes()
}

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

  const source = map.getSource('request-routes') as mapboxgl.GeoJSONSource | undefined
  if (!source) return

  source.setData(routeLineFeatures.value)
}

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

  const source = map.getSource('request-markers') as mapboxgl.GeoJSONSource | undefined
  if (!source) return

  source.setData(markersFeatureCollection.value)
}

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

  const lineCoords = routeLineFeatures.value.features.flatMap((feature) => feature.geometry.coordinates)
  const markerCoords = routeMarkers.value.map(marker => [marker.lng, marker.lat] as [number, number])
  const coordinates = lineCoords.length > 0 ? lineCoords : markerCoords

  if (coordinates.length === 0) 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: 36,
    maxZoom: 8
  })

  didFitBounds.value = true
}

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

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

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

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

    map.addLayer({
      id: 'request-markers-circles',
      type: 'circle',
      source: 'request-markers',
      paint: {
        'circle-radius': 8,
        'circle-color': '#f97316',
        'circle-stroke-width': 2,
        'circle-stroke-color': '#ffffff'
      }
    })

    map.on('click', 'request-markers-circles', (e) => {
      const feature = e.features?.[0]
      if (!feature) return
      const coordinates = (feature.geometry as GeoJSON.Point).coordinates.slice() as [number, number]
      const featureProps = feature.properties
      const title = featureProps?.name || 'Точка'
      const label = featureProps?.label || ''

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

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

    isMapReady.value = true
    updateMarkersSource()
    updateRoutesSource()
    fitMapToRoutes()
  }

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

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

watch(
  () => markersFeatureCollection.value,
  () => {
    updateMarkersSource()
    if (isMapReady.value) {
      fitMapToRoutes()
    }
  },
  { deep: true }
)

watch(
  () => routeLineFeatures.value,
  () => {
    updateRoutesSource()
    if (isMapReady.value) {
      fitMapToRoutes()
    }
  },
  { deep: true }
)
</script>
