//! View-ready state for native mobile presentation shells.

use crate::{
    config::{Config, ConfigError},
    repository::{Repository, RepositoryError},
};

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum MobileTab {
    #[default]
    Home,
    Passwords,
    Search,
    Totp,
    Preferences,
}

impl MobileTab {
    pub const ALL: [Self; 5] = [
        Self::Home,
        Self::Passwords,
        Self::Search,
        Self::Totp,
        Self::Preferences,
    ];

    pub const fn title(self) -> &'static str {
        match self {
            Self::Home => "Home",
            Self::Passwords => "Passwords",
            Self::Search => "Search",
            Self::Totp => "TOTP",
            Self::Preferences => "Preferences",
        }
    }

    pub const fn system_image(self) -> &'static str {
        match self {
            Self::Home => "house",
            Self::Passwords => "key",
            Self::Search => "magnifyingglass",
            Self::Totp => "timer",
            Self::Preferences => "gearshape",
        }
    }

    pub const fn selected_system_image(self) -> &'static str {
        match self {
            Self::Home => "house.fill",
            Self::Passwords => "key.fill",
            Self::Search => "magnifyingglass",
            Self::Totp => "timer",
            Self::Preferences => "gearshape.fill",
        }
    }

    pub(crate) fn config_value(self) -> &'static str {
        match self {
            Self::Home => "home",
            Self::Passwords => "passwords",
            Self::Search => "search",
            Self::Totp => "totp",
            Self::Preferences => "preferences",
        }
    }

    pub(crate) fn from_config(value: &str) -> Result<Self, ConfigError> {
        match value {
            "home" => Ok(Self::Home),
            "passwords" => Ok(Self::Passwords),
            "search" => Ok(Self::Search),
            "totp" => Ok(Self::Totp),
            "preferences" => Ok(Self::Preferences),
            _ => Err(ConfigError::InvalidField {
                field: "ui.selected_mobile_tab",
            }),
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MobileShellState {
    Loading,
    Empty,
    Ready,
    Locked,
    Error,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MobilePage {
    tab: MobileTab,
    title: String,
    system_image: String,
    selected_system_image: String,
    state: MobileShellState,
    state_title: String,
    state_detail: String,
}

impl MobilePage {
    pub fn tab(&self) -> MobileTab {
        self.tab
    }

    pub fn title(&self) -> &str {
        &self.title
    }

    pub fn system_image(&self) -> &str {
        &self.system_image
    }

    pub fn selected_system_image(&self) -> &str {
        &self.selected_system_image
    }

    pub fn state(&self) -> MobileShellState {
        self.state
    }

    pub fn state_title(&self) -> &str {
        &self.state_title
    }

    pub fn state_detail(&self) -> &str {
        &self.state_detail
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MobileShell {
    selected_tab: MobileTab,
    pages: Vec<MobilePage>,
}

impl MobileShell {
    pub fn load() -> Self {
        match Config::load(None) {
            Ok(config) => match Repository::open(config.vault()) {
                Ok(_) => Self::new(config.mobile_tab(), MobileShellState::Ready, None),
                Err(RepositoryError::Io {
                    source: std::io::ErrorKind::NotFound,
                    ..
                }) => Self::new(config.mobile_tab(), MobileShellState::Empty, None),
                Err(error) => Self::new(
                    config.mobile_tab(),
                    MobileShellState::Error,
                    Some(format!("Password store could not be opened: {error}")),
                ),
            },
            Err(ConfigError::NotFound { .. }) => {
                Self::new(MobileTab::Home, MobileShellState::Empty, None)
            }
            Err(error) => Self::new(
                MobileTab::Home,
                MobileShellState::Error,
                Some(format!("Configuration could not be loaded: {error}")),
            ),
        }
    }

    pub fn fixture(state: MobileShellState) -> Self {
        Self::new(MobileTab::Home, state, None)
    }

    pub fn selected_tab(&self) -> MobileTab {
        self.selected_tab
    }

    pub fn pages(&self) -> &[MobilePage] {
        &self.pages
    }

    fn new(selected_tab: MobileTab, state: MobileShellState, error: Option<String>) -> Self {
        let pages = MobileTab::ALL
            .into_iter()
            .map(|tab| {
                let (state_title, state_detail) = state_copy(tab, state, error.as_deref());
                MobilePage {
                    tab,
                    title: tab.title().to_owned(),
                    system_image: tab.system_image().to_owned(),
                    selected_system_image: tab.selected_system_image().to_owned(),
                    state,
                    state_title,
                    state_detail,
                }
            })
            .collect();
        Self {
            selected_tab,
            pages,
        }
    }
}

pub fn store_selected_tab(tab: MobileTab) -> Result<(), ConfigError> {
    store_selected_tab_from(Config::load(None), tab)
}

fn store_selected_tab_from(
    config: Result<Config, ConfigError>,
    tab: MobileTab,
) -> Result<(), ConfigError> {
    match config {
        Ok(config) => config.update_mobile_tab(tab),
        Err(ConfigError::NotFound { .. }) => Ok(()),
        Err(error) => Err(error),
    }
}

fn state_copy(tab: MobileTab, state: MobileShellState, error: Option<&str>) -> (String, String) {
    match state {
        MobileShellState::Loading => (
            format!("Loading {}", tab.title()),
            "Preparing the password-store presentation.".to_owned(),
        ),
        MobileShellState::Empty => (
            "No Password Store".to_owned(),
            "Set up a local store to use IronStorage.".to_owned(),
        ),
        MobileShellState::Ready => (
            tab.title().to_owned(),
            "The password store is ready.".to_owned(),
        ),
        MobileShellState::Locked => (
            "IronStorage is Locked".to_owned(),
            "Authenticate to reveal protected content.".to_owned(),
        ),
        MobileShellState::Error => (
            "IronStorage Is Unavailable".to_owned(),
            error
                .unwrap_or("The password-store state could not be loaded.")
                .to_owned(),
        ),
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use crate::config::ConfigError;

    use super::{MobilePage, MobileShell, MobileShellState, MobileTab, store_selected_tab_from};

    #[test]
    fn every_fixture_is_view_ready_for_all_five_tabs() {
        for state in [
            MobileShellState::Loading,
            MobileShellState::Empty,
            MobileShellState::Ready,
            MobileShellState::Locked,
            MobileShellState::Error,
        ] {
            let shell = MobileShell::fixture(state);
            assert_eq!(shell.selected_tab(), MobileTab::Home);
            assert_eq!(shell.pages().len(), 5);
            assert_eq!(
                shell
                    .pages()
                    .iter()
                    .map(MobilePage::tab)
                    .collect::<Vec<_>>(),
                MobileTab::ALL
            );
            assert!(shell.pages().iter().all(|page| {
                page.state() == state
                    && !page.title().is_empty()
                    && !page.system_image().is_empty()
                    && !page.selected_system_image().is_empty()
                    && !page.state_title().is_empty()
                    && !page.state_detail().is_empty()
            }));
        }
    }

    #[test]
    fn tab_selection_before_onboarding_is_a_supported_no_op() {
        assert_eq!(
            store_selected_tab_from(
                Err(ConfigError::NotFound {
                    path: PathBuf::from("config.toml"),
                }),
                MobileTab::Preferences,
            ),
            Ok(())
        );
    }
}
