import { type ReactNode, type RefObject, useMemo } from "react";
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import tg, { STORAGE_KEYS } from "@/tg";
import type { SoundKey } from "./sounds";
import { SOUNDS, preloadSounds } from "./sounds";

type AudioCtxValue = {
  isEnabled: boolean;
  setIsEnabled: (enabled: boolean) => void;
  loopingRef: RefObject<Map<SoundKey, HTMLAudioElement>>;
};

const AudioCtx = createContext<AudioCtxValue | null>(null);

export function useAudioSettings(): {
  isEnabled: boolean;
  setIsEnabled: (enabled: boolean) => void;
} {
  const ctx = useContext(AudioCtx);
  if (!ctx) throw new Error("useAudioSettings must be used within AudioProvider");
  return { isEnabled: ctx.isEnabled, setIsEnabled: ctx.setIsEnabled };
}

export function usePlaySound(): (
  key: SoundKey,
  { mode, force }?: { mode?: "once" | "loop"; force: boolean },
) => void {
  const ctx = useContext(AudioCtx);
  if (!ctx) throw new Error("usePlaySound must be used within AudioProvider");

  const { isEnabled, loopingRef } = ctx;

  return useCallback(
    (key: SoundKey, { mode, force }: { mode?: "once" | "loop"; force?: boolean } = {}) => {
      if (!isEnabled && !force) return;

      if (mode === "loop" && loopingRef.current.has(key)) return;

      const audio = new Audio(SOUNDS[key]);

      if (mode === "loop") {
        audio.loop = true;
        loopingRef.current.set(key, audio);
      } else {
        audio.addEventListener(
          "ended",
          () => {
            audio.src = "";
          },
          { once: true },
        );
      }

      audio.play().catch(() => {
        if (mode === "loop") loopingRef.current.delete(key);
      });
    },
    [isEnabled, loopingRef],
  );
}

export function AudioProvider({ children }: { children: ReactNode }) {
  const [isEnabled, setIsEnabled] = useState(true);
  const hydratedRef = useRef(false);
  const loopingRef = useRef<Map<SoundKey, HTMLAudioElement>>(null);
  if (!loopingRef.current) {
    loopingRef.current = new Map();
    preloadSounds();
  }

  useEffect(() => {
    tg.storage.getItem(STORAGE_KEYS.soundEnabled).then((value) => {
      if (value !== "") {
        setIsEnabled(value !== "false");
      }
      hydratedRef.current = true;
    });
  }, []);

  useEffect(() => {
    if (!hydratedRef.current) return;
    tg.storage.setItem(STORAGE_KEYS.soundEnabled, String(isEnabled));
  }, [isEnabled]);

  useEffect(() => {
    if (!isEnabled) {
      for (const audio of loopingRef.current!.values()) {
        audio.pause();
        audio.src = "";
      }
      loopingRef.current!.clear();
    }
  }, [isEnabled]);

  return (
    <AudioCtx
      value={useMemo(
        () =>
          ({
            isEnabled,
            setIsEnabled,
            loopingRef,
          }) as AudioCtxValue,
        [isEnabled],
      )}
    >
      {children}
    </AudioCtx>
  );
}
