#!/usr/bin/env python3
# Copyright (c) 2026 Huawei Technologies Co., Ltd.
# openUBMC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

"""生成 libmcpp 资源协作接口 API 文档。

功能:从 libmcpp 生产源码的 MC_INTERFACE/MC_REFLECT 自动发现接口,以实现侧
成员、类型和读写权限为文档主体,并使用匹配的 mdb_interface 定义补充说明、
取值范围和复杂类型定义。
输入:mdb_interface 仓库路径。
输出:docs/api/mdb_interfaces.md。
执行:在 libmcpp 根目录运行 `python scripts/generate_mdb_api_docs.py
--mdb-root ../mdb_interface`。
退出码:普通生成模式以文档是否成功写出为准;--check 模式在文档需要
重新生成、无法读取或扫描存在警告时返回 1。接口差异由一致性比较工具负责判定。
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

from mdb_api_common import (
    Interface,
    Method,
    Parameter,
    Property,
    definition_reference,
    load_implementation_interfaces,
    load_mdb_interfaces,
    markdown_text,
    value_range,
)


ROOT = Path(__file__).resolve().parent.parent
DEFAULT_OUTPUT = ROOT / "docs" / "api" / "mdb_interfaces.md"


def partition_interfaces(
    implementation: dict[str, Interface], mdb: dict[str, Interface]
) -> tuple[dict[str, Interface], list[str]]:
    """按是否存在 MDB 定义拆分 libmcpp 实现接口。"""

    comparable = {
        name: interface for name, interface in implementation.items() if name in mdb
    }
    missing_mdb = sorted(set(implementation) - set(mdb))
    return comparable, missing_mdb


def _casefold_member(mapping: dict, name: str):
    """精确匹配成员名,或在大小写无歧义时匹配同名成员。"""

    if name in mapping:
        return mapping[name]
    matches = [value for key, value in mapping.items() if key.casefold() == name.casefold()]
    return matches[0] if len(matches) == 1 else None


def _property_view(actual: Property, expected: Property | None) -> Property:
    """以实现属性为主体,使用 MDB 补充说明和约束。"""

    return Property(
        name=actual.name,
        type_name=actual.type_name,
        access=actual.access,
        value_range=expected.value_range if expected is not None else "NA",
        description=expected.description if expected is not None else "NA(MDB 未定义)",
        source_member=actual.source_member,
    )


def _parameter_view(
    actual: Parameter,
    expected_parameters: list[Parameter],
    index: int,
) -> Parameter:
    """以实现参数类型为主体,按位置补充 MDB 参数元数据。"""

    expected = expected_parameters[index] if index < len(expected_parameters) else None
    return Parameter(
        name=expected.name if expected is not None else actual.name,
        type_name=actual.type_name,
        description=(
            expected.description
            if expected is not None and expected.description
            else "NA(MDB 未提供参数说明)"
        ),
        value_range=expected.value_range if expected is not None else "NA",
        reference=expected.reference if expected is not None else "",
    )


def _method_view(actual: Method, expected: Method | None) -> Method:
    """以实现方法签名为主体,使用 MDB 补充名称和说明。"""

    expected_inputs = expected.inputs if expected is not None else []
    expected_outputs = expected.outputs if expected is not None else []
    return Method(
        name=actual.name,
        inputs=[
            _parameter_view(parameter, expected_inputs, index)
            for index, parameter in enumerate(actual.inputs)
        ],
        outputs=[
            _parameter_view(parameter, expected_outputs, index)
            for index, parameter in enumerate(actual.outputs)
        ],
        description=(
            expected.description
            if expected is not None and expected.description
            else "NA(MDB 未提供方法说明)"
        ),
        source_member=actual.source_member,
    )


def _parameter_summary(parameters: list[Parameter]) -> str:
    if not parameters:
        return "NA"
    return "<br>".join(
        f"`{markdown_text(parameter.name)}: {markdown_text(parameter.type_name)}`"
        for parameter in parameters
    )


def _render_properties(
    implementation: Interface, expected: Interface | None
) -> list[str]:
    """渲染实现侧存在的属性;接口没有属性时不生成本节。"""

    if not implementation.properties:
        return []
    lines = [
        "### 属性",
        "",
        "| 属性名 | 类型 | 读写 | 取值范围 | 说明 |",
        "| ------ | ---- | ---- | -------- | ---- |",
    ]
    expected_properties = expected.properties if expected is not None else {}
    for actual in implementation.properties.values():
        metadata = _casefold_member(expected_properties, actual.name)
        prop = _property_view(actual, metadata)
        lines.append(
            f"| {markdown_text(prop.name)} | {markdown_text(prop.type_name)} | "
            f"{prop.access} | {markdown_text(prop.value_range)} | "
            f"{markdown_text(prop.description)} |"
        )
    return lines


def _render_parameter_table(title: str, parameters: list[Parameter]) -> list[str]:
    if not parameters:
        return []
    lines = [
        f"##### {title}",
        "",
        "| 参数名 | 类型 | 引用类型 | 取值范围 | 说明 |",
        "| ------ | ---- | -------- | -------- | ---- |",
    ]
    for parameter in parameters:
        lines.append(
            f"| {markdown_text(parameter.name)} | {markdown_text(parameter.type_name)} | "
            f"{markdown_text(parameter.reference) or 'NA'} | "
            f"{markdown_text(parameter.value_range)} | "
            f"{markdown_text(parameter.description)} |"
        )
    return lines


def _render_complex_type(
    reference: str,
    definitions: dict,
    rendered: set[str],
) -> tuple[list[str], list[str]]:
    """展开单个 MDB 引用类型,并返回其中继续引用的类型。"""

    if not reference or reference in rendered:
        return [], []
    rendered.add(reference)
    definition = definitions.get(reference)
    if not isinstance(definition, dict):
        return [
            f"##### 复杂类型 `{markdown_text(reference)}`",
            "",
            "MDB 中未找到该引用类型的字段定义。",
        ], []

    metadata_keys = {"$comment", "description", "title"}
    enum_items = [
        (name, item) for name, item in definition.items() if name not in metadata_keys
    ]
    enum_types = {type(item) for _, item in enum_items}
    is_enum = (
        bool(enum_items)
        and len(enum_types) == 1
        and enum_types.pop() in {bool, float, int, str}
    )
    if is_enum:
        lines = [
            f"##### 复杂类型 `{markdown_text(reference)}`(枚举)",
            "",
            "| 名称 | 值 |",
            "| ---- | -- |",
        ]
        for name, item in enum_items:
            lines.append(f"| {markdown_text(name)} | {markdown_text(str(item))} |")
        return lines, []

    lines = [
        f"##### 复杂类型 `{markdown_text(reference)}`(结构)",
        "",
        "| 字段名 | 类型 | 引用类型 | 取值范围 | 说明 |",
        "| ------ | ---- | -------- | -------- | ---- |",
    ]
    nested_references = []
    for field_name, field_definition in definition.items():
        if not isinstance(field_definition, dict):
            continue
        nested_reference = definition_reference(field_definition)
        if nested_reference:
            nested_references.append(nested_reference)
        lines.append(
            f"| {markdown_text(field_name)} | "
            f"{markdown_text(field_definition.get('baseType', 'unknown'))} | "
            f"{markdown_text(nested_reference) or 'NA'} | "
            f"{markdown_text(value_range(field_definition))} | "
            f"{markdown_text(field_definition.get('description', 'NA'))} |"
        )
    return lines, nested_references


def _render_complex_types(
    parameters: list[Parameter],
    definitions: dict,
    rendered: set[str],
) -> list[str]:
    references = [parameter.reference for parameter in parameters if parameter.reference]
    lines = []
    while references:
        reference = references.pop(0)
        section, nested = _render_complex_type(reference, definitions, rendered)
        if section:
            if lines:
                lines.append("")
            lines.extend(section)
        references.extend(nested)
    return lines


def _render_methods(
    implementation: Interface, expected: Interface | None
) -> list[str]:
    """渲染实现侧存在的方法;接口没有方法时不生成本节。"""

    if not implementation.methods:
        return []
    expected_methods = expected.methods if expected is not None else {}
    methods = [
        _method_view(actual, _casefold_member(expected_methods, actual.name))
        for actual in implementation.methods.values()
    ]
    lines = [
        "### 方法",
        "",
        "| 方法名 | 入参 | 返回值 | 说明 |",
        "| ------ | ---- | ------ | ---- |",
    ]
    for method in methods:
        lines.append(
            f"| {markdown_text(method.name)} | {_parameter_summary(method.inputs)} | "
            f"{_parameter_summary(method.outputs)} | {markdown_text(method.description)} |"
        )

    definitions = expected.definitions if expected is not None else {}
    for method in methods:
        lines.extend(["", f"#### {method.name}", "", markdown_text(method.description)])
        input_table = _render_parameter_table("入参", method.inputs)
        if input_table:
            lines.extend(["", *input_table])
        output_table = _render_parameter_table("返回值", method.outputs)
        if output_table:
            lines.extend(["", *output_table])
        # 每个方法独立展开其引用类型,保证读者无需跳转到其他方法查找字段定义。
        complex_types = _render_complex_types(
            [*method.inputs, *method.outputs], definitions, set()
        )
        if complex_types:
            lines.extend(["", *complex_types])
    return lines


def render_document(
    implementation: dict[str, Interface],
    mdb: dict[str, Interface],
) -> str:
    """以 libmcpp 实现能力为主体渲染完整 Markdown 文档。"""

    lines = [
        "# libmcpp 资源协作接口 API",
        "",
        "<!-- 本文件由 scripts/generate_mdb_api_docs.py 生成,请勿手工修改。 -->",
        "",
        "本文档以 libmcpp 生产源码中通过 `MC_INTERFACE` 和 `MC_REFLECT` "
        "实际暴露的接口能力为准。接口、属性、方法、类型和读写权限来自 libmcpp;"
        "匹配的 mdb_interface 定义用于补充说明、取值范围和复杂类型字段。"
        "实现与标准定义不一致的内容在一致性比较报告中呈现。",
        "",
        "- 属性读写:根据 libmcpp 反射只读标记生成 RO、RW 或 NA",
        "- 取值范围和说明:取自匹配的 mdb_interface 定义;无定义时为 NA",
        "",
        "## 接口概览",
        "",
        "| 接口名 | libmcpp 类 | 实现位置 | 标准接口 | MDB 定义位置 |",
        "| ------ | ----------- | -------- | -------- | ------------ |",
    ]
    for name, interface in implementation.items():
        expected = mdb.get(name)
        mdb_path = f"`{expected.source_file}`" if expected is not None else "NA"
        lines.append(
            f"| `{name}` | `{interface.class_name}` | `{interface.source_file}` | "
            f"{'是' if expected is not None else '否'} | {mdb_path} |"
        )

    for name, interface in implementation.items():
        expected = mdb.get(name)
        lines.extend(
            [
                "",
                f"## {name}",
                "",
                f"- libmcpp 类:`{interface.class_name}`",
                f"- 实现位置:`{interface.source_file}`",
                (
                    f"- MDB 定义:`{expected.source_file}`"
                    if expected is not None
                    else "- MDB 定义:NA(非标准接口)"
                ),
            ]
        )
        properties = _render_properties(interface, expected)
        if properties:
            lines.extend(["", *properties])
        methods = _render_methods(interface, expected)
        if methods:
            lines.extend(["", *methods])
    return "\n".join(lines).rstrip() + "\n"


def _print_missing_mdb(missing_mdb: list[str]) -> None:
    """把缺少 MDB 定义的实现接口打印到标准错误。"""

    if not missing_mdb:
        return
    print("以下 libmcpp 实现接口未在 mdb_interface 中定义:", file=sys.stderr)
    for name in missing_mdb:
        print(f"  - {name}", file=sys.stderr)


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--mdb-root", required=True, type=Path, help="mdb_interface 仓库根目录")
    parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT, help="Markdown 输出文件")
    parser.add_argument("--check", action="store_true", help="只检查现有文档是否需要重新生成")
    args = parser.parse_args()

    mdb_root = args.mdb_root.resolve()
    scan_warnings: list[str] = []
    implementation = load_implementation_interfaces(ROOT, scan_warnings)
    try:
        mdb = load_mdb_interfaces(mdb_root, scan_warnings)
    except FileNotFoundError as error:
        print(f"无法加载 MDB 定义:{error}", file=sys.stderr)
        print(
            "请通过 --mdb-root 指定 mdb_interface 仓库根目录。",
            file=sys.stderr,
        )
        return 1
    _, missing_mdb = partition_interfaces(implementation, mdb)

    content = render_document(implementation, mdb)
    output = args.output.resolve()
    if scan_warnings:
        print("扫描 libmcpp 源码或 MDB 定义时发现以下警告:", file=sys.stderr)
        for warning in scan_warnings:
            print(f"  - {warning}", file=sys.stderr)
    if args.check:
        if not output.is_file():
            print(f"文档需要重新生成: {output}", file=sys.stderr)
            return 1
        try:
            actual_content = output.read_text(encoding="utf-8")
        except (OSError, UnicodeDecodeError) as error:
            print(f"无法读取现有文档 {output}: {error}", file=sys.stderr)
            return 1
        if actual_content != content:
            print(f"文档需要重新生成: {output}", file=sys.stderr)
            return 1
        _print_missing_mdb(missing_mdb)
        if scan_warnings:
            print(
                "文档内容与可解析结果一致,但存在扫描警告,无法确认文档完整性。",
                file=sys.stderr,
            )
            return 1
        print(f"文档已是最新: {output}")
        return 0

    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text(content, encoding="utf-8", newline="\n")
    print(f"已生成 {len(implementation)} 个接口的文档: {output}")
    for name in implementation:
        print(f"  - {name}")
    _print_missing_mdb(missing_mdb)
    if missing_mdb or scan_warnings:
        print(
            "文档已成功写出;上述完整性问题请使用一致性比较工具处理。",
            file=sys.stderr,
        )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())