/**
 * useStorage — React hook wrapping StorageManager.
 *
 * Provides a [value, setValue, remove] tuple backed by localStorage with
 * cross-tab synchronisation via the window `storage` event.
 *
 * Uses useSyncExternalStore to subscribe to external storage events so that
 * cross-tab updates cause an immediate re-render without a polling loop.
 */
import { useMemo, useCallback, useRef, useSyncExternalStore } from 'react'
import { StorageManager } from '../../services/storage'
import type { StorageConfig } from '../../services/storage'

export type { StorageConfig }

export interface UseStorageOptions<T> {
  defaultValue?: T
  /** Optional namespace/prefix override forwarded to StorageManager. */
  namespace?: string
}

type UseStorageReturn<T> = [T, (value: T) => void, () => void]

/**
 * @param key     - The key within the storage namespace.
 * @param options - Optional default value and namespace override.
 * @returns `[value, setValue, remove]` — stable tuple usable as state.
 */
export function useStorage<T = unknown>(
  key: string,
  options?: UseStorageOptions<T>
): UseStorageReturn<T> {
  const { defaultValue, namespace } = options ?? {}

  const manager = useMemo(
    () => new StorageManager(namespace !== undefined ? { prefix: namespace } : undefined),
    [namespace]
  )

  const snapshotRef = useRef<{ serialised: string; value: T }>({
    serialised: '',
    value: defaultValue as T,
  })

  const getSnapshot = useCallback((): T => {
    const raw = manager.get(key)
    const value = raw !== null ? (raw as T) : (defaultValue as T)
    const serialised = JSON.stringify(value)
    if (serialised !== snapshotRef.current.serialised) {
      snapshotRef.current = { serialised, value }
    }
    return snapshotRef.current.value
  }, [manager, key, defaultValue])

  // Subscribe to localStorage events from this tab and other tabs.
  // - Native `StorageEvent` fires only in OTHER tabs (cross-tab sync).
  // - Our synthetic CustomEvent('storage') fires in THIS tab when setValue/remove
  //   are called so that useSyncExternalStore re-reads the snapshot.
  const subscribe = useCallback((onStoreChange: () => void): (() => void) => {
    const nativeListener = (event: StorageEvent): void => {
      if (event.storageArea === localStorage || event.storageArea === null) {
        onStoreChange()
      }
    }
    const syntheticListener = (): void => {
      onStoreChange()
    }
    window.addEventListener('storage', nativeListener as EventListener)
    window.addEventListener('cm:storage-change', syntheticListener)
    return () => {
      window.removeEventListener('storage', nativeListener as EventListener)
      window.removeEventListener('cm:storage-change', syntheticListener)
    }
  }, [])

  const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)

  const setValue = useCallback(
    (newValue: T): void => {
      manager.set(key, newValue)
      // Dispatch a synthetic event so the same-tab useSyncExternalStore
      // subscriber also reacts (the native `storage` event only fires in OTHER tabs).
      window.dispatchEvent(new CustomEvent('cm:storage-change'))
    },
    [manager, key]
  )

  const remove = useCallback((): void => {
    manager.remove(key)
    window.dispatchEvent(new CustomEvent('cm:storage-change'))
  }, [manager, key])

  return [value, setValue, remove]
}
