import maplibregl from "maplibre-gl"

const trackingMinTimeMs = 5000

const defaultStyle = {
  version: 8,
  sources: {
    osm: {
      type: "raster",
      tiles: [document.documentElement.dataset.mapTileUrl],
      tileSize: 256,
      attribution: "© OpenStreetMap contributors"
    }
  },
  layers: [{id: "osm", type: "raster", source: "osm"}]
}

const markerPoints = element => {
  try {
    const points = JSON.parse(element.dataset.markers || "[]")

    if (!Array.isArray(points)) return []

    return points.filter(point =>
      Number.isFinite(point?.longitude) && Number.isFinite(point?.latitude)
    )
  } catch (_error) {
    return []
  }
}

const createAidMap = element => {
  const state = {
    element,
    map: null,
    markers: []
  }

  state.markLoading = () => {
    if (state.element.dataset.mapReady !== "true") {
      state.element.dataset.mapReady = "false"
    }
  }

  state.markReady = () => {
    state.element.dataset.mapReady = "true"
  }

  state.showUnavailable = () => {
    state.map = null
    state.element.dataset.mapUnavailable = "true"
    state.markReady()

    if (state.element.dataset.demoMap !== "true") {
      const fallback = document.createElement("p")
      fallback.className = "grid h-full place-items-center p-6 text-center text-sm"
      fallback.dataset.mapFallback = "true"
      fallback.textContent = state.element.dataset.mapUnavailableLabel
      state.element.replaceChildren(fallback)
      return
    }

    const fallback = document.createElement("div")
    fallback.className = "home-demo-map-fallback"
    fallback.dataset.mapFallback = "true"

    const note = document.createElement("p")
    note.className = "home-demo-map-fallback-note"
    note.textContent = state.element.dataset.mapUnavailableLabel
    fallback.append(note)

    const points = document.createElement("div")
    points.className = "home-demo-map-fallback-points"

    markerPoints(state.element).forEach((point, index) => {
      const item = document.createElement("div")
      item.className = "home-demo-map-fallback-point"

      const marker = document.createElement("span")
      marker.className = "home-demo-map-fallback-marker"
      marker.setAttribute("aria-hidden", "true")
      marker.textContent = String(index + 1)

      const copy = document.createElement("span")
      copy.className = "home-demo-map-fallback-copy"

      const title = document.createElement("strong")
      title.textContent = point.title || ""

      const location = document.createElement("small")
      location.textContent = point.location || ""

      copy.append(title, location)
      item.append(marker, copy)
      points.append(item)
    })

    fallback.append(points)
    state.element.replaceChildren(fallback)
  }

  state.renderMarkers = () => {
    if (!state.map) return

    state.markers.forEach(marker => marker.remove())
    state.markers = []

    const points = markerPoints(state.element)
    points.forEach(point => {
      const popupContent = document.createElement("div")
      const title = document.createElement("strong")
      title.textContent = point.title || ""
      popupContent.append(title, document.createElement("br"))
      popupContent.append(document.createTextNode(point.location || ""))

      const popup = new maplibregl.Popup({offset: 18}).setDOMContent(popupContent)
      const marker = new maplibregl.Marker({color: point.exact ? "#d6573b" : "#278467"})
        .setLngLat([point.longitude, point.latitude])
        .setPopup(popup)
        .addTo(state.map)
      state.markers.push(marker)
    })

    if (points.length === 1) {
      state.map.flyTo({center: [points[0].longitude, points[0].latitude], zoom: 12})
    } else if (points.length > 1) {
      const bounds = new maplibregl.LngLatBounds()
      points.forEach(point => bounds.extend([point.longitude, point.latitude]))
      state.map.fitBounds(bounds, {padding: 52, maxZoom: 13})
    }
  }

  state.mount = () => {
    state.markLoading()
    const demoMap = state.element.dataset.demoMap === "true"

    if (!supportsMapCanvas()) {
      state.showUnavailable()
      return
    }

    try {
      state.map = new maplibregl.Map({
        container: state.element,
        style: defaultStyle,
        center: [30.5234, 50.4501],
        zoom: 5,
        attributionControl: true,
        dragRotate: !demoMap,
        pitchWithRotate: !demoMap,
        scrollZoom: !demoMap
      })
    } catch (_error) {
      state.showUnavailable()
      return
    }

    if (demoMap) state.map.touchZoomRotate.disableRotation()

    state.map.addControl(
      new maplibregl.NavigationControl({showCompass: !demoMap}),
      demoMap ? "bottom-right" : "top-right"
    )
    state.map.on("dataloading", state.markLoading)
    state.map.on("load", state.markReady)
    state.map.on("idle", state.markReady)
    state.renderMarkers()
  }

  state.destroy = () => {
    state.map?.off("dataloading", state.markLoading)
    state.map?.off("load", state.markReady)
    state.map?.off("idle", state.markReady)
    state.map?.remove()
  }

  return state
}

export const mountStaticAidMaps = root => {
  root.querySelectorAll("[data-static-aid-map]").forEach(element => {
    if (element.dataset.staticMapMounted === "true") return

    const mount = () => {
      element.dataset.staticMapMounted = "true"
      element._aidMap = createAidMap(element)
      element._aidMap.mount()
    }

    if (!("IntersectionObserver" in window)) {
      mount()
      return
    }

    const observer = new IntersectionObserver(entries => {
      if (!entries.some(entry => entry.isIntersecting)) return

      observer.disconnect()
      mount()
    }, {rootMargin: "240px"})

    observer.observe(element)
  })
}

export const Hooks = {
  AidMap: {
    mounted() {
      this.aidMap = createAidMap(this.el)
      this.aidMap.mount()
    },
    updated() {
      this.aidMap.renderMarkers()
    },
    destroyed() {
      this.aidMap.destroy()
    }
  },

  LocationPicker: {
    mounted() {
      this.el.addEventListener("click", () => {
        const latitudeInput = document.querySelector(this.el.dataset.latitudeTarget)
        const longitudeInput = document.querySelector(this.el.dataset.longitudeTarget)

        if (!latitudeInput || !longitudeInput || !navigator.geolocation) {
          this.el.textContent = "Location is unavailable"
          this.el.classList.add("btn-error")
          return
        }

        navigator.geolocation.getCurrentPosition(
          position => {
            latitudeInput.value = position.coords.latitude
            longitudeInput.value = position.coords.longitude
            latitudeInput.dispatchEvent(new Event("input", {bubbles: true}))
            longitudeInput.dispatchEvent(new Event("input", {bubbles: true}))
            this.el.textContent = "Location added"
            this.el.classList.add("btn-success")
          },
          () => {
            this.el.textContent = "Location permission denied"
            this.el.classList.add("btn-error")
          },
          {enableHighAccuracy: true, timeout: 10000}
        )
      })
    }
  },

  LiveTracking: {
    mounted() {
      this.destroying = false
      this.lastLocationSentAt = 0
      this.pendingLocation = null
      this.locationTimer = undefined

      this.sendLocation = payload => {
        this.lastLocationSentAt = Date.now()
        this.pendingLocation = null
        this.pushEvent("location-update", payload)
      }

      this.queueLocation = payload => {
        const elapsed = Date.now() - this.lastLocationSentAt

        if (this.lastLocationSentAt === 0 || elapsed >= trackingMinTimeMs) {
          if (this.locationTimer !== undefined) {
            window.clearTimeout(this.locationTimer)
            this.locationTimer = undefined
          }

          this.sendLocation(payload)
          return
        }

        this.pendingLocation = payload

        if (this.locationTimer === undefined) {
          this.locationTimer = window.setTimeout(() => {
            this.locationTimer = undefined

            if (!this.destroying && this.pendingLocation) {
              this.sendLocation(this.pendingLocation)
            }
          }, trackingMinTimeMs - elapsed)
        }
      }

      if (typeof window.WhoNeedHelpAndroid?.postMessage === "function") {
        this.nativeError = () => {
          this.pushEvent("location-error", {})
          this.pushEvent("stop-tracking", {})
        }
        window.addEventListener("wnh:native-tracking-error", this.nativeError)
        return
      }

      this.watchId = navigator.geolocation.watchPosition(
        position => this.queueLocation({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
          accuracy_meters: position.coords.accuracy
        }),
        () => {
          if (!this.destroying) this.pushEvent("location-error", {})
        },
        {enableHighAccuracy: true, maximumAge: 5000, timeout: 15000}
      )
    },
    destroyed() {
      this.destroying = true
      this.pendingLocation = null

      if (this.locationTimer !== undefined) {
        window.clearTimeout(this.locationTimer)
        this.locationTimer = undefined
      }

      if (this.nativeError) {
        window.removeEventListener("wnh:native-tracking-error", this.nativeError)
      }
      if (this.watchId !== undefined) navigator.geolocation.clearWatch(this.watchId)
    }
  }
}

function supportsMapCanvas() {
  const canvas = document.createElement("canvas")
  const attributes = {
    alpha: true,
    depth: true,
    stencil: true,
    premultipliedAlpha: true
  }

  try {
    const context =
      canvas.getContext("webgl2", attributes) ||
      canvas.getContext("webgl", attributes)

    if (!context) return false

    context.getExtension("WEBGL_lose_context")?.loseContext()
    return true
  } catch (_error) {
    return false
  }
}
