use libflorescence::file::Path;
use libflorescence::repo;

use iced::time::Instant;

#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Msg {
    /// Directional key press
    PressDir(Dir),
    /// Directional key press with a modifier
    AltPressDir(Dir),
}

/// Msgs that do not need to be explicitly allowed (see
/// [`crate::action::is_allowed`])
#[derive(Debug, Clone, PartialEq)]
pub enum UnfilteredMsg {
    /// Directional key release
    ReleaseDir(Dir),
    /// Mouse select
    Select(Select),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Dir {
    Down,
    Up,
    Right,
    Left,
}

#[derive(Debug, Default)]
pub struct State {
    /// Primary selection state. This is distinct from `status`, `channel`,
    /// `entire_log` sub-states fields to which each case corresponds, because
    /// they are preserved in different primary selection states.
    pub primary: Primary,
    /// Sub-selection in [`Primary::Status`] state
    pub status: Option<Status>,
    /// Sub-selection in [`Primary::Channel`] state
    pub channel: Option<Channel>,
    /// Sub-selection in [`Primary::EntireLog`] state
    pub entire_log: Option<LogChange>,
    /// Sub-selection in [`Primary::CompareRemote`] state
    pub compare_remote: Option<CompareRemote>,
    /// Last directional key down that's not yet been released
    pub held_key: Option<HeldKey>,
}

#[derive(Debug)]
pub enum Unified<'a> {
    Status(Option<&'a Status>),
    Channel(Option<&'a Channel>),
    EntireLog(Option<&'a LogChange>),
    CompareRemote(Option<&'a CompareRemote>),
}

#[derive(Debug, Default, Clone, Copy)]
pub enum Primary {
    #[default]
    Status,
    Channel,
    EntireLog,
    CompareRemote,
}

#[derive(Debug)]
pub struct HeldKey {
    pub dir: Dir,
    pub last_tick: Instant,
}

#[derive(Debug)]
pub enum Status {
    UntrackedFile {
        ix: usize,
        path: Path,
        diff_selected: bool,
    },
    ChangedFile {
        ix: usize,
        path: Path,
        diff_selected: bool,
    },
    LogChange(LogChange),
}

#[derive(Debug)]
pub struct LogChangeFileSelection {
    pub ix: usize,
    pub path: String,
    pub diff_selected: bool,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Select {
    UntrackedFile {
        ix: usize,
        path: Path,
    },
    ChangedFile {
        ix: usize,
        path: Path,
    },
    LogChange {
        ix: usize,
        hash: repo::ChangeHash,
        message: String,
    },
    LogChangeFile {
        ix: usize,
        path: String,
    },
    Channel {
        ix: usize,
        name: String,
    },
    CompareRemote {
        ix: usize,
    },
    CompareRemoteFile {
        ix: usize,
        path: String,
    },
}

#[derive(Debug)]
pub struct Channel {
    pub ix: usize,
    pub name: String,
    pub log: Option<LogChange>,
}

#[derive(Debug)]
pub struct LogChange {
    pub ix: usize,
    pub hash: repo::ChangeHash,
    pub message: String,
    pub file: Option<LogChangeFileSelection>,
}

#[derive(Debug)]
pub struct CompareRemote {
    pub ix: Option<usize>,
    pub hash: Option<repo::ChangeHash>,
    pub file: Option<LogChangeFileSelection>,
    pub remote: String,
    pub remote_channel: String,
}

pub fn unify(state: &State) -> Unified<'_> {
    let State {
        primary,
        status,
        channel,
        entire_log,
        compare_remote,
        held_key: _,
    } = state;
    match primary {
        Primary::Status => Unified::Status(status.as_ref()),
        Primary::Channel => Unified::Channel(channel.as_ref()),
        Primary::EntireLog => Unified::EntireLog(entire_log.as_ref()),
        Primary::CompareRemote => {
            Unified::CompareRemote(compare_remote.as_ref())
        }
    }
}