import type {MapPlacePoint} from '@crumbs/core/schemas'
import Supercluster from 'supercluster'

import {clamp, regionToBbox, type ClusterRegion} from '@/lib/bbox'
import {placeMarker} from '@/lib/map'

// distinct nearby places collide at low zoom; cluster them so the map thins
// out as you zoom out. the server already collapses crumbs to one point per
// place, so a leaf is always a single place

type PointProps = {placeId: string; crumbCount: number}
type ClusterProps = {crumbCount: number}

export type {ClusterRegion}

export type ClusterIndex = {
  index: Supercluster<PointProps, ClusterProps>
  pointsById: Map<string, MapPlacePoint>
}

const MAX_ZOOM = 16

export const buildClusterIndex = (
  points: MapPlacePoint[],
): ClusterIndex => {
  const index = new Supercluster<PointProps, ClusterProps>({
    radius: 60,
    maxZoom: MAX_ZOOM,
    map: (props) => ({crumbCount: props.crumbCount}),
    reduce: (acc, props) => {
      acc.crumbCount += props.crumbCount
    },
  })
  index.load(
    points.map((point) => ({
      type: 'Feature' as const,
      geometry: {
        type: 'Point' as const,
        coordinates: [point.longitude, point.latitude],
      },
      properties: {
        placeId: point.placeId,
        crumbCount: point.crumbCount,
      },
    })),
  )
  return {
    index,
    pointsById: new Map(points.map((p) => [p.placeId, p])),
  }
}

// synthetic id keeps cluster taps distinguishable from place/crumb taps

const CLUSTER_MARKER_PREFIX = 'cluster:'

export const clusterMarkerId = (id: number): string =>
  `${CLUSTER_MARKER_PREFIX}${id}`

export const parseClusterMarkerId = (
  markerId?: string,
): number | null => {
  if (!markerId?.startsWith(CLUSTER_MARKER_PREFIX)) return null
  const n = Number(markerId.slice(CLUSTER_MARKER_PREFIX.length))
  return Number.isFinite(n) ? n : null
}

type ClusterOrPoint =
  | Supercluster.ClusterFeature<ClusterProps>
  | Supercluster.PointFeature<PointProps>

const isCluster = (
  f: ClusterOrPoint,
): f is Supercluster.ClusterFeature<ClusterProps> =>
  'cluster' in f.properties && f.properties.cluster === true

export const clusterMarkers = (
  {index, pointsById}: ClusterIndex,
  region: ClusterRegion,
  tintColor: string,
) => {
  const zoom = clamp(Math.round(region.zoom), 0, MAX_ZOOM)
  return index
    .getClusters(regionToBbox(region), zoom)
    .map((feature) => {
      const [longitude, latitude] = feature.geometry.coordinates
      if (isCluster(feature))
        return {
          id: clusterMarkerId(feature.properties.cluster_id),
          coordinates: {latitude: latitude!, longitude: longitude!},
          title: `${feature.properties.crumbCount} crumbs`,
          monogram: String(feature.properties.crumbCount),
          tintColor,
        }
      const point = pointsById.get(feature.properties.placeId)
      return point ? placeMarker(point, tintColor) : null
    })
    .filter((m): m is NonNullable<typeof m> => m !== null)
}

export const clusterExpansionZoom = (
  {index}: ClusterIndex,
  clusterId: number,
): number | null => {
  try {
    return index.getClusterExpansionZoom(clusterId)
  } catch {
    return null
  }
}
