use std::time::Duration;

use druid::{
    im::Vector,
    lens::Unit,
    widget::{CrossAxisAlignment, Either, Flex, Label, Scroll, Slider, Split, ViewSwitcher},
    Color, Env, Insets, Key, LensExt, Menu, MenuItem, Selector, Widget, WidgetExt, WindowDesc,
};

use crate::{
    cmd,
    controller::{AfterDelay, NavController, SessionController},
    data::{Alert, AlertStyle, AppState, Nav, Playback, PlaylistDetail, Route},
    widget::{
        icons, icons::SvgIcon, Border, Empty, Maybe, MyWidgetExt, Overlay, ThemeScope,
        ViewDispatcher,
    },
};

pub mod album;
pub mod artist;
pub mod find;
pub mod home;
pub mod library;
pub mod menu;
pub mod playback;
pub mod playlist;
pub mod preferences;
pub mod recommend;
pub mod search;
pub mod theme;
pub mod track;
pub mod user;
pub mod utils;

pub fn main_window() -> WindowDesc<AppState> {
    let win = WindowDesc::new(root_widget())
        .title(compute_main_window_title)
        .with_min_size((theme::grid(65.0), theme::grid(25.0)))
        .window_size((theme::grid(80.0), theme::grid(100.0)))
        .show_title(false)
        .transparent_titlebar(true);
    if cfg!(target_os = "macos") {
        win.menu(menu::main_menu)
    } else {
        win
    }
}

pub fn preferences_window() -> WindowDesc<AppState> {
    let win_size = (theme::grid(50.0), theme::grid(45.0));

    // On Windows, the window size includes the titlebar.
    let win_size = if cfg!(target_os = "windows") {
        const WINDOWS_TITLEBAR_OFFSET: f64 = 6.0;
        (win_size.0, win_size.1 + WINDOWS_TITLEBAR_OFFSET)
    } else {
        win_size
    };

    let win = WindowDesc::new(preferences_widget())
        .title("Preferences")
        .window_size(win_size)
        .resizable(false)
        .show_title(false)
        .transparent_titlebar(true);
    if cfg!(target_os = "macos") {
        win.menu(menu::main_menu)
    } else {
        win
    }
}

pub fn account_setup_window() -> WindowDesc<AppState> {
    let win = WindowDesc::new(account_setup_widget())
        .title("Log In")
        .window_size((theme::grid(50.0), theme::grid(45.0)))
        .resizable(false)
        .show_title(false)
        .transparent_titlebar(true);
    if cfg!(target_os = "macos") {
        win.menu(menu::main_menu)
    } else {
        win
    }
}

fn preferences_widget() -> impl Widget<AppState> {
    ThemeScope::new(
        preferences::preferences_widget()
            .background(theme::BACKGROUND_DARK)
            .expand(),
    )
}

fn account_setup_widget() -> impl Widget<AppState> {
    ThemeScope::new(
        preferences::account_setup_widget()
            .background(theme::BACKGROUND_DARK)
            .expand(),
    )
}

fn root_widget() -> impl Widget<AppState> {
    let playlists = Scroll::new(playlist::list_widget())
        .vertical()
        .expand_height();
    let sidebar = Flex::column()
        .must_fill_main_axis(true)
        .with_child(sidebar_logo_widget())
        .with_child(sidebar_menu_widget())
        .with_default_spacer()
        .with_flex_child(playlists, 1.0)
        .with_child(volume_slider())
        .with_default_spacer()
        .with_child(user::user_widget())
        .padding(if cfg!(target_os = "macos") {
            // Accommodate the window controls on Mac.
            Insets::new(0.0, 24.0, 0.0, 0.0)
        } else {
            Insets::ZERO
        })
        .background(theme::BACKGROUND_DARK);

    let topbar = Flex::row()
        .must_fill_main_axis(true)
        .with_child(topbar_back_button_widget())
        .with_child(topbar_title_widget())
        .background(Border::Bottom.with_color(theme::BACKGROUND_DARK));

    let main = Flex::column()
        .cross_axis_alignment(CrossAxisAlignment::Start)
        .with_child(topbar)
        .with_flex_child(Overlay::bottom(route_widget(), alert_widget()), 1.0)
        .with_child(playback::panel_widget())
        .background(theme::BACKGROUND_LIGHT);

    let split = Split::columns(sidebar, main)
        .split_point(0.2)
        .bar_size(1.0)
        .min_size(150.0, 300.0)
        .min_bar_area(1.0)
        .solid_bar(true);

    ThemeScope::new(split)
        .controller(SessionController)
        .controller(NavController)
    // .debug_invalidation()
    // .debug_widget_id()
    // .debug_paint_layout()
}

fn alert_widget() -> impl Widget<AppState> {
    const BG: Key<Color> = Key::new("app.alert.BG");
    const DISMISS_ALERT: Selector = Selector::new("app.alert.dismiss");
    const ALERT_DURATION: Duration = Duration::from_secs(5);

    Maybe::or_empty(|| {
        Flex::row()
            .with_child(
                Label::dynamic(|alert: &Alert, _| match alert.style {
                    AlertStyle::Error => "Error:".to_string(),
                })
                .with_font(theme::UI_FONT_MEDIUM),
            )
            .with_default_spacer()
            .with_flex_child(Label::raw().lens(Alert::message), 1.0)
            .padding(theme::grid(2.0))
            .background(BG)
            .env_scope(|env, alert: &Alert| {
                env.set(
                    BG,
                    match alert.style {
                        AlertStyle::Error => env.get(theme::RED),
                    },
                )
            })
            .controller(AfterDelay::new(ALERT_DURATION, |ctx, _, _| {
                ctx.submit_command(DISMISS_ALERT);
            }))
    })
    .on_command(DISMISS_ALERT, |_, _, alert| {
        alert.take();
    })
    .lens(AppState::alert)
}

fn route_widget() -> impl Widget<AppState> {
    ViewDispatcher::new(
        |state: &AppState, _| state.nav.route(),
        |route: &Route, _, _| match route {
            Route::Home => Scroll::new(home::home_widget().padding(theme::grid(1.0)))
                .vertical()
                .boxed(),
            Route::SavedTracks => {
                Scroll::new(library::saved_tracks_widget().padding(theme::grid(1.0)))
                    .vertical()
                    .boxed()
            }
            Route::SavedAlbums => {
                Scroll::new(library::saved_albums_widget().padding(theme::grid(1.0)))
                    .vertical()
                    .boxed()
            }
            Route::SearchResults => Scroll::new(search::results_widget().padding(theme::grid(1.0)))
                .vertical()
                .boxed(),
            Route::AlbumDetail => Scroll::new(album::detail_widget().padding(theme::grid(1.0)))
                .vertical()
                .boxed(),
            Route::ArtistDetail => Scroll::new(artist::detail_widget().padding(theme::grid(1.0)))
                .vertical()
                .boxed(),
            Route::PlaylistDetail => Flex::column()
                .with_child(
                    find::finder_widget(cmd::FIND_IN_PLAYLIST, "Find in Playlist...")
                        .lens(AppState::playlist_detail.then(PlaylistDetail::finder)),
                )
                .with_flex_child(
                    Scroll::new(playlist::detail_widget().padding(theme::grid(1.0))).vertical(),
                    1.0,
                )
                .boxed(),
            Route::Recommendations => {
                Scroll::new(recommend::results_widget().padding(theme::grid(1.0)))
                    .vertical()
                    .boxed()
            }
        },
    )
    .expand()
}

fn sidebar_logo_widget() -> impl Widget<AppState> {
    icons::LOGO
        .scale((29.0, 32.0))
        .with_color(theme::GREY_500)
        .padding((0.0, theme::grid(2.0), 0.0, theme::grid(1.0)))
        .center()
        .lens(Unit)
}

fn sidebar_menu_widget() -> impl Widget<AppState> {
    Flex::column()
        .with_default_spacer()
        .with_child(sidebar_link_widget("Home", Nav::Home))
        .with_child(sidebar_link_widget("Tracks", Nav::SavedTracks))
        .with_child(sidebar_link_widget("Albums", Nav::SavedAlbums))
        .with_child(search::input_widget().padding((theme::grid(1.0), theme::grid(1.0))))
}

fn sidebar_link_widget(title: &str, link_nav: Nav) -> impl Widget<AppState> {
    Label::new(title)
        .padding((theme::grid(2.0), theme::grid(1.0)))
        .expand_width()
        .link()
        .env_scope({
            let link_nav = link_nav.clone();
            move |env, nav: &Nav| {
                env.set(
                    theme::LINK_COLD_COLOR,
                    if &link_nav == nav {
                        env.get(theme::MENU_BUTTON_BG_ACTIVE)
                    } else {
                        env.get(theme::MENU_BUTTON_BG_INACTIVE)
                    },
                );
                env.set(
                    theme::TEXT_COLOR,
                    if &link_nav == nav {
                        env.get(theme::MENU_BUTTON_FG_ACTIVE)
                    } else {
                        env.get(theme::MENU_BUTTON_FG_INACTIVE)
                    },
                );
            }
        })
        .on_click(move |ctx, _, _| {
            ctx.submit_command(cmd::NAVIGATE.with(link_nav.clone()));
        })
        .lens(AppState::nav)
}

fn volume_slider() -> impl Widget<AppState> {
    const SAVE_DELAY: Duration = Duration::from_millis(100);
    const SAVE_TO_CONFIG: Selector = Selector::new("app.volume.save-to-config");

    Flex::column()
        .with_child(
            Label::dynamic(|&volume: &f64, _| format!("Volume: {}%", (volume * 100.0).floor()))
                .with_text_color(theme::PLACEHOLDER_COLOR)
                .with_text_size(theme::TEXT_SIZE_SMALL),
        )
        .with_default_spacer()
        .with_child(
            Slider::new()
                .with_range(0.0, 1.0)
                .expand_width()
                .env_scope(|env, _| {
                    env.set(theme::BASIC_WIDGET_HEIGHT, theme::grid(1.5));
                    env.set(theme::FOREGROUND_LIGHT, env.get(theme::GREY_400));
                    env.set(theme::FOREGROUND_DARK, env.get(theme::GREY_400));
                }),
        )
        .padding((theme::grid(1.5), 0.0))
        .on_debounce(SAVE_DELAY, |ctx, _, _| ctx.submit_command(SAVE_TO_CONFIG))
        .lens(AppState::playback.then(Playback::volume))
        .on_command(SAVE_TO_CONFIG, |_, _, data| {
            data.config.volume = data.playback.volume;
            data.config.save();
        })
}

fn topbar_back_button_widget() -> impl Widget<AppState> {
    let icon = icons::BACK.scale((10.0, theme::grid(2.0)));
    let disabled = icon
        .clone()
        .with_color(theme::GREY_600)
        .padding(theme::grid(1.0));
    let enabled = icon
        .padding(theme::grid(1.0))
        .link()
        .rounded(theme::BUTTON_BORDER_RADIUS)
        .on_click(|ctx, _, _| {
            ctx.submit_command(cmd::NAVIGATE_BACK.with(1));
        })
        .context_menu(history_menu);
    Either::new(
        |history: &Vector<Nav>, _| history.is_empty(),
        disabled,
        enabled,
    )
    .padding(theme::grid(1.0))
    .lens(AppState::history)
}

fn history_menu(history: &Vector<Nav>) -> Menu<AppState> {
    let mut menu = Menu::empty();

    for (index, history) in history.iter().rev().take(10).enumerate() {
        let skip_back_in_history_n_times = index + 1;
        menu = menu.entry(
            MenuItem::new(history.full_title())
                .command(cmd::NAVIGATE_BACK.with(skip_back_in_history_n_times)),
        );
    }

    menu
}

fn topbar_title_widget() -> impl Widget<AppState> {
    Flex::row()
        .cross_axis_alignment(CrossAxisAlignment::Center)
        .with_child(route_title_widget())
        .with_spacer(theme::grid(0.5))
        .with_child(route_icon_widget())
        .lens(AppState::nav)
}

fn route_icon_widget() -> impl Widget<Nav> {
    ViewSwitcher::new(
        |nav: &Nav, _| nav.clone(),
        |nav: &Nav, _, _| {
            let icon = |icon: &SvgIcon| icon.scale(theme::ICON_SIZE_SMALL);
            match &nav {
                Nav::Home => Empty.boxed(),
                Nav::SavedTracks => Empty.boxed(),
                Nav::SavedAlbums => Empty.boxed(),
                Nav::SearchResults(_) => icon(&icons::SEARCH).boxed(),
                Nav::AlbumDetail(_) => icon(&icons::ALBUM).boxed(),
                Nav::ArtistDetail(_) => icon(&icons::ARTIST).boxed(),
                Nav::PlaylistDetail(_) => icon(&icons::PLAYLIST).boxed(),
                Nav::Recommendations(_) => icon(&icons::SEARCH).boxed(),
            }
        },
    )
}

fn route_title_widget() -> impl Widget<Nav> {
    Label::dynamic(|nav: &Nav, _| nav.title())
        .with_font(theme::UI_FONT_MEDIUM)
        .with_text_size(theme::TEXT_SIZE_LARGE)
}

fn compute_main_window_title(data: &AppState, _env: &Env) -> String {
    if let Some(now_playing) = &data.playback.now_playing {
        format!(
            "{} - {}",
            now_playing.item.artist_name(),
            now_playing.item.name
        )
    } else {
        "Psst".to_owned()
    }
}
