from __future__ import annotations

import random
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path

import pyray as rl

from . import music, sfx
from .config import CrimsonConfig
from .console import ConsoleState


@dataclass(slots=True)
class AudioState:
    ready: bool
    music: music.MusicState
    sfx: sfx.SfxState


def init_audio_state(config: CrimsonConfig, assets_dir: Path, console: ConsoleState) -> AudioState:
    music_disabled = config.music_disabled
    sound_disabled = config.sound_disabled
    music_volume = config.music_volume
    sfx_volume = config.sfx_volume

    music_enabled = not music_disabled
    sfx_enabled = not sound_disabled
    if not music_enabled and not sfx_enabled:
        console.log.log("audio: disabled (music + sfx)")
        console.log.flush()
        return AudioState(
            ready=False,
            music=music.init_music_state(ready=False, enabled=False, volume=music_volume),
            sfx=sfx.init_sfx_state(ready=False, enabled=False, volume=sfx_volume),
        )

    if not rl.is_audio_device_ready():
        rl.init_audio_device()
    ready = bool(rl.is_audio_device_ready())
    if not ready:
        console.log.log("audio: device init failed")
        console.log.flush()
        return AudioState(
            ready=False,
            music=music.init_music_state(ready=False, enabled=False, volume=music_volume),
            sfx=sfx.init_sfx_state(ready=False, enabled=False, volume=sfx_volume),
        )

    state = AudioState(
        ready=True,
        music=music.init_music_state(ready=True, enabled=music_enabled, volume=music_volume),
        sfx=sfx.init_sfx_state(ready=True, enabled=sfx_enabled, volume=sfx_volume),
    )
    sfx.load_sfx_index(state.sfx, assets_dir, console)
    music.load_music_tracks(state.music, assets_dir, console)
    return state


def play_music(state: AudioState, track_name: str) -> None:
    music.play_music(state.music, track_name)


def stop_music(state: AudioState) -> None:
    music.stop_music(state.music)


def trigger_game_tune(state: AudioState, *, rand: Callable[[], int] | None = None) -> str | None:
    return music.trigger_game_tune(state.music, rand=rand)


def play_sfx(
    state: AudioState | None,
    key: str | None,
    *,
    rng: random.Random | None = None,
    allow_variants: bool = True,
    reflex_boost_timer: float = 0.0,
) -> None:
    if state is None:
        return
    sfx.play_sfx(
        state.sfx,
        key,
        rng=rng,
        allow_variants=allow_variants,
        reflex_boost_timer=float(reflex_boost_timer),
    )


def set_sfx_volume(state: AudioState | None, volume: float) -> None:
    if state is None:
        return
    sfx.set_sfx_volume(state.sfx, volume)


def set_music_volume(state: AudioState | None, volume: float) -> None:
    if state is None:
        return
    music.set_music_volume(state.music, volume)


def update_audio(state: AudioState, dt: float) -> None:
    music.update_music(state.music, dt)


def shutdown_audio(state: AudioState) -> None:
    if not state.ready:
        return
    sfx.shutdown_sfx(state.sfx)
    music.shutdown_music(state.music)
    rl.close_audio_device()
