from __future__ import annotations

import inspect
import sys
from collections import defaultdict
from pathlib import Path
from types import ModuleType
from typing import Any

OPERATOR_ORDER: tuple[str, ...] = (
    "||",
    "&&",
    "==",
    "!=",
    "<",
    "<=",
    ">",
    ">=",
    "+",
    "-",
    "*",
    "/",
    "%",
    "in",
    "not in",
)


def _escape(text: str) -> str:
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace("{", "&#123;")
        .replace("}", "&#125;")
    )


def _clean_docstring(text: str | None) -> str:
    if not text:
        return "No description provided."
    lines: list[str] = []
    seen_content = False
    for raw in text.splitlines():
        line = raw.rstrip()
        stripped = line.strip()
        if stripped in {"Args:", "Returns:", "Raises:"}:
            break
        if seen_content and not stripped:
            break
        if stripped:
            seen_content = True
        lines.append(line)

    while lines and not lines[0].strip():
        lines.pop(0)
    while lines and not lines[-1].strip():
        lines.pop()
    if not lines:
        return "No description provided."

    normalized: list[str] = []
    for line in lines:
        stripped = line.strip()
        if not stripped:
            if normalized and normalized[-1]:
                normalized.append("")
            continue
        if normalized and normalized[-1]:
            normalized[-1] = f"{normalized[-1]} {stripped}"
        else:
            normalized.append(stripped)
    return "\n".join(normalized)


def _load_functions_module(root_dir: Path) -> ModuleType:
    sys.path.insert(0, str(root_dir))
    import tracecat.expressions.functions as functions_module

    return functions_module


def _format_signature(func: Any) -> str:
    try:
        signature = inspect.signature(func)
    except (TypeError, ValueError):
        return "(...)"
    return str(signature).replace("'", "")


def _build_alias_map(function_mapping: dict[str, Any]) -> dict[str, list[str]]:
    names_by_callable: dict[int, list[str]] = defaultdict(list)
    for name, func in function_mapping.items():
        names_by_callable[id(func)].append(name)

    aliases: dict[str, list[str]] = {}
    for names in names_by_callable.values():
        sorted_names = sorted(names)
        for name in sorted_names:
            aliases[name] = [alias for alias in sorted_names if alias != name]
    return aliases


def main() -> None:
    root_dir = Path(__file__).resolve().parent.parent
    output_file = root_dir / "docs" / "snippets" / "functions-reference.mdx"
    functions_module = _load_functions_module(root_dir)

    function_mapping: dict[str, Any] = functions_module._FUNCTION_MAPPING
    operators: dict[str, Any] = functions_module.OPERATORS
    aliases = _build_alias_map(function_mapping)

    lines = [
        "{/* Auto-generated by scripts/generate_functions_docs.py; do not edit by hand. */}",
        "",
        "## Function reference",
        "",
    ]

    for name in sorted(function_mapping):
        func = function_mapping[name]
        signature = _escape(_format_signature(func))
        description = _clean_docstring(getattr(func, "__doc__", None))
        lines.append(f'<ResponseField name="FN.{_escape(name)}" type="function">')
        lines.append("")
        lines.append(f"  `FN.{_escape(name)}{signature}`")
        lines.append("")
        for doc_line in description.splitlines():
            lines.append(f"  {_escape(doc_line)}" if doc_line else "")
        if alias_names := aliases.get(name):
            lines.append("")
            lines.append(
                f"  Aliases: {', '.join(f'`FN.{_escape(alias)}`' for alias in alias_names)}"
            )
        lines.append("")
        lines.append("</ResponseField>")
        lines.append("")

    lines.append("## Operator equivalents")
    lines.append("")
    for operator in OPERATOR_ORDER:
        func = operators[operator]
        mapped_names = sorted(
            name for name, target in function_mapping.items() if target is func
        )
        lines.append(f'<ResponseField name="{operator}" type="operator">')
        if mapped_names:
            lines.append("")
            lines.append(
                f"  Equivalent function names: {', '.join(f'`FN.{_escape(name)}`' for name in mapped_names)}"
            )
        lines.append("")
        lines.append("</ResponseField>")
        lines.append("")

    output_file.write_text("\n".join(lines), encoding="utf-8")


if __name__ == "__main__":
    main()
