<template>
  <div class="h-auto" id="id-prediction-map-container">

    <div class="grid grid-cols-1 2xl:grid-cols-5 lg:gap-1 lg:border-r ml-2 mt-2 md:ml-4 md:mr-4">

      <div class="lg:border-r lg:col-span-3">
        <div id="id-map-cont" class="">
          <div class="flex">
            <span class="ml-2">
              <input type="checkbox" id="checkboxMapNavigationLocked" v-model="mapNavigationLocked" />
              <span class="ml-2">
                  <label class="text-red-600" for="checkboxMapNavigationLocked" v-if="mapNavigationLocked">locked map navigation!</label>
                  <label class="text-blue-600" for="checkboxMapNavigationLocked" v-else>map navigation unlocked</label>
              </span>
            </span>
            <details id="detail-prompt-examples-array" :open="detailIsOpenRef" class="ml-2">

              <summary><i>prompt examples here</i></summary>
              <div class="grid grid-cols-1 md:grid-cols-3" id="prompt-examples-array">
                <div class="text-xs font-extralight flex bg-green-200">
                  <textarea
                      id="prompt-text-placeholder"
                      v-model="promptTextPlaceholderRef"
                      class="p-2 border-2 border-indigo-500/100 w-full"
                  />
                </div>
                <StringArray
                    :string-array="promptTextArray"
                    :string-prefix="promptTextPlaceholderRef"
                    @set-prompt="(stringPrompt: string) => promptTextRef = stringPrompt"
                />
              </div>

            </details>
          </div>

          <div class="flex w-full lg:w-2/3">
            <div class="md:pt-1 md:pb-1 md:mr-1 mr-2">
              <ButtonMapSendStringRequest
                  id="id-button-submit"
                  class="w-full h-8 mt-2 text-sm font-extralight min-w-[180px] max-w-[180px]"
                  :current-base-map-name="currentBaseMapNameRef"
                  :map="map"
                  :promptText="promptTextRef"
                  :response-message="responseMessageRef"
                  :send-m-l-request="sendMLStringRequest"
                  :waiting-string="waitingString"
              />
            </div>
            <textarea
                  id="prompt-text-llm-ref"
                  v-model="promptTextRef"
                  :placeholder=promptTextPlaceholderRef
                  rows="2"
                  class="w-full md:pt-1 md:pb-1 border-2 mt-2 mb-2 mr-2 border-indigo-500/100"
              ></textarea>
          </div>

          <div id="map" class="map-predictions"/>
        </div>
      </div>

      <div class="lg:col-span-2">
        <div class="lg:pl-2 lg:pr-2 lg:border-l lg:border-3" id="id-map-info">

          <h1>Map Info</h1>
          <div class="grid grid-cols-1 md:grid-cols-3">
            <StatsGrid :stats-array="[
              {statName: 'current Zoom', statValue: currentZoomRef},
              {statName: 'current map name/type', statValue: currentBaseMapNameRef}
            ]"/>
          </div>
          <h2 id="lisa-response-text">LISA response: <span class="text-lg text-blue-600">{{ responseMessageLisaRef }}</span></h2>
          <div v-if="responseMessageRef === waitingString"/>
          <h2 v-else-if="responseMessageRef || responseMessageRef == '-'" class="text-lg text-red-600">
            {{ responseMessageRef }}</h2>
          <div v-else>
            <div class="grid grid-cols-1 md:grid-cols-3">
              <StatsGrid :stats-array="[
                  {statName: 'request duration', statValue: `${durationRef.toFixed(2)}s`},
                  {statName: 'polygons number', statValue: numberOfPolygonsRef},
                  {statName: 'predicted masks number', statValue: numberOfPredictedMasksRef},
                ]"/>
            </div>
          </div>
        </div>

      </div>

    </div>
  </div>
</template>

<script lang="ts" setup>
import {
  control as LeafletControl,
  Evented as LEvented,
  geoJSON as LeafletGeoJSON,
  type LatLng,
  Map as LMap,
  map as LeafletMap,
  tileLayer,
  TileLayer as LTileLayer
} from 'leaflet'
import 'leaflet-providers'
import { onMounted, onUpdated, ref, type Ref } from 'vue'
// workaround because of dist/ content not included in @trincadev/driver.js tag release tarball
import { driver } from "../../node_modules/@trincadev/driver.js/src/driver"

import {
  durationRef,
  maxZoom,
  minZoom,
  numberOfPolygonsRef,
  numberOfPredictedMasksRef,
  OpenStreetMap,
  prefix,
  promptPlaceHolder,
  promptTextArray,
  responseMessageRef,
  responseMessageLisaRef,
  Satellite,
  waitingString
} from './constants'
import {
  getExtentCurrentViewMapBBox,
  getGeoJSONRequest,
  getQueryParams,
  getSelectedPointCoordinate,
  updateMapData
} from '@/components/helpers'
import type {IBodyLatLngWithStringPoints, SourceTileType} from '@/components/types';
import StatsGrid from '@/components/StatsGrid.vue';
import StringArray from '@/components/StringArray.vue';
import ButtonMapSendStringRequest from '@/components/buttons/ButtonMapSendStringRequest.vue';

const lisaDriverObj = driver({
  showProgress: true,
  steps: [
    {
      element: 'id-prediction-map-container', popover: {
        title: 'SamGIS with LISA',
        description: 'A quick tour about functionalities of SamGIS with LISA',
        onNextClick: () => {
          detailIsOpenRef.value = true
          lisaDriverObj.moveNext();
        }
      }
    },
    {
      element: '#map',
      popover: {
        title: 'Geographic map',
        description: 'Choose here the map area where you can execute your machine learning prompt'
      }
    },
    {
      element: "#prompt-examples-array",
      popover: {title: 'Some prompt examples', description: 'A selection of prompt examples'}
    },
    {
      element: "#prompt-text-placeholder", popover: {
        title: 'Default prompt prefix',
        description: 'A good LLM prompt prefix tailored for photogrammetry, remote sensing and geomorphology (editable)'
      }
    },
    {
      element: "#prompt-text-llm-ref", popover: {
        title: 'Prompt text',
        description: 'Editable text area for the LLM text prompt (you can precompile it clicking on the examples)'
      }
    },
    {
      element: "#id-button-submit",
      popover: {title: 'LLM submit button', description: 'submit button for the LISA request'}
    },
    {
      element: '.leaflet-control-layers-toggle',
      popover: {title: 'Map provider selector', description: 'select a different map provider'}
    },
    {
      element: '#lisa-response-text',
      popover: {title: 'LISA response text', description: 'Output area with the LISA response text'}
    },
    {
      element: '#id-map-info', popover: {
        title: 'map info',
        description: 'Section about various map info',
        onNextClick: () => {
          detailIsOpenRef.value = false
          lisaDriverObj.moveNext();
        }
      }
    },
    {
      element: "#detail-prompt-examples-array", popover: {
        title: 'Detail: Array of Prompt Examples',
        description: 'Click here to expand the array of prompt examples (closed by default)'
      }
    },
  ]
});

const currentBaseMapNameRef = ref("")
const currentMapBBoxRef = ref()
const currentZoomRef = ref()
const promptTextRef: Ref<string> = ref("")
const promptTextPlaceholderRef: Ref<string> = ref(promptPlaceHolder)
const detailIsOpenRef: Ref<boolean> = ref(false)
const mapNavigationLocked = ref(false)
let map: LMap
type ServiceTiles = {
  [key: SourceTileType]: LTileLayer;
};

const props = defineProps<{
  mapBounds: Array<LatLng>,
  mapName: string,
  description: string
}>()

const getPopupContentPoint = (leafletEvent: LEvented, label: number): HTMLDivElement => {
  let popupContent: HTMLDivElement = document.createElement('div')
  let currentPointLayer: LatLng = getSelectedPointCoordinate(leafletEvent)

  popupContent.innerHTML = `<span>lat:${JSON.stringify(currentPointLayer.lat)}<br/>`
  popupContent.innerHTML += `lng:${JSON.stringify(currentPointLayer.lng)}<br/>`
  popupContent.innerHTML += `label:${label}, id:${leafletEvent.layer._leaflet_id}</span>`

  const popupDiv: HTMLDivElement = document.createElement('div')
  popupDiv.className = 'leaflet-popup-content-inner'
  popupDiv.appendChild(popupContent)

  return popupDiv
}

const sendMLStringRequest = async (leafletMap: LMap, promptRequest: string, sourceType: SourceTileType = OpenStreetMap) => {
  mapNavigationLocked.value = true
  const bodyRequest: IBodyLatLngWithStringPoints = {
    bbox: getExtentCurrentViewMapBBox(leafletMap),
    string_prompt: promptRequest,
    zoom: leafletMap.getZoom(),
    source_type: sourceType
  }
  try {
    const geojsonOutputOnMounted = await getGeoJSONRequest(bodyRequest, '/infer_lisa')
    const featureNew = LeafletGeoJSON(geojsonOutputOnMounted)
    leafletMap.addLayer(featureNew)
  } catch (errGeojsonOutputOnMounted) {
    console.error('sendMLRequest:: sourceType: ', sourceType)
    console.error('sendMLRequest:: promptRequest: ', promptRequest.length, '::', promptRequest)
    console.error('sendMLRequest:: bodyRequest => ', bodyRequest, "#")
    console.error("errGeojsonOutputOnMounted => ", errGeojsonOutputOnMounted)
  }
}

const updateZoomBboxMap = (localMap: LMap) => {
  currentZoomRef.value = localMap.getZoom()
  currentMapBBoxRef.value = getExtentCurrentViewMapBBox(localMap)
}

const getCurrentBasemap = (url: string, providersArray: ServiceTiles) => {
  for (const [key, value] of Object.entries(providersArray)) {
    if (value._url == url) {
      return key
    }
  }
}

onMounted(async () => {
  const osmTile = tileLayer.provider(OpenStreetMap)
  const params = getQueryParams()
  let localVarSatellite: SourceTileType = params.source ? params.source : Satellite
  let localVarSatelliteOptions = params.options ? params.options : {}
  const satelliteTile = tileLayer.provider(localVarSatellite, localVarSatelliteOptions)
  let localVarTerrain: SourceTileType = "nextzen.terrarium"
  const terrainTile = new LTileLayer(
      "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png", {
        id: localVarTerrain,
        attribution: "<a href='https://nextzen.org'>nextzen</a>," +
            "<a href='https://registry.opendata.aws/terrain-tiles/'>Mapzen Terrain Tiles - AWS opendata registry</a>," +
            "<a href='https://github.com/tilezen/joerd/blob/master/docs/attribution.md'>Mapzen Source Attributions</a>."
      }
  )

  let baseMaps: ServiceTiles = {OpenStreetMap: osmTile}
  baseMaps[localVarSatellite] = satelliteTile
  baseMaps[localVarTerrain] = terrainTile
  currentBaseMapNameRef.value = OpenStreetMap

  map = LeafletMap('map', {
    layers: [osmTile],
    minZoom: minZoom,
    maxZoom: maxZoom
  })
  map.fitBounds(props.mapBounds)
  map.attributionControl.setPrefix(prefix)
  LeafletControl.scale({position: 'bottomleft', imperial: false, metric: true}).addTo(map)

  LeafletControl.layers(baseMaps).addTo(map)
  updateZoomBboxMap(map)

  map.on('zoomend', (e: LEvented) => {
    updateZoomBboxMap(map)
  })

  map.on('mouseup', (e: LEvented) => {
    currentMapBBoxRef.value = getExtentCurrentViewMapBBox(map)
  })

  updateMapData(map, getPopupContentPoint, promptTextRef)
  map.on('baselayerchange', (e: LEvented) => {
    currentBaseMapNameRef.value = getCurrentBasemap(e.layer._url, baseMaps)
  })

  lisaDriverObj.drive();
})

onUpdated(() => {
  if (mapNavigationLocked.value) {
    map.setMaxZoom(currentZoomRef.value)
    map.setMinZoom(currentZoomRef.value)
    map.options.maxBoundsViscosity = 1.0
    map.setMaxBounds(map.getBounds())
  }
  if (!mapNavigationLocked.value) {
    map.setMaxZoom(maxZoom)
    map.setMinZoom(minZoom)
    map.options.maxBoundsViscosity = 0.0
    map.setMaxBounds([
      [90, 180],
      [-90, -180]
    ])
  }
})
</script>

