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

use std::borrow::Cow;

#[derive(Debug)]
pub enum FileContent<'a> {
    Decoded(Cow<'a, str>),
    Image(Vec<u8>),
    UnknownEncoding,
}

#[derive(Debug)]
pub enum File {
    Decoded(DecodedFile),
    Undecodable(UndecodableFile),
    Image(Vec<u8>),
}

#[derive(Debug)]
pub struct DecodedFile {
    pub combined: Combined,
    pub diffs_without_contents: Vec<DiffWithoutContents>,
}

#[derive(Debug)]
pub struct UndecodableFile {
    pub diffs_with_contents: Vec<(DiffWithContents, IdHash)>,
    pub diffs_without_contents: Vec<DiffWithoutContents>,
}

/// A file combined with its diffs into sections
#[derive(Debug)]
pub struct Combined {
    pub sections: Vec<Section>,
    pub max_line_num: usize,
}

#[derive(Debug)]
pub enum DiffWithContents {
    Add {
        contents: Option<String>,
    },
    Edit {
        line: usize,
        deleted: bool,
        contents: String,
    },
    Replacement {
        line: usize,
        /// Deleted line
        change_contents: String,
        /// Added lines
        replacement_contents: String,
    },
    // Also present in `DiffWithoutContents` to show that the whole file is
    // deleted
    Del {
        contents: Option<String>,
    },
    Undel,
}

#[derive(Debug)]
pub enum DiffWithoutContents {
    // _________________________________________________________________________
    // Cases that never have contents:
    Move {
        old_path: String,
    },
    MoveEdge,
    Del,
    SolveNameConflict,
    UnsolveNameConflict,
    SolveOrderConflict,
    UnsolveOrderConflict,
    ResurrectZombines,
    AddRoot,
    DelRoot,
    // _________________________________________________________________________
    // Cases that normally have contents, but in these cases the contents are
    // not decodable:
    Edit {
        line: usize,
        deleted: bool,
        contents: UndecodableContents,
    },
    Replacement {
        line: usize,
        /// Deleted line
        change_contents: UndecodableContents,
        /// Added lines
        replacement_contents: UndecodableContents,
    },
}

#[derive(Debug)]
pub enum UndecodableContents {
    /// Short byte sequence of unknown encoding encoded with base64 for
    /// display. Must be shorter than [`crate::repo::MAX_LEN_BASE64_DISPLAY`]
    ShortBase64(String),
    UnknownEncoding,
}

#[derive(Debug)]
pub enum Section {
    Unchanged(Lines),
    /// `deleted` and `added` are together because for
    /// `ChangedFileDiffWithContents::Replacement` they begin on the same line
    /// number
    Changed {
        deleted: Lines,
        added: Lines,
        diff_id: IdHash,
    },
}

/// INVARIANT: There must be no new-lines in any of the strings, the source
/// string must be split on those.
pub type Lines = Vec<String>;

pub fn contents_to_lines(contents: &str) -> Lines {
    contents.split('\n').map(str::to_string).collect()
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Id {
    pub diff: repo::ChangedFileDiff,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct IdRef<'a> {
    pub diff: &'a repo::ChangedFileDiff,
}

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

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(diff: &repo::ChangedFileDiff) -> IdHash {
    id_ref_hash(&IdRef { diff })
}