"""UKO Ontology Domain Registry and Turtle syntax validation.

Central registry that binds each UKO Layer 1 domain to its namespace
IRI, OWL class inventory, and ``DetailLevelMap``.  Provides Turtle
syntax validation for the project ontology file
(``docs/ontology/uko.ttl``).

## Registered Domains

| Prefix        | Namespace IRI                                      |
|---------------|----------------------------------------------------|
| ``uko:``      | ``https://cleveragents.ai/ontology/uko#``          |
| ``uko-code:`` | ``https://cleveragents.ai/ontology/uko/code#``     |
| ``uko-doc:``  | ``https://cleveragents.ai/ontology/uko/doc#``      |
| ``uko-data:`` | ``https://cleveragents.ai/ontology/uko/data#``     |
| ``uko-infra:``| ``https://cleveragents.ai/ontology/uko/infra#``    |

## Turtle Validation

``validate_turtle()`` performs a lightweight parse of Turtle files
without requiring ``rdflib``.  It checks:

1. Every ``@prefix`` declaration has balanced syntax.
2. Every statement ends with ``.`` (or ``;`` / ``,`` for continuation).
3. All ``@prefix`` IRIs referenced in the body are declared.
4. Balanced angle brackets (``<…>``), quotes, and parentheses.

Based on ``docs/specification.md`` §41830-42332 (UKO Ontology Hierarchy).
"""

from __future__ import annotations

from typing import NamedTuple

from cleveragents.domain.models.acms._turtle_validation import (
    TurtleValidationError,
    validate_turtle,
    validate_turtle_file,
)
from cleveragents.domain.models.acms.crp import DetailLevelMap

__all__: list[str] = [
    "LAYER0_CLASSES",
    "LAYER1_CODE_CLASSES",
    "LAYER1_DATA_CLASSES",
    "LAYER1_DOC_CLASSES",
    "LAYER1_INFRA_CLASSES",
    "UKO_BASE_IRI",
    "UKO_CODE_IRI",
    "UKO_DATA_IRI",
    "UKO_DOC_IRI",
    "UKO_INFRA_IRI",
    "DomainDescriptor",
    "TurtleValidationError",
    "build_detail_map_chain",
    "get_domain",
    "get_layer1_domains",
    "list_domains",
    "validate_turtle",
    "validate_turtle_file",
]

# ---------------------------------------------------------------------------
# Constants — namespace IRIs  (§41873-41892)
# ---------------------------------------------------------------------------

UKO_BASE_IRI: str = "https://cleveragents.ai/ontology/uko#"
"""Layer 0 universal foundation namespace."""

UKO_CODE_IRI: str = "https://cleveragents.ai/ontology/uko/code#"
"""Layer 1 general software namespace."""

UKO_DOC_IRI: str = "https://cleveragents.ai/ontology/uko/doc#"
"""Layer 1 documents namespace."""

UKO_DATA_IRI: str = "https://cleveragents.ai/ontology/uko/data#"
"""Layer 1 data schemas namespace."""

UKO_INFRA_IRI: str = "https://cleveragents.ai/ontology/uko/infra#"
"""Layer 1 infrastructure namespace."""

# ---------------------------------------------------------------------------
# Layer 0 class inventory
# ---------------------------------------------------------------------------

LAYER0_CLASSES: frozenset[str] = frozenset(
    {
        "uko:InformationUnit",
        "uko:Container",
        "uko:Atom",
        "uko:Annotation",
        "uko:Boundary",
    }
)
"""Layer 0 base classes defined in the UKO ontology."""

# ---------------------------------------------------------------------------
# Layer 1 class inventories — from spec §41934-42331
# ---------------------------------------------------------------------------

LAYER1_CODE_CLASSES: frozenset[str] = frozenset(
    {
        "uko-code:Module",
        "uko-code:Callable",
        "uko-code:TypeDefinition",
        "uko-code:TestCase",
        "uko-code:Import",
    }
)
"""``uko-code:`` classes for general software."""

LAYER1_DOC_CLASSES: frozenset[str] = frozenset(
    {
        # Containers
        "uko-doc:Document",
        "uko-doc:Part",
        "uko-doc:Chapter",
        "uko-doc:Section",
        "uko-doc:Subsection",
        # Atoms
        "uko-doc:Paragraph",
        "uko-doc:CodeBlock",
        "uko-doc:Figure",
        "uko-doc:Table",
        "uko-doc:BlockQuote",
        "uko-doc:ListBlock",
        # Annotations
        "uko-doc:Footnote",
        "uko-doc:Citation",
        "uko-doc:Comment",
        # Boundaries
        "uko-doc:TableOfContents",
        "uko-doc:Index",
        "uko-doc:Glossary",
    }
)
"""``uko-doc:`` classes for documents."""

LAYER1_DATA_CLASSES: frozenset[str] = frozenset(
    {
        # Containers
        "uko-data:Database",
        "uko-data:Schema",
        "uko-data:Table",
        "uko-data:View",
        # Atoms
        "uko-data:Column",
        "uko-data:StoredProcedure",
        "uko-data:Trigger",
        "uko-data:Migration",
        # Annotations
        "uko-data:Constraint",
        "uko-data:Index",
        "uko-data:ColumnComment",
        # Boundaries
        "uko-data:ForeignKey",
        "uko-data:APIEndpoint",
    }
)
"""``uko-data:`` classes for data schemas."""

LAYER1_INFRA_CLASSES: frozenset[str] = frozenset(
    {
        # Containers
        "uko-infra:Service",
        "uko-infra:ConfigBlock",
        "uko-infra:DeploymentUnit",
        # Atoms
        "uko-infra:ConfigKey",
        "uko-infra:EnvironmentVariable",
        # Boundaries
        "uko-infra:Endpoint",
        "uko-infra:Port",
    }
)
"""``uko-infra:`` classes for infrastructure."""

# ---------------------------------------------------------------------------
# DomainDescriptor — metadata for a UKO domain
# ---------------------------------------------------------------------------


class DomainDescriptor(NamedTuple):
    """Immutable metadata for a single UKO domain."""

    prefix: str
    """Turtle prefix (e.g., ``"uko-code:"``)."""
    iri: str
    """Full namespace IRI."""
    layer: int
    """Ontology layer (0, 1, 2, or 3)."""
    classes: frozenset[str]
    """Set of class QNames defined in this domain."""
    detail_map: DetailLevelMap
    """The authoritative ``DetailLevelMap`` for this domain."""


# ---------------------------------------------------------------------------
# Authoritative DetailLevelMap definitions  (spec §41934-42331)
# ---------------------------------------------------------------------------
# These are defined here in the domain layer (not in the application layer)
# because they are pure data declarations tied to the ontology spec,
# not application-level services.  The application-layer preset functions
# in depth_breadth_projection.py delegate to these same values.

_CODE_LEVELS: dict[str, int] = {
    "MODULE_LISTING": 0,
    "MODULE_GRAPH": 1,
    "MEMBER_LISTING": 2,
    "MEMBER_SUMMARY": 3,
    "SIGNATURES": 4,
    "SIGNATURES_WITH_DOCS": 5,
    "STRUCTURAL_OUTLINE": 6,
    "KEY_LOGIC": 7,
    "NEAR_COMPLETE": 8,
    "FULL_SOURCE": 9,
}

_DOC_LEVELS: dict[str, int] = {
    "TITLE_ONLY": 0,
    "TABLE_OF_CONTENTS_L1": 1,
    "TABLE_OF_CONTENTS_L2": 2,
    "FULL_TOC": 3,
    "TOC_WITH_SUMMARIES": 4,
    "SECTION_OVERVIEWS": 5,
    "TOPIC_SENTENCES": 6,
    "PARAGRAPH_SUMMARIES": 7,
    "STRUCTURAL_DETAIL": 8,
    "NEAR_COMPLETE": 9,
    "FULL_CONTENT": 10,
}

_DATA_LEVELS: dict[str, int] = {
    "SCHEMA_LISTING": 0,
    "TABLE_LISTING": 1,
    "COLUMN_LISTING": 2,
    "TYPED_COLUMNS": 3,
    "CONSTRAINTS": 4,
    "RELATIONSHIPS": 5,
    "INDEXES_AND_STATS": 6,
    "DDL": 7,
    "DDL_WITH_TRIGGERS": 8,
    "FULL_PROCEDURES": 9,
    "WITH_SAMPLE_DATA": 10,
    "FULL_CATALOG": 11,
}

_INFRA_LEVELS: dict[str, int] = {
    "SERVICE_LISTING": 0,
    "SERVICE_GRAPH": 1,
    "ENDPOINT_LISTING": 2,
    "ENDPOINT_DETAIL": 3,
    "CONFIG_KEYS": 4,
    "CONFIG_VALUES": 5,
    "RESOURCE_LIMITS": 6,
    "FULL_CONFIG": 7,
    "WITH_DEPLOYMENT": 8,
}

# ---------------------------------------------------------------------------
# OntologyRegistry
# ---------------------------------------------------------------------------

_PRESETS_LOADED: bool = False
_DOMAINS: dict[str, DomainDescriptor] = {}


def _ensure_presets() -> None:
    """Lazily build the domain registry on first access."""
    global _PRESETS_LOADED
    if _PRESETS_LOADED:
        return

    _DOMAINS.update(
        {
            "uko:": DomainDescriptor(
                prefix="uko:",
                iri=UKO_BASE_IRI,
                layer=0,
                classes=LAYER0_CLASSES,
                detail_map=DetailLevelMap(
                    domain="uko:",
                    levels={},
                    max_depth=0,
                ),
            ),
            "uko-code:": DomainDescriptor(
                prefix="uko-code:",
                iri=UKO_CODE_IRI,
                layer=1,
                classes=LAYER1_CODE_CLASSES,
                detail_map=DetailLevelMap(
                    domain="uko-code:",
                    levels=dict(_CODE_LEVELS),
                    max_depth=9,
                ),
            ),
            "uko-doc:": DomainDescriptor(
                prefix="uko-doc:",
                iri=UKO_DOC_IRI,
                layer=1,
                classes=LAYER1_DOC_CLASSES,
                detail_map=DetailLevelMap(
                    domain="uko-doc:",
                    levels=dict(_DOC_LEVELS),
                    max_depth=10,
                ),
            ),
            "uko-data:": DomainDescriptor(
                prefix="uko-data:",
                iri=UKO_DATA_IRI,
                layer=1,
                classes=LAYER1_DATA_CLASSES,
                detail_map=DetailLevelMap(
                    domain="uko-data:",
                    levels=dict(_DATA_LEVELS),
                    max_depth=11,
                ),
            ),
            "uko-infra:": DomainDescriptor(
                prefix="uko-infra:",
                iri=UKO_INFRA_IRI,
                layer=1,
                classes=LAYER1_INFRA_CLASSES,
                detail_map=DetailLevelMap(
                    domain="uko-infra:",
                    levels=dict(_INFRA_LEVELS),
                    max_depth=8,
                ),
            ),
        }
    )
    _PRESETS_LOADED = True


def get_domain(prefix: str) -> DomainDescriptor:
    """Look up a domain descriptor by prefix.

    Args:
        prefix: UKO prefix (e.g., ``"uko-code:"``).

    Returns:
        The matching :class:`DomainDescriptor`.

    Raises:
        ValueError: If *prefix* is empty or unknown.
    """
    if not prefix:
        raise ValueError("Domain prefix must not be empty")
    _ensure_presets()
    desc = _DOMAINS.get(prefix)
    if desc is None:
        raise ValueError(f"Unknown UKO domain prefix: {prefix!r}")
    return desc


def list_domains() -> list[DomainDescriptor]:
    """Return all registered domain descriptors, sorted by layer then prefix."""
    _ensure_presets()
    return sorted(_DOMAINS.values(), key=lambda d: (d.layer, d.prefix))


def get_layer1_domains() -> list[DomainDescriptor]:
    """Return only Layer 1 domain descriptors."""
    return [d for d in list_domains() if d.layer == 1]


def build_detail_map_chain(
    *prefixes: str,
) -> DetailLevelMap:
    """Build a chained ``DetailLevelMap`` from most-general to most-specific.

    The inheritance chain is constructed so that the first prefix
    is the root (no parent) and each subsequent prefix's map has the
    previous one as its parent.  This supports the Layer 3 → 2 → 1 → 0
    resolution described in the spec.

    Example::

        chain = build_detail_map_chain("uko-code:", "uko-oo:", "uko-py:")
        chain.resolve("MODULE_LISTING")  # inherited from uko-code:

    Args:
        prefixes: One or more domain prefixes from most-general to
            most-specific.

    Returns:
        The innermost (most-specific) ``DetailLevelMap`` with its
        full inheritance chain wired.

    Raises:
        ValueError: If any prefix is unknown or no prefixes given.
    """
    if not prefixes:
        raise ValueError("At least one domain prefix is required")

    current: DetailLevelMap | None = None
    for pfx in prefixes:
        desc = get_domain(pfx)
        current = DetailLevelMap(
            domain=desc.prefix,
            parent=current,
            levels=dict(desc.detail_map.levels),
            max_depth=desc.detail_map.max_depth,
        )
    assert current is not None  # at least one iteration guaranteed
    return current


# Re-exported from _turtle_validation for backward compatibility.
# TurtleValidationError, validate_turtle, validate_turtle_file are
# imported at the top of this module.
