<script setup lang="ts">
import {
  computed,
  ref,
  shallowRef,
  onBeforeUnmount,
  onMounted,
  watch,
  nextTick,
} from 'vue'
import { loadModules } from 'esri-loader'
import axios from 'axios'
import { useCounterMixin } from '@/stores/mixin'
import { useQuasar } from 'quasar'
import { usePrivacyStore } from '@/stores/privacy'
import markerRedUrl from '@/assets/markers/marker-red.svg?url'
import markerBlueUrl from '@/assets/markers/marker-blue.svg?url'

const mixin = useCounterMixin()
const { messageError } = mixin
const privacyStore = usePrivacyStore()

const emit = defineEmits(['update:location'])

// Accept initial POI from parent to prevent skeleton on re-mount
const props = defineProps<{
  initialPOI?: string
}>()

const $q = useQuasar()

// Throttle mechanism to prevent excessive location updates
const LOCATION_UPDATE_THROTTLE_MS = 500
let lastLocationUpdate = 0

// Track if map is initialized to prevent re-initialization
const isMapInitialized = ref<boolean>(false)
const isInitializing = ref<boolean>(false)
const currentScreenSize = ref<boolean>($q.screen.gt.xs) // Track screen size to detect changes
const mobileMapExpanded = ref<boolean>(true)

function updateLocation(latitude: number, longitude: number, namePOI: string) {
  const now = Date.now()
  // Skip update if called too frequently (within throttle period)
  if (now - lastLocationUpdate < LOCATION_UPDATE_THROTTLE_MS) {
    return
  }
  lastLocationUpdate = now
  // ส่ง event ไปยัง parent component เพื่ออัพเดทค่า props
  emit('update:location', latitude, longitude, namePOI)
}

const poiPlaceName = ref<string>(props.initialPOI || '') // ชื่อพื้นที่ใกล้เคียง - use initialPOI if provided
const mapView = shallowRef<any>(null) // Store mapView reference for cleanup (use shallowRef to avoid reactivity issues)
const desktopMapContainerRef = ref<HTMLElement | null>(null)
const mobileMapContainerRef = ref<HTMLElement | null>(null)
const isMapVisible = computed(
  () => !!mapView.value && !mapView.value.destroyed && isMapInitialized.value
)

// Unique container ID for each component instance to avoid conflicts
const mapContainerId = `mapViewDisplay_${Math.random()
  .toString(36)
  .substring(2, 9)}`

// ArcGIS API key from environment variable
const apiKey = ref<string>(
  'YLATgWuywoeRLHn6KImj5rg7UaP8bJoR9jiTldoCVBHlqFIebwMSA5wIXEmcYhwXwMHkmNISEYtUz3x0oiGIIx0bIXXnUwi0OzupoOEtDrQIsRPVtor7gaPpXEmH8TrNaMT3snf6zO_yujHLGzborg-L9aeAjTJn4ndL6f8qFmRzYcX93E2vyA-7XCufLYTRsdTE5Aq-9hnx1q9PmYVMqhAZpL7dWqn3JgO33fRXetk.'
)
const zoomMap = ref<number>(18)
const textTooltip = ref<string>(
  'พื้นที่ใกล้เคียงคือ สถานที่สำคัญรอบตัวคุณ (ไม่ใช่ตำแหน่งปัจจุบัน)'
)
let attachRetryTimer: number | undefined
let mapInitVersion = 0
let isRecoveryReinitializing = false

function getActiveMapContainer() {
  return $q.screen.gt.xs
    ? desktopMapContainerRef.value
    : mobileMapContainerRef.value
}

async function waitForActiveMapContainer(maxAttempts = 25, delayMs = 120) {
  for (let i = 0; i < maxAttempts; i += 1) {
    await nextTick()
    const container = getActiveMapContainer()

    if (container && container.clientWidth > 0 && container.clientHeight > 0) {
      return container
    }

    await new Promise((resolve) => setTimeout(resolve, delayMs))
  }

  return null
}

function clearAttachRetryTimer() {
  if (attachRetryTimer) {
    clearTimeout(attachRetryTimer)
    attachRetryTimer = undefined
  }
}

async function recoverMapByReinitialize() {
  if (isRecoveryReinitializing) {
    return
  }

  isRecoveryReinitializing = true

  try {
    mapInitVersion += 1
    isInitializing.value = false
    clearAttachRetryTimer()

    if (mapView.value && !mapView.value.destroyed) {
      mapView.value.destroy()
      mapView.value = null
    }

    isMapInitialized.value = false
    await nextTick()
    await initializeMap()
  } finally {
    isRecoveryReinitializing = false
  }
}

function reattachAndResizeMap(retry = 0) {
  if (!mapView.value || mapView.value.destroyed) {
    return
  }

  const activeContainer = getActiveMapContainer()

  if (
    !activeContainer ||
    activeContainer.clientWidth === 0 ||
    activeContainer.clientHeight === 0
  ) {
    if (retry < 15) {
      clearAttachRetryTimer()
      attachRetryTimer = window.setTimeout(() => {
        reattachAndResizeMap(retry + 1)
      }, 140)
    } else if (!isRecoveryReinitializing) {
      void recoverMapByReinitialize()
    }
    return
  }

  clearAttachRetryTimer()

  nextTick(() => {
    if (!mapView.value || mapView.value.destroyed) {
      return
    }

    if (mapView.value.container !== activeContainer) {
      mapView.value.container = null
      mapView.value.container = activeContainer
    }

    // เรียก resize() อย่างปลอดภัยด้วยการตรวจสอบว่ามี method นี้หรือไม่
    try {
      if (typeof mapView.value.resize === 'function') {
        mapView.value.resize()
      }
    } catch (error) {
      console.warn('Map resize failed:', error)
    }

    if (typeof mapView.value.requestRender === 'function') {
      mapView.value.requestRender?.()
    }

    mapView.value
      .goTo(
        {
          center: mapView.value.center,
          zoom: mapView.value.zoom,
        },
        { animate: false }
      )
      .catch(() => {
        // Ignore interrupted goTo during rapid orientation changes.
      })
  })
}

async function initializeMap() {
  if (isInitializing.value || (mapView.value && !mapView.value.destroyed)) {
    return
  }

  const initVersion = ++mapInitVersion

  isInitializing.value = true

  try {
    // Load modules of ArcGIS
    const [esriConfig, Map, MapView, Point, Graphic, TileLayer] =
      await loadModules([
        'esri/config',
        'esri/Map',
        'esri/views/MapView',
        'esri/geometry/Point',
        'esri/Graphic',
        // 'esri/layers/TileLayer',
      ])
    // Set apiKey
    // esriConfig.apiKey =
    //   'AAPK4f700a4324d04e9f8a1a134e0771ac45FXWawdCl-OotFfr52gz9XKxTDJTpDzw_YYcwbmKDDyAJswf14FoPyw0qBkN64DvP'

    // Create a FeatureLayer using a custom server URL
    // const hillshadeLayer = new TileLayer({
    //   url: `https://bmagis.bangkok.go.th/arcgis/rest/services/cache/BMA_3D_2D_Cache/MapServer`,
    // })

    const map = new Map({
      basemap: 'streets',
      // basemap: 'arcgis-topographic',
      // layers: [hillshadeLayer],
    })

    if (initVersion !== mapInitVersion) {
      return
    }

    const position = await new Promise<GeolocationPosition>(
      (resolve, reject) => {
        navigator.geolocation.getCurrentPosition(resolve, reject, {
          enableHighAccuracy: true,
          timeout: 10000,
        })
      }
    )

    if (initVersion !== mapInitVersion) {
      return
    }

    const { latitude, longitude } = position.coords

    const mapContainer = await waitForActiveMapContainer()

    if (!mapContainer || initVersion !== mapInitVersion) {
      isMapInitialized.value = false
      return
    }

    mapView.value = new MapView({
      container: mapContainer,
      map: map,
      center: {
        latitude: latitude,
        longitude: longitude,
      }, // Set the initial map center current position

      zoom: zoomMap.value,
      constraints: {
        snapToZoom: false, // Disables snapping to the zoom level
        minZoom: zoomMap.value, // Set minimum zoom level
        maxZoom: zoomMap.value, // Set maximum zoom level (same as minZoom for fixed zoom)
      },

      ui: {
        components: [], // Empty array to remove all default UI components
      },
    })

    await mapView.value.when()

    if (initVersion !== mapInitVersion) {
      if (mapView.value && !mapView.value.destroyed) {
        mapView.value.destroy()
      }
      mapView.value = null
      return
    }

    isMapInitialized.value = true
    reattachAndResizeMap()

    // ตำแหน่งของผู้ใช้
    const userPoint = new Point({ longitude, latitude })
    const userSymbol = {
      type: 'picture-marker',
      url: markerRedUrl,
      width: '32px',
      height: '32px',
    }
    const userGraphic = new Graphic({
      geometry: userPoint,
      symbol: userSymbol,
    })

    // เพิ่มการตรวจสอบความปลอดภัยก่อนเข้าถึง graphics
    if (mapView.value && mapView.value.graphics && !mapView.value.destroyed) {
      mapView.value.graphics.add(userGraphic)
    }
    // Get POI place ยิงไปขอที่ server ของกทม.ก่อน
    // await axios
    //   .get(
    //     'https://bmagis.bangkok.go.th/portal/sharing/servers/e4732c3a9fe549ab8bc697573b468f68/rest/services/World/GeocodeServer/reverseGeocode/',
    //     {
    //       params: {
    //         f: 'json', // Format JSON response
    //         distance: 2000,
    //         category: 'POI',
    //         location: {
    //           spatialReference: { wkid: 4326 },
    //           x: longitude,
    //           y: latitude,
    //         },
    //         token: apiKey.value,
    //       },
    //     }
    //   )
    //   .then((response) => {
    //     // console.log('poi', response.data.location)
    //     poiPlaceName.value = response.data.address
    //       ? response.data.address.PlaceName === ''
    //         ? response.data.address.ShortLabel
    //         : response.data.address.PlaceName
    //       : 'ไม่พบข้อมูล'
    //     const poiPoint = new Point({
    //       longitude: response.data.location.x,
    //       latitude: response.data.location.y,
    //     })
    //     const poiSymbol = {
    //       type: 'picture-marker',
    //       url: markerBlueUrl,
    //       width: '32px',
    //       height: '32px',
    //     }
    //     const poiGraphic = new Graphic({
    //       geometry: poiPoint,
    //       symbol: poiSymbol,
    //     })
    //     mapView.value.graphics.add(poiGraphic)
    //     // อัปเดตการแสดงผลให้แสดงทั้งตำแหน่งของผู้ใช้และ POI
    //     mapView.value.goTo({
    //       target: [userPoint, poiPoint],
    //       zoom: zoomMap.value,
    //     })

    //     // Mark map as initialized
    //     isMapInitialized.value = true

    //     updateLocation(latitude, longitude, poiPlaceName.value)
    //   })
    //   .catch(async (error) => {
    // console.error('Error fetching points of interest:', error)
    // Get POI place ยิงไปขอที่ server arcgis ไม่ต้องใช้ token
    await axios
      .get(
        'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode/',
        {
          params: {
            f: 'json', // Format JSON response
            distance: 2000,
            category: 'POI',
            location: {
              spatialReference: { wkid: 4326 },
              x: longitude,
              y: latitude,
            },
          },
        }
      )
      .then((response) => {
        if (initVersion !== mapInitVersion || !mapView.value) {
          return
        }

        // console.log('poi', response.data.location)
        poiPlaceName.value = response.data.address
          ? response.data.address.PlaceName === ''
            ? response.data.address.ShortLabel
            : response.data.address.PlaceName
          : 'ไม่พบข้อมูล'
        const poiPoint = new Point({
          longitude: response.data.location.x,
          latitude: response.data.location.y,
        })
        const poiSymbol = {
          type: 'picture-marker',
          url: markerBlueUrl,
          width: '32px',
          height: '32px',
        }
        const poiGraphic = new Graphic({
          geometry: poiPoint,
          symbol: poiSymbol,
        })

        // เพิ่มการตรวจสอบความปลอดภัยก่อนเข้าถึง graphics และ goTo
        if (mapView.value && !mapView.value.destroyed) {
          if (mapView.value.graphics) {
            mapView.value.graphics.add(poiGraphic)
          }
          // อัปเดตการแสดงผลให้แสดงทั้งตำแหน่งของผู้ใช้และ POI
          if (typeof mapView.value.goTo === 'function') {
            mapView.value.goTo({
              target: [userPoint, poiPoint],
              zoom: zoomMap.value,
            }).catch(() => {
              // Ignore goTo errors
            })
          }
        }

        updateLocation(latitude, longitude, poiPlaceName.value)
      })
      .catch(() => {
        if (initVersion !== mapInitVersion) {
          return
        }
        poiPlaceName.value = poiPlaceName.value || 'ไม่พบข้อมูล'
        updateLocation(latitude, longitude, poiPlaceName.value)
      })

    if (initVersion === mapInitVersion) {
      reattachAndResizeMap()
    }
  } catch (error) {
    if (initVersion === mapInitVersion) {
      isMapInitialized.value = false
    }
    console.error('Error loading the map', error)
  } finally {
    if (initVersion === mapInitVersion) {
      isInitializing.value = false
    }
  }
}

const locationGranted = ref<boolean>(false)
// Function to request location permission
const requestLocationPermission = () => {
  // เช็คสิทธิ์ privacy ก่อนเข้าถึงแผนที่
  if (!privacyStore.isAccepted) {
    $q.notify({
      type: 'warning',
      message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งานแผนที่',
      position: 'top',
    })
    return
  }

  if (!navigator.geolocation) {
    messageError(
      $q,
      '',
      'ไม่สามารถระบุตำแหน่งปัจจุบันได้ เบราว์เซอร์ของคุณไม่รองรับ Geolocation'
    )
    return
  }

  navigator.geolocation.getCurrentPosition(
    async (position) => {
      // Permission granted
      locationGranted.value = true

      const { latitude, longitude } = position.coords
      // console.log('Current position:', latitude, longitude)

      if (!latitude || !longitude) {
        messageError($q, '', 'ไม่สามารถระบุตำแหน่งปัจจุบันได้')
        return
      }

      // Center map on user's location if map is initialized
      if (privacyStore.isAccepted) {
        await initializeMap()
      }
    },
    (error) => {
      // Permission denied
      locationGranted.value = false

      switch (error.code) {
        case error.PERMISSION_DENIED:
          messageError(
            $q,
            '',
            'ไม่สามารถระบุตำแหน่งปัจจุบันได้ เนื่องจากคุณปฏิเสธการเข้าถึงตำแหน่ง กรุณาเปิดการเข้าถึงตำแหน่ง'
          )
          break
        case error.POSITION_UNAVAILABLE:
          messageError($q, '', 'ไม่สามารถระบุตำแหน่งปัจจุบันได้')
          break
        case error.TIMEOUT:
          messageError($q, '', 'การร้องขอตำแหน่งหมดเวลา กรุณาลองอีกครั้ง')
          break
        default:
          messageError(
            $q,
            '',
            'ไม่สามารถระบุตำแหน่งปัจจุบันได้ เกิดข้อผิดพลาดไม่ทราบสาเหตุในการเข้าถึงตำแหน่ง'
          )
          break
      }
    },
    { enableHighAccuracy: true }
  )
}

// Also add a resize event listener as a fallback for orientation changes
// that might not trigger the Quasar screen watcher
let resizeTimeout: number | undefined
const handleWindowResize = () => {
  // Debounce resize events to avoid excessive calls
  if (resizeTimeout) {
    clearTimeout(resizeTimeout)
  }
  resizeTimeout = window.setTimeout(() => {
    handleMapResize()
  }, 300)
}

/**
 * Handle map resize when screen orientation changes
 * ArcGIS MapView needs to be manually resized when its container changes dimensions
 * Only call resize() if the map is fully initialized
 */
function handleMapResize() {
  if (mapView.value && !mapView.value.destroyed && isMapInitialized.value) {
    // Use nextTick to ensure the DOM has finished updating before resizing
    reattachAndResizeMap()
  }
}

/**
 * Watch for screen size/orientation changes
 * When the screen size changes (e.g., portrait to landscape), we need to re-initialize the map
 * because the container div changes between desktop and mobile versions
 */
watch(
  () => $q.screen.gt.xs,
  async (newValue) => {
    // Skip if no actual change (same layout)
    if (newValue === currentScreenSize.value) {
      return
    }

    currentScreenSize.value = newValue
    if (!newValue) {
      mobileMapExpanded.value = true
    }
    mapInitVersion += 1
    isInitializing.value = false
    clearAttachRetryTimer()

    // Always destroy and re-initialize when screen size changes
    // This ensures the map is attached to the correct container
    if (mapView.value && !mapView.value.destroyed) {
      mapView.value.destroy()
      mapView.value = null
    }

    // Reset initialization state
    isMapInitialized.value = false

    // Wait for DOM to update with new layout
    await nextTick()
    await waitForActiveMapContainer(30, 100)

    // Re-initialize the map
    if (privacyStore.isAccepted) {
      await initializeMap()

      if (!mapView.value || mapView.value.destroyed) {
        await recoverMapByReinitialize()
      }

      reattachAndResizeMap()
    }
  }
)

// Watch for initialPOI prop changes to update poiPlaceName
watch(
  () => props.initialPOI,
  (newValue) => {
    if (newValue && newValue !== poiPlaceName.value) {
      poiPlaceName.value = newValue
    }
  }
)

// Set up resize event listener on mount
onMounted(async () => {
  window.addEventListener('resize', handleWindowResize)

  // Component can be remounted after orientation/layout switches.
  // Recreate map automatically when consent is already granted.
  if (privacyStore.isAccepted && (!mapView.value || mapView.value.destroyed)) {
    await waitForActiveMapContainer(30, 100)
    await initializeMap()
    reattachAndResizeMap()
  }
})

// Cleanup map resources when component unmounts
onBeforeUnmount(() => {
  clearAttachRetryTimer()

  if (mapView.value) {
    mapView.value.destroy()
    mapView.value = null
  }
  // Clean up resize event listener
  window.removeEventListener('resize', handleWindowResize)
  // Clear timeout if it exists
  if (resizeTimeout) {
    clearTimeout(resizeTimeout)
  }
})

defineExpose({
  requestLocationPermission,
})
</script>

<template>
  <!-- Loading skeleton - show until map is visible -->
  <div v-if="!isMapVisible" class="col-12">
    <q-skeleton
      :height="$q.screen.gt.xs ? '35vh' : '45px'"
      width="100%"
      :style="$q.screen.gt.xs ? ';' : 'border-radius: 20px'"
      class="bg-grey-4"
    />
  </div>

  <q-card
    bordered
    flat
    class="col-12 bg-grey-2 shadow-0"
    :class="{ 'map-hidden': !isMapVisible }"
    :style="$q.screen.gt.xs ? ';' : 'border-radius: 20px'"
  >
    <div v-if="$q.screen.gt.xs">
      <div
        :id="mapContainerId"
        ref="desktopMapContainerRef"
        style="height: 35vh; pointer-events: none"
      ></div>

      <div
        :class="
          $q.screen.gt.xs
            ? 'q-pa-xs text-weight-medium text-grey-8'
            : 'q-pa-xs text-weight-medium text-grey-8'
        "
      >
        พื้นที่ใกล้เคียง
        <q-icon name="mdi-information-outline" size="xs" class="q-mr-xs" />

        <q-tooltip anchor="top middle" self="bottom middle" :offset="[0, 5]">
          {{ textTooltip }}
        </q-tooltip>
        <span class="q-px-sm">:</span>
        {{ poiPlaceName }}
      </div>
    </div>

    <q-card v-else style="border-radius: 20px">
      <q-expansion-item
        v-model="mobileMapExpanded"
        class="shadow-1 overflow-hidden bg-grey-4 text-left q-pa-xs"
        style="border-radius: 20px"
        dense
        default-opened
      >
        <template v-slot:header>
          <q-item-section avatar class="q-pr-none expanAS">
            <q-icon name="mdi-map-marker" color="primary" />
          </q-item-section>
          <q-item-section>
            <q-item-label caption class="text-grey-7">
              พื้นที่ใกล้เคียง
              <q-icon
                name="mdi-information-outline"
                size="xs"
                class="q-mr-xs cursor-pointer"
                @click.stop="$q.dialog({ message: textTooltip, ok: 'ตกลง' })"
              />
            </q-item-label>
            <q-item-label class="text-weight-medium text-grey-9">
              {{ poiPlaceName || '-' }}
            </q-item-label>
          </q-item-section>
        </template>

        <div
          :id="mapContainerId"
          ref="mobileMapContainerRef"
          style="height: 20vh"
        ></div>
      </q-expansion-item>
    </q-card>
  </q-card>
</template>

<style>
.expanAS.q-item__section--avatar {
  min-width: 40px !important;
}

.map-hidden {
  visibility: hidden;
}
</style>
