# tools/lib/citation.py
"""Generate citation metadata and export formats for proofs."""

import json as _json

from tools.lib.depends_on import (
    DependsOnEntry, Identifier, PREREQUISITE_RELATIONS, canonical_identifier,
)
from tools.lib.latex_utils import strip_latex


def build_citation_context(
    proof_data: dict,
    canonical_url: str,
    slug: str,
    *,
    doi_data: dict | None = None,
    binder_url_fallback: str | None = None,
    commit_sha: str | None = None,
) -> dict:
    """Build citation context from proof data and optional DOI sidecar.

    Kwargs:
        doi_data: Parsed doi.json for a minted proof, or None. When set,
            its ``binder_url`` populates the DOI-mode launcher URL.
        binder_url_fallback: Slug-mode Binder URL computed by the caller
            (build-site.py) for unminted proofs. Used when ``doi_data`` is
            falsy; unused otherwise.
        commit_sha: 40-hex commit SHA the caller used to build the
            ``binder_url_fallback``. Echoed back into the returned dict
            so templates can render a short-SHA provenance hint.
    """
    generator = proof_data["generator"]
    date = generator["generated_at"]
    year = date[:4] if date else ""
    claim = strip_latex(proof_data["claim_natural"])
    verdict = proof_data.get("verdict", "")
    # v3 structured verdict — extract string value for display
    if isinstance(verdict, dict):
        base = verdict.get("value", "")
        if verdict.get("qualified") and verdict.get("qualifier") == "unverified_citations":
            verdict_str = f"{base} (with unverified citations)"
        else:
            verdict_str = base
    else:
        verdict_str = verdict
    verdict_display = verdict_str.capitalize() if verdict_str else ""
    claim_type = proof_data.get("claim_formal", {}).get("claim_type")
    title_prefix = "Deductive Proof" if claim_type == "theorem" else "Claim Verification"
    title = f'{title_prefix}: \u201c{claim}\u201d \u2014 {verdict_display}'
    slug_sanitized = slug.replace("-", "_")

    doi = None
    concept_doi = None
    binder_url = None
    if doi_data:
        doi = doi_data.get("doi")
        concept_doi = doi_data.get("concept_doi")
        binder_url = doi_data.get("binder_url")
    else:
        binder_url = binder_url_fallback

    return {
        "title": title,
        "author": "Proof Engine",
        "year": year,
        "date": date,
        "url": canonical_url,
        "doi": doi,
        "concept_doi": concept_doi,
        "binder_url": binder_url,
        "commit_sha": commit_sha,
        "version": generator.get("version", ""),
        "verdict": verdict_str,
        "slug_sanitized": slug_sanitized,
        "repository": "https://github.com/yaniv-golan/proof-engine",
    }


def _bibtex_escape(text: str) -> str:
    """Escape special BibTeX characters."""
    return text.replace("&", r"\&").replace("%", r"\%").replace("#", r"\#")


def generate_bibtex(ctx: dict) -> str:
    """Generate BibTeX citation string."""
    lines = [
        f"@misc{{proofengine_{ctx['slug_sanitized']},",
        f"  title   = {{{_bibtex_escape(ctx['title'])}}},",
        f"  author  = {{{{Proof Engine}}}},",
        f"  year    = {{{ctx['year']}}},",
        f"  url     = {{{ctx['url']}}},",
        f"  note    = {{Verdict: {ctx['verdict']}. Generated by proof-engine v{ctx['version']}}},",
    ]
    if ctx["doi"]:
        lines.append(f"  doi     = {{{ctx['doi']}}},")
    lines.append("}")
    return "\n".join(lines) + "\n"


def generate_ris(ctx: dict) -> str:
    """Generate RIS citation string."""
    lines = [
        "TY  - DATA",
        f"TI  - {ctx['title']}",
        "AU  - Proof Engine",
        f"PY  - {ctx['year']}",
        f"UR  - {ctx['url']}",
        f"N1  - Verdict: {ctx['verdict']}. Generated by proof-engine v{ctx['version']}",
    ]
    if ctx["doi"]:
        lines.append(f"DO  - {ctx['doi']}")
    lines.append("ER  -")
    return "\n".join(lines) + "\n"


def _identifier_url(ctx: dict) -> str:
    """Return the preferred identifier URL (DOI link or canonical URL)."""
    if ctx["doi"]:
        return f"https://doi.org/{ctx['doi']}"
    return ctx["url"]


def generate_apa(ctx: dict) -> str:
    """Generate APA citation string."""
    return f"Proof Engine. ({ctx['year']}). {ctx['title']}. {_identifier_url(ctx)}"


def generate_chicago(ctx: dict) -> str:
    """Generate Chicago citation string."""
    return f'Proof Engine. "{ctx["title"]}." {ctx["year"]}. {_identifier_url(ctx)}.'


def generate_cite_txt(ctx: dict) -> str:
    """Generate combined APA + Chicago citation text file."""
    return f"APA:\n{generate_apa(ctx)}\n\nChicago:\n{generate_chicago(ctx)}\n"


def _identifier_uri(ident: Identifier, base_url: str, site_url: str) -> str:
    if ident.type == "doi":
        return f"https://doi.org/{ident.value}"
    if ident.type == "swhid":
        return f"https://archive.softwareheritage.org/{ident.value}"
    if ident.type == "handle":
        return f"https://hdl.handle.net/{ident.value}"
    if ident.type == "arxiv":
        return f"https://arxiv.org/abs/{ident.value}"
    if ident.type == "isbn":
        return f"urn:isbn:{ident.value}"
    if ident.type == "slug":
        return f"{site_url}{base_url}proofs/{ident.value}/"
    if ident.type == "pmc":
        return f"https://pmc.ncbi.nlm.nih.gov/articles/{ident.value}/"
    return ident.value


_CFF_TYPE_BY_CANONICAL: dict[str, str] = {
    "doi": "article",
    "arxiv": "article",
    "pmc": "article",
    "swhid": "software",
    "slug": "software",
    "isbn": "book",
    "url": "website",
    "handle": "generic",
}


def _cff_reference(
    entry: DependsOnEntry, base_url: str, site_url: str,
) -> dict:
    """Render one depends_on entry as a CFF reference dict."""
    canon = canonical_identifier(entry)
    ref: dict = {"type": _CFF_TYPE_BY_CANONICAL[canon.type]}

    if canon.type == "doi":
        ref["doi"] = canon.value
        ref["url"] = f"https://doi.org/{canon.value}"
    elif canon.type == "arxiv":
        ref["url"] = f"https://arxiv.org/abs/{canon.value}"
    elif canon.type == "swhid":
        ref["url"] = f"https://archive.softwareheritage.org/{canon.value}"
    elif canon.type == "slug":
        ref["url"] = f"{site_url}{base_url}proofs/{canon.value}/"
    elif canon.type == "isbn":
        ref["isbn"] = canon.value
    elif canon.type == "url":
        ref["url"] = canon.value
    elif canon.type == "handle":
        ref["url"] = f"https://hdl.handle.net/{canon.value}"
    elif canon.type == "pmc":
        ref["url"] = f"https://pmc.ncbi.nlm.nih.gov/articles/{canon.value}/"

    if entry.note:
        ref["title"] = entry.note
    elif canon.type == "slug":
        ref["title"] = canon.value

    extras = [i for i in entry.identifiers if i is not canon]
    if extras:
        # CFF allowed identifier types: doi, url, swh, other.
        cff_type_map = {"doi": "doi", "swhid": "swh"}
        ref["identifiers"] = []
        for extra in extras:
            ref["identifiers"].append({
                "type": cff_type_map.get(extra.type, "url"),
                "value": _identifier_uri(extra, base_url, site_url),
            })

    return ref


def build_cff(
    ctx: dict,
    depends_on: list[DependsOnEntry],
    base_url: str = "/",
    site_url: str = "https://proofengine.info",
) -> str:
    """Render the proof's CITATION.cff (1.2.0) as a YAML string."""
    import yaml as _yaml
    payload: dict = {
        "cff-version": "1.2.0",
        "message": "If you use this proof, please cite it as below.",
        "title": ctx["title"],
        "version": ctx["version"],
        "date-released": ctx["date"],
        "authors": [{"name": "Proof Engine"}],
        "url": ctx["url"],
        "repository-code": ctx["repository"],
        "license": "MIT",
    }
    if ctx.get("doi"):
        payload["doi"] = ctx["doi"]
    if depends_on:
        payload["references"] = [
            _cff_reference(entry, base_url, site_url) for entry in depends_on
        ]
    return _yaml.dump(payload, sort_keys=False, default_flow_style=False)


def build_codemeta(
    ctx: dict,
    depends_on: list[DependsOnEntry],
    base_url: str = "/",
    site_url: str = "https://proofengine.info",
) -> str:
    """Render the proof's codemeta.json (CodeMeta 3.0) as a JSON string."""
    payload: dict = {
        "@context": "https://w3id.org/codemeta/3.0",
        "@type": "SoftwareSourceCode",
        "name": ctx["title"],
        "description": f"Verdict: {ctx['verdict']}",
        "version": ctx["version"],
        "dateCreated": ctx["date"],
        "license": "https://spdx.org/licenses/MIT",
        "codeRepository": ctx["repository"],
        "url": ctx["url"],
        "author": [{"@type": "Organization", "name": "Proof Engine"}],
    }
    if ctx.get("doi"):
        payload["identifier"] = f"https://doi.org/{ctx['doi']}"
    if depends_on:
        based: list[dict] = []
        for entry in depends_on:
            # CodeMeta isBasedOn is for true prerequisites only — citations
            # (References) and inverse edges (IsCitedBy etc.) belong elsewhere
            # and would make the metadata semantically false here.
            if entry.relation not in PREREQUISITE_RELATIONS:
                continue
            canon = canonical_identifier(entry)
            uri = _identifier_uri(canon, base_url, site_url)
            block: dict = {
                "@type": "CreativeWork",
                "@id": uri,
                "identifier": [
                    _identifier_uri(i, base_url, site_url)
                    for i in entry.identifiers
                ],
            }
            if entry.note:
                block["name"] = entry.note
            elif canon.type == "slug":
                block["name"] = canon.value
            based.append(block)
        if based:
            payload["isBasedOn"] = based
    return _json.dumps(payload, indent=2)
