"""Main conversion pipeline: OpenAPI dict -> LAPIS document."""

from __future__ import annotations

import copy
import re
from typing import Any

from lapis_spec.emitter import emit
from lapis_spec.models.lapis import LapisDocument
from lapis_spec.models.openapi import OpenAPIDict
from lapis_spec.parser.ref_resolver import resolve_refs
from lapis_spec.parser.schema_flattener import flatten_schemas
from lapis_spec.sections.errors import convert_errors
from lapis_spec.sections.flows import convert_flows
from lapis_spec.sections.limits import convert_limits
from lapis_spec.sections.meta import convert_meta
from lapis_spec.sections.ops import convert_ops
from lapis_spec.sections.types import convert_types
from lapis_spec.sections.webhooks import convert_webhooks

_SCHEMA_REF_RE = re.compile(r"^#/components/schemas/(.+)$")


def convert(doc: OpenAPIDict) -> LapisDocument:
    """Convert a parsed OpenAPI document to a LAPIS document model.

    The input should be a raw dict from YAML/JSON parsing (already
    validated).  ``$ref`` resolution and schema flattening are handled
    internally.
    """
    tagged = _annotate_refs(copy.deepcopy(doc))
    _tag_schema_names(tagged)

    resolved = resolve_refs(tagged)
    flattened = flatten_schemas(resolved)

    meta = convert_meta(flattened)
    types, schema_name_map = convert_types(flattened)
    ops = convert_ops(flattened, schema_name_map)
    webhooks = convert_webhooks(flattened, schema_name_map)
    errors, error_base_type = convert_errors(flattened, schema_name_map)
    limits = convert_limits(flattened)
    flows = convert_flows(flattened)

    return LapisDocument(
        meta=meta,
        types=types,
        ops=ops,
        webhooks=webhooks,
        errors=errors,
        error_base_type=error_base_type,
        limits=limits,
        flows=flows,
    )


def convert_to_string(doc: OpenAPIDict) -> str:
    """Convert a parsed OpenAPI document directly to LAPIS text."""
    lapis_doc = convert(doc)
    return emit(lapis_doc)


def _tag_schema_names(doc: OpenAPIDict) -> None:
    """Add ``x-schema-name`` to each schema in ``components.schemas``."""
    schemas = doc.get("components", {}).get("schemas", {})
    for name, schema in schemas.items():
        if isinstance(schema, dict):
            schema["x-schema-name"] = name


def _annotate_refs(doc: OpenAPIDict) -> OpenAPIDict:
    """Add ``x-schema-name`` on ``$ref`` dicts pointing to schemas.

    After ref resolution the resolver replaces ``$ref`` dicts with the
    target content.  By adding the tag to the *referencing* dict before
    resolution, the tag survives into the resolved result and the
    section converters can recognise named types.
    """
    _walk_annotate(doc)
    return doc


def _walk_annotate(node: Any) -> None:
    if isinstance(node, dict):
        if "$ref" in node:
            ref = node["$ref"]
            if isinstance(ref, str):
                m = _SCHEMA_REF_RE.match(ref)
                if m:
                    node["x-schema-name"] = m.group(1)
        for v in node.values():
            _walk_annotate(v)
    elif isinstance(node, list):
        for item in node:
            _walk_annotate(item)
