use std::{
    io::{Write, stdout},
    path::{Path, PathBuf},
    process::ExitCode,
    time::Duration,
};

use game_audio::{
    SoundSystem,
    track::{Refresh, SongPlayer},
};
use termion::{event::Key, input::TermRead, raw::IntoRawMode};

const SEEK_STEP: Duration = Duration::from_secs(5);
const SPEED_STEP: f32 = 0.1;
const VOLUME_STEP: f32 = 0.1;

enum Action {
    Next,
    Quit,
}

fn main() -> ExitCode {
    let mut refresh = Refresh::Cached;
    let mut tracks: Vec<PathBuf> = std::env::args()
        .skip(1)
        .filter(|argument| {
            if argument == "--refresh" || argument == "-r" {
                refresh = Refresh::Force;
                false
            } else {
                true
            }
        })
        .map(PathBuf::from)
        .collect();
    let mut speed = 1.0;
    if tracks.len() > 1
        && let Some(text) = tracks.last().and_then(|path| path.to_str())
        && let Ok(value) = text.parse::<f32>()
        && value > 0.0
    {
        speed = value;
        tracks.pop();
    }
    if tracks.is_empty() {
        eprintln!("Usage: track-play [--refresh] <input.track>... [speed]");
        return ExitCode::FAILURE;
    }

    let system = SoundSystem::new();
    if !system.is_active() {
        eprintln!("No audio device available");
        return ExitCode::FAILURE;
    }

    let Ok(mut raw_stdout) = stdout().into_raw_mode() else {
        eprintln!("Failed to enable terminal raw mode");
        return ExitCode::FAILURE;
    };
    let _ = write!(
        raw_stdout,
        "Space pause · Enter next · q/Esc quit · ←/→ seek · [/] speed · ↑/↓ volume\r\n"
    );

    let mut player = SongPlayer::new();
    let mut keys = termion::async_stdin().keys();
    let mut volume = 1.0;
    let total = tracks.len();

    let single = tracks.len() == 1;
    for (index, track) in tracks.iter().enumerate() {
        // A single file follows its @jump graph; multiple files are a manual
        // playlist advanced with Enter.
        let result = if single {
            player.play_graph_path_with(&system, track, refresh)
        } else {
            player.play_path_with(&system, track, refresh)
        };
        if let Err(error) = result {
            let _ = write!(raw_stdout, "\r\n");
            eprintln!("Playing {track}: {error}", track = track.display());
            return ExitCode::FAILURE;
        }
        player.set_speed(speed);
        player.set_master_volume(volume);

        match play_track(
            &mut player,
            &system,
            &mut keys,
            &mut raw_stdout,
            track,
            index + 1,
            total,
            &mut speed,
            &mut volume,
        ) {
            Action::Quit => return ExitCode::SUCCESS,
            Action::Next => (),
        }
    }
    ExitCode::SUCCESS
}

#[allow(clippy::too_many_arguments)]
fn play_track(
    player: &mut SongPlayer,
    system: &SoundSystem,
    keys: &mut termion::input::Keys<termion::AsyncReader>,
    out: &mut impl Write,
    track: &Path,
    index: usize,
    total: usize,
    speed: &mut f32,
    volume: &mut f32,
) -> Action {
    loop {
        for key in keys.by_ref().flatten() {
            match key {
                Key::Char('q') | Key::Esc => return Action::Quit,
                Key::Char('\n') => return Action::Next,
                Key::Char(' ') => {
                    if player.is_paused() {
                        player.resume();
                    } else {
                        player.pause();
                    }
                }
                Key::Left => {
                    let target = player.position().saturating_sub(SEEK_STEP);
                    let _ = player.seek(target);
                }
                Key::Right => {
                    let target = player.position() + SEEK_STEP;
                    let _ = player.seek(target);
                }
                Key::Char('[') => {
                    *speed = (*speed - SPEED_STEP).max(SPEED_STEP);
                    player.set_speed(*speed);
                }
                Key::Char(']') => {
                    *speed += SPEED_STEP;
                    player.set_speed(*speed);
                }
                Key::Up => {
                    *volume += VOLUME_STEP;
                    player.set_master_volume(*volume);
                }
                Key::Down => {
                    *volume = (*volume - VOLUME_STEP).max(0.0);
                    player.set_master_volume(*volume);
                }
                _ => (),
            }
        }

        let _ = player.update(system);
        let status = if player.is_paused() {
            "paused "
        } else if player.is_playing() {
            "playing"
        } else if player.follows_jump() {
            // A jump is scheduled; the next song starts on a later update.
            "playing"
        } else {
            return Action::Next;
        };
        // During graph playback the current song changes as @jump moves
        // between files; show that name, not the entry track.
        let current = player.current_song().unwrap_or(track);
        let _ = write!(
            out,
            "\r[{index}/{total}] {name} · {status} · {position:.1}s · speed {speed:.1}× · volume {volume:.0}%   ",
            name = current
                .file_name()
                .unwrap_or(current.as_os_str())
                .to_string_lossy(),
            position = player.position().as_secs_f32(),
            volume = *volume * 100.0,
        );
        let _ = out.flush();

        std::thread::sleep(Duration::from_millis(50));
    }
}
