param(
    [string[]]$Files = @(
        "source/icd_mapper.cpp",
        "source/interface.cpp",
        "source/main.cpp",
        "source/mq.cpp"
    )
)

$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $PSScriptRoot
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)

function Split-Words([string]$name) {
    $text = $name -replace '^~', ''
    $text = $text -creplace '([a-z0-9])([A-Z])', '$1 $2'
    return ($text -replace '_', ' ').ToLowerInvariant()
}

function Brief-For([string]$name, [string]$fileName) {
    $display = '“' + $name + '”'
    if ($name -match '^(is|has|can|should|looks|declares|matches|contains|equals|same)') {
        return "判断 $display 所表达的条件；该函数只读取输入，不负责修改业务状态。"
    }
    if ($name -match '^(read|load)') { return "读取或加载 $display 对应的数据，并转换为本模块后续逻辑使用的内存结构。" }
    if ($name -match '^(write|save|export)') { return "把 $display 对应的内存数据序列化并写入目标位置。" }
    if ($name -match '^(parse|decode)') { return "解析 $display 对应的文本或结构，提取经过校验的业务字段。" }
    if ($name -match '^(find|search|lookup)') { return "在现有数据结构中查找 $display 对应的对象或位置。" }
    if ($name -match '^(collect|gather|scan)') { return "遍历输入结构并收集 $display 所需的候选数据。" }
    if ($name -match '^(build|make|create|new)') { return "构造 $display 对应的对象、消息、窗口控件或输出结构。" }
    if ($name -match '^(update|refresh|populate|rebuild|reset)') { return "根据当前模型状态刷新 $display 对应的缓存或界面。" }
    if ($name -match '^(run|handle|on|wndProc)') { return "处理 $display 对应的业务入口或事件，并调度下游函数完成实际工作。" }
    if ($name -match '^(add|append|insert|ensure)') { return "向目标结构追加或保证存在 $display 所描述的数据。" }
    if ($name -match '^(trim|lower|normalize|sanitize|safe|clean|collapse)') { return "规范化 $display 对应的字符串或标识，减少格式差异对匹配造成的影响。" }
    if ($name -match '^(send|fetch|download)') { return "执行 $display 对应的外部通信，并把结果交给日志、事件或落盘流程。" }
    if ($name -match '^(compare|match)') { return "比较或匹配 $display 涉及的数据，返回匹配结果或构造匹配信息。" }
    if ($name -match '^(generate|reverse)') { return "根据当前配置和编辑状态生成 $display 对应的输出内容。" }
    if ($name -match '^(set|apply|commit|sync)') { return "把 $display 对应的变更应用到模型、控件或输出文档。" }
    if ($name -eq 'main') { return "命令行入口；无参数时转入 Win32 GUI，有参数时执行兼容的批处理解析流程。" }
    if ($fileName -eq 'interface.cpp') { return "实现界面层的 $display 功能，并在 Win32 控件与 AppState 业务模型之间传递数据。" }
    if ($fileName -eq 'mq.cpp') { return "实现外部通信层的 $display 功能，服务于 HTTP、RocketMQ 或运行时文件管理。" }
    if ($fileName -eq 'main.cpp') { return "实现解析/报表层的 $display 功能，参与 XML、JSON、origin 与工作簿之间的数据转换。" }
    return "实现 $display 对应的模块内功能。"
}

function Detail-For([string]$fileName) {
    switch ($fileName) {
        'interface.cpp' { return '界面使用原生 Win32 API；函数通常读取或更新 AppState，并通过控件 ID、消息通知和 ListView/TreeView API 驱动交互。除 MQ 事件投递外，界面控件操作应在 GUI 线程执行。' }
        'mq.cpp' { return '本文件组合使用 WinHTTP、Windows 动态库 API、RocketMQ C 接口、nlohmann::json 与 std::filesystem；异常会转换为日志、错误字符串或离线 outbox。共享 MQ 会话状态由互斥锁保护。' }
        'main.cpp' { return '本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出；调用方应关注输入格式与路径有效性。' }
        'icd_mapper.cpp' { return '本文件使用 tinyxml2 遍历 ICD/Excel XML，并使用 Windows 代码页 API 兼容 GBK/GB2312；文件操作采用 std::filesystem 与二进制流，失败时抛出异常交给公共入口处理。' }
        default { return '函数属于工具内部实现，输入输出与副作用以参数和返回值为边界。' }
    }
}

function Param-Description([string]$name) {
    if ($name -match '^(app|state)$') { return '应用状态；包含窗口句柄、当前选择、预览模型和运行期标志。' }
    if ($name -match 'cfg|config') { return '运行配置；提供接口地址、MQ 参数、路径和当前数据模式。' }
    if ($name -match '(path|file|dir|root|output|workbook|xmlPath|jsonPath|originPath)$') { return '文件或目录路径；函数按 UTF-8/Windows 路径规则读取或写入。' }
    if ($name -match '^(doc|document)$') { return 'tinyxml2 文档对象；节点的生命周期由该文档管理。' }
    if ($name -match '(row|cell|node|element|item|sheet)$') { return '当前处理的数据行、单元格、XML 节点或模型项。' }
    if ($name -match '^(text|raw|body|value|name|key|tag|topic|label|title|search)$') { return '待解析、比较、显示或发送的 UTF-8 文本。' }
    if ($name -match '^(hwnd|parent|ctl|control|list|tree)$') { return 'Win32 窗口或控件句柄；必须在有效窗口生命周期内使用。' }
    if ($name -match '(index|idx|count|size|start|end|offset|col|rowIndex|id)$') { return '索引、数量、范围或偏移参数；具体边界由函数体校验。' }
    if ($name -match '^(log|event|callback|fn)$') { return '回调函数；用于向界面或日志系统报告进度和结果。' }
    if ($name -match '^(out|result|message|err|error)$') { return '输出参数；函数在成功或失败时写入结果或诊断信息。' }
    return '调用方传入的业务参数；其用途与约束由类型、名称及函数体共同定义。'
}

function Return-Description([string]$prefix, [string]$name) {
    if ($name -eq 'main' -or $prefix -match '(^|\s)int\s*$') { return '整数状态、索引或计数；入口函数以 0 表示成功。' }
    if ($prefix -match '(^|\s)bool\s*$') { return '条件成立或操作成功时为 true，否则为 false。' }
    if ($prefix -match '(^|\s)void\s*$' -or $name.StartsWith('~')) { return $null }
    return '构造、解析或查找到的结果；所有权与生命周期遵循返回类型。'
}

function Split-Parameters([string]$header) {
    $open = $header.IndexOf('(')
    if ($open -lt 0) { return @() }
    $depth = 0
    $close = -1
    for ($i = $open; $i -lt $header.Length; $i++) {
        if ($header[$i] -eq '(') { $depth++ }
        elseif ($header[$i] -eq ')') {
            $depth--
            if ($depth -eq 0) { $close = $i; break }
        }
    }
    if ($close -lt 0) { return @() }
    $body = $header.Substring($open + 1, $close - $open - 1).Trim()
    if ([string]::IsNullOrWhiteSpace($body) -or $body -eq 'void') { return @() }

    $parts = New-Object System.Collections.Generic.List[string]
    $start = 0; $angle = 0; $paren = 0; $bracket = 0; $brace = 0
    for ($i = 0; $i -lt $body.Length; $i++) {
        switch ($body[$i]) {
            '<' { $angle++ }
            '>' { if ($angle -gt 0) { $angle-- } }
            '(' { $paren++ }
            ')' { if ($paren -gt 0) { $paren-- } }
            '[' { $bracket++ }
            ']' { if ($bracket -gt 0) { $bracket-- } }
            '{' { $brace++ }
            '}' { if ($brace -gt 0) { $brace-- } }
            ',' {
                if ($angle -eq 0 -and $paren -eq 0 -and $bracket -eq 0 -and $brace -eq 0) {
                    $parts.Add($body.Substring($start, $i - $start).Trim())
                    $start = $i + 1
                }
            }
        }
    }
    $parts.Add($body.Substring($start).Trim())
    return $parts
}

function Parameter-Name([string]$declaration, [int]$position) {
    $decl = ($declaration -replace '=.*$', '').Trim()
    if ($decl -match '\(\s*\*\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)') { return $Matches[1] }
    $matches = [regex]::Matches($decl, '[A-Za-z_][A-Za-z0-9_]*')
    if ($matches.Count -eq 0) { return "arg$position" }
    $candidate = $matches[$matches.Count - 1].Value
    if ($candidate -in @('const','unsigned','signed','long','short','int','char','bool','double','float','void')) { return "arg$position" }
    return $candidate
}

function Find-Functions([string[]]$lines) {
    $found = New-Object System.Collections.Generic.List[object]
    for ($i = 0; $i -lt $lines.Count; $i++) {
        $first = $lines[$i].Trim()
        if ([string]::IsNullOrWhiteSpace($first) -or $first.StartsWith('#') -or
            $first.StartsWith('//') -or $first.StartsWith('/*') -or $first.StartsWith('*')) { continue }
        if ($first -match '^(if|for|while|switch|catch|return|else|do|try)\b') { continue }
        if ($first.StartsWith('(') -or $first.StartsWith('!') -or $first.StartsWith(':')) { continue }
        # A definition must begin on this line. Requiring '(' here prevents a closing brace
        # or an ordinary statement from borrowing the next function's signature.
        if (-not $first.Contains('(')) { continue }

        $headerLines = New-Object System.Collections.Generic.List[string]
        $hasBrace = $false
        for ($j = $i; $j -lt [Math]::Min($lines.Count, $i + 16); $j++) {
            $part = $lines[$j].Trim()
            $headerLines.Add($part)
            if ($part.Contains(';') -and -not $part.Contains('{')) { break }
            if ($part.Contains('{')) { $hasBrace = $true; break }
        }
        if (-not $hasBrace) { continue }
        $header = ($headerLines -join ' ')
        $beforeBrace = $header.Substring(0, $header.IndexOf('{')).Trim()
        if (-not $beforeBrace.Contains('(') -or -not $beforeBrace.Contains(')')) { continue }
        if ($beforeBrace -match '\[.*\]\s*\(' -or $beforeBrace -match '\b(auto|const auto|auto&)\s+\w+\s*=') { continue }
        if ($beforeBrace -match '^\s*(if|for|while|switch|catch)\s*\(') { continue }
        if ($beforeBrace.Contains('(*')) { continue }

        $open = $beforeBrace.IndexOf('(')
        $left = $beforeBrace.Substring(0, $open).Trim()
        $nameMatch = [regex]::Match($left, '([A-Za-z_~][A-Za-z0-9_:~]*)\s*$')
        if (-not $nameMatch.Success) { continue }
        $fullName = $nameMatch.Groups[1].Value
        $name = ($fullName -split '::')[-1]
        if ($name -in @('if','for','while','switch','catch')) { continue }
        $prefix = $left.Substring(0, $nameMatch.Index).Trim()
        if ($prefix.Contains('=')) { continue }
        if ($prefix -notmatch '[A-Za-z_]') { continue }
        # Ordinary calls such as ListView_SetItemText(...) also have no return-type prefix.
        # Constructors have no return-type prefix and resemble ordinary calls, so this
        # conservative scanner leaves them to a short manual review after generation.
        if ([string]::IsNullOrWhiteSpace($prefix) -and -not $name.StartsWith('~')) { continue }
        if ($prefix.EndsWith('.') -or $prefix.EndsWith('->')) { continue }
        $found.Add([pscustomobject]@{ Index=$i; Header=$beforeBrace; Name=$name; Prefix=$prefix })
    }
    return $found
}

foreach ($relative in $Files) {
    $path = Join-Path $root $relative
    if (-not (Test-Path -LiteralPath $path)) { throw "Missing source file: $path" }
    $text = [IO.File]::ReadAllText($path, [Text.Encoding]::UTF8)
    $newline = if ($text.Contains("`r`n")) { "`r`n" } else { "`n" }
    $lines = [regex]::Split($text, "\r?\n")
    $functions = @(Find-Functions $lines)
    $byIndex = @{}
    foreach ($fn in $functions) { $byIndex[$fn.Index] = $fn }
    $out = New-Object System.Collections.Generic.List[string]
    $fileName = [IO.Path]::GetFileName($path)
    $inserted = 0
    for ($i = 0; $i -lt $lines.Count; $i++) {
        if ($byIndex.ContainsKey($i)) {
            $fn = $byIndex[$i]
            $already = $false
            # A function with many parameters can have a fairly long generated Doxygen
            # block. Search far enough back that a second run remains idempotent.
            for ($k = [Math]::Max(0, $i - 64); $k -lt $i; $k++) {
                if ($lines[$k] -like "*Function: $($fn.Name)*") { $already = $true; break }
            }
            if (-not $already) {
                $indent = ([regex]::Match($lines[$i], '^\s*')).Value
                $out.Add($indent + '/**')
                $out.Add($indent + " * Function: $($fn.Name)")
                $out.Add($indent + ' *')
                $out.Add($indent + ' * @brief ' + (Brief-For $fn.Name $fileName))
                $out.Add($indent + ' * @details ' + (Detail-For $fileName))
                $position = 1
                foreach ($param in @(Split-Parameters $fn.Header)) {
                    $paramName = Parameter-Name $param $position
                    $out.Add($indent + " * @param $paramName " + (Param-Description $paramName))
                    $position++
                }
                $returnText = Return-Description $fn.Prefix $fn.Name
                if ($null -ne $returnText) { $out.Add($indent + ' * @return ' + $returnText) }
                $out.Add($indent + ' */')
                $inserted++
            }
        }
        $out.Add($lines[$i])
    }
    [IO.File]::WriteAllText($path, ($out -join $newline), $utf8NoBom)
    Write-Host "[OK] ${relative}: detected=$($functions.Count), inserted=$inserted"
}
