"""BibTeX differ module.

Compares two lists of CitationRecords by citation key and produces a
structured diff report listing added, removed, and changed entries.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from alice_cli.cite.models import CitationRecord

# Fields to compare between two records.  Internal/pipeline fields
# (warnings, resolved_source, citation_type, confidence_score,
# verification_status) are excluded because they are not part of the
# bibliographic content.
_COMPARABLE_STR_FIELDS: list[str] = [
    "entry_type",
    "title",
    "year",
    "journal",
    "volume",
    "number",
    "pages",
    "publisher",
    "doi",
    "url",
    "abstract",
    "isbn",
    "arxiv_id",
    "pmid",
]

_COMPARABLE_LIST_FIELDS: list[str] = [
    "authors",
]


@dataclass
class FieldChange:
    """A single field-level change between two entries."""

    field: str
    old_value: str
    new_value: str


@dataclass
class DiffEntry:
    """A single entry in the diff report."""

    citation_key: str
    status: str  # "added", "removed", "changed"
    field_changes: list[FieldChange] = field(default_factory=list)


@dataclass
class DiffReport:
    """Structured output from the BibTeX differ."""

    added: list[DiffEntry] = field(default_factory=list)
    removed: list[DiffEntry] = field(default_factory=list)
    changed: list[DiffEntry] = field(default_factory=list)

    @property
    def is_identical(self) -> bool:
        return not self.added and not self.removed and not self.changed


def _compare_fields(record_a: CitationRecord, record_b: CitationRecord) -> list[FieldChange]:
    """Compare all fields between two records and return differences."""
    changes: list[FieldChange] = []

    for fname in _COMPARABLE_STR_FIELDS:
        val_a = getattr(record_a, fname, "")
        val_b = getattr(record_b, fname, "")
        if val_a != val_b:
            changes.append(FieldChange(field=fname, old_value=str(val_a), new_value=str(val_b)))

    for fname in _COMPARABLE_LIST_FIELDS:
        val_a = getattr(record_a, fname, [])
        val_b = getattr(record_b, fname, [])
        if val_a != val_b:
            changes.append(
                FieldChange(
                    field=fname,
                    old_value=str(val_a),
                    new_value=str(val_b),
                )
            )

    # Compare extra_fields dicts
    extra_a = record_a.extra_fields or {}
    extra_b = record_b.extra_fields or {}
    if extra_a != extra_b:
        all_keys = sorted(set(extra_a.keys()) | set(extra_b.keys()))
        for key in all_keys:
            v_a = extra_a.get(key, "")
            v_b = extra_b.get(key, "")
            if v_a != v_b:
                changes.append(
                    FieldChange(
                        field=f"extra_fields.{key}",
                        old_value=v_a,
                        new_value=v_b,
                    )
                )

    return changes


def diff_bibtex(
    records_a: list[CitationRecord],
    records_b: list[CitationRecord],
) -> DiffReport:
    """Compare two lists of CitationRecords by citation key.

    Entries in *records_b* but not *records_a* are "added".
    Entries in *records_a* but not *records_b* are "removed".
    Entries with the same key but different field values are "changed".
    """
    map_a: dict[str, CitationRecord] = {r.citation_key: r for r in records_a}
    map_b: dict[str, CitationRecord] = {r.citation_key: r for r in records_b}

    keys_a = set(map_a.keys())
    keys_b = set(map_b.keys())

    added: list[DiffEntry] = []
    removed: list[DiffEntry] = []
    changed: list[DiffEntry] = []

    # Added: in b but not a
    for key in sorted(keys_b - keys_a):
        added.append(DiffEntry(citation_key=key, status="added"))

    # Removed: in a but not b
    for key in sorted(keys_a - keys_b):
        removed.append(DiffEntry(citation_key=key, status="removed"))

    # Changed: in both but with differing fields
    for key in sorted(keys_a & keys_b):
        field_changes = _compare_fields(map_a[key], map_b[key])
        if field_changes:
            changed.append(
                DiffEntry(
                    citation_key=key,
                    status="changed",
                    field_changes=field_changes,
                )
            )

    return DiffReport(added=added, removed=removed, changed=changed)
