pub(crate) mod shader;
pub(crate) mod video;

use crate::player::Player;
use crate::ui::shared::screensaver::ScreensaverInhibitor;
use crate::ui::shared::styles::{
    self, ACCENT_RED, ACCENT_RED_HOVER, BG_CONTROL_BAR, GRAY_30, GRAY_70,
};
use iced::{Element, Task};
use std::sync::Arc;
use std::time::{Duration, Instant};

const CURSOR_HIDE_DELAY: Duration = Duration::from_secs(2);
const DOUBLE_CLICK_WINDOW: Duration = Duration::from_millis(300);

#[derive(Debug, Clone)]
pub(crate) enum Message {
    LeftClick(iced::window::Id),
    #[allow(dead_code)]
    SpaceKey(iced::window::Id),
    Seek(f64),
    SwitchAudioStream(AudioStreamOption),
}

pub(crate) fn update(state: &mut State, msg: Message) -> Task<crate::ui::Message> {
    match msg {
        Message::LeftClick(window_id) => {
            let now = Instant::now();
            let is_double = state
                .last_click
                .map(|prev| now.duration_since(prev) < DOUBLE_CLICK_WINDOW)
                .unwrap_or(false);
            state.last_click = Some(now);

            if is_double {
                state.last_click = None;
                state.is_fullscreen = !state.is_fullscreen;
                let mode = if state.is_fullscreen {
                    iced::window::Mode::Fullscreen
                } else {
                    iced::window::Mode::Windowed
                };
                return iced::window::set_mode(window_id, mode);
            }
            Task::none()
        }
        Message::SpaceKey(_) => {
            if !state.paused {
                log::debug!("player paused");
                if let Err(e) = state.ctx.pause() {
                    log::error!("failed to pause player: {e}");
                    return iced::exit();
                }
            } else {
                log::debug!("player resumed");
                if let Err(e) = state.ctx.resume() {
                    log::error!("failed to resume player: {e}");
                    return iced::exit();
                }
            }
            state.paused = !state.paused;
            Task::none()
        }
        Message::Seek(secs) => {
            if let Err(e) = state.ctx.seek(secs) {
                log::error!("seek failed: {e}");
                return iced::exit();
            }
            state.paused = false;
            Task::none()
        }
        Message::SwitchAudioStream(option) => {
            if let Err(e) = state.ctx.switch_audio_stream(option.index) {
                log::error!("failed to switch audio stream: {e}");
            }
            state.selected_audio_stream = Some(option);
            Task::none()
        }
    }
}

pub(crate) struct State {
    pub(crate) ctx: Player,
    pub(crate) current_frame: Option<Arc<shader::YuvFrameData>>,
    pub(crate) frames_dropped: usize,
    pub(crate) frames_processed: usize,
    pub(crate) last_click: Option<Instant>,
    pub(crate) is_fullscreen: bool,
    pub(crate) current_time: f64,
    pub(crate) total_duration: f64,
    pub(crate) paused: bool,
    pub(crate) audio_streams: Vec<AudioStreamOption>,
    pub(crate) selected_audio_stream: Option<AudioStreamOption>,
    pub(crate) yuv_buffer: Option<video::YuvScratch>,
    pub(crate) _screensaver_inhibitor: Option<ScreensaverInhibitor>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AudioStreamOption {
    pub(crate) index: usize,
    pub(crate) label: String,
}

impl std::fmt::Display for AudioStreamOption {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.label)
    }
}

pub(crate) fn view<'a>(
    ctx: &'a Player,
    current_frame: &'a Option<Arc<shader::YuvFrameData>>,
    current_time: f64,
    total_duration: f64,
    is_fullscreen: bool,
    audio_streams: &'a [AudioStreamOption],
    selected_audio_stream: &'a Option<AudioStreamOption>,
    last_mouse_move: Instant,
) -> Element<'a, crate::ui::Message> {
    let video_content: Element<_> = if current_frame.is_some() {
        iced::widget::Shader::new(shader::VideoProgram {
            frame: current_frame.clone(),
            video_width: ctx.media_info().width,
            video_height: ctx.media_info().height,
        })
        .width(iced::Length::Fill)
        .height(iced::Length::Fill)
        .into()
    } else {
        iced::widget::text("buffering...").into()
    };

    let video_area = iced::widget::container(video_content)
        .width(iced::Length::Fill)
        .height(iced::Length::Fill)
        .center_x(iced::Length::Fill)
        .center_y(iced::Length::Fill)
        .style(|_theme| iced::widget::container::Style {
            background: Some(iced::Background::Color(iced::Color::BLACK)),
            ..Default::default()
        });

    let seek_slider = iced::widget::slider(0.0..=total_duration, current_time, |v| {
        crate::ui::Message::Player(Message::Seek(v))
    })
    .step(0.1)
    .style(move |_theme, status| {
        let handle_color = match status {
            iced::widget::slider::Status::Hovered => ACCENT_RED_HOVER,
            _ => ACCENT_RED,
        };
        iced::widget::slider::Style {
            rail: iced::widget::slider::Rail {
                backgrounds: (ACCENT_RED.into(), GRAY_30.into()),
                width: 4.0,
                border: iced::border::rounded(2),
            },
            handle: iced::widget::slider::Handle {
                shape: iced::widget::slider::HandleShape::Circle { radius: 6.0 },
                background: handle_color.into(),
                border_width: 0.0,
                border_color: iced::Color::TRANSPARENT,
            },
        }
    });

    let time_label = iced::widget::text(format!(
        "{} / {}",
        format_time(current_time),
        format_time(total_duration)
    ))
    .size(12)
    .color(GRAY_70);

    let mut info_row = iced::widget::row![time_label].spacing(styles::SPACING_DEFAULT);

    if audio_streams.len() > 1 {
        let audio_picker =
            iced::widget::pick_list(audio_streams, selected_audio_stream.as_ref(), |opt| {
                crate::ui::Message::Player(Message::SwitchAudioStream(opt))
            })
            .text_size(12);

        info_row = info_row
            .push(iced::widget::Space::new().width(iced::Length::Fill))
            .push(audio_picker);
    }

    let control_bar = iced::widget::container(
        iced::widget::column![seek_slider, info_row].spacing(styles::SPACING_TIGHT),
    )
    .width(iced::Length::Fill)
    .padding(iced::Padding::new(styles::PADDING_SMALL))
    .style(|_theme| iced::widget::container::Style {
        background: Some(iced::Background::Color(BG_CONTROL_BAR)),
        ..Default::default()
    });

    if is_fullscreen {
        let cursor = if last_mouse_move.elapsed() > CURSOR_HIDE_DELAY {
            iced::mouse::Interaction::Hidden
        } else {
            iced::mouse::Interaction::Idle
        };
        iced::widget::mouse_area(video_area)
            .on_move(|_| crate::ui::Message::MouseMoved)
            .interaction(cursor)
            .into()
    } else {
        iced::widget::column![video_area, control_bar].into()
    }
}

fn format_time(seconds: f64) -> String {
    let total_secs = seconds.max(0.0) as u64;
    let hours = total_secs / 3600;
    let mins = (total_secs % 3600) / 60;
    let secs = total_secs % 60;
    if hours > 0 {
        format!("{hours}:{mins:02}:{secs:02}")
    } else {
        format!("{mins:02}:{secs:02}")
    }
}
