/**
 * useConsent — React hook wrapping the vanilla consent manager.
 *
 * Exposes the current ConsentCategories snapshot and stable mutation
 * callbacks.  Uses useSyncExternalStore to subscribe to a custom DOM event
 * (`cm:consent-changed`) that we dispatch whenever consent is mutated
 * through this hook, providing cross-component reactivity without polling.
 */
import { useSyncExternalStore, useCallback, useMemo, useRef } from 'react'
import { createConsentManager, getDefaultConsentManager } from '../../consent/index'
import type { ConsentCategories, ConsentConfig, CategoryInfo } from '../../consent/index'

export type { ConsentCategories, ConsentConfig, CategoryInfo }

type ConsentManager = ReturnType<typeof createConsentManager>

const CONSENT_EVENT = 'cm:consent-changed'

/** Dispatch the notification event so all hook subscribers re-render. */
function notifyConsentChange(): void {
  window.dispatchEvent(new CustomEvent(CONSENT_EVENT))
}

export interface UseConsentResult {
  /** The underlying consent manager instance. */
  manager: ConsentManager
  /** Current stored consent categories snapshot (null when not yet set). */
  preferences: ConsentCategories | null
  /**
   * Update a single category.
   * Persists to localStorage (via `setStoredConsent`) and re-renders.
   */
  setCategory: (category: keyof ConsentCategories, value: boolean) => void
  /** Accept all optional categories and persist. */
  acceptAll: () => void
  /** Reject all optional categories (keep essential) and persist. */
  rejectAll: () => void
  /** Fetch the server-side list of consent categories. */
  fetchCategories: () => Promise<CategoryInfo[]>
  /** Persist current preferences to the server. */
  saveConsent: (categories: ConsentCategories, method?: string) => Promise<boolean>
}

/**
 * @param config - Optional ConsentConfig.  When omitted the module-level
 *   default manager is used.  Pass an explicit config for an isolated instance.
 */
export function useConsent(config?: ConsentConfig): UseConsentResult {
  const manager = useMemo<ConsentManager>(
    () => (config !== undefined ? createConsentManager(config) : getDefaultConsentManager()),
    [config] // config reference controls re-creation
  )

  // useSyncExternalStore: subscribe via custom event; snapshot reads stored consent.
  const subscribe = useCallback((onStoreChange: () => void): (() => void) => {
    window.addEventListener(CONSENT_EVENT, onStoreChange)
    return () => {
      window.removeEventListener(CONSENT_EVENT, onStoreChange)
    }
  }, [])

  // Cache the last snapshot by JSON value so useSyncExternalStore gets a stable
  // reference when the store has not changed — avoids the infinite-loop warning.
  const snapshotRef = useRef<{ raw: string; value: ConsentCategories | null }>({
    raw: '',
    value: null,
  })

  const getSnapshot = useCallback((): ConsentCategories | null => {
    const current = manager.getStoredConsent()
    const serialised = current === null ? '' : JSON.stringify(current)
    if (serialised !== snapshotRef.current.raw) {
      snapshotRef.current = { raw: serialised, value: current }
    }
    return snapshotRef.current.value
  }, [manager])

  const preferences = useSyncExternalStore<ConsentCategories | null>(
    subscribe,
    getSnapshot,
    getSnapshot
  )

  const setCategory = useCallback(
    (category: keyof ConsentCategories, value: boolean): void => {
      const current: ConsentCategories = manager.getStoredConsent() ?? {
        essential: true,
        performance: false,
        analytics: false,
        advertising: false,
        social_media: false,
      }
      const updated: ConsentCategories = {
        ...current,
        [category]: value,
        essential: category === 'essential' ? true : (current.essential ?? true),
      }
      manager.setStoredConsent(updated)
      notifyConsentChange()
    },
    [manager]
  )

  const acceptAll = useCallback((): void => {
    const categories = manager.acceptAll()
    manager.setStoredConsent(categories)
    notifyConsentChange()
  }, [manager])

  const rejectAll = useCallback((): void => {
    const categories = manager.rejectAll()
    manager.setStoredConsent(categories)
    notifyConsentChange()
  }, [manager])

  const fetchCategories = useCallback(
    (): Promise<CategoryInfo[]> => manager.fetchCategories(),
    [manager]
  )

  const saveConsent = useCallback(
    (categories: ConsentCategories, method?: string): Promise<boolean> => {
      const result = manager.saveConsent(categories, method)
      notifyConsentChange()
      return result
    },
    [manager]
  )

  return { manager, preferences, setCategory, acceptAll, rejectAll, fetchCategories, saveConsent }
}
