/* git/types.rs
 *
 * Copyright 2026 Christian Kruse
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiffLineKind {
    Context,
    Addition,
    Deletion,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WordRange {
    pub start: usize,
    pub end: usize,
}

#[derive(Debug, Clone, PartialEq)]
pub struct DiffLine {
    pub kind: DiffLineKind,
    pub old_lineno: Option<u32>,
    pub new_lineno: Option<u32>,
    pub content: String,
    pub word_ranges: Option<Vec<WordRange>>,
}

#[derive(Debug, Clone)]
pub struct DiffHunk {
    pub header: String,
    pub old_start: u32,
    pub old_lines: u32,
    pub new_start: u32,
    pub new_lines: u32,
    pub lines: Vec<DiffLine>,
}

#[derive(Debug, Clone)]
pub struct FileDiff {
    pub path: String,
    pub hunks: Vec<DiffHunk>,
    pub insertions: usize,
    pub deletions: usize,
    pub is_binary: bool,
    pub is_too_large: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiffMode {
    Staged,
    Unstaged,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchKind {
    Local,
    Remote,
}

impl From<BranchKind> for git2::BranchType {
    fn from(kind: BranchKind) -> Self {
        match kind {
            BranchKind::Local => git2::BranchType::Local,
            BranchKind::Remote => git2::BranchType::Remote,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileStatus {
    Modified,
    New,
    Deleted,
    Renamed,
    TypeChange,
    Conflicted,
}

impl FileStatus {
    pub fn label(&self) -> &'static str {
        match self {
            Self::Modified | Self::TypeChange => "M",
            Self::New => "A",
            Self::Deleted => "D",
            Self::Renamed => "R",
            Self::Conflicted => "C",
        }
    }

    pub fn tooltip(&self) -> &'static str {
        match self {
            Self::Modified | Self::TypeChange => "Modified",
            Self::New => "Added",
            Self::Deleted => "Deleted",
            Self::Renamed => "Renamed",
            Self::Conflicted => "Conflicted",
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct ChangedFile {
    pub path: String,
    pub index_status: Option<FileStatus>,
    pub wt_status: Option<FileStatus>,
}

impl ChangedFile {
    pub fn is_fully_staged(&self) -> bool {
        self.index_status.is_some() && self.wt_status.is_none()
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct StashEntry {
    pub index: usize,
    pub message: String,
    pub oid: String,
}

#[derive(Debug, Clone)]
pub struct CommitInfo {
    pub oid: String,
    pub summary: String,
    pub body: String,
    pub author: String,
    pub author_email: String,
    pub time: i64,
    pub committer: String,
    pub committer_email: String,
    pub committer_time: i64,
    pub parents: Vec<String>,
    pub is_merge: bool,
}

#[derive(Debug, Clone)]
pub struct BranchInfo {
    pub name: String,
    pub is_head: bool,
    pub latest_commit_summary: String,
    pub latest_commit_author: String,
    pub latest_commit_time: i64,
    pub upstream_name: Option<String>,
    pub ahead: usize,
    pub behind: usize,
    pub total_commits: usize,
    pub total_commits_capped: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BranchEntry {
    pub name: String,
    pub has_upstream: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepoState {
    Clean,
    Rebase,
    Merge,
    CherryPick,
    Revert,
}

impl From<git2::RepositoryState> for RepoState {
    fn from(state: git2::RepositoryState) -> Self {
        match state {
            git2::RepositoryState::Rebase
            | git2::RepositoryState::RebaseInteractive
            | git2::RepositoryState::RebaseMerge => RepoState::Rebase,
            git2::RepositoryState::Merge => RepoState::Merge,
            git2::RepositoryState::CherryPick | git2::RepositoryState::CherryPickSequence => RepoState::CherryPick,
            git2::RepositoryState::Revert | git2::RepositoryState::RevertSequence => RepoState::Revert,
            // Bisect, ApplyMailbox, ApplyMailboxOrRebase: not yet supported, treated as clean
            _ => RepoState::Clean,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RebaseOutcome {
    Completed { commits: Option<usize> },
    Paused { has_conflicts: bool },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeadKind {
    Branch,
    Tag,
    Detached,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HeadUpstreamStatus {
    pub ahead: usize,
    pub behind: usize,
}

#[derive(Debug)]
pub struct GitData {
    pub branches: Vec<BranchEntry>,
    pub head_branch: Option<String>,
    pub head_kind: HeadKind,
    pub head_upstream: Option<HeadUpstreamStatus>,
    pub tags: Vec<String>,
    pub remotes: Vec<(String, Vec<String>)>,
    pub stashes: Vec<StashEntry>,
    pub repo_state: RepoState,
    pub rebase_has_conflicts: bool,
    pub rewriteable_oids: std::collections::HashSet<String>,
}

#[derive(Debug, Clone)]
pub struct TagInfo {
    pub name: String,
    pub message: Option<String>,
    pub tagger: Option<String>,
    pub tagger_time: Option<i64>,
    pub commit_summary: String,
    pub commit_author: String,
    pub commit_time: i64,
    pub total_commits: usize,
    pub total_commits_capped: bool,
}

pub(super) const INDEX_FLAGS: &[(git2::Status, FileStatus)] = &[
    (git2::Status::INDEX_NEW, FileStatus::New),
    (git2::Status::INDEX_MODIFIED, FileStatus::Modified),
    (git2::Status::INDEX_DELETED, FileStatus::Deleted),
    (git2::Status::INDEX_RENAMED, FileStatus::Renamed),
    (git2::Status::INDEX_TYPECHANGE, FileStatus::TypeChange),
];

pub(super) const WT_FLAGS: &[(git2::Status, FileStatus)] = &[
    (git2::Status::WT_NEW, FileStatus::New),
    (git2::Status::WT_MODIFIED, FileStatus::Modified),
    (git2::Status::WT_DELETED, FileStatus::Deleted),
    (git2::Status::WT_RENAMED, FileStatus::Renamed),
    (git2::Status::WT_TYPECHANGE, FileStatus::TypeChange),
];

#[derive(Debug, Clone, Default)]
pub struct QuickStatus {
    pub branch: Option<String>,
    pub dirty: bool,
    pub valid: bool,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct DirtySummary {
    pub has_tracked_changes: bool,
    pub has_untracked: bool,
}

impl DirtySummary {
    pub fn is_dirty(&self) -> bool {
        self.has_tracked_changes || self.has_untracked
    }
}

#[derive(Debug, Clone)]
pub struct PushDefaults {
    pub remotes: Vec<String>,
    pub suggested_remote: String,
    pub local_branch: String,
    pub suggested_remote_branch: String,
    pub has_upstream: bool,
}

#[derive(Debug)]
pub struct PushOptions {
    pub remote_name: String,
    pub local_branch: String,
    pub remote_branch: String,
    pub force: bool,
    pub set_upstream: bool,
    pub push_tags: bool,
}

#[derive(Debug, Clone, Copy)]
pub enum CheckoutOutcome {
    Switched,
    CreatedTrackingBranch,
    SwitchedToExistingLocal,
}

#[derive(Debug, Clone, Copy, Default)]
pub enum StashPushFlags {
    #[default]
    Default,
    IncludeUntracked,
}

#[derive(Debug, Clone)]
pub struct UpstreamRef {
    pub name: String,
    pub oid: String,
}

#[derive(Debug, Default, Clone)]
pub struct BranchRefs {
    pub local: Vec<String>,
    pub remote: Vec<String>,
}

#[derive(Debug)]
pub struct CommitPage {
    pub commits: Vec<CommitInfo>,
    pub has_more: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PullOutcome {
    AlreadyUpToDate,
    FastForward { commits: usize },
    Merged { commits: usize },
    MergeConflicts { conflicted_files: Vec<String> },
    Rebased { commits: usize },
    RebaseConflicts,
}

impl PullOutcome {
    pub fn has_conflicts(&self) -> bool {
        matches!(self, PullOutcome::MergeConflicts { .. } | PullOutcome::RebaseConflicts)
    }
}

#[derive(Debug, Clone)]
pub struct PullDefaults {
    pub remotes: Vec<String>,
    pub suggested_remote: String,
    pub suggested_remote_branch: String,
    pub rebase_default: bool,
    pub dirty: bool,
}

#[derive(Debug, Clone)]
pub struct PushSettings {
    pub remote: String,
    pub local_branch: String,
    pub branch: String,
    pub force: bool,
    pub set_upstream: bool,
    pub push_tags: bool,
}

#[derive(Debug, Clone)]
pub struct CloneProgress {
    pub received_objects: usize,
    pub total_objects: usize,
    pub indexed_deltas: usize,
    pub total_deltas: usize,
    pub received_bytes: usize,
}

#[derive(Debug, Clone)]
pub struct PullSettings {
    pub remote: String,
    pub branch: String,
    pub rebase: bool,
    pub stash: bool,
    pub prune: bool,
    pub fetch_tags: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeStrategy {
    FastForwardOrMergeCommit,
    MergeCommit,
    RebaseFastForward,
    RebaseMergeCommit,
    Squash,
}

impl AsRef<str> for MergeStrategy {
    fn as_ref(&self) -> &'static str {
        match self {
            Self::FastForwardOrMergeCommit => "fast-forward-or-merge-commit",
            Self::MergeCommit => "merge-commit",
            Self::RebaseFastForward => "rebase-fast-forward",
            Self::RebaseMergeCommit => "rebase-merge-commit",
            Self::Squash => "squash",
        }
    }
}

impl From<&str> for MergeStrategy {
    fn from(key: &str) -> Self {
        match key {
            "merge-commit" => Self::MergeCommit,
            "rebase-fast-forward" => Self::RebaseFastForward,
            "rebase-merge-commit" => Self::RebaseMergeCommit,
            "squash" => Self::Squash,
            _ => Self::FastForwardOrMergeCommit,
        }
    }
}

#[derive(Debug, Clone)]
pub struct MergeSettings {
    pub source_ref: String,
    pub source_kind: BranchKind,
    pub strategy: MergeStrategy,
    pub squash_message: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MergeOutcome {
    AlreadyUpToDate,
    FastForward { commits: usize },
    Merged { commits: usize },
    Rebased { commits: usize },
    Squashed { commits: usize },
    MergeConflicts { conflicted_files: Vec<String> },
    RebaseConflicts,
}

impl MergeOutcome {
    pub fn has_conflicts(&self) -> bool {
        matches!(self, Self::MergeConflicts { .. } | Self::RebaseConflicts)
    }
}
