"""Lightweight Turtle (RDF) syntax validation.

Validates ``.ttl`` files without requiring ``rdflib``.  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

import re
from pathlib import Path

__all__: list[str] = [
    "TurtleValidationError",
    "validate_turtle",
    "validate_turtle_file",
]

# ---------------------------------------------------------------------------
# Regexes
# ---------------------------------------------------------------------------

# Regex for @prefix declarations:  @prefix <name>: <iri> .
_PREFIX_RE = re.compile(
    r"@prefix\s+([a-zA-Z][a-zA-Z0-9_-]*)\s*:\s*<([^>]+)>\s*\.",
)

# Regex for prefixed names used in the body (e.g., uko-code:Module)
_PREFIXED_NAME_RE = re.compile(
    r"\b([a-zA-Z][a-zA-Z0-9_-]*):([a-zA-Z_]\w*)\b",
)


# ---------------------------------------------------------------------------
# Exception
# ---------------------------------------------------------------------------


class TurtleValidationError(Exception):
    """Raised when Turtle syntax validation fails."""

    def __init__(self, errors: list[str]) -> None:
        self.errors = errors
        super().__init__(f"Turtle validation failed with {len(errors)} error(s)")


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def validate_turtle(content: str) -> list[str]:
    """Perform lightweight Turtle syntax validation.

    Returns a list of error strings (empty if valid).  This is NOT a
    full OWL reasoner — it catches common structural issues:

    1. All ``@prefix`` declarations are well-formed.
    2. Every prefixed name (e.g., ``uko-code:Module``) references a
       declared prefix.
    3. Angle brackets ``<…>`` are balanced on each line.
    4. Every top-level statement ends with ``.``.

    Args:
        content: Turtle file content as a string.

    Returns:
        List of error messages (empty means valid).
    """
    if not content or not content.strip():
        return ["Empty Turtle content"]

    errors: list[str] = []
    lines = content.splitlines()

    # 1. Collect declared prefixes
    declared_prefixes: set[str] = set()
    for match in _PREFIX_RE.finditer(content):
        declared_prefixes.add(match.group(1))

    # Also accept standard prefixes that may not be declared but are
    # used in Turtle (rdf, rdfs, owl, xsd are universal).
    declared_prefixes.update({"rdf", "rdfs", "owl", "xsd"})

    # 2. Check that all prefixed names reference declared prefixes
    for lineno, line in enumerate(lines, 1):
        stripped = line.strip()
        if stripped.startswith("#") or not stripped:
            continue
        for m in _PREFIXED_NAME_RE.finditer(stripped):
            pfx = m.group(1)
            if pfx not in declared_prefixes:
                errors.append(
                    f"Line {lineno}: undeclared prefix '{pfx}:' in '{m.group(0)}'"
                )

    # 3. Check angle bracket balance per line (ignoring comments)
    for lineno, line in enumerate(lines, 1):
        stripped = line.strip()
        if stripped.startswith("#") or not stripped:
            continue
        # Strip string literals to avoid false positives
        no_strings = re.sub(r'"[^"]*"', "", stripped)
        opens = no_strings.count("<")
        closes = no_strings.count(">")
        if opens != closes:
            errors.append(
                f"Line {lineno}: unbalanced angle brackets "
                f"({opens} open, {closes} close)"
            )

    # 4. Verify statements end with '.' (Turtle requirement)
    # Accumulate non-comment, non-blank, non-prefix lines and check
    # that the content ends with statements terminated by '.'.
    body_lines: list[str] = []
    for line in lines:
        stripped = line.strip()
        if not stripped or stripped.startswith("#") or stripped.startswith("@prefix"):
            continue
        body_lines.append(stripped)

    if body_lines:
        # The last non-blank body line should end with '.'
        last = body_lines[-1]
        if not last.endswith("."):
            errors.append(
                f"Final statement does not end with '.': '{last[:60]}...'"
                if len(last) > 60
                else f"Final statement does not end with '.': '{last}'"
            )

    return errors


def validate_turtle_file(path: str | Path) -> list[str]:
    """Validate a Turtle file on disk.

    Args:
        path: Path to the ``.ttl`` file.

    Returns:
        List of error messages (empty means valid).

    Raises:
        FileNotFoundError: If *path* does not exist.
        ValueError: If *path* is empty.
    """
    if not path:
        raise ValueError("Path must not be empty")
    p = Path(path)
    if not p.exists():
        raise FileNotFoundError(f"Turtle file not found: {p}")
    content = p.read_text(encoding="utf-8")
    return validate_turtle(content)
