use crate::{diff, hash_one, repo, AHash, NoHashMap};

use std::fmt::Display;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Path {
    pub raw: String,
    pub is_dir: bool,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Id {
    pub path: Path,
    pub file_kind: Kind,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct IdRef<'a> {
    pub path: &'a Path,
    pub file_kind: Kind,
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct IdHash(pub AHash);

pub type IdMap<V> = NoHashMap<IdHash, V>;

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct LogId {
    /// Hash of the log change
    pub hash: repo::ChangeHash,
    pub path: String,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct LogIdRef<'a> {
    pub hash: repo::ChangeHash,
    pub path: &'a str,
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct LogIdHash(pub AHash);

pub type LogIdMap<V> = NoHashMap<LogIdHash, V>;

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Kind {
    Untracked,
    Changed,
}

#[derive(Debug)]
pub enum Diff {
    Loading,
    Loaded(diff::File),
}

pub fn id_hash(id: &Id) -> IdHash {
    IdHash(hash_one(id))
}

pub fn id_ref_hash(id: &IdRef<'_>) -> IdHash {
    IdHash(hash_one(id))
}

pub fn id_parts_hash(path: &Path, file_kind: Kind) -> IdHash {
    id_ref_hash(&IdRef { path, file_kind })
}

pub fn log_id_hash(id: &LogId) -> LogIdHash {
    LogIdHash(hash_one(id))
}

pub fn log_id_ref_hash(id: &LogIdRef<'_>) -> LogIdHash {
    LogIdHash(hash_one(id))
}

pub fn log_id_parts_hash(hash: repo::ChangeHash, path: &str) -> LogIdHash {
    log_id_ref_hash(&LogIdRef { path, hash })
}

impl Display for Path {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Path { raw: path, is_dir } = self;
        // Adds slash when it's a dir
        write!(f, "{path}{}", if *is_dir { "/" } else { "" })
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn id_hash_eq_id_ref_hash() {
        let raw = "some random path".to_string();
        let path = Path { raw, is_dir: false };
        let file_kind = Kind::Untracked;

        assert_eq!(
            id_ref_hash(&IdRef {
                path: &path,
                file_kind
            }),
            id_hash(&Id {
                path: path.clone(),
                file_kind
            })
        );
        assert_eq!(
            id_ref_hash(&IdRef {
                path: &path,
                file_kind
            }),
            id_parts_hash(&path, file_kind),
        );

        assert_ne!(
            id_ref_hash(&IdRef {
                path: &path,
                file_kind
            }),
            id_hash(&Id {
                path,
                file_kind: Kind::Changed
            })
        );
    }

    #[test]
    fn log_id_hash_eq_log_id_ref_hash() {
        let path = "some change file path";
        let hash = repo::hash_bytes(&[0, 1, 2]);

        assert_eq!(
            log_id_ref_hash(&LogIdRef { path, hash }),
            log_id_hash(&LogId {
                path: path.to_string(),
                hash
            })
        );
        assert_eq!(
            log_id_ref_hash(&LogIdRef { path, hash }),
            log_id_parts_hash(hash, path),
        );

        assert_ne!(
            log_id_ref_hash(&LogIdRef { path, hash }),
            log_id_hash(&LogId {
                path: path.to_string(),
                hash: repo::hash_bytes(&[255])
            })
        );
    }
}