pub use indexmap::IndexSet;

use crate::{action, diff, log, selection, to_record};

use inflorescence_iced_widget::{dir_picker, nav_scrollable, report};
use libflorescence::identity::Id;
use libflorescence::{repo, store};

use iced::widget::text_editor;

use std::collections::HashMap;
use std::path::PathBuf;

pub fn is_ready(state: &ManagingRepo) -> Option<&ReadyState> {
    if let ManagingRepoSubState::Ready(state) = &state.sub {
        return Some(state);
    }
    None
}

pub fn is_ready_mut(state: &mut ManagingRepo) -> Option<&mut ReadyState> {
    if let ManagingRepoSubState::Ready(state) = &mut state.sub {
        return Some(state);
    }
    None
}

#[derive(Debug)]
pub struct State {
    pub sub: SubState,
    pub window_size: iced::Size,
    pub window_scale: f32,
    /// Cache allowed actions. Used for action filter and for view.
    pub allowed_actions: Vec<action::Binding>,
    pub sub_menu: Option<SubMenu>,
    pub report: report::Container,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum SubState {
    PickingProject(PickingProject),
    PickingRepoDir(PickingRepoDir),
    ManagingRepo(ManagingRepo),
}

#[derive(Debug)]
pub struct PickingProject {
    pub is_blocking: bool,
    pub projects: Option<store::Projects>,
    pub selection: PickingProjectSelection,
    pub projects_nav: nav_scrollable::State,
}

#[derive(Debug, Clone, Copy, Default)]
pub enum PickingProjectSelection {
    #[default]
    FindOrCreate,
    Existing {
        ix: usize,
    },
}

#[derive(Debug)]
pub struct PickingRepoDir {
    pub picker: dir_picker::State,
    pub waiting_to_init: Option<ProjectInitKind>,
}

#[derive(Debug, Clone, Copy)]
pub enum ProjectInitKind {
    New,
    ImportFromGit,
}

#[derive(Debug)]
pub struct ManagingRepo {
    pub repo_path: PathBuf,
    pub sub: ManagingRepoSubState,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SubMenu {
    Push {
        /// Confirmed remote selection
        remote: Option<String>,
        opt: Option<PushOption>,
    },
    Pull {
        /// Confirmed remote selection
        remote: Option<String>,
        opt: Option<PullOption>,
    },
    ResetChange,
    InitRepo {
        path: PathBuf,
    },
    ImportFromGit {
        path: PathBuf,
    },
    Add {
        recursive: bool,
    },
    CompareRemote {
        /// Confirmed remote selection
        remote: Option<String>,
        /// Confirmed remote channel selection
        remote_channel: Option<String>,
        opt: Option<CompareRemoteOption>,
    },
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PushOption {
    SelectingRemote {
        /// Set while selecting remote and discarded on cancel
        remote: Option<String>,
    },
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PullOption {
    SelectingRemote {
        /// Set while selecting remote and discarded on cancel
        remote: Option<String>,
    },
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum CompareRemoteOption {
    SelectingRemote {
        /// Set while selecting remote and discarded on cancel
        remote: Option<String>,
    },
    InputingRemoteChannel {
        /// Set while selecting channel and discarded on cancel
        channel: Option<String>,
    },
}

#[allow(
    clippy::large_enum_variant,
    reason = "There's only ever 1 instance and the small sized cases are expected to always turn into the largest `Ready` case"
)]
#[derive(Debug)]
pub enum ManagingRepoSubState {
    Loading {
        user_ids: Option<Vec<Id>>,
        repo: Option<repo::State>,
    },
    SelectingIdentity {
        ids: Vec<Id>,
        selection_ix: usize,
        selection_nav: nav_scrollable::State,
        // This will be set if confirmed before a repo is loaded
        confirmed_selection_ix: Option<usize>,
        repo: Option<repo::State>,
    },
    NoIdFound {
        repo: Option<repo::State>,
    },
    Ready(ReadyState),
}

#[derive(Debug)]
pub struct ReadyState {
    pub user_id: Id,
    pub repo: repo::State,
    pub selection: selection::State,
    pub navigation: Navigation,
    pub record_changes: Option<RecordChanges>,
    /// `Some` when we're writing a name of the new channel forked from current
    pub forking_channel_name: Option<String>,
    pub logs: Logs,
    pub to_record: to_record::State,
    pub jobs: IndexSet<Job>,
    // Outer key is remote name, inner key is remote channel name
    pub record_dichotomy:
        HashMap<String, HashMap<String, repo::RecordDichotomy>>,
}

pub fn get_record_dichotomy<'a>(
    record_dichotomy: &'a HashMap<
        String,
        HashMap<String, repo::RecordDichotomy>,
    >,
    remote: &str,
    remote_channel: &str,
) -> Option<&'a repo::RecordDichotomy> {
    record_dichotomy
        .get(remote)
        .and_then(|map| map.get(remote_channel))
}

pub fn get_record_dichotomy_mut<'a>(
    record_dichotomy: &'a mut HashMap<
        String,
        HashMap<String, repo::RecordDichotomy>,
    >,
    remote: &str,
    channel: &str,
) -> Option<&'a mut repo::RecordDichotomy> {
    record_dichotomy
        .get_mut(remote)
        .and_then(|map| map.get_mut(channel))
}

#[derive(Debug)]
pub enum RecordChanges {
    Typing {
        msg: String,
        desc: text_editor::Content,
    },
    Canceled {
        old_msg: String,
        old_desc: String,
    },
}

#[derive(Debug, Default)]
pub struct Navigation {
    /// Scrollable status view contains the overview of untracked files,
    /// changed files and most recent log changes
    pub status_nav: nav_scrollable::State,
    /// Logs shown in the status view.
    pub status_logs_navs: log::Navs,
    /// Other channels selection's nav
    pub other_channels_nav: nav_scrollable::State,
    /// Other channel log change selection's nav
    pub other_channel_log_nav: nav_scrollable::State,
    /// Selected channel's name
    #[cfg(debug_assertions)]
    pub other_channel_name: Option<String>,
    /// Navs for a log of selected channel other than the current
    pub other_channel_logs_navs: log::Navs,
    /// Entire log's change selection nav
    pub entire_log_nav: nav_scrollable::State,
    /// Logs for the entire log
    pub entire_logs_navs: log::Navs,
    /// Nav for comparing remote with local
    pub compare_remote_nav: nav_scrollable::State,
    /// Navs for logs comparing remote with local
    pub compare_remote_navs: log::Navs,
    /// Diffs for untracked and changed files shown in status view
    pub files_diffs: diff::FilesState,
    /// Diffs of status log changes, entire log changes, other channels' logs
    /// and changes from comparing remote
    pub log_diffs: log::FilesAndState,
}

#[derive(Debug, Default)]
pub struct Logs {
    /// Populated when requested to look at the entire log of changes
    pub entire_log: Option<Log>,
    /// Keys are channel names
    pub other_channels_logs: HashMap<String, Log>,
}

#[derive(Debug)]
pub enum Log {
    Loading,
    Loaded { log: repo::Log },
}

/// Background jobs
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Job {
    Pull {
        remote: String,
        channel: String,
    },
    Push {
        remote: String,
        channel: String,
    },
    CompareRemote {
        remote: String,
        remote_channel: String,
    },
}

pub fn init_channel_nav(
    navigation: &mut Navigation,
    #[cfg(debug_assertions)] name: String,
) {
    navigation.other_channel_log_nav = nav_scrollable::State::default();
    navigation.other_channel_logs_navs = log::Navs::default();
    #[cfg(debug_assertions)]
    {
        navigation.other_channel_name = Some(name);
    }
}