#![allow(dead_code)]

//! Sync delta ledger — records entity changes during a sync run.
//!
//! After sync completes, the dashboard and list screens can query the
//! ledger to highlight "new since last sync" items. The ledger is
//! ephemeral (per-run, not persisted to disk).

use std::collections::HashSet;

/// Kind of change that occurred to an entity during sync.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChangeKind {
    New,
    Updated,
}

/// Entity type for the ledger.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LedgerEntityType {
    Issue,
    MergeRequest,
    Discussion,
    Note,
}

/// Per-run record of changed entity IDs during sync.
///
/// Used to highlight newly synced items in list/dashboard views.
#[derive(Debug, Default)]
pub struct SyncDeltaLedger {
    pub new_issue_iids: HashSet<i64>,
    pub updated_issue_iids: HashSet<i64>,
    pub new_mr_iids: HashSet<i64>,
    pub updated_mr_iids: HashSet<i64>,
    pub new_discussion_count: u64,
    pub updated_discussion_count: u64,
    pub new_note_count: u64,
}

impl SyncDeltaLedger {
    /// Record a change to an entity.
    pub fn record_change(&mut self, entity_type: LedgerEntityType, iid: i64, kind: ChangeKind) {
        match (entity_type, kind) {
            (LedgerEntityType::Issue, ChangeKind::New) => {
                self.new_issue_iids.insert(iid);
            }
            (LedgerEntityType::Issue, ChangeKind::Updated) => {
                self.updated_issue_iids.insert(iid);
            }
            (LedgerEntityType::MergeRequest, ChangeKind::New) => {
                self.new_mr_iids.insert(iid);
            }
            (LedgerEntityType::MergeRequest, ChangeKind::Updated) => {
                self.updated_mr_iids.insert(iid);
            }
            (LedgerEntityType::Discussion, ChangeKind::New) => {
                self.new_discussion_count += 1;
            }
            (LedgerEntityType::Discussion, ChangeKind::Updated) => {
                self.updated_discussion_count += 1;
            }
            (LedgerEntityType::Note, ChangeKind::New) => {
                self.new_note_count += 1;
            }
            (LedgerEntityType::Note, ChangeKind::Updated) => {
                // Notes don't have a meaningful "updated" count.
            }
        }
    }

    /// Produce a summary of changes from this sync run.
    #[must_use]
    pub fn summary(&self) -> super::sync::SyncSummary {
        use super::sync::{EntityChangeCounts, SyncSummary};
        SyncSummary {
            issues: EntityChangeCounts {
                new: self.new_issue_iids.len() as u64,
                updated: self.updated_issue_iids.len() as u64,
            },
            merge_requests: EntityChangeCounts {
                new: self.new_mr_iids.len() as u64,
                updated: self.updated_mr_iids.len() as u64,
            },
            discussions: EntityChangeCounts {
                new: self.new_discussion_count,
                updated: self.updated_discussion_count,
            },
            notes: EntityChangeCounts {
                new: self.new_note_count,
                updated: 0,
            },
            ..Default::default()
        }
    }

    /// Whether any entity was an issue IID that was newly added in this sync.
    #[must_use]
    pub fn is_new_issue(&self, iid: i64) -> bool {
        self.new_issue_iids.contains(&iid)
    }

    /// Whether any entity was an MR IID that was newly added in this sync.
    #[must_use]
    pub fn is_new_mr(&self, iid: i64) -> bool {
        self.new_mr_iids.contains(&iid)
    }

    /// Total changes recorded.
    #[must_use]
    pub fn total_changes(&self) -> u64 {
        self.new_issue_iids.len() as u64
            + self.updated_issue_iids.len() as u64
            + self.new_mr_iids.len() as u64
            + self.updated_mr_iids.len() as u64
            + self.new_discussion_count
            + self.updated_discussion_count
            + self.new_note_count
    }

    /// Clear the ledger for a new sync run.
    pub fn clear(&mut self) {
        self.new_issue_iids.clear();
        self.updated_issue_iids.clear();
        self.new_mr_iids.clear();
        self.updated_mr_iids.clear();
        self.new_discussion_count = 0;
        self.updated_discussion_count = 0;
        self.new_note_count = 0;
    }
}

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

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

    #[test]
    fn test_record_new_issues() {
        let mut ledger = SyncDeltaLedger::default();
        ledger.record_change(LedgerEntityType::Issue, 1, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Issue, 2, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Issue, 3, ChangeKind::Updated);

        assert_eq!(ledger.new_issue_iids.len(), 2);
        assert_eq!(ledger.updated_issue_iids.len(), 1);
        assert!(ledger.is_new_issue(1));
        assert!(ledger.is_new_issue(2));
        assert!(!ledger.is_new_issue(3));
    }

    #[test]
    fn test_record_new_mrs() {
        let mut ledger = SyncDeltaLedger::default();
        ledger.record_change(LedgerEntityType::MergeRequest, 10, ChangeKind::New);
        ledger.record_change(LedgerEntityType::MergeRequest, 20, ChangeKind::Updated);

        assert!(ledger.is_new_mr(10));
        assert!(!ledger.is_new_mr(20));
    }

    #[test]
    fn test_summary_counts() {
        let mut ledger = SyncDeltaLedger::default();
        ledger.record_change(LedgerEntityType::Issue, 1, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Issue, 2, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Issue, 3, ChangeKind::Updated);
        ledger.record_change(LedgerEntityType::MergeRequest, 10, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Discussion, 0, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Note, 0, ChangeKind::New);

        let summary = ledger.summary();
        assert_eq!(summary.issues.new, 2);
        assert_eq!(summary.issues.updated, 1);
        assert_eq!(summary.merge_requests.new, 1);
        assert_eq!(summary.discussions.new, 1);
        assert_eq!(summary.notes.new, 1);
    }

    #[test]
    fn test_total_changes() {
        let mut ledger = SyncDeltaLedger::default();
        ledger.record_change(LedgerEntityType::Issue, 1, ChangeKind::New);
        ledger.record_change(LedgerEntityType::MergeRequest, 10, ChangeKind::Updated);
        ledger.record_change(LedgerEntityType::Note, 0, ChangeKind::New);

        assert_eq!(ledger.total_changes(), 3);
    }

    #[test]
    fn test_dedup_same_iid() {
        let mut ledger = SyncDeltaLedger::default();
        // Recording same IID twice should deduplicate.
        ledger.record_change(LedgerEntityType::Issue, 1, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Issue, 1, ChangeKind::New);

        assert_eq!(ledger.new_issue_iids.len(), 1);
    }

    #[test]
    fn test_clear() {
        let mut ledger = SyncDeltaLedger::default();
        ledger.record_change(LedgerEntityType::Issue, 1, ChangeKind::New);
        ledger.record_change(LedgerEntityType::Note, 0, ChangeKind::New);

        ledger.clear();

        assert_eq!(ledger.total_changes(), 0);
        assert!(ledger.new_issue_iids.is_empty());
    }

    #[test]
    fn test_empty_ledger_summary() {
        let ledger = SyncDeltaLedger::default();
        let summary = ledger.summary();
        assert_eq!(summary.total_changes(), 0);
        assert!(!summary.has_errors());
    }
}
