"""Emit a LAPIS document model as formatted text."""

from __future__ import annotations

from lapis_spec.models.lapis import (
    LapisDocument,
    LapisEnumType,
    LapisError,
    LapisField,
    LapisFlow,
    LapisLimits,
    LapisMeta,
    LapisObjectType,
    LapisOperation,
    LapisPlan,
    LapisQuota,
    LapisRateLimit,
    LapisWebhook,
)


def emit(doc: LapisDocument) -> str:
    """Convert a :class:`LapisDocument` to its LAPIS text representation."""
    parts: list[str] = []
    parts.append("# LAPIS v0.1.0")
    parts.append("")
    parts.append(_emit_meta(doc.meta))

    if doc.types:
        parts.append("")
        parts.append(_emit_types(doc.types))

    if doc.ops:
        parts.append("")
        parts.append(_emit_ops(doc.ops))

    if doc.webhooks:
        parts.append("")
        parts.append(_emit_webhooks(doc.webhooks))

    if doc.errors:
        parts.append("")
        parts.append(_emit_errors(doc.errors, doc.error_base_type))

    if doc.limits:
        parts.append("")
        parts.append(_emit_limits(doc.limits))

    if doc.flows:
        parts.append("")
        parts.append(_emit_flows(doc.flows))

    parts.append("")
    return "\n".join(parts)


def _emit_meta(meta: LapisMeta) -> str:
    lines = ["[meta]"]
    lines.append(f"api: {meta.api}")
    lines.append(f"base: {meta.base}")
    if meta.version:
        lines.append(f"version: {meta.version}")
    if meta.desc:
        lines.append(f"desc: {meta.desc}")
    if meta.auth:
        lines.append(f"auth: {meta.auth}")
    if meta.format and meta.format != "json":
        lines.append(f"format: {meta.format}")
    return "\n".join(lines)


def _emit_types(types: list[LapisEnumType | LapisObjectType]) -> str:
    lines = ["[types]"]
    for i, t in enumerate(types):
        if isinstance(t, LapisEnumType):
            lines.append(f"{t.name}: {' | '.join(t.values)}")
        elif isinstance(t, LapisObjectType):
            if i > 0 or (i == 0 and lines[-1] != "[types]"):
                lines.append("")
            lines.append(f"{t.name}:")
            for field in t.fields:
                lines.append(f"  {_emit_field_def(field)}")
        if isinstance(t, LapisEnumType):
            pass
    return "\n".join(lines)


def _emit_field_def(field: LapisField) -> str:
    """Emit a field definition line (used in [types] and inline)."""
    opt = "?" if field.optional else ""
    line = f"{field.name}{opt}: {field.type_expr}"
    if field.default is not None:
        line += f" = {field.default}"
    if field.since:
        line += f" @since:{field.since}"
    if field.deprecated:
        line += " @deprecated"
        if field.deprecated_note:
            line += f' "{field.deprecated_note}"'
    return line


def _emit_ops(ops: list[LapisOperation]) -> str:
    lines = ["[ops]"]
    for i, op in enumerate(ops):
        if i > 0:
            lines.append("")
        header = f"{op.name} {op.method} {op.path}"
        if op.modifiers:
            header += " " + " ".join(op.modifiers)
        if op.since:
            header += f" @since:{op.since}"
        lines.append(header)
        if op.description:
            lines.append(f"  {op.description}")
        for inp in op.inputs:
            lines.append(f"  > {_emit_input_field(inp)}")
        if op.output_ref:
            lines.append(f"  < {op.output_ref}")
        for out in op.outputs:
            lines.append(f"  < {out.name}: {out.type_expr}")
    return "\n".join(lines)


def _emit_input_field(field: LapisField) -> str:
    opt = "?" if field.optional else ""
    line = f"{field.name}{opt}: {field.type_expr}"
    if field.default is not None:
        line += f" = {field.default}"
    if field.location:
        line += f" @{field.location}"
    return line


def _emit_webhooks(webhooks: list[LapisWebhook]) -> str:
    lines = ["[webhooks]"]
    for i, wh in enumerate(webhooks):
        if i > 0:
            lines.append("")
        lines.append(f"{wh.name} -> {wh.method} {wh.path}")
        if wh.description:
            lines.append(f"  {wh.description}")
        for trigger in wh.triggers:
            lines.append(f"  ! {trigger}")
        for out in wh.outputs:
            opt = "?" if out.optional else ""
            line = f"  < {out.name}{opt}: {out.type_expr}"
            if out.header_name:
                line += f" @header:{out.header_name}"
            lines.append(line)
    return "\n".join(lines)


def _emit_errors(errors: list[LapisError], base_type: str | None) -> str:
    lines = ["[errors]"]
    if base_type:
        lines.append(f"# Base structure for all errors: {base_type}")
        lines.append("")

    for i, err in enumerate(errors):
        if i > 0 and not base_type:
            lines.append("")
        elif i > 0:
            lines.append("")
        header = f"{err.code} {err.name}"
        if err.ops:
            header += f" @ops:{','.join(err.ops)}"
        lines.append(header)
        if err.description:
            lines.append(f"  {err.description}")
        for f in err.fields:
            opt = "?" if f.optional else ""
            lines.append(f"  ~ {f.name}{opt}: {f.type_expr}")
    return "\n".join(lines)


def _emit_limits(limits: LapisLimits) -> str:
    lines = ["[limits]"]
    if limits.headers:
        lines.append(f"headers: {limits.headers}")
    if limits.on_exceed:
        lines.append(f"on_exceed: {limits.on_exceed}")
    if limits.max_body:
        lines.append(f"max_body: {limits.max_body}")
    if limits.max_upload:
        lines.append(f"max_upload: {limits.max_upload}")
    if limits.max_response:
        lines.append(f"max_response: {limits.max_response}")
    if limits.max_batch is not None:
        lines.append(f"max_batch: {limits.max_batch}")

    for rate in limits.rates:
        lines.append(_emit_rate(rate))
    for quota in limits.quotas:
        lines.append(_emit_quota(quota))

    for plan in limits.plans:
        lines.append("")
        lines.append(f"plan: {plan.name}")
        _emit_plan_body(plan, lines)

    return "\n".join(lines)


def _emit_rate(rate: LapisRateLimit, indent: str = "") -> str:
    line = f"{indent}rate: {rate.count}/{rate.window}"
    if rate.scope:
        line += f" {rate.scope}"
    return line


def _emit_quota(quota: LapisQuota, indent: str = "") -> str:
    if quota.unlimited:
        line = f"{indent}quota: unlimited"
    else:
        line = f"{indent}quota: {quota.count}/{quota.period}"
    if quota.scope:
        line += f" {quota.scope}"
    if quota.description:
        line += f' "{quota.description}"'
    return line


def _emit_plan_body(plan: LapisPlan, lines: list[str]) -> None:
    for rate in plan.rates:
        lines.append(_emit_rate(rate, indent="  "))
    for quota in plan.quotas:
        lines.append(_emit_quota(quota, indent="  "))
    if plan.max_batch is not None:
        lines.append(f"  max_batch: {plan.max_batch}")


def _emit_flows(flows: list[LapisFlow]) -> str:
    lines = ["[flows]"]
    for i, flow in enumerate(flows):
        if i > 0:
            lines.append("")
        lines.append(f'{flow.name} "{flow.description}"')
        lines.append(f"  {flow.chain}")
        for cond in flow.conditions:
            lines.append(f"  ? {cond.op}: {cond.description}")
        if flow.narrative:
            for narrative_line in flow.narrative.split("\n"):
                lines.append(f"  {narrative_line.strip()}")
    return "\n".join(lines)
