import { useEffect, useId } from 'react'
import { KeepAwake as unavailable } from './unavailable'
import {
  assertKeepAwakeEnabled,
  assertKeepAwakeTag,
  normalizeKeepAwakeTag,
} from './validate'

const active = new Set<string>()
let lock: WakeLockSentinel | undefined
let operation: Promise<void> = Promise.resolve()

function live(): boolean {
  return !!lock && !lock.released
}

function apply(owner: 'setEnabled' | 'activate' | 'deactivate'): Promise<void> {
  const next = operation
    .catch(() => {})
    .then(async () => {
      if (active.size === 0) {
        await lock?.release()
        lock = undefined
        return
      }
      if (live()) return
      if (!navigator.wakeLock)
        throw new Error(`KeepAwake.${owner}: Screen Wake Lock is unavailable`)
      lock = await navigator.wakeLock.request('screen')
    })
  operation = next
  return next
}

export const KeepAwake = Object.freeze({
  isEnabled: async (): Promise<boolean> =>
    typeof window !== 'undefined' && active.size > 0 && live(),
  setEnabled: (enabled: boolean): Promise<void> => {
    assertKeepAwakeEnabled(enabled)
    if (typeof window === 'undefined') return unavailable.setEnabled(enabled)
    if (enabled) active.add(normalizeKeepAwakeTag(undefined))
    else active.delete(normalizeKeepAwakeTag(undefined))
    return apply('setEnabled')
  },
  activate: (tag?: string): Promise<void> => {
    assertKeepAwakeTag(tag, 'activate')
    if (typeof window === 'undefined') return unavailable.activate(tag)
    active.add(normalizeKeepAwakeTag(tag))
    return apply('activate')
  },
  deactivate: (tag?: string): Promise<void> => {
    assertKeepAwakeTag(tag, 'deactivate')
    if (typeof window === 'undefined') return unavailable.deactivate(tag)
    active.delete(normalizeKeepAwakeTag(tag))
    return apply('deactivate')
  },
})

export function useKeepAwake(tag?: string): void {
  const fallback = useId()
  assertKeepAwakeTag(tag, 'useKeepAwake')
  // a hook without a tag owns an id unique to its component instance, never
  // the shared default tag, matching expo-keep-awake.
  const effective = tag ?? fallback
  useEffect(() => {
    if (typeof window === 'undefined') return
    // a hook has no caller to reject to, so failures are reported, as
    // usePreventScreenCapture does.
    KeepAwake.activate(effective).catch((error: unknown) => {
      console.error(`Failed to keep the screen awake: ${String(error)}`)
    })
    return () => {
      KeepAwake.deactivate(effective).catch(() => {})
    }
  }, [effective])
}
