//! Serializable types for the commit domain.

use crate::GitError;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct CommitOid(pub String);

impl CommitOid {
    pub fn new(hex: &str) -> Self {
        Self(hex.to_lowercase())
    }

    pub fn from_oid(oid: git2::Oid) -> Self {
        Self(oid.to_string())
    }

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

    pub fn to_oid(&self) -> Result<git2::Oid, GitError> {
        git2::Oid::from_str(&self.0).map_err(|_| GitError::InvalidOid(self.0.clone()))
    }

    pub fn from_git2(oid: git2::Oid) -> Self {
        Self(oid.to_string())
    }
}

impl std::fmt::Display for CommitOid {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl AsRef<str> for CommitOid {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitSignature {
    pub name: String,
    pub email: String,
    pub time_secs: i64,
    pub offset_minutes: i32,
}

impl CommitSignature {
    pub fn from_git2(sig: git2::Signature<'_>) -> Self {
        Self {
            name: sig.name().unwrap_or("").to_string(),
            email: sig.email().unwrap_or("").to_string(),
            time_secs: sig.when().seconds(),
            offset_minutes: sig.when().offset_minutes(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitMeta {
    pub oid: CommitOid,
    pub message: String,
    pub summary: String,
    pub author: CommitSignature,
    pub committer: CommitSignature,
    pub tree_id: CommitOid,
    pub parent_ids: Vec<CommitOid>,
    pub encoding: Option<String>,
}

impl CommitMeta {
    pub fn from_git2(commit: &git2::Commit<'_>) -> Self {
        Self {
            oid: CommitOid::from_git2(commit.id()),
            message: commit.message().unwrap_or("").to_string(),
            summary: commit.summary().unwrap_or("").to_string(),
            author: CommitSignature::from_git2(commit.author()),
            committer: CommitSignature::from_git2(commit.committer()),
            tree_id: CommitOid::from_git2(commit.tree_id()),
            parent_ids: commit.parent_ids().map(CommitOid::from_git2).collect(),
            encoding: commit.message_encoding().map(String::from),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitDiffStats {
    pub oid: String,
    pub files_changed: usize,
    pub insertions: usize,
    pub deletions: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitDiffFile {
    pub path: Option<String>,
    pub status: String,
    pub is_binary: bool,
    pub size: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitDiffHunk {
    pub old_start: u32,
    pub old_lines: u32,
    pub new_start: u32,
    pub new_lines: u32,
    pub header: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitBlameHunk {
    pub commit_oid: CommitOid,
    pub final_start_line: u32,
    pub final_lines: u32,
    pub orig_start_line: u32,
    pub orig_lines: u32,
    pub boundary: bool,
    pub orig_path: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitBlameLine {
    pub commit_oid: CommitOid,
    pub line_no: u32,
    pub content: String,
    pub orig_path: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitRefInfo {
    pub name: String,
    pub target: CommitOid,
    pub is_remote: bool,
    pub is_tag: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitReflogEntry {
    pub oid_new: CommitOid,
    pub oid_old: CommitOid,
    pub committer_name: String,
    pub committer_email: String,
    pub time_secs: i64,
    pub offset_minutes: i32,
    pub message: Option<String>,
    pub ref_name: String,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct CommitSort(pub u32);

impl CommitSort {
    pub const TOPOLOGICAL: Self = Self(git2::Sort::TOPOLOGICAL.bits());
    pub const TIME: Self = Self(git2::Sort::TIME.bits());
    pub const REVERSE: Self = Self(git2::Sort::REVERSE.bits());

    pub fn to_git2(self) -> git2::Sort {
        git2::Sort::from_bits(self.0).unwrap_or(git2::Sort::NONE)
    }
}
