#![allow(dead_code)] // Phase 2: consumed by MR Detail screen

//! Merge request detail screen state.
//!
//! Holds MR metadata, file changes, discussions, cross-references,
//! and UI state. Supports progressive hydration identical to
//! Issue Detail: metadata loads first, discussions load async.

use crate::message::EntityKey;
use crate::view::common::cross_ref::{CrossRef, CrossRefState};
use crate::view::common::discussion_tree::{DiscussionNode, DiscussionTreeState};

// ---------------------------------------------------------------------------
// MrMetadata
// ---------------------------------------------------------------------------

/// Full metadata for a single merge request, fetched from the local DB.
#[derive(Debug, Clone)]
pub struct MrMetadata {
    /// MR IID (project-scoped).
    pub iid: i64,
    /// Project path (e.g., "group/project").
    pub project_path: String,
    /// MR title.
    pub title: String,
    /// MR description (markdown).
    pub description: String,
    /// Current state: "opened", "merged", "closed", "locked".
    pub state: String,
    /// Whether this is a draft/WIP MR.
    pub draft: bool,
    /// Author username.
    pub author: String,
    /// Assigned usernames.
    pub assignees: Vec<String>,
    /// Reviewer usernames.
    pub reviewers: Vec<String>,
    /// Label names.
    pub labels: Vec<String>,
    /// Source branch name.
    pub source_branch: String,
    /// Target branch name.
    pub target_branch: String,
    /// Detailed merge status (e.g., "mergeable", "checking").
    pub merge_status: String,
    /// Created timestamp (ms epoch).
    pub created_at: i64,
    /// Updated timestamp (ms epoch).
    pub updated_at: i64,
    /// Merged timestamp (ms epoch), if merged.
    pub merged_at: Option<i64>,
    /// GitLab web URL.
    pub web_url: String,
    /// Discussion count (for display before discussions load).
    pub discussion_count: usize,
    /// File change count.
    pub file_change_count: usize,
}

// ---------------------------------------------------------------------------
// FileChange
// ---------------------------------------------------------------------------

/// A file changed in the merge request.
#[derive(Debug, Clone)]
pub struct FileChange {
    /// Previous file path (if renamed).
    pub old_path: Option<String>,
    /// New/current file path.
    pub new_path: String,
    /// Type of change.
    pub change_type: FileChangeType,
}

/// The type of file change in an MR.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileChangeType {
    Added,
    Modified,
    Deleted,
    Renamed,
}

impl FileChangeType {
    /// Short icon for display.
    #[must_use]
    pub const fn icon(&self) -> &str {
        match self {
            Self::Added => "+",
            Self::Modified => "~",
            Self::Deleted => "-",
            Self::Renamed => "R",
        }
    }

    /// Parse from DB string.
    #[must_use]
    pub fn parse_db(s: &str) -> Self {
        match s {
            "added" => Self::Added,
            "deleted" => Self::Deleted,
            "renamed" => Self::Renamed,
            _ => Self::Modified,
        }
    }
}

// ---------------------------------------------------------------------------
// MrDetailData
// ---------------------------------------------------------------------------

/// Bundle returned by the metadata fetch action.
///
/// Metadata + cross-refs + file changes load in Phase 1 (fast).
/// Discussions load separately in Phase 2.
#[derive(Debug, Clone)]
pub struct MrDetailData {
    pub metadata: MrMetadata,
    pub cross_refs: Vec<CrossRef>,
    pub file_changes: Vec<FileChange>,
}

// ---------------------------------------------------------------------------
// MrTab
// ---------------------------------------------------------------------------

/// Active tab in the MR detail view.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MrTab {
    /// Overview: description + cross-refs.
    #[default]
    Overview,
    /// File changes list.
    Files,
    /// Discussions (general + diff).
    Discussions,
}

impl MrTab {
    /// Cycle to the next tab.
    #[must_use]
    pub fn next(self) -> Self {
        match self {
            Self::Overview => Self::Files,
            Self::Files => Self::Discussions,
            Self::Discussions => Self::Overview,
        }
    }

    /// Cycle to the previous tab.
    #[must_use]
    pub fn prev(self) -> Self {
        match self {
            Self::Overview => Self::Discussions,
            Self::Files => Self::Overview,
            Self::Discussions => Self::Files,
        }
    }

    /// Human-readable label.
    #[must_use]
    pub const fn label(&self) -> &str {
        match self {
            Self::Overview => "Overview",
            Self::Files => "Files",
            Self::Discussions => "Discussions",
        }
    }
}

// ---------------------------------------------------------------------------
// MrDetailState
// ---------------------------------------------------------------------------

/// State for the MR detail screen.
#[derive(Debug, Default)]
pub struct MrDetailState {
    /// Entity key for the currently displayed MR.
    pub current_key: Option<EntityKey>,
    /// MR metadata (Phase 1 load).
    pub metadata: Option<MrMetadata>,
    /// File changes (loaded with metadata in Phase 1).
    pub file_changes: Vec<FileChange>,
    /// Discussion nodes (Phase 2 async load).
    pub discussions: Vec<DiscussionNode>,
    /// Whether discussions have finished loading.
    pub discussions_loaded: bool,
    /// Cross-references (loaded with metadata in Phase 1).
    pub cross_refs: Vec<CrossRef>,
    /// Discussion tree UI state.
    pub tree_state: DiscussionTreeState,
    /// Cross-reference list UI state.
    pub cross_ref_state: CrossRefState,
    /// Description scroll offset.
    pub description_scroll: usize,
    /// File list selected index.
    pub file_selected: usize,
    /// File list scroll offset.
    pub file_scroll: usize,
    /// Active tab.
    pub active_tab: MrTab,
}

impl MrDetailState {
    /// Reset state for a new MR.
    pub fn load_new(&mut self, key: EntityKey) {
        self.current_key = Some(key);
        self.metadata = None;
        self.file_changes.clear();
        self.discussions.clear();
        self.discussions_loaded = false;
        self.cross_refs.clear();
        self.tree_state = DiscussionTreeState::default();
        self.cross_ref_state = CrossRefState::default();
        self.description_scroll = 0;
        self.file_selected = 0;
        self.file_scroll = 0;
        self.active_tab = MrTab::Overview;
    }

    /// Apply Phase 1 data (metadata + cross-refs + file changes).
    pub fn apply_metadata(&mut self, data: MrDetailData) {
        self.metadata = Some(data.metadata);
        self.cross_refs = data.cross_refs;
        self.file_changes = data.file_changes;
    }

    /// Apply Phase 2 data (discussions).
    pub fn apply_discussions(&mut self, discussions: Vec<DiscussionNode>) {
        self.discussions = discussions;
        self.discussions_loaded = true;
    }

    /// Whether we have metadata loaded.
    #[must_use]
    pub fn has_metadata(&self) -> bool {
        self.metadata.is_some()
    }

    /// Switch to the next tab.
    pub fn next_tab(&mut self) {
        self.active_tab = self.active_tab.next();
    }

    /// Switch to the previous tab.
    pub fn prev_tab(&mut self) {
        self.active_tab = self.active_tab.prev();
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::view::common::cross_ref::CrossRefKind;

    #[test]
    fn test_mr_detail_state_default() {
        let state = MrDetailState::default();
        assert!(state.current_key.is_none());
        assert!(state.metadata.is_none());
        assert!(state.discussions.is_empty());
        assert!(!state.discussions_loaded);
        assert!(state.file_changes.is_empty());
        assert_eq!(state.active_tab, MrTab::Overview);
    }

    #[test]
    fn test_load_new_resets_state() {
        let mut state = MrDetailState {
            discussions_loaded: true,
            description_scroll: 10,
            active_tab: MrTab::Files,
            ..MrDetailState::default()
        };

        state.load_new(EntityKey::mr(1, 42));
        assert_eq!(state.current_key, Some(EntityKey::mr(1, 42)));
        assert!(state.metadata.is_none());
        assert!(!state.discussions_loaded);
        assert_eq!(state.description_scroll, 0);
        assert_eq!(state.active_tab, MrTab::Overview);
    }

    #[test]
    fn test_apply_metadata() {
        let mut state = MrDetailState::default();
        state.load_new(EntityKey::mr(1, 42));

        let data = MrDetailData {
            metadata: MrMetadata {
                iid: 42,
                project_path: "group/proj".into(),
                title: "Fix auth".into(),
                description: "MR description".into(),
                state: "opened".into(),
                draft: false,
                author: "alice".into(),
                assignees: vec!["bob".into()],
                reviewers: vec!["carol".into()],
                labels: vec!["backend".into()],
                source_branch: "fix-auth".into(),
                target_branch: "main".into(),
                merge_status: "mergeable".into(),
                created_at: 1_700_000_000_000,
                updated_at: 1_700_000_060_000,
                merged_at: None,
                web_url: "https://gitlab.com/group/proj/-/merge_requests/42".into(),
                discussion_count: 2,
                file_change_count: 3,
            },
            cross_refs: vec![CrossRef {
                kind: CrossRefKind::RelatedIssue,
                entity_key: EntityKey::issue(1, 10),
                label: "Related issue".into(),
                navigable: true,
            }],
            file_changes: vec![FileChange {
                old_path: None,
                new_path: "src/auth.rs".into(),
                change_type: FileChangeType::Modified,
            }],
        };

        state.apply_metadata(data);
        assert!(state.has_metadata());
        assert_eq!(state.metadata.as_ref().unwrap().iid, 42);
        assert_eq!(state.cross_refs.len(), 1);
        assert_eq!(state.file_changes.len(), 1);
    }

    #[test]
    fn test_tab_cycling() {
        let tab = MrTab::Overview;
        assert_eq!(tab.next(), MrTab::Files);
        assert_eq!(tab.next().next(), MrTab::Discussions);
        assert_eq!(tab.next().next().next(), MrTab::Overview);

        assert_eq!(tab.prev(), MrTab::Discussions);
        assert_eq!(tab.prev().prev(), MrTab::Files);
    }

    #[test]
    fn test_tab_labels() {
        assert_eq!(MrTab::Overview.label(), "Overview");
        assert_eq!(MrTab::Files.label(), "Files");
        assert_eq!(MrTab::Discussions.label(), "Discussions");
    }

    #[test]
    fn test_file_change_type_icon() {
        assert_eq!(FileChangeType::Added.icon(), "+");
        assert_eq!(FileChangeType::Modified.icon(), "~");
        assert_eq!(FileChangeType::Deleted.icon(), "-");
        assert_eq!(FileChangeType::Renamed.icon(), "R");
    }

    #[test]
    fn test_file_change_type_parse_db() {
        assert_eq!(FileChangeType::parse_db("added"), FileChangeType::Added);
        assert_eq!(FileChangeType::parse_db("deleted"), FileChangeType::Deleted);
        assert_eq!(FileChangeType::parse_db("renamed"), FileChangeType::Renamed);
        assert_eq!(
            FileChangeType::parse_db("modified"),
            FileChangeType::Modified
        );
        assert_eq!(
            FileChangeType::parse_db("unknown"),
            FileChangeType::Modified
        );
    }

    #[test]
    fn test_next_prev_tab_on_state() {
        let mut state = MrDetailState::default();
        assert_eq!(state.active_tab, MrTab::Overview);

        state.next_tab();
        assert_eq!(state.active_tab, MrTab::Files);

        state.prev_tab();
        assert_eq!(state.active_tab, MrTab::Overview);
    }
}
