import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { useContentCatalogPayload } from '@/business/content-catalog/hooks';
import { playBuiltInSound } from '@/business/classroom-timer/audio';
import { getFirstQueryError, isAnyLoading } from '@/shared/business/queryState';
import {
  createTimerParticles,
  formatTimerTime,
  getTimerProgress,
  getTimerUrgencyColor,
  parseBoundedInteger,
} from '@/business/classroom-timer/selectors';
import {
  DEFAULT_CUSTOM_MINUTES,
  DEFAULT_CUSTOM_SECONDS,
  DEFAULT_TIMER_SECONDS,
  FULLSCREEN_PARTICLE_COUNT,
  TIMER_CARD_PARTICLE_COUNT,
  TIMER_FINISH_REPEAT_DELAY_MS,
  TIMER_PREVIEW_DURATION_MS,
} from '@/shared/constants/classroomTimer';
import { CONTENT_CATALOG_TYPES } from '@/shared/constants/contentCatalog';
import type {
  ClassroomTimerTip,
  PresetTimerOption,
  SensoryBackground,
  SensoryBackgroundId,
  TimerSoundOption,
  TimerSoundType,
} from '@/shared/types/classroomTimer';

declare global {
  interface Window {
    readonly webkitAudioContext?: typeof AudioContext;
  }
}

export function useClassroomTimer() {
  const backgroundsQuery = useContentCatalogPayload<readonly SensoryBackground[]>(
    CONTENT_CATALOG_TYPES.classroomTimerBackgrounds,
    [],
  );
  const soundsQuery = useContentCatalogPayload<readonly TimerSoundOption[]>(
    CONTENT_CATALOG_TYPES.classroomTimerSounds,
    [],
  );
  const presetsQuery = useContentCatalogPayload<readonly PresetTimerOption[]>(
    CONTENT_CATALOG_TYPES.classroomTimerPresets,
    [],
  );
  const tipsQuery = useContentCatalogPayload<readonly ClassroomTimerTip[]>(
    CONTENT_CATALOG_TYPES.classroomTimerTips,
    [],
  );
  const [totalSeconds, setTotalSeconds] = useState(DEFAULT_TIMER_SECONDS);
  const [remainingSeconds, setRemainingSeconds] = useState(DEFAULT_TIMER_SECONDS);
  const [isRunning, setIsRunning] = useState(false);
  const [isFinished, setIsFinished] = useState(false);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [selectedBackgroundId, setSelectedBackgroundId] = useState<SensoryBackgroundId | null>(null);
  const [selectedSoundId, setSelectedSoundId] = useState<TimerSoundType | null>(null);
  const [soundEnabled, setSoundEnabled] = useState(true);
  const [customMinutes, setCustomMinutes] = useState(DEFAULT_CUSTOM_MINUTES);
  const [customSeconds, setCustomSeconds] = useState(DEFAULT_CUSTOM_SECONDS);
  const [previewPlaying, setPreviewPlaying] = useState(false);

  const intervalRef = useRef<number | null>(null);
  const audioCtxRef = useRef<AudioContext | null>(null);
  const fullscreenRef = useRef<HTMLDivElement>(null);

  const displayParticles = useMemo(
    () => createTimerParticles(TIMER_CARD_PARTICLE_COUNT, 6, 3),
    [],
  );
  const fullscreenParticles = useMemo(
    () => createTimerParticles(FULLSCREEN_PARTICLE_COUNT, 8, 4),
    [],
  );
  const backgrounds = backgroundsQuery.payload;
  const sounds = soundsQuery.payload;
  const presets = presetsQuery.payload;
  const tips = tipsQuery.payload;
  const contentQueries = [backgroundsQuery, soundsQuery, presetsQuery, tipsQuery];
  const selectedBackground = useMemo(
    () => backgrounds.find((background) => background.id === selectedBackgroundId) ?? backgrounds[0] ?? null,
    [backgrounds, selectedBackgroundId],
  );
  const selectedSound = useMemo(
    () => sounds.find((sound) => sound.id === selectedSoundId) ?? sounds[0] ?? null,
    [selectedSoundId, sounds],
  );

  const getAudioContext = useCallback((): AudioContext => {
    if (!audioCtxRef.current) {
      const AudioContextConstructor = window.AudioContext || window.webkitAudioContext;

      if (!AudioContextConstructor) {
        throw new Error('Web Audio API is not supported in this browser.');
      }

      audioCtxRef.current = new AudioContextConstructor();
    }

    if (audioCtxRef.current.state === 'suspended') {
      void audioCtxRef.current.resume();
    }

    return audioCtxRef.current;
  }, []);

  useEffect(() => {
    if (isRunning && remainingSeconds > 0) {
      intervalRef.current = window.setInterval(() => {
        setRemainingSeconds((previousValue) => {
          if (previousValue <= 1) {
            setIsRunning(false);
            setIsFinished(true);
            return 0;
          }

          return previousValue - 1;
        });
      }, 1000);
    }

    return () => {
      if (intervalRef.current) {
        window.clearInterval(intervalRef.current);
      }
    };
  }, [isRunning, remainingSeconds]);

  useEffect(() => {
    if (!isFinished || !soundEnabled || !selectedSound) {
      return;
    }

    const audioContext = getAudioContext();
    playBuiltInSound(selectedSound.id, audioContext);
    const repeatSoundTimeout = window.setTimeout(() => {
      playBuiltInSound(selectedSound.id, audioContext);
    }, TIMER_FINISH_REPEAT_DELAY_MS);

    return () => window.clearTimeout(repeatSoundTimeout);
  }, [getAudioContext, isFinished, selectedSound, soundEnabled]);

  useEffect(() => {
    const handleFullscreenChange = () => {
      if (!document.fullscreenElement) {
        setIsFullscreen(false);
      }
    };

    document.addEventListener('fullscreenchange', handleFullscreenChange);
    return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
  }, []);

  const handleStart = useCallback(() => {
    if (remainingSeconds <= 0) {
      return;
    }

    getAudioContext();
    setIsRunning(true);
    setIsFinished(false);
  }, [getAudioContext, remainingSeconds]);

  const handlePause = useCallback(() => {
    setIsRunning(false);
  }, []);

  const handleReset = useCallback(() => {
    setIsRunning(false);
    setIsFinished(false);
    setRemainingSeconds(totalSeconds);
  }, [totalSeconds]);

  const handleSetTime = useCallback((seconds: number) => {
    setIsRunning(false);
    setIsFinished(false);
    setTotalSeconds(seconds);
    setRemainingSeconds(seconds);
  }, []);

  const handleCustomTime = useCallback(() => {
    const total = customMinutes * 60 + customSeconds;
    if (total > 0) {
      handleSetTime(total);
    }
  }, [customMinutes, customSeconds, handleSetTime]);

  const toggleFullscreen = useCallback(async () => {
    if (!isFullscreen) {
      if (fullscreenRef.current) {
        await fullscreenRef.current.requestFullscreen();
      }
      setIsFullscreen(true);
      return;
    }

    if (document.fullscreenElement) {
      await document.exitFullscreen();
    }
    setIsFullscreen(false);
  }, [isFullscreen]);

  const previewSound = useCallback((soundId: TimerSoundType) => {
    if (previewPlaying) {
      return;
    }

    setPreviewPlaying(true);
    const audioContext = getAudioContext();
    playBuiltInSound(soundId, audioContext);
    window.setTimeout(() => setPreviewPlaying(false), TIMER_PREVIEW_DURATION_MS);
  }, [getAudioContext, previewPlaying]);

  const progress = getTimerProgress(totalSeconds, remainingSeconds);
  const formattedTime = formatTimerTime(remainingSeconds);
  const urgencyColor = selectedBackground
    ? getTimerUrgencyColor(isFinished, progress, selectedBackground)
    : 'text-cyan-100';

  return {
    state: {
      backgrounds,
      sounds,
      presets,
      tips,
      totalSeconds,
      remainingSeconds,
      isRunning,
      isFinished,
      isFullscreen,
      selectedBackground,
      selectedSound,
      soundEnabled,
      customMinutes,
      customSeconds,
      previewPlaying,
      progress,
      formattedTime,
      urgencyColor,
      displayParticles,
      fullscreenParticles,
      isContentLoading: isAnyLoading(...contentQueries),
      contentError: getFirstQueryError(...contentQueries),
    },
    refs: {
      fullscreenRef,
    },
    actions: {
      handleStart,
      handlePause,
      handleReset,
      handleSetTime,
      handleCustomTime,
      toggleFullscreen,
      previewSound,
      setSelectedBackground: (background: SensoryBackground) => setSelectedBackgroundId(background.id),
      setSelectedSound: (sound: TimerSoundOption) => setSelectedSoundId(sound.id),
      setSoundEnabled,
      setCustomMinutes,
      setCustomSeconds,
      parseCustomMinutes: (value: string) => setCustomMinutes(parseBoundedInteger(value, 0, 120)),
      parseCustomSeconds: (value: string) => setCustomSeconds(parseBoundedInteger(value, 0, 59)),
    },
  };
}
