import importlib.util
import json
import subprocess
import sys
import tempfile
import textwrap
import unittest
from pathlib import Path
from unittest import mock

SCRIPT = Path(__file__).parents[1] / "scripts" / "convert_tsf.py"


def load_script():
    spec = importlib.util.spec_from_file_location(
        "tsl_codegen_convert_tsf", SCRIPT
    )
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    return module


class ConvertTsfCliTest(unittest.TestCase):
    def setUp(self):
        self.temp_dir = tempfile.TemporaryDirectory()
        self.root = Path(self.temp_dir.name)
        self.output = self.root / "entry.json"

    def tearDown(self):
        self.temp_dir.cleanup()

    def write_tsf(self, name, source):
        path = self.root / f"{name}.tsf"
        path.write_text(textwrap.dedent(source).lstrip(), encoding="utf-8")
        return path

    def write_nested_tsf(self, directory, name, source):
        path = self.root / directory / f"{name}.tsf"
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(textwrap.dedent(source).lstrip(), encoding="utf-8")
        return path

    def run_cli(
        self,
        *inputs,
        fmt="json",
        output=None,
        module="示例 / 函数",
        path="base/example",
    ):
        output = output or self.output
        return subprocess.run(
            [
                sys.executable,
                str(SCRIPT),
                *map(str, inputs),
                "--format",
                fmt,
                "--module",
                module,
                "--path",
                path,
                "--output",
                str(output),
            ],
            capture_output=True,
            text=True,
            encoding="utf-8",
        )

    def read_declarations(self):
        return json.loads(self.output.read_text(encoding="utf-8"))[
            "declarations"
        ]

    def read_declaration(self):
        declarations = self.read_declarations()
        self.assertEqual(1, len(declarations))
        return declarations[0]

    def test_json_converts_complete_function_document(self):
        source = self.write_tsf(
            "Normalize",
            """
            function Normalize(mode: integer = 0): integer;
            begin
                /// 按指定模式处理并返回模式值。
                /// 第二行说明。
                /// @tags: 示例 枚举
                /// @param: mode {INTEGER} 处理模式，默认 0
                /// @values: mode
                ///   0: 原样返回
                ///   1: 去重
                ///   "auto": 自动判断
                /// @returns: INTEGER
                /// @example: 使用默认模式
                ///   return Normalize();
                /// @output:
                ///   0
                /// @example: 指定模式
                ///   return Normalize(1);
                return mode;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual("", result.stdout)
        self.assertIn(f"已写入 {self.output}", result.stderr)
        self.assertEqual(
            {
                "module": "示例 / 函数",
                "path": "base/example",
                "declarations": [
                    {
                        "kind": "function",
                        "name": "Normalize",
                        "signature": "Normalize(mode)",
                        "desc": "按指定模式处理并返回模式值。\n第二行说明。",
                        "tags": ["示例", "枚举"],
                        "params": [
                            {
                                "name": "mode",
                                "type": "integer",
                                "optional": True,
                                "desc": "处理模式，默认 0",
                                "values": [
                                    {"value": 0, "desc": "原样返回"},
                                    {"value": 1, "desc": "去重"},
                                    {"value": "auto", "desc": "自动判断"},
                                ],
                            }
                        ],
                        "returns": "integer",
                        "examples": [
                            {
                                "desc": "使用默认模式",
                                "code": "return Normalize();",
                                "output": "0",
                            },
                            {
                                "desc": "指定模式",
                                "code": "return Normalize(1);",
                            },
                        ],
                    }
                ],
            },
            json.loads(self.output.read_text(encoding="utf-8")),
        )

    def test_documented_parameter_types_fill_untyped_signature(self):
        source = self.write_tsf(
            "DocumentedTypes",
            """
            function DocumentedTypes(x, y): integer;
            begin
                /// 补充无类型签名的参数类型
                /// @param: x {integer} 第一个值
                /// @param: y {array of integer} 第二个值
                return x;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            [
                {"name": "x", "type": "integer", "desc": "第一个值"},
                {
                    "name": "y",
                    "type": "array of integer",
                    "desc": "第二个值",
                },
            ],
            self.read_declaration()["params"],
        )

    def test_declared_parameter_types_use_colon_and_semicolon(self):
        source = self.write_tsf(
            "DeclaredTypes",
            """
            function DeclaredTypes(x: integer; y: array of integer): integer;
            begin
                /// 读取签名中的参数类型
                /// @param: x 第一个值
                /// @param: y 第二个值
                return x;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            [
                {"name": "x", "type": "integer", "desc": "第一个值"},
                {
                    "name": "y",
                    "type": "array of integer",
                    "desc": "第二个值",
                },
            ],
            self.read_declaration()["params"],
        )

    def test_matching_documented_parameter_type_keeps_declared_spelling(self):
        source = self.write_tsf(
            "MatchingParamType",
            """
            function MatchingParamType(value: Array Of Integer): integer;
            begin
                /// 校验重复记录的参数类型
                /// @param: value {array of integer} 输入值
                return 1;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            [
                {
                    "name": "value",
                    "type": "Array Of Integer",
                    "desc": "输入值",
                }
            ],
            self.read_declaration()["params"],
        )

    def test_parameter_type_mismatch_creates_structural_draft(self):
        source = self.write_tsf(
            "ParamTypeMismatch",
            """
            function ParamTypeMismatch(value: integer): integer;
            begin
                /// 校验参数类型
                /// @param: value {string} 输入值
                return value;
            end;
            """,
        )
        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("", function["desc"])
        self.assertEqual(
            [{"name": "value", "type": "integer", "desc": ""}],
            function["params"],
        )
        self.assertEqual("integer", function["returns"])

    def test_malformed_parameter_docs_create_structural_draft(self):
        cases = {
            "EmptyParamType": "/// @param: value {} 输入值",
            "UnclosedParamType": "/// @param: value {integer 输入值",
            "MissingParamDescription": "/// @param: value {integer}",
        }
        for name, param_line in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(
                    name,
                    f"""
                    function {name}(value): integer;
                    begin
                        /// 校验参数类型格式
                        {param_line}
                        return value;
                    end;
                    """,
                )

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                function = self.read_declaration()
                self.assertEqual("", function["desc"])
                self.assertEqual(
                    [{"name": "value", "type": "", "desc": ""}],
                    function["params"],
                )
                self.assertEqual("integer", function["returns"])

    def test_multiple_tsf_files_are_merged_in_input_order(self):
        first = self.write_tsf(
            "First",
            """
            function First(): integer;
            begin
                /// 第一个函数。
                return 1;
            end;
            """,
        )
        second = self.write_tsf(
            "Second",
            """
            function Second(): string;
            begin
                /// 第二个函数。
                return "second";
            end;
            """,
        )

        result = self.run_cli(first, second)

        self.assertEqual(0, result.returncode, result.stderr)
        data = json.loads(self.output.read_text(encoding="utf-8"))
        self.assertEqual(
            ["First()", "Second()"],
            [
                function["signature"]
                for function in data["declarations"]
            ],
        )

    def test_mixed_function_class_unit_inputs_preserve_cli_order(self):
        cls = self.write_tsf(
            "Widget",
            """
            type Widget = class
            /// 组件。
            end;
            """,
        )
        function = self.write_tsf(
            "OpenWidget",
            """
            function OpenWidget(): Widget;
            begin
                /// 打开组件。
                return nil;
            end;
            """,
        )
        unit = self.write_tsf(
            "WidgetRuntime",
            """
            unit WidgetRuntime;
            /// 运行时接口。
            interface
            implementation
            end.
            """,
        )

        result = self.run_cli(cls, function, unit)

        self.assertEqual(0, result.returncode, result.stderr)
        declarations = json.loads(
            self.output.read_text(encoding="utf-8")
        )["declarations"]
        self.assertEqual(
            [
                ("class", "Widget"),
                ("function", "OpenWidget"),
                ("unit", "WidgetRuntime"),
            ],
            [(item["kind"], item["name"]) for item in declarations],
        )

    def test_yaml_serialization_uses_same_data_structure(self):
        try:
            import yaml
        except ImportError:
            self.skipTest("未安装 pyyaml")
        source = self.write_tsf(
            "Describe",
            """
            function Describe(value: string);
            begin
                /// 返回输入内容。
                /// @param: value 输入内容
                /// @returns: string
                return value;
            end;
            """,
        )
        output = self.root / "entry.yaml"

        result = self.run_cli(source, fmt="yaml", output=output)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            {
                "module": "示例 / 函数",
                "path": "base/example",
                "declarations": [
                    {
                        "kind": "function",
                        "name": "Describe",
                        "signature": "Describe(value)",
                        "desc": "返回输入内容。",
                        "params": [
                            {
                                "name": "value",
                                "type": "string",
                                "desc": "输入内容",
                            }
                        ],
                        "returns": "string",
                    }
                ],
            },
            yaml.safe_load(output.read_text(encoding="utf-8")),
        )

    def test_gb18030_encoded_tsf_is_supported(self):
        source = self.root / "Legacy.tsf"
        source.write_bytes(textwrap.dedent("""
                function Legacy(): string;
                begin
                    /// 返回中文内容。
                    return "中文";
                end;
                """).lstrip().encode("gb18030"))

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("返回中文内容。", function["desc"])

    def test_utf8_bom_encoded_tsf_is_supported(self):
        source = self.root / "BomDemo.tsf"
        source.write_text(
            "function BomDemo(): integer;\n"
            "begin\n"
            "    /// 带 BOM 的说明。\n"
            "    return 1;\n"
            "end;\n",
            encoding="utf-8-sig",
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("带 BOM 的说明。", function["desc"])

    def test_output_must_not_overwrite_an_input_tsf(self):
        source = self.write_tsf(
            "KeepSource",
            """
            function KeepSource(): integer;
            begin
                /// 保留源文件。
                return 1;
            end;
            """,
        )
        original = source.read_text(encoding="utf-8")

        result = self.run_cli(source, output=source)

        self.assertEqual(1, result.returncode)
        self.assertIn("输出文件不能覆盖输入 tsf", result.stderr)
        self.assertEqual(original, source.read_text(encoding="utf-8"))

    def test_atomic_write_failure_preserves_existing_output_and_cleans_temp(self):
        module = load_script()
        self.assertTrue(
            hasattr(module, "atomic_write"), "atomic_write is missing"
        )
        self.output.write_text("原内容\n", encoding="utf-8")

        with mock.patch.object(
            module.os, "replace", side_effect=OSError("replace failed")
        ):
            with self.assertRaisesRegex(OSError, "replace failed"):
                module.atomic_write(self.output, "新内容\n")

        self.assertEqual(
            "原内容\n", self.output.read_text(encoding="utf-8")
        )
        self.assertEqual(
            [], list(self.root.glob(f".{self.output.name}.*.tmp"))
        )

    def test_missing_parameter_metadata_and_return_type_create_a_draft(self):
        source = self.write_tsf(
            "Draft",
            """
            function Draft(value);
            begin
                /// 尚待完善的函数。
                return value;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual(
            [{"name": "value", "type": "", "desc": ""}],
            function["params"],
        )
        self.assertEqual("", function["returns"])

    def test_legacy_document_block_creates_structural_draft(self):
        source = self.write_tsf(
            "LegacyDraft",
            """
            function LegacyDraft(required: integer; optional = 1): array;
            begin
                {**
                    @param(required)(integer) 必填值
                    @param(optional)(integer) 可选值
                    @return(array) 结果
                **}
                return array();
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            {
                "kind": "function",
                "name": "LegacyDraft",
                "signature": "LegacyDraft(required, optional)",
                "desc": "",
                "params": [
                    {"name": "required", "type": "integer", "desc": ""},
                    {
                        "name": "optional",
                        "type": "",
                        "optional": True,
                        "desc": "",
                    },
                ],
                "returns": "array",
            },
            self.read_declaration(),
        )

    def test_json_draft_completes_fixed_fields_for_every_declaration_kind(self):
        function_source = self.write_tsf(
            "DraftFunction",
            """
            function DraftFunction();
            begin
                /// 草稿函数
                return;
            end;
            """,
        )
        class_source = self.write_tsf(
            "DraftClass",
            """
            type DraftClass = class
                class function Build(); // 构建草稿
                property Title read title_;
                value_;
                const Limit = 1;
            end;
            """,
        )
        unit_source = self.write_tsf(
            "DraftUnit",
            """
            unit DraftUnit;
            interface
                function Open();
                var Current;
                const Limit = 1;
                type Item = class
                    function Read();
                end;
            implementation
            end.
            """,
        )

        result = self.run_cli(function_source, class_source, unit_source)

        self.assertEqual(0, result.returncode, result.stderr)
        function, cls, unit = self.read_declarations()
        self.assertEqual([], function.get("params"))
        self.assertEqual("", function.get("returns"))

        method, prop, field, constant = cls["members"]
        self.assertEqual([], method.get("params"))
        self.assertEqual("", method.get("returns"))
        self.assertEqual("", prop.get("type"))
        self.assertEqual([], prop.get("params"))
        self.assertEqual("", field.get("type"))
        self.assertEqual("", constant.get("type"))

        unit_function, variable, unit_constant, nested_class = unit["members"]
        self.assertEqual([], unit_function.get("params"))
        self.assertEqual("", unit_function.get("returns"))
        self.assertEqual("", variable.get("type"))
        self.assertEqual("", unit_constant.get("type"))
        self.assertEqual([], nested_class["members"][0].get("params"))
        self.assertEqual("", nested_class["members"][0].get("returns"))

        for item in (function, cls, unit, method, prop, field, constant):
            self.assertNotIn("tags", item)

    def test_documented_untyped_parameter_and_return_remain_empty_draft(self):
        source = self.write_tsf(
            "DocumentedDraft",
            """
            function DocumentedDraft(value);
            begin
                /// 保留没有类型注解的函数
                /// @param: value 输入值
                return value;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual(
            [{"name": "value", "type": "", "desc": "输入值"}],
            function["params"],
        )
        self.assertEqual("", function["returns"])

    def test_yaml_draft_uses_the_same_complete_fixed_fields(self):
        try:
            import yaml
        except ImportError:
            self.skipTest("未安装 pyyaml")
        source = self.write_tsf(
            "YamlDraft",
            """
            type YamlDraft = class
                class function Build(); // 构建草稿
                property Title read title_;
                const Limit = 1;
            end;
            """,
        )
        output = self.root / "draft.yaml"

        result = self.run_cli(source, fmt="yaml", output=output)

        self.assertEqual(0, result.returncode, result.stderr)
        method, prop, constant = yaml.safe_load(
            output.read_text(encoding="utf-8")
        )["declarations"][0]["members"]
        self.assertEqual([], method.get("params"))
        self.assertEqual("", method.get("returns"))
        self.assertEqual("", prop.get("type"))
        self.assertEqual([], prop.get("params"))
        self.assertEqual("", constant.get("type"))

    def test_help_is_written_in_chinese(self):
        result = subprocess.run(
            [sys.executable, str(SCRIPT), "--help"],
            capture_output=True,
            text=True,
            encoding="utf-8",
        )

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertIn("用法：", result.stdout)
        self.assertIn("位置参数：", result.stdout)
        self.assertIn("选项：", result.stdout)
        self.assertIn("输出格式", result.stdout)
        self.assertIn("function、class 和 unit", result.stdout)
        self.assertIn("declarations", result.stdout)
        self.assertNotIn("usage:", result.stdout)
        self.assertNotIn("positional arguments:", result.stdout)
        self.assertNotIn("options:", result.stdout)

    def test_return_type_mismatch_creates_structural_draft(self):
        source = self.write_tsf(
            "Mismatch",
            """
            function Mismatch(): integer;
            begin
                /// 返回一个值。
                /// @returns: string
                return 1;
            end;
            """,
        )
        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("", function["desc"])
        self.assertEqual("integer", function["returns"])

    def test_late_document_block_creates_structural_draft(self):
        source = self.write_tsf(
            "LateDoc",
            """
            function LateDoc(): integer;
            begin
                value := 1;
                /// 这个文档块出现得太晚。
                return value;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("", function["desc"])
        self.assertEqual("integer", function["returns"])

    def test_missing_body_does_not_borrow_later_function_document(self):
        source = self.write_tsf(
            "MissingOwnBody",
            "function MissingOwnBody(): integer;\n"
            "function Helper(): integer;\n"
            "begin\n"
            "    /// 辅助函数文档。\n"
            "    return 1;\n"
            "end;\n",
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("MissingOwnBody", function["name"])
        self.assertEqual("", function["desc"])

    def test_duplicate_enum_value_creates_structural_draft(self):
        source = self.write_tsf(
            "DuplicateValue",
            """
            function DuplicateValue(mode: integer): integer;
            begin
                /// 检查模式。
                /// @param: mode 模式
                /// @values: mode
                ///   1: 第一项
                ///   1: 重复项
                return mode;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("", function["desc"])
        self.assertEqual(
            [{"name": "mode", "type": "integer", "desc": ""}],
            function["params"],
        )
        self.assertEqual("integer", function["returns"])

    def test_directives_after_examples_create_structural_draft(self):
        source = self.write_tsf(
            "BadOrder",
            """
            function BadOrder(value: integer): integer;
            begin
                /// 返回输入值。
                /// @example: 基本用法
                ///   return BadOrder(1);
                /// @param: value 输入值
                return value;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        function = self.read_declaration()
        self.assertEqual("", function["desc"])
        self.assertEqual(
            [{"name": "value", "type": "integer", "desc": ""}],
            function["params"],
        )
        self.assertEqual("integer", function["returns"])

    def test_class_converts_only_first_matching_public_class(self):
        source = self.write_tsf(
            "Widget",
            """
            type Widget = class(BaseOne, BaseTwo)
            /// 对外组件。
            /// @tags: 组件 示例
            public
                /// 创建组件。
                /// @param: name 组件名称
                function create(name: string); overload;
                /// 从配置创建。
                /// @param: path 配置路径
                class function FromConfig(path: string): Widget;
                /// 组件标题。
                property Title: string read title_ write title_;
            protected
                /// 下一个编号。
                static next_id_: integer;
            private
                /// 内部状态。
                hidden_: string;
            end;

            type WidgetHelper = class
            public
                function Hidden(): integer;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            {
                "kind": "class",
                "name": "Widget",
                "desc": "对外组件。",
                "tags": ["组件", "示例"],
                "bases": ["BaseOne", "BaseTwo"],
                "members": [
                    {
                        "kind": "method",
                        "name": "create",
                        "visibility": "public",
                        "binding": "instance",
                        "signature": "create(name)",
                        "desc": "创建组件。",
                        "params": [
                            {
                                "name": "name",
                                "type": "string",
                                "desc": "组件名称",
                            }
                        ],
                        "returns": "",
                        "modifiers": ["overload"],
                    },
                    {
                        "kind": "method",
                        "name": "FromConfig",
                        "visibility": "public",
                        "binding": "class",
                        "signature": "FromConfig(path)",
                        "desc": "从配置创建。",
                        "params": [
                            {
                                "name": "path",
                                "type": "string",
                                "desc": "配置路径",
                            }
                        ],
                        "returns": "Widget",
                    },
                    {
                        "kind": "property",
                        "name": "Title",
                        "visibility": "public",
                        "desc": "组件标题。",
                        "type": "string",
                        "params": [],
                        "access": "readwrite",
                    },
                    {
                        "kind": "field",
                        "name": "next_id_",
                        "visibility": "protected",
                        "desc": "下一个编号。",
                        "type": "integer",
                        "static": True,
                    },
                ],
            },
            self.read_declaration(),
        )

    def test_class_out_of_class_implementation_does_not_duplicate_member(self):
        source = self.write_tsf(
            "Widget",
            """
            type Widget = class
            /// 对外组件。
            public
                /// 保存组件。
                /// @param: path 保存路径
                function Save(path: string): boolean;
            end;

            function Widget.Save(path: string): boolean;
            begin
                return true;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        members = self.read_declaration()["members"]
        self.assertEqual(["Save"], [member["name"] for member in members])

    def test_class_method_trailing_comment_fills_missing_description(self):
        source = self.write_tsf(
            "InlineComments",
            """
            type InlineComments = class
                class function Plain(value); // 普通行尾说明
                class function Overloaded(value); overload; // 重载行尾说明
                /// 正式文档说明
                class function Documented(value); // 不覆盖正式文档
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            ["普通行尾说明", "重载行尾说明", "正式文档说明"],
            [member["desc"] for member in self.read_declaration()["members"]],
        )

    def test_class_parameterized_property_preserves_params_and_values(self):
        source = self.write_tsf(
            "Indexed",
            """
            type Indexed = class
            /// 索引集合。
            public
                /// 按索引读取项目。
                /// @param: index 项目索引
                /// @values: index
                ///   0: 第一项
                property Items(index: integer): string read GetItem write SetItem;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        member = self.read_declaration()["members"][0]
        self.assertEqual("property", member["kind"])
        self.assertEqual("string", member["type"])
        self.assertEqual("readwrite", member["access"])
        self.assertEqual(
            [
                {
                    "name": "index",
                    "type": "integer",
                    "desc": "项目索引",
                    "values": [{"value": 0, "desc": "第一项"}],
                }
            ],
            member["params"],
        )

    def test_class_instance_and_static_constants_are_preserved(self):
        source = self.write_tsf(
            "Constants",
            """
            type Constants = class
            /// 常量集合。
            public
                /// 默认数量。
                const DefaultSize: integer = 0;
                /// 是否禁用。
                static const Disabled = false;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        members = self.read_declaration()["members"]
        self.assertEqual(["DefaultSize", "Disabled"], [item["name"] for item in members])
        self.assertEqual("integer", members[0]["type"])
        self.assertEqual("0", members[0]["value"])
        self.assertNotIn("static", members[0])
        self.assertEqual("false", members[1]["value"])
        self.assertIs(members[1]["static"], True)

    def test_class_virtual_and_override_modifiers_preserve_source_order(self):
        source = self.write_tsf(
            "Modifiers",
            """
            type Modifiers = class
            /// 修饰符示例。
            public
                /// 检查状态。
                function Inspect(): integer; virtual; overload;
                /// 重置状态。
                function Reset(); override;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        members = self.read_declaration()["members"]
        self.assertEqual(["virtual", "overload"], members[0]["modifiers"])
        self.assertEqual(["override"], members[1]["modifiers"])

    def test_class_inline_method_is_collected_once(self):
        source = self.write_tsf(
            "InlineClass",
            """
            type InlineClass = class
            /// 内联类。
            public
                /// 读取当前值。
                function Value(): integer;
                begin
                    return 1;
                end;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        members = self.read_declaration()["members"]
        self.assertEqual(["Value"], [member["name"] for member in members])

    def test_class_skips_unsupported_public_declaration(self):
        source = self.write_tsf(
            "UnsupportedClass",
            """
            type UnsupportedClass = class
            public
                type Alias = integer;
            end;
            """,
        )
        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual([], self.read_declaration()["members"])

    def test_missing_class_and_unit_member_types_remain_explicit_drafts(self):
        cls_source = self.write_tsf(
            "TypeDraft",
            """
            type TypeDraft = class
            /// 类型草稿。
            public
                /// 未知字段类型。
                value_;
                /// 未知属性类型。
                property Value read value_;
            end;
            """,
        )

        class_result = self.run_cli(cls_source)

        self.assertEqual(0, class_result.returncode, class_result.stderr)
        class_members = self.read_declaration()["members"]
        self.assertEqual("", class_members[0]["type"])
        self.assertEqual("", class_members[1]["type"])
        self.assertEqual([], class_members[1]["params"])

        unit_source = self.write_tsf(
            "UnitTypeDraft",
            """
            unit UnitTypeDraft;
            /// 类型草稿接口。
            interface
            /// 未知变量类型。
            var Current;
            implementation
            end.
            """,
        )

        unit_result = self.run_cli(unit_source)

        self.assertEqual(0, unit_result.returncode, unit_result.stderr)
        unit_member = self.read_declaration()["members"][0]
        self.assertEqual("", unit_member["type"])

    def test_unit_collects_only_interface_members_in_source_order(self):
        source = self.write_tsf(
            "DemoUnit",
            """
            unit DemoUnit;
            /// 运行时接口。
            /// @tags: 运行时 文档
            interface
            uses RuntimeSupport;
            /// 默认数量。
            const DefaultSize = 100;
            /// 当前名称。
            var CurrentName: string;
            /// 打开对象。
            /// @param: path 文件路径
            function Open(path: string): Document;
            type Document = class
            /// 文档对象。
            public
                /// 保存文档。
                function Save(): boolean;
            end;
            implementation
            /// 这个实现文档不属于 API。
            var hidden_: integer;
            type HiddenDocument = class
            public
                function Hidden(): integer;
            end;
            const HiddenValue = 1;
            function Open(path: string): Document;
            begin
                return nil;
            end;
            initialization
            /// 生命周期文档也不属于 API。
            hidden_ := 1;
            finalization
            hidden_ := 0;
            end.
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        unit = self.read_declaration()
        self.assertEqual("unit", unit["kind"])
        self.assertEqual("DemoUnit", unit["name"])
        self.assertEqual("运行时接口。", unit["desc"])
        self.assertEqual(["运行时", "文档"], unit["tags"])
        self.assertEqual(
            ["DefaultSize", "CurrentName", "Open", "Document"],
            [member["name"] for member in unit["members"]],
        )
        self.assertEqual(
            {
                "kind": "constant",
                "name": "DefaultSize",
                "desc": "默认数量。",
                "type": "",
                "value": "100",
            },
            unit["members"][0],
        )
        self.assertEqual(
            {
                "kind": "variable",
                "name": "CurrentName",
                "desc": "当前名称。",
                "type": "string",
            },
            unit["members"][1],
        )
        self.assertEqual("Document", unit["members"][2]["returns"])
        self.assertEqual("class", unit["members"][3]["kind"])
        self.assertEqual("Save", unit["members"][3]["members"][0]["name"])
        serialized = json.dumps(unit, ensure_ascii=False)
        self.assertNotIn("hidden_", serialized)
        self.assertNotIn("HiddenDocument", serialized)
        self.assertNotIn("HiddenValue", serialized)
        self.assertNotIn("生命周期文档", serialized)

    def test_incomplete_interface_class_does_not_scan_implementation(self):
        source = self.write_tsf(
            "IncompleteInterfaceClass",
            "unit IncompleteInterfaceClass;\n"
            "interface\n"
            "type PublicType = class\n"
            "public\n"
            "function Keep(): integer;\n"
            "implementation\n"
            "function Hidden(): integer;\n"
            "begin\n"
            "    return 1;\n"
            "end;\n"
            "end.\n",
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        unit = self.read_declaration()
        self.assertEqual(["PublicType"], [member["name"] for member in unit["members"]])
        self.assertEqual(
            ["Keep"],
            [member["name"] for member in unit["members"][0]["members"]],
        )

    def test_unit_filename_mismatch_keeps_declared_name(self):
        source = self.write_tsf(
            "Wrong",
            "unit Right;\ninterface\nimplementation\nend.\n",
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual("Right", self.read_declaration()["name"])

    def test_unit_preserves_multiple_interface_classes_in_source_order(self):
        source = self.write_tsf(
            "ClassUnit",
            """
            unit ClassUnit;
            /// 类接口。
            interface
            type First = class
            /// 第一个类。
            end;
            type Second = class
            /// 第二个类。
            end;
            implementation
            end.
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        members = self.read_declaration()["members"]
        self.assertEqual(["First", "Second"], [member["name"] for member in members])

    def test_unit_unbound_document_is_ignored(self):
        source = self.write_tsf(
            "UnboundUnit",
            """
            unit UnboundUnit;
            interface
            /// 无法绑定的接口文档。
            // 普通注释切断绑定。
            var Current: integer;
            implementation
            end.
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        current = self.read_declaration()["members"][0]
        self.assertEqual("Current", current["name"])
        self.assertEqual("", current["desc"])

    def test_unit_unbound_header_document_is_ignored(self):
        source = self.write_tsf(
            "UnboundHeaderUnit",
            """
            unit UnboundHeaderUnit;
            /// 无法绑定的 unit 文档。
            // 普通注释切断绑定。
            interface
            implementation
            end.
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual("", self.read_declaration()["desc"])

    def test_unit_ignores_tokens_after_terminal_end_dot(self):
        source = self.write_tsf(
            "TrailingUnit",
            """
            unit TrailingUnit;
            interface
            implementation
            end.
            function Trailing(): integer;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual("TrailingUnit", self.read_declaration()["name"])

    def test_class_missing_docs_default_visibility_and_private_filter_create_draft(self):
        source = self.write_tsf(
            "DraftClass",
            """
            type DraftClass = class
                value_: integer;
            private
                /// 不应进入草稿。
                hidden_: string;
            protected
                /// 检查状态。
                function Inspect();
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        cls = self.read_declaration()
        self.assertEqual("", cls["desc"])
        self.assertEqual(["value_", "Inspect"], [item["name"] for item in cls["members"]])
        self.assertEqual("public", cls["members"][0]["visibility"])
        self.assertEqual("", cls["members"][0]["desc"])
        self.assertEqual("protected", cls["members"][1]["visibility"])

    def test_class_private_multi_name_fields_and_constants_are_ignored(self):
        source = self.write_tsf(
            "PrivateMulti",
            """
            type PrivateMulti = class
            /// 私有声明示例。
            private
                /// 多名称私有字段。
                left_, right_: integer;
                /// 多名称私有常量。
                const First, Second = 1;
            public
                /// 对外值。
                value_: integer;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        members = self.read_declaration()["members"]
        self.assertEqual(["value_"], [member["name"] for member in members])

    def test_class_structure_errors_create_best_effort_drafts(self):
        cases = {
            "WrongName": (
                "type Actual = class\nend;\n",
                "Actual",
                [],
            ),
            "StaticMethod": (
                "type StaticMethod = class\npublic\nstatic function Bad();\nend;\n",
                "StaticMethod",
                ["Bad"],
            ),
            "ManyFields": (
                "type ManyFields = class\npublic\nleft_, right_: integer;\nend;\n",
                "ManyFields",
                ["left_", "right_"],
            ),
        }
        for name, (text, expected_name, expected_members) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)
                result = self.run_cli(source)
                self.assertEqual(0, result.returncode, result.stderr)
                declaration = self.read_declaration()
                self.assertEqual(expected_name, declaration["name"])
                self.assertEqual(
                    expected_members,
                    [member["name"] for member in declaration["members"]],
                )

    def test_class_and_unit_constants_split_multiple_names_into_drafts(self):
        cases = {
            "ManyConstants": (
                "type ManyConstants = class\npublic\nconst Left, Right = 1;\nend;\n",
                3,
            ),
            "ManyUnitConstants": (
                "unit ManyUnitConstants;\n"
                "interface\n"
                "const Left, Right = 1;\n"
                "implementation\n"
                "end.\n",
                3,
            ),
        }
        for name, (text, _line) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                self.assertEqual(
                    ["Left", "Right"],
                    [member["name"] for member in self.read_declaration()["members"]],
                )

    def test_class_converts_procedure_to_method_draft(self):
        source = self.write_tsf(
            "ProcedureClass",
            """
            type ProcedureClass = class
            public
                procedure Open();
            end;
            """,
        )
        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        method = self.read_declaration()["members"][0]
        self.assertEqual("Open", method["name"])
        self.assertEqual("method", method["kind"])
        self.assertEqual("", method["returns"])

    def test_class_unbound_document_is_ignored(self):
        source = self.write_tsf(
            "UnboundDoc",
            """
            type UnboundDoc = class
            /// 类描述。
            public
                /// 无法绑定的成员描述。
                // 普通注释切断绑定。
                value_: integer;
            end;
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        field = self.read_declaration()["members"][0]
        self.assertEqual("value_", field["name"])
        self.assertEqual("", field["desc"])

    def test_unit_converts_shorthand_and_skips_unsupported_types(self):
        cases = {
            "ShortUnit": (
                "unit ShortUnit;\nfunction Open(): integer;\nbegin return 1; end;\nend.\n",
                ["Open"],
            ),
            "ProcedureUnit": (
                "unit ProcedureUnit;\ninterface\nprocedure Open();\nimplementation\nend.\n",
                ["Open"],
            ),
            "AliasUnit": (
                "unit AliasUnit;\ninterface\ntype Size = integer;\nimplementation\nend.\n",
                [],
            ),
        }
        for name, (text, expected_members) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)
                result = self.run_cli(source)
                self.assertEqual(0, result.returncode, result.stderr)
                self.assertEqual(
                    expected_members,
                    [member["name"] for member in self.read_declaration()["members"]],
                )

    def test_malformed_class_and_unit_documents_create_structural_drafts(self):
        cls = self.write_tsf(
            "BadClassDocs",
            """
            type BadClassDocs = class
            public
                /// 打开值
                /// @param: value {string} 输入值
                function Open(value: integer): integer;
            end;
            """,
        )
        unit = self.write_tsf(
            "BadUnitDocs",
            """
            unit BadUnitDocs;
            interface
                /// 打开值
                /// @returns: string
                function Open(): integer;
            implementation
            end.
            """,
        )

        result = self.run_cli(cls, unit)

        self.assertEqual(0, result.returncode, result.stderr)
        class_method = self.read_declarations()[0]["members"][0]
        unit_function = self.read_declarations()[1]["members"][0]
        self.assertEqual("", class_method["desc"])
        self.assertEqual("integer", class_method["params"][0]["type"])
        self.assertEqual("", unit_function["desc"])
        self.assertEqual("integer", unit_function["returns"])

    def test_incomplete_class_and_unit_keep_recognizable_members(self):
        cases = {
            "IncompleteClass": (
                "type IncompleteClass = class\n"
                "public\n"
                "function Keep(value: integer): integer;\n",
                "Keep",
            ),
            "IncompleteUnit": (
                "unit IncompleteUnit;\n"
                "function Keep(value: integer): integer;\n",
                "Keep",
            ),
        }
        for name, (text, expected_member) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                self.assertEqual(
                    [expected_member],
                    [member["name"] for member in self.read_declaration()["members"]],
                )

    def test_broken_member_does_not_hide_following_function(self):
        cases = {
            "RecoverClass": (
                "type RecoverClass = class\n"
                "public\n"
                "property Broken\n"
                "function Keep(): integer;\n"
                "end;\n",
                "class",
            ),
            "RecoverUnit": (
                "unit RecoverUnit;\n"
                "interface\n"
                "const Broken\n"
                "function Keep(): integer;\n"
                "implementation\n"
                "end.\n",
                "unit",
            ),
        }
        for name, (text, expected_kind) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                declaration = self.read_declaration()
                self.assertEqual(expected_kind, declaration["kind"])
                self.assertIn(
                    "Keep",
                    [member["name"] for member in declaration["members"]],
                )

    def test_unnamed_class_and_unit_functions_are_skipped(self):
        cases = {
            "UnnamedClassFunction": (
                "type UnnamedClassFunction = class\n"
                "public\n"
                "function\n"
                "function Keep(): integer;\n"
                "end;\n",
                "class",
            ),
            "UnnamedUnitFunction": (
                "unit UnnamedUnitFunction;\n"
                "interface\n"
                "function\n"
                "function Keep(): integer;\n"
                "implementation\n"
                "end.\n",
                "unit",
            ),
        }
        for name, (text, expected_kind) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                declaration = self.read_declaration()
                self.assertEqual(expected_kind, declaration["kind"])
                self.assertEqual(
                    ["Keep"],
                    [member["name"] for member in declaration["members"]],
                )

    def test_unnamed_members_do_not_hide_following_functions(self):
        cases = {
            "UnnamedProperty": (
                "type UnnamedProperty = class\n"
                "public\n"
                "property\n"
                "function Keep(): integer;\n"
                "end;\n",
                "class",
            ),
            "UnnamedConstant": (
                "unit UnnamedConstant;\n"
                "interface\n"
                "const\n"
                "function Keep(): integer;\n"
                "implementation\n"
                "end.\n",
                "unit",
            ),
        }
        for name, (text, expected_kind) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                declaration = self.read_declaration()
                self.assertEqual(expected_kind, declaration["kind"])
                self.assertEqual(
                    ["Keep"],
                    [member["name"] for member in declaration["members"]],
                )

    def test_method_missing_signature_semicolon_does_not_hide_next_member(self):
        source = self.write_tsf(
            "BrokenInlineMethod",
            "type BrokenInlineMethod = class\n"
            "public\n"
            "function Broken()\n"
            "begin\n"
            "    return 1;\n"
            "end;\n"
            "function Keep(): integer;\n"
            "end;\n",
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            ["Broken", "Keep"],
            [member["name"] for member in self.read_declaration()["members"]],
        )

    def test_broken_headers_do_not_hide_first_function(self):
        cases = {
            "BrokenBase": (
                "type BrokenBase = class(Base\n"
                "function Keep(): integer;\n",
                "class",
            ),
            "MissingClassName": (
                "type\n"
                "class\n"
                "function Keep(): integer;\n",
                "class",
            ),
            "BrokenUnitHeader": (
                "unit BrokenUnitHeader\n"
                "function Keep(): integer;\n",
                "unit",
            ),
            "MissingUnitName": (
                "unit\n"
                "function Keep(): integer;\n",
                "unit",
            ),
        }
        for name, (text, expected_kind) in cases.items():
            with self.subTest(name=name):
                source = self.write_tsf(name, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                declaration = self.read_declaration()
                self.assertEqual(expected_kind, declaration["kind"])
                self.assertEqual(
                    ["Keep"],
                    [member["name"] for member in declaration["members"]],
                )

    def test_declaration_truncation_after_name_always_creates_a_draft(self):
        module = load_script()
        samples = {
            "function": (
                "TruncateFunction.tsf",
                "function TruncateFunction(required: integer; optional = 1): array;\n"
                "begin\n"
                "    return array();\n"
                "end;\n",
            ),
            "class": (
                "TruncateClass.tsf",
                "type TruncateClass = class(Base)\n"
                "public\n"
                "function Open(value: integer): string;\n"
                "property Value: integer read value_ write value_;\n"
                "const Limit = 2;\n"
                "value_: integer;\n"
                "end;\n",
            ),
            "unit": (
                "TruncateUnit.tsf",
                "unit TruncateUnit;\n"
                "interface\n"
                "function Open(value: integer): string;\n"
                "const Limit = 2;\n"
                "var Current: integer;\n"
                "implementation\n"
                "end.\n",
            ),
        }
        for expected_kind, (filename, source) in samples.items():
            name_end = module.tokenize(source)[1].end
            for cut in range(name_end, len(source) + 1):
                with self.subTest(kind=expected_kind, cut=cut):
                    declaration = module.complete_draft_fields(
                        module.convert_source(source[:cut], Path(filename))
                    )
                    self.assertEqual(expected_kind, declaration["kind"])
                    self.assertTrue(declaration["name"])

    def test_unit_fallback_keeps_bare_const_and_var_sections(self):
        source = self.write_tsf(
            "RecoverSections",
            """
            unit RecoverSections;
            interface
                /// 无法绑定
                // 切断文档绑定
                const First = 1;
                      Second = 2;
                var Current: integer;
                    Previous: integer;
            implementation
            end.
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            ["First", "Second", "Current", "Previous"],
            [member["name"] for member in self.read_declaration()["members"]],
        )

    def test_unit_var_and_const_sections_keep_each_declaration(self):
        source = self.write_tsf(
            "StateUnit",
            """
            unit StateUnit;
            /// 状态接口。
            interface
            var
                /// 当前名称。
                CurrentName: string;
                /// 当前编号。
                CurrentId: integer;
            const
                /// 默认名称。
                DefaultName = "demo";
                /// 默认编号。
                DefaultId = 1;
            implementation
            end.
            """,
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        members = self.read_declaration()["members"]
        self.assertEqual(
            ["CurrentName", "CurrentId", "DefaultName", "DefaultId"],
            [member["name"] for member in members],
        )
        self.assertEqual(
            ["当前名称。", "当前编号。", "默认名称。", "默认编号。"],
            [member["desc"] for member in members],
        )

    def test_function_overloads_are_preserved_in_input_order(self):
        first = self.write_nested_tsf(
            "first",
            "Parse",
            """
            function Parse(path: string): integer;
            begin
                /// 按路径解析。
                /// @param: path 文件路径
                return 1;
            end;
            """,
        )
        second = self.write_nested_tsf(
            "second",
            "Parse",
            """
            function Parse(mode: integer): integer;
            begin
                /// 按模式解析。
                /// @param: mode 解析模式
                return 1;
            end;
            """,
        )

        result = self.run_cli(first, second)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            ["Parse(path)", "Parse(mode)"],
            [item["signature"] for item in self.read_declarations()],
        )

    def test_duplicate_function_signature_is_preserved_in_input_order(self):
        first = self.write_nested_tsf(
            "first",
            "Parse",
            """
            function Parse(value: string): integer;
            begin
                /// 解析值。
                /// @param: value 输入值
                return 1;
            end;
            """,
        )
        second = self.write_nested_tsf(
            "second",
            "parse",
            """
            function parse(value: string): integer;
            begin
                /// 再次解析值。
                /// @param: value 输入值
                return 1;
            end;
            """,
        )
        result = self.run_cli(first, second)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            ["Parse(value)", "parse(value)"],
            [item["signature"] for item in self.read_declarations()],
        )

    def test_duplicate_class_and_unit_names_are_preserved_in_input_order(self):
        cases = (
            (
                self.write_nested_tsf(
                    "class-a", "Widget", "type Widget = class\nend;\n"
                ),
                self.write_nested_tsf(
                    "class-b", "widget", "type widget = class\nend;\n"
                ),
                "重复 class",
            ),
            (
                self.write_nested_tsf(
                    "unit-a",
                    "Runtime",
                    "unit Runtime;\ninterface\nimplementation\nend.\n",
                ),
                self.write_nested_tsf(
                    "unit-b",
                    "runtime",
                    "unit runtime;\ninterface\nimplementation\nend.\n",
                ),
                "重复 unit",
            ),
        )
        for first, second, kind in cases:
            with self.subTest(kind=kind):
                result = self.run_cli(first, second)

                self.assertEqual(0, result.returncode, result.stderr)
                self.assertEqual(
                    2,
                    len(self.read_declarations()),
                )

    def test_same_name_across_kinds_is_allowed(self):
        function = self.write_nested_tsf(
            "function",
            "Widget",
            """
            function Widget(): integer;
            begin
                /// 同名函数。
                return 1;
            end;
            """,
        )
        cls = self.write_nested_tsf(
            "class", "Widget", "type Widget = class\nend;\n"
        )

        result = self.run_cli(function, cls)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            ["function", "class"],
            [item["kind"] for item in self.read_declarations()],
        )

    def test_multiple_classes_and_units_are_allowed(self):
        inputs = (
            self.write_tsf("FirstClass", "type FirstClass = class\nend;\n"),
            self.write_tsf("SecondClass", "type SecondClass = class\nend;\n"),
            self.write_tsf(
                "FirstUnit",
                "unit FirstUnit;\ninterface\nimplementation\nend.\n",
            ),
            self.write_tsf(
                "SecondUnit",
                "unit SecondUnit;\ninterface\nimplementation\nend.\n",
            ),
        )

        result = self.run_cli(*inputs)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            ["FirstClass", "SecondClass", "FirstUnit", "SecondUnit"],
            [item["name"] for item in self.read_declarations()],
        )

    def test_top_level_procedure_creates_function_draft(self):
        source = self.write_tsf(
            "ProcedureDemo",
            "procedure ProcedureDemo(value: integer);\nbegin\nend;\n",
        )

        result = self.run_cli(source)

        self.assertEqual(0, result.returncode, result.stderr)
        self.assertEqual(
            {
                "kind": "function",
                "name": "ProcedureDemo",
                "signature": "ProcedureDemo(value)",
                "desc": "",
                "params": [
                    {"name": "value", "type": "integer", "desc": ""}
                ],
                "returns": "",
            },
            self.read_declaration(),
        )

    def test_malformed_top_level_function_keeps_recognizable_signature(self):
        cases = {
            "MissingBody": (
                "function MissingBody(value: integer): array;\n",
                "MissingBody",
                [{"name": "value", "type": "integer", "desc": ""}],
                "array",
            ),
            "BrokenParams": (
                "function BrokenParams(required: integer; optional = 1\n",
                "BrokenParams",
                [
                    {"name": "required", "type": "integer", "desc": ""},
                    {
                        "name": "optional",
                        "type": "",
                        "optional": True,
                        "desc": "",
                    },
                ],
                "",
            ),
            "MissingName": (
                "function\n",
                "MissingName",
                [],
                "",
            ),
        }
        for filename, (text, name, params, returns) in cases.items():
            with self.subTest(filename=filename):
                source = self.write_tsf(filename, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                declaration = self.read_declaration()
                self.assertEqual(name, declaration["name"])
                parameter_names = ", ".join(
                    parameter["name"] for parameter in params
                )
                self.assertEqual(
                    f"{name}({parameter_names})",
                    declaration["signature"],
                )
                self.assertEqual(params, declaration["params"])
                self.assertEqual(returns, declaration["returns"])

    def test_unrecognized_tsf_header_still_creates_function_draft(self):
        cases = {
            "LooseCall": (
                "LegacyName(first, second)\n",
                "LegacyName",
                ["first", "second"],
            ),
            "EmptySource": ("", "EmptySource", []),
        }
        for filename, (text, name, params) in cases.items():
            with self.subTest(filename=filename):
                source = self.write_tsf(filename, text)

                result = self.run_cli(source)

                self.assertEqual(0, result.returncode, result.stderr)
                declaration = self.read_declaration()
                self.assertEqual("function", declaration["kind"])
                self.assertEqual(name, declaration["name"])
                self.assertEqual(
                    params,
                    [parameter["name"] for parameter in declaration["params"]],
                )


if __name__ == "__main__":
    unittest.main()
