import { useEffect, useMemo, useReducer } from "react"
import Supercluster, { type ClusterProperties } from "supercluster"

import { useMapViewport } from "./use-map-viewport"

import type { FeatureCollection, GeoJsonProperties, Point } from "geojson"

export function useSupercluster<T extends GeoJsonProperties>(
  geojson: FeatureCollection<Point, T>,
  superclusterOptions: Supercluster.Options<T, ClusterProperties>
) {
  // create the clusterer and keep it
  const clusterer = useMemo(() => {
    return new Supercluster(superclusterOptions)
  }, [superclusterOptions])

  // version-number for the data loaded into the clusterer
  // (this is needed to trigger updating the clusters when data was changed)
  const [version, dataWasUpdated] = useReducer((x: number) => x + 1, 0)

  // when data changes, load it into the clusterer
  useEffect(() => {
    clusterer.load(geojson.features)
    dataWasUpdated()
  }, [clusterer, geojson])

  // get bounding-box and zoomlevel from the map
  const { bbox, zoom } = useMapViewport({ padding: 100 })

  // retrieve the clusters within the current viewport
  const clusters = useMemo(() => {
    // don't try to read clusters before data was loaded into the clusterer (version===0),
    // otherwise getClusters will crash
    if (!clusterer || version === 0) return []

    return clusterer.getClusters(bbox, zoom)
  }, [version, clusterer, bbox, zoom])

  function getClusterZoom(clusterId: number) {
    if (!clusterer || version === 0) {
      return null
    }

    return clusterer.getClusterExpansionZoom(clusterId)
  }

  // retrieve the hotel ids included in the cluster
  const containedHotels = clusters.map((cluster) => {
    if (cluster.properties?.cluster && typeof cluster.id === "number") {
      return clusterer.getLeaves(cluster.id).map((hotel) => Number(hotel.id))
    }
    return []
  })

  return {
    clusters,
    containedHotels,
    getClusterZoom,
  }
}
