"""
JSON-LD Parser -- convert JSON-LD documents to internal models.

Handles both concept-scheme and field-mapping JSON-LD documents.
"""

from __future__ import annotations

from typing import Any

from pycharter.wiki.ontology.models import (
    ConceptDefinition,
    ConceptScheme,
    FieldOntology,
    Lineage,
    OntologyArtifact,
    Relationship,
    RelationshipType,
)
from pycharter.wiki.shared.errors import OntologyError

_PREDICATE_TO_REL_TYPE = {
    # Legacy predicates
    "references": RelationshipType.REFERENCES,
    "derivedFrom": RelationshipType.DERIVED_FROM,
    "sameAs": RelationshipType.SAME_AS,
    "relatedTo": RelationshipType.RELATED_TO,
    # W3C-mapped predicates (emitter output names)
    "broader": RelationshipType.IS_A,
    "wasDerivedFrom": RelationshipType.DERIVED_FROM,
    "exactMatch": RelationshipType.SAME_AS,
    "related": RelationshipType.RELATED_TO,
    "hasPart": RelationshipType.HAS,
    # Knowledge schema predicates
    "dependsOn": RelationshipType.DEPENDS_ON,
    "precedes": RelationshipType.PRECEDES,
    "causes": RelationshipType.CAUSES,
    "implements": RelationshipType.IMPLEMENTS,
    "governs": RelationshipType.GOVERNS,
    "describes": RelationshipType.DESCRIBES,
    "equivalentTo": RelationshipType.EQUIVALENT_TO,
}


def _local_name(uri_or_str: Any) -> str:
    """Extract the local name from a URI, prefixed name, or plain string."""
    s = str(uri_or_str)
    for sep in ("#", "/", ":"):
        if sep in s:
            s = s.rsplit(sep, 1)[-1]
    return s


def _ensure_list(val: Any) -> list:
    if val is None:
        return []
    return val if isinstance(val, list) else [val]


# ── Concept scheme ───────────────────────────────────────────────────────────


def parse_concepts_jsonld(data: dict[str, Any]) -> ConceptScheme:
    """
    Parse a SKOS JSON-LD document into a ConceptScheme.

    Expects a ``@graph`` containing one ``skos:ConceptScheme`` node and
    zero-or-more ``skos:Concept`` nodes.
    """
    if not isinstance(data, dict):
        raise OntologyError(
            f"JSON-LD data must be a dictionary, got {type(data).__name__}"
        )

    graph = data.get("@graph", [])
    if not graph:
        if "@type" in data:
            graph = [data]
        else:
            raise OntologyError("JSON-LD document has no @graph and no @type")

    scheme_node: dict[str, Any] | None = None
    concept_nodes: list[dict[str, Any]] = []

    for node in graph:
        node_type = node.get("@type", "")
        types = _ensure_list(node_type)
        type_names = [_local_name(t) for t in types]

        if "ConceptScheme" in type_names:
            scheme_node = node
        elif "Concept" in type_names:
            concept_nodes.append(node)

    if scheme_node is None:
        raise OntologyError("No skos:ConceptScheme node found in JSON-LD")

    scheme = ConceptScheme(
        id=_local_name(scheme_node.get("@id", "")),
        title=scheme_node.get("prefLabel", ""),
        version=scheme_node.get("versionInfo", "0.0.0"),
        description=scheme_node.get("definition", ""),
    )

    for cn in concept_nodes:
        cid = _local_name(cn.get("@id", ""))
        broader_raw = cn.get("broader")
        broader = _local_name(broader_raw) if broader_raw else None

        replaced_raw = cn.get("isReplacedBy")
        replaced_by = _local_name(replaced_raw) if replaced_raw else None

        concept = ConceptDefinition(
            id=cid,
            label=cn.get("prefLabel", cid),
            definition=cn.get("definition"),
            broader=broader,
            tags=_ensure_list(cn.get("tag")),
            alt_labels=_ensure_list(cn.get("altLabel")),
            notation=cn.get("notation"),
            examples=_ensure_list(cn.get("example")),
            exact_match=cn.get("exactMatch"),
            deprecated=bool(cn.get("deprecated", False)),
            replaced_by=replaced_by,
            node_kind=cn.get("nodeKind"),
        )
        scheme.concepts.append(concept)

    return scheme


# ── Field mappings ───────────────────────────────────────────────────────────


def parse_ontology_jsonld(data: dict[str, Any]) -> OntologyArtifact:
    """
    Parse a field-mapping JSON-LD document into an OntologyArtifact.

    Expects a ``@graph`` of ``statfyi:FieldMapping`` nodes.
    """
    if not isinstance(data, dict):
        raise OntologyError(
            f"JSON-LD data must be a dictionary, got {type(data).__name__}"
        )

    graph = data.get("@graph", [])
    if not graph:
        if "@type" in data:
            graph = [data]
        else:
            raise OntologyError("JSON-LD document has no @graph and no @type")

    version = "0.0.0"
    fields: dict[str, FieldOntology] = {}

    for node in graph:
        field_id = node.get("@id", "")
        field_name = _local_name(field_id)

        concept_raw = node.get("mapsTo", "")
        concept = _local_name(concept_raw)

        if not concept:
            continue

        ver = node.get("versionInfo")
        if ver:
            version = ver

        derived_from_raw = node.get("wasDerivedFrom")
        source_system = node.get("wasGeneratedBy")
        lineage = None
        if derived_from_raw or source_system:
            lineage = Lineage(
                derived_from=(
                    _local_name(derived_from_raw) if derived_from_raw else None
                ),
                source_system=source_system,
            )

        relationships: list[Relationship] = []
        for pred, rel_type in _PREDICATE_TO_REL_TYPE.items():
            targets = _ensure_list(node.get(pred))
            for t in targets:
                relationships.append(Relationship(target=_local_name(t), type=rel_type))

        fo = FieldOntology(
            concept=concept,
            tags=_ensure_list(node.get("tag")),
            owner=node.get("creator"),
            description=node.get("description"),
            deprecated=bool(node.get("deprecated", False)),
            lineage=lineage,
            relationships=relationships,
        )
        fields[field_name] = fo

    return OntologyArtifact(version=version, fields=fields)
