"""Reference cleaner: normalize whitespace and extract identifiers."""

from __future__ import annotations

import re

from alice_cli.cite.models import CleanedReference, Identifier, IdentifierType

# ── Regex patterns for identifier extraction ──────────────────────────────────

# Zenodo DOI must be checked before general DOI (it's a special case)
_ZENODO_DOI_RE = re.compile(r"(?:doi:\s*|https?://doi\.org/)?(10\.5281/zenodo\.\d+)", re.IGNORECASE)

# General DOI pattern, may be prefixed with "doi:" or "https://doi.org/"
_DOI_RE = re.compile(r"(?:doi:\s*|https?://doi\.org/)?(10\.\d{4,}/\S+)", re.IGNORECASE)

# arXiv ID: explicit "arXiv:" prefix or bare YYMM.NNNNN(vN) pattern
_ARXIV_RE = re.compile(r"arXiv:(\d{4}\.\d{4,5}(?:v\d+)?)", re.IGNORECASE)
_ARXIV_BARE_RE = re.compile(r"\b(\d{4}\.\d{5})\b")

# PMID pattern
_PMID_RE = re.compile(r"PMID:\s*(\d+)", re.IGNORECASE)

# ISBN: 10 or 13 digits with optional hyphens
_ISBN_RE = re.compile(
    r"\b(?:ISBN[:\s-]*)?((?:\d[-\s]?){9}[\dXx]|(?:\d[-\s]?){12}\d)\b",
    re.IGNORECASE,
)

# GitHub URL
_GITHUB_RE = re.compile(r"(https://github\.com/[\w.\-]+/[\w.\-]+)")

# Semantic Scholar paper ID (40-char hex SHA or S2 corpus ID)
_S2_RE = re.compile(r"\bS2(?:CID)?:\s*(\d+)\b", re.IGNORECASE)
_S2_SHA_RE = re.compile(r"\b([0-9a-f]{40})\b", re.IGNORECASE)

# DBLP key (e.g. "journals/corr/VaswaniSPUJGKP17" or "conf/nips/VaswaniSPUJGKP17")
_DBLP_RE = re.compile(
    r"\bdblp:\s*((?:journals|conf|books|series|phd|reference)/[\w/.\-]+)",
    re.IGNORECASE,
)

# ORCID iD (e.g. "0000-0002-1825-0097")
_ORCID_RE = re.compile(r"(?:orcid(?:\.org)?[:/\s]+)(\d{4}-\d{4}-\d{4}-\d{3}[\dX])", re.IGNORECASE)
_ORCID_URL_RE = re.compile(r"https?://orcid\.org/(\d{4}-\d{4}-\d{4}-\d{3}[\dX])", re.IGNORECASE)

# ── Internal whitespace normalization ─────────────────────────────────────────

_MULTI_WS_RE = re.compile(r"\s+")


def _normalize_isbn(raw: str) -> str:
    """Strip hyphens and spaces from an ISBN string."""
    return re.sub(r"[-\s]", "", raw)


def clean_reference(raw: str) -> CleanedReference:
    """Normalize whitespace and extract an identifier from a raw reference string.

    Returns a ``CleanedReference`` with the original text, the whitespace-normalized
    text, and an ``Identifier`` if a known pattern was found (or ``None``).
    """
    cleaned = _MULTI_WS_RE.sub(" ", raw).strip()

    # Try each identifier pattern in priority order.
    # Zenodo DOI before general DOI (Zenodo is a special case of DOI).

    m = _ZENODO_DOI_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.ZENODO, value=m.group(1)),
        )

    m = _ARXIV_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.ARXIV, value=m.group(1)),
        )

    m = _PMID_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.PMID, value=m.group(1)),
        )

    m = _GITHUB_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.GITHUB, value=m.group(1)),
        )

    m = _ORCID_URL_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.ORCID, value=m.group(1)),
        )

    m = _ORCID_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.ORCID, value=m.group(1)),
        )

    m = _DBLP_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.DBLP, value=m.group(1)),
        )

    m = _S2_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.SEMANTIC_SCHOLAR, value=m.group(1)),
        )

    m = _DOI_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.DOI, value=m.group(1)),
        )

    m = _ISBN_RE.search(cleaned)
    if m:
        isbn_value = _normalize_isbn(m.group(1))
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.ISBN, value=isbn_value),
        )

    m = _ARXIV_BARE_RE.search(cleaned)
    if m:
        return CleanedReference(
            original=raw,
            cleaned_text=cleaned,
            identifier=Identifier(type=IdentifierType.ARXIV, value=m.group(1)),
        )

    # No identifier found — return as free text.
    return CleanedReference(original=raw, cleaned_text=cleaned, identifier=None)
