use embedded_graphics::{
    pixelcolor::Gray8,
    prelude::{DrawTarget, OriginDimensions},
};

use crate::{input::InputEvent, wifi::WifiEvent, WifiAccessPoint, WifiCredentials, WifiPassword};

use super::{
    frame::Frame,
    screens::{
        library::{LibraryAction, LibraryScreen},
        onboarding::{OnboardingAction, OnboardingScreen},
        reader::{ReaderAction, ReaderScreen},
    },
    RefreshHint,
};

pub struct App {
    route: Route,
    pending_command: Option<AppCommand>,
}

enum Route {
    Onboarding(OnboardingScreen),
    Library(LibraryScreen),
    Reader(ReaderScreen),
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AppUpdate {
    pub dirty: bool,
    pub refresh: RefreshHint,
    pub command: Option<AppCommand>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AppCommand {
    ScanWifi,
    JoinWifi {
        access_point: WifiAccessPoint,
        password: WifiPassword,
    },
    JoinSavedWifi {
        credentials: WifiCredentials,
    },
}

impl App {
    pub fn new() -> Self {
        Self {
            route: Route::Onboarding(OnboardingScreen::new()),
            pending_command: None,
        }
    }

    pub fn new_with_saved_wifi(credentials: Option<WifiCredentials>) -> Self {
        let Some(credentials) = credentials else {
            return Self::new();
        };

        let ssid = credentials.ssid.clone();
        Self {
            route: Route::Onboarding(OnboardingScreen::new_connecting_saved(ssid)),
            pending_command: Some(AppCommand::JoinSavedWifi { credentials }),
        }
    }

    pub fn take_pending_command(&mut self) -> Option<AppCommand> {
        self.pending_command.take()
    }

    pub fn update(&mut self, event: InputEvent) -> AppUpdate {
        match &mut self.route {
            Route::Onboarding(screen) => {
                let update = screen.update(event);
                match update.action {
                    OnboardingAction::None => {
                        AppUpdate::new(update.dirty, update.refresh, update.command)
                    }
                    OnboardingAction::OpenLibrary => {
                        self.route = Route::Library(LibraryScreen::new());
                        AppUpdate::new(true, RefreshHint::Full, None)
                    }
                }
            }
            Route::Library(screen) => {
                let update = screen.update(event);
                match update.action {
                    LibraryAction::None => {
                        AppUpdate::new(update.dirty, update.refresh, update.command)
                    }
                    LibraryAction::OpenOnboarding => {
                        self.route = Route::Onboarding(OnboardingScreen::new());
                        AppUpdate::new(true, RefreshHint::Full, None)
                    }
                    LibraryAction::OpenReader { book_index } => {
                        self.route = Route::Reader(ReaderScreen::new(book_index));
                        AppUpdate::new(true, RefreshHint::Full, None)
                    }
                }
            }
            Route::Reader(screen) => {
                let update = screen.update(event);
                match update.action {
                    ReaderAction::None => {
                        AppUpdate::new(update.dirty, update.refresh, update.command)
                    }
                    ReaderAction::OpenLibrary { selected_book } => {
                        self.route =
                            Route::Library(LibraryScreen::new_with_selected(selected_book));
                        AppUpdate::new(true, RefreshHint::Full, None)
                    }
                }
            }
        }
    }

    pub fn handle_wifi_event(&mut self, event: WifiEvent) -> AppUpdate {
        match &mut self.route {
            Route::Onboarding(screen) => {
                let update = screen.handle_wifi_event(event);
                match update.action {
                    OnboardingAction::None => {
                        AppUpdate::new(update.dirty, update.refresh, update.command)
                    }
                    OnboardingAction::OpenLibrary => {
                        self.route = Route::Library(LibraryScreen::new());
                        AppUpdate::new(true, RefreshHint::Full, None)
                    }
                }
            }
            _ => AppUpdate::new(false, RefreshHint::None, None),
        }
    }

    pub fn render<D>(&self, target: &mut D) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let mut frame = Frame::new(target);
        frame.clear()?;

        match &self.route {
            Route::Onboarding(screen) => screen.render(&mut frame),
            Route::Library(screen) => screen.render(&mut frame),
            Route::Reader(screen) => screen.render(&mut frame),
        }
    }
}

impl AppUpdate {
    pub const fn new(dirty: bool, refresh: RefreshHint, command: Option<AppCommand>) -> Self {
        Self {
            dirty,
            refresh,
            command,
        }
    }
}

impl Default for App {
    fn default() -> Self {
        Self::new()
    }
}
