import { createStore, type StateCreator } from 'zustand'
import {
  WebMercatorViewport,
  type MapViewState,
  type PickingInfo,
} from '@deck.gl/core'
import type {
  CommonSlice,
  MapProviderProps,
  MapStore,
  MapSlice,
  ViewStateSlice,
  AreLayerLoadedSlice,
  AreMapsIdleSlice,
  SpatialFilterSlice,
  LayerActionsSlice,
} from './types'
import { transitionFlyConfig } from './const'
import type { DeckGLComponentProps, InstanceRef } from '../../types'
import { MAPS_BASEMAP } from '../../const'
import type { GoogleMapsOverlay } from '@deck.gl/google-maps'
import deepmerge from 'deepmerge'
import type { MjolnirPointerEvent } from 'mjolnir.js'

const createMapSlice = (initProps: MapProviderProps['values']) => {
  const defaultProps = initProps.reduce(
    (acc, curr) => {
      const { id, basemap } = curr
      acc[id] = {
        isLoaded: false,
        basemap,
        instanceRef: {
          current: {} as InstanceRef,
        },
        overlayRef: {
          current: null,
        },
        layers: curr.layers ?? [],
        controller: curr.controller ?? true,
      }
      return acc
    },
    {} as MapSlice['maps'],
  )

  return ((set, get) => ({
    maps: defaultProps,
    setBasemap: (id, basemap) =>
      set((state) => {
        const { type: currentType } = MAPS_BASEMAP[state.maps[id]!.basemap]
        const { type: newType } = MAPS_BASEMAP[basemap]

        const isLoaded = currentType === newType && !!state.maps[id]?.isLoaded

        return {
          maps: {
            ...state.maps,
            [id]: {
              ...state.maps[id]!,
              isLoaded,
              basemap,
            },
          },
        }
      }),
    setLoad: (id, isLoaded) =>
      set((state) => ({
        maps: {
          ...state.maps,
          [id]: {
            ...state.maps[id]!,
            isLoaded,
          },
        },
      })),
    getOverlayInstance: (id) => {
      const store = get()
      return store.maps[id]?.overlayRef?.current
    },
    getDeckInstance: (id) => {
      const store = get()
      const current = store.maps[id]?.instanceRef?.current
      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
      return (
        // @ts-expect-error accessing protected props
        (current as InstanceRef)?.deck ?? (current as GoogleMapsOverlay)?._deck
      )
    },
    getViewState: (id) => {
      const store = get()
      const deck = store.getDeckInstance(id)

      // @ts-expect-error - viewState is not in the types
      if (!deck?.viewState) return

      const { width = 0, height = 0 } = deck
      return {
        // @ts-expect-error - viewState is not in the types
        ...deck.viewState,
        // @ts-expect-error - viewState is not in the types
        width: (deck.viewState.width ?? width) as number,
        // @ts-expect-error - viewState is not in the types
        height: (deck.viewState.height ?? height) as number,
      }
    },
    getViewports: (id, rect) => {
      const store = get()
      const deck = store.getDeckInstance(id)
      return deck?.getViewports(rect)
    },
    getViewport: (id, index = 0, rect) => {
      const store = get()
      const deck = store.getDeckInstance(id)
      return deck?.getViewports(rect)?.[index]
    },
    getBounds: (id, bounds, options) => {
      const store = get()
      if (!store.maps?.[id]?.isLoaded) return

      const viewState = store.getViewState(id)

      if (!viewState) return

      const viewport = new WebMercatorViewport(viewState)

      const newBounds = viewport.fitBounds(bounds, options?.boundsOptions)

      return {
        latitude: newBounds.latitude,
        longitude: newBounds.longitude,
        zoom: newBounds.zoom,
        ...options?.viewStateOptions,
      }
    },
    addLayers: (id, layers) =>
      set((state) => {
        const maps = { ...state.maps }
        const _layers = maps[id]?.layers ?? []

        const set = new Set([..._layers, ...layers])

        maps[id] = {
          ...maps[id]!,
          layers: [...set],
        }
        return { maps }
      }),
    removeLayers: (id, layers) =>
      set((state) => {
        const maps = { ...state.maps }
        const _layers = maps[id]?.layers ?? []
        maps[id] = {
          ...maps[id]!,
          layers: _layers.filter((layer) => !layers.includes(layer)),
        }
        return { maps }
      }),
    getController: (id) => {
      const store = get()
      return store.maps[id]?.controller ?? {}
    },
    setController: (id, controller) =>
      set((state) => {
        const currentController = state.maps[id]?.controller ?? {}

        const _currentController =
          typeof currentController === 'boolean' ? {} : currentController

        const newController =
          !controller || typeof controller === 'boolean'
            ? controller
            : deepmerge<DeckGLComponentProps['controller']>(
                _currentController,
                controller,
              )

        return {
          maps: {
            ...state.maps,
            [id]: {
              ...state.maps[id]!,
              controller: newController,
            },
          },
        }
      }),
  })) as StateCreator<MapSlice, [], [], MapSlice>
}

const createAreLayersLoaded = (initProps: MapProviderProps['values']) => {
  const defaultProps = initProps.reduce(
    (acc, curr) => {
      const { id } = curr
      acc[id] = false
      return acc
    },
    {} as AreLayerLoadedSlice['areLayersLoaded'],
  )

  return ((set) => ({
    areLayersLoaded: defaultProps,
    setLayersLoad: (id, isLoaded) =>
      set((state) => ({
        areLayersLoaded: {
          ...state.areLayersLoaded,
          [id]: isLoaded,
        },
      })),
  })) as StateCreator<AreLayerLoadedSlice, [], [], AreLayerLoadedSlice>
}

const createAreMapsIdle = (initProps: MapProviderProps['values']) => {
  const defaultProps = initProps.reduce(
    (acc, curr) => {
      const { id } = curr
      acc[id] = false
      return acc
    },
    {} as AreMapsIdleSlice['areMapsIdle'],
  )

  return ((set) => ({
    areMapsIdle: defaultProps,
    setIdle: (id, isIdle) =>
      set((state) => ({
        areMapsIdle: {
          ...state.areMapsIdle,
          [id]: isIdle,
        },
      })),
  })) as StateCreator<AreMapsIdleSlice, [], [], AreMapsIdleSlice>
}

const createViewStateSlice = (initProps: MapProviderProps['values']) => {
  const defaultProps = initProps.reduce(
    (acc, curr) => {
      const initialViewState = curr.initialViewState ?? curr.viewState
      acc.initialViewStates[curr.id] = initialViewState
      acc.viewStates[curr.id] = initialViewState
      acc.viewports[curr.id] = null
      return acc
    },
    {
      viewStates: {} as ViewStateSlice['viewStates'],
      viewports: {} as ViewStateSlice['viewports'],
      initialViewStates: {} as ViewStateSlice['initialViewStates'],
    },
  )

  return ((set, get) => ({
    viewports: defaultProps.viewports,
    initialViewStates: defaultProps.initialViewStates,
    updateViewPort: (id, viewState) => {
      const store = get() as MapStore
      if (!store.maps[id]?.isLoaded) return

      const _viewState = { ...store.getViewState(id), ...viewState }

      if (!_viewState) return

      const viewport = new WebMercatorViewport(_viewState)

      set((state) => ({
        viewports: {
          ...state.viewports,
          [id]: viewport,
        },
      }))
    },
    viewStates: defaultProps.viewStates,
    setViewState: (id, viewState) =>
      set((state: MapStore) => ({
        viewStates: {
          ...state.viewStates,
          [id]: {
            ...state.viewStates[id],
            ...viewState,
          },
        },
        areMapsIdle: {
          ...state.areMapsIdle,
          [id]: false,
        },
      })),
    setInitialViewState: (id, viewState) =>
      set((state) => ({
        initialViewStates: {
          ...state.initialViewStates,
          [id]: viewState,
        },
      })),
    setZoom: (id, zoom) =>
      set((state: MapStore) => ({
        viewStates: {
          ...state.viewStates,
          [id]: {
            ...state.viewStates[id]!,
            zoom,
          },
        },
        areMapsIdle: {
          ...state.areMapsIdle,
          [id]: false,
        },
      })),
    setMinZoom: (id, minZoom) =>
      set((state: MapStore) => ({
        viewStates: {
          ...state.viewStates,
          [id]: {
            ...state.viewStates[id]!,
            minZoom,
          },
        },
        areMapsIdle: {
          ...state.areMapsIdle,
          [id]: false,
        },
      })),
    setMaxZoom: (id, maxZoom) =>
      set((state: MapStore) => ({
        viewStates: {
          ...state.viewStates,
          [id]: {
            ...state.viewStates[id]!,
            maxZoom,
          },
        },
        areMapsIdle: {
          ...state.areMapsIdle,
          [id]: false,
        },
      })),
  })) as StateCreator<ViewStateSlice, [], [], ViewStateSlice>
}

const createCommonSlice = ((_, get) => ({
  fitBounds: (id, bounds, options) => {
    const store = get()
    const newViewState = store.getBounds(id, bounds, options)

    if (!newViewState) return

    store.setViewState(id, newViewState)
    store.updateViewPort(id, newViewState)
  },
  flyTo: (id, target, options = {}) => {
    const store = get()

    const viewStateOptions = {
      ...transitionFlyConfig,
      ...options?.viewStateOptions,
    }

    // Merge with current view state and options
    const finalViewState = {
      ...target,
      ...viewStateOptions,
    } as MapViewState

    store.setViewState(id, finalViewState)
    store.updateViewPort(id, finalViewState)
  },
  loadInitialViewState: (id, initialViewState) => {
    const store = get()
    store.setInitialViewState(id, initialViewState)
    store.flyTo(id, initialViewState, {
      viewStateOptions: { transitionDuration: 0 },
    })
  },
  resetViewState: (id: string) => {
    const store = get()
    const initialViewStates = store.initialViewStates
    if (!initialViewStates[id]) return
    store.flyTo(id, initialViewStates[id])
  },
})) as StateCreator<MapStore, [], [], CommonSlice>

const createSpatialFilterSlice = (initProps: MapProviderProps['values']) => {
  const defaultProps = initProps.reduce(
    (acc, curr) => {
      acc[curr.id] = curr.spatialFilter ?? {} // Initialize every mapId to an empty record
      return acc
    },
    {} as SpatialFilterSlice['spatialFilter'],
  )
  return ((set) => ({
    spatialFilter: defaultProps,
    setSpatialFilter: (mapId, id, value) => {
      set((state) => {
        const spatialFilter = structuredClone(state.spatialFilter)

        spatialFilter[mapId] = {
          ...spatialFilter[mapId],
          [id]: value,
        }

        return {
          spatialFilter,
        }
      })
    },
    removeSpatialFilter: (mapId, id) => {
      set((state) => {
        const spatialFilter = structuredClone(state.spatialFilter)
        if (spatialFilter[mapId]) {
          delete spatialFilter[mapId][id]
        }
        return {
          spatialFilter,
        }
      })
    },
  })) as StateCreator<SpatialFilterSlice, [], [], SpatialFilterSlice>
}

const createLayerActionsSlice = ((_, get) => ({
  setLayerHover: (
    mapId,
    layerId,
    geometry,
    callback = (pickedObjects: PickingInfo[]) => pickedObjects[0],
  ) => {
    const store = get() as MapStore
    const deckInstance = store.getDeckInstance(mapId)
    const viewport = store.getViewport(mapId)

    // @ts-expect-error - widgetManager is protected
    const widgetManager = deckInstance?.widgetManager

    if (!widgetManager || !viewport) return

    const [x, y] = viewport.project(geometry)

    if (x == null || y == null || isNaN(x) || isNaN(y)) return

    const pickeds = deckInstance.pickMultipleObjects({
      x,
      y,
      radius: 5,
      layerIds: [layerId],
    })
    const picked = callback(pickeds)

    if (!picked) return

    const object = {
      ...picked,
      x,
      y,
    }

    const event = new MouseEvent('mouseenter') as unknown as MjolnirPointerEvent

    picked.layer?.onHover?.(object, event)
    widgetManager.onHover?.(object, event)
  },
  clearLayersHover: (mapId) => {
    const store = get() as MapStore
    const deckInstance = store.getDeckInstance(mapId)
    // @ts-expect-error - widgetManager is protected
    const widgetManager = deckInstance?.widgetManager

    if (!widgetManager) return

    const event = new MouseEvent('mouseleave') as unknown as MjolnirPointerEvent

    widgetManager.onHover?.({} as PickingInfo, event)
  },
})) as StateCreator<LayerActionsSlice, [], [], LayerActionsSlice>

/**
 * Creates a Zustand store that manages all map-related state including map instances, view states, layers, spatial filters, and layer actions.
 *
 * @param initProps - Array of map configurations used to initialize the store with default values for each map instance.
 * @returns A Zustand store instance containing all map state slices.
 */
export const createMapStore = (initProps: MapProviderProps['values']) => {
  return createStore<MapStore>()((...args) => {
    return {
      ...createMapSlice(initProps)(...args),
      ...createAreLayersLoaded(initProps)(...args),
      ...createAreMapsIdle(initProps)(...args),
      ...createViewStateSlice(initProps)(...args),
      ...createSpatialFilterSlice(initProps)(...args),
      ...createCommonSlice(...args),
      ...createLayerActionsSlice(...args),
    }
  })
}
