import t, c
from stdint import *
import stdio
import string
import stdlib
import memhub
import viperlib
import w32.fileio as fileio
import w32.win32file
import w32.win32base
import sys
import ast
import llvmlite
import subprocess
import argparse
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.Handles.HandlesImports as HandlesImports
import lib.core.BuildPipeline as BuildPipeline
import lib.core.StubMerger as StubMerger
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config

# 全局 mbuddy 指针
_mbuddy: memhub.MemManager | t.CPtr

# 源代码缓冲区大小（1MB）
SRC_BUF_SIZE: t.CDefine = 1048576

# 最大源文件数（递归扫描子目录后文件数增多，增大到 64）
MAX_SRC_FILES: t.CDefine = 64


@t.NoVTable
class SrcFileEntry:
    """源文件条目"""
    Path: str       # 完整路径
    Sha1: str       # SHA1 前16字符


# ============================================================
# _ScanDirForPyFiles - 递归扫描目录，收集 .py 文件到 entries
#
# 替代旧的非递归 FindFirstFileA(dir/*.py) 扫描，支持子目录
# （如 App/lib/core/Handles/）。遇到目录递归扫描，遇到 .py
# 文件读取内容、计算 SHA1、加入 entries。
#
# Args:
#   mb: 内存池
#   dir_path: 当前扫描目录
#   entries: SrcFileEntry 数组
#   entry_size: 单个条目大小（SrcFileEntry.__sizeof__()）
#   file_count: 当前已收集的文件数
#   max_files: 最大文件数
#
# Returns:
#   新的 file_count
# ============================================================
def _ScanDirForPyFiles(mb: memhub.MemBuddy | t.CPtr,
                       dir_path: str,
                       entries: SrcFileEntry | t.CPtr,
                       entry_size: t.CSizeT,
                       file_count: int, max_files: int) -> int:
    """递归扫描目录，收集 .py 文件到 entries，返回新的 file_count"""
    if dir_path is None or entries is None:
        return file_count
    if file_count >= max_files:
        return file_count

    dir_len: t.CSizeT = string.strlen(dir_path)
    pattern: bytes = stdlib.malloc(dir_len + 16)
    if pattern is None:
        return file_count
    viperlib.snprintf(pattern, dir_len + 16, "%s/*", dir_path)

    find_data_size: t.CSizeT = w32.win32file.WIN32_FIND_DATAA.__sizeof__()
    find_data: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(find_data_size + 16)
    if find_data is None:
        stdlib.free(pattern)
        return file_count
    string.memset(find_data, 0, find_data_size + 16)

    handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data)
    if handle == w32.win32base.INVALID_HANDLE_VALUE:
        stdlib.free(pattern)
        stdlib.free(find_data)
        return file_count

    while True:
        fname: str = find_data.cFileName
        if fname is not None:
            fname_len: t.CSizeT = string.strlen(fname)
            if fname_len > 0:
                # 跳过 . 和 ..
                is_dot: int = 0
                if fname_len == 1 and fname[0] == '.':
                    is_dot = 1
                elif fname_len == 2 and fname[0] == '.' and fname[1] == '.':
                    is_dot = 1
                if is_dot == 0:
                    # 检查是否是目录
                    attrs: ULONG = find_data.dwFileAttributes
                    is_dir: int = 0
                    if (attrs & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
                        is_dir = 1
                    if is_dir != 0:
                        # 递归扫描子目录
                        sub_dir: bytes = stdlib.malloc(dir_len + fname_len + 2)
                        if sub_dir is not None:
                            viperlib.snprintf(sub_dir, dir_len + fname_len + 2, "%s/%s", dir_path, fname)
                            file_count = _ScanDirForPyFiles(mb, sub_dir, entries, entry_size, file_count, max_files)
                            stdlib.free(sub_dir)
                    else:
                        # 检查是否是 .py 文件
                        if fname_len > 3:
                            if fname[fname_len - 3] == '.' and fname[fname_len - 2] == 'p' and fname[fname_len - 1] == 'y':
                                if file_count < max_files:
                                    full_path: bytes = stdlib.malloc(dir_len + fname_len + 2)
                                    if full_path is not None:
                                        viperlib.snprintf(full_path, dir_len + fname_len + 2, "%s/%s", dir_path, fname)
                                        sf: fileio.File | t.CPtr = fileio.File(full_path, fileio.MODE.R)
                                        if not sf.closed:
                                            sbuf: bytes = stdlib.malloc(SRC_BUF_SIZE)
                                            if sbuf is not None:
                                                br: LONG = sf.read_all(sbuf, SRC_BUF_SIZE)
                                                sf.close()
                                                if br > 0:
                                                    if br < SRC_BUF_SIZE:
                                                        sbuf[br] = 0
                                                    else:
                                                        sbuf[SRC_BUF_SIZE - 1] = 0
                                                    sha1_val: str = Utils.compute_sha1(mb, sbuf)
                                                    if sha1_val is not None:
                                                        ea: t.CUInt64T = t.CUInt64T(entries) + file_count * entry_size
                                                        ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
                                                        if ent is not None:
                                                            ent.Path = full_path
                                                            ent.Sha1 = sha1_val
                                                            file_count += 1
                                                            stdio.printf("[project] %s (sha1=%s)\n", fname, sha1_val)
                                                stdlib.free(sbuf)
                                        # full_path 不释放：ent.Path 引用它

        if w32.win32file.FindNextFileA(handle, find_data) == 0:
            break

    w32.win32file.FindClose(handle)
    stdlib.free(pattern)
    stdlib.free(find_data)
    return file_count


def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr,
                        source_dir: str, temp_dir: str, output_dir: str,
                        cc_cmd: str, cc_flags: str,
                        linker_cmd: str, linker_flags: str, linker_output: str,
                        includes_binary_dir: str,
                        includes_dir: str,
                        do_phase1: int, do_phase2: int,
                        log: VLogger.Logger | t.CPtr,
                        args: argparse.ParsedArgs | t.CPtr) -> int:
    """多文件项目编译

    Returns: 0 成功，非 0 失败
    """
    if source_dir is None:
        stdio.printf("[project] source_dir 为空\n")
        return 1

    stdio.printf("[project] 多文件项目编译: %s\n", source_dir)

    # === 1. 递归扫描 source_dir 下的 .py 文件（包括子目录）===
    entry_size: t.CSizeT = SrcFileEntry.__sizeof__()
    entries: SrcFileEntry | t.CPtr = stdlib.malloc(MAX_SRC_FILES * entry_size)
    if entries is None:
        return 1
    string.memset(entries, 0, MAX_SRC_FILES * entry_size)

    file_count: int = _ScanDirForPyFiles(mb, source_dir, entries, entry_size, 0, MAX_SRC_FILES)
    stdio.printf("[project] 共 %d 个源文件\n", file_count)

    if file_count == 0:
        stdlib.free(entries)
        return 1

    # 初始化 AST 表
    ast._init_tables(mb)

    # 确保 temp/output 目录存在
    BuildPipeline.ensure_dir(temp_dir)
    BuildPipeline.ensure_dir(output_dir)

    # 追加 App 源文件 SHA1 到 _sha1_map.txt（供日志转存和符号查找使用）
    # 格式: {sha1}:App/{filename}\n （与 includes 条目格式对应）
    td_len_am: t.CSizeT = string.strlen(temp_dir)
    map_path_am: bytes = stdlib.malloc(td_len_am + 32)
    if map_path_am is not None:
        viperlib.snprintf(map_path_am, td_len_am + 32, "%s/_sha1_map.txt", temp_dir)
        mf: fileio.File | t.CPtr = fileio.File(map_path_am, fileio.MODE.A)
        if not mf.closed:
            line_am: bytes = stdlib.malloc(512)
            if line_am is not None:
                for i in range(file_count):
                    ea_am: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
                    ent_am: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_am, t.CPtr))
                    if ent_am is None or ent_am.Path is None or ent_am.Sha1 is None:
                        continue
                    # 从路径提取文件名（最后一个 / 或 \\ 之后的部分）
                    p_str: str = ent_am.Path
                    p_len: t.CSizeT = string.strlen(p_str)
                    f_start: t.CSizeT = 0
                    for j in range(p_len):
                        if p_str[j] == '/' or p_str[j] == '\\':
                            f_start = j + 1
                    viperlib.snprintf(line_am, 512, "%s:App/%s\n", ent_am.Sha1, p_str + f_start)
                    ll_am: t.CSizeT = string.strlen(line_am)
                    mf.write(line_am, ll_am)
                stdlib.free(line_am)
            mf.close()
            stdio.printf("[project] 已追加 %d 个 App 文件到 _sha1_map.txt\n", file_count)
        stdlib.free(map_path_am)

    # 构建模块 SHA1 映射（供跨模块函数调用名混淆使用）
    td_len_pb: t.CSizeT = string.strlen(temp_dir)
    pb_sha1_arr: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
    pb_mod_arr: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
    pb_inc_count: int = 0
    if pb_sha1_arr is not None and pb_mod_arr is not None:
        pb_inc_count = StubMerger._BuildIncludesSha1Map(temp_dir, td_len_pb, pb_sha1_arr, pb_mod_arr)
    # 追加用户源文件的 (模块名, SHA1) 到全局映射
    # 使跨模块方法调用能通过 from_imports + _lookup_module_sha1 找到正确的 SHA1
    if pb_sha1_arr is not None and pb_mod_arr is not None:
        for i in range(file_count):
            ea_us: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
            ent_us: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_us, t.CPtr))
            if ent_us is None or ent_us.Path is None or ent_us.Sha1 is None:
                continue
            if pb_inc_count >= StubMerger.MAX_INCLUDES:
                break
            # 从路径提取模块名（文件名去掉 .py 后缀）
            path_str: str = ent_us.Path
            path_len: t.CSizeT = string.strlen(path_str)
            fname_start: t.CSizeT = 0
            for j in range(path_len):
                if path_str[j] == '/' or path_str[j] == '\\':
                    fname_start = j + 1
            mod_len: t.CSizeT = path_len - fname_start
            if mod_len < 4:
                continue
            mod_len -= 3
            if mod_len >= 64:
                mod_len = 63
            # 写入模块名
            mod_idx: t.CSizeT = t.CSizeT(pb_inc_count) * 64
            for k in range(mod_len):
                pb_mod_arr[mod_idx + k] = path_str[fname_start + k]
            pb_mod_arr[mod_idx + mod_len] = '\0'
            # 写入 SHA1
            sha1_idx: t.CSizeT = t.CSizeT(pb_inc_count) * 17
            string.strcpy(pb_sha1_arr + sha1_idx, ent_us.Sha1)
            pb_inc_count += 1
        HandlesExprCall.set_module_sha1_map(pb_sha1_arr, pb_mod_arr, pb_inc_count)


    # === 2. Phase A: 为每个文件生成 stub + text ===
    if do_phase1 != 0:
        if log is not None:
            log.banner("Phase A: 生成 stub + text")

        PHASE_A_IR_SIZE: t.CSizeT = 262144
        td_len_pa: t.CSizeT = string.strlen(temp_dir)

        for i in range(file_count):
            ea: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
            ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
            if ent is None or ent.Path is None:
                continue

            # 计算源文件的包名（相对 source_dir 的目录部分）
            src_dir_len_pa: t.CSizeT = string.strlen(source_dir)
            pkg_pa: str = None
            if string.strlen(ent.Path) > src_dir_len_pa + 1:
                rel_path_pa: str = ent.Path + src_dir_len_pa + 1
                pkg_pa = HandlesImports.compute_package_from_relpath(mb, rel_path_pa)
            tr_a: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, ent.Path, ent.Sha1, pkg_pa)
            if tr_a is None:
                continue

            # dump stub IR (declarations only)
            stub_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE)
            if stub_buf_a is None:
                continue
            tr_a.dump_ir(stub_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_STUB)
            stub_len_a: t.CSizeT = string.strlen(stub_buf_a)

            # save stub.ll
            stub_path_a: bytes = stdlib.malloc(td_len_pa + 32)
            if stub_path_a is not None:
                viperlib.snprintf(stub_path_a, td_len_pa + 32, "%s/%s.stub.ll", temp_dir, ent.Sha1)
                sf_a: fileio.File | t.CPtr = fileio.File(stub_path_a, fileio.MODE.W)
                if not sf_a.closed:
                    sf_a.write(stub_buf_a, stub_len_a)
                    sf_a.close()
                stdlib.free(stub_path_a)
            stdlib.free(stub_buf_a)

            # dump text IR (definitions only)
            text_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE)
            if text_buf_a is None:
                continue
            tr_a.dump_ir(text_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_TEXT)
            text_len_a: t.CSizeT = string.strlen(text_buf_a)

            # save text.ll
            text_path_a: bytes = stdlib.malloc(td_len_pa + 32)
            if text_path_a is not None:
                viperlib.snprintf(text_path_a, td_len_pa + 32, "%s/%s.text.ll", temp_dir, ent.Sha1)
                tf_a: fileio.File | t.CPtr = fileio.File(text_path_a, fileio.MODE.W)
                if not tf_a.closed:
                    tf_a.write(text_buf_a, text_len_a)
                    tf_a.close()
                stdlib.free(text_path_a)
            stdlib.free(text_buf_a)

            # save dependencies (_imported_modules) for Phase B
            deps_path_a: bytes = stdlib.malloc(td_len_pa + 32)
            if deps_path_a is not None:
                viperlib.snprintf(deps_path_a, td_len_pa + 32, "%s/%s.deps.txt", temp_dir, ent.Sha1)
                df_a: fileio.File | t.CPtr = fileio.File(deps_path_a, fileio.MODE.W)
                if not df_a.closed:
                    if tr_a._imported_modules is not None:
                        dl_a: t.CSizeT = string.strlen(tr_a._imported_modules)
                        df_a.write(tr_a._imported_modules, dl_a)
                    df_a.close()
                stdlib.free(deps_path_a)

        if do_phase2 == 0:
            stdio.printf("[project] Phase A 完成（仅 stub 生成）\n")
            stdlib.free(entries)
            return 0

    # === 3. Phase B: 编译每个文件为 .obj ===
    if do_phase2 != 0:
        if log is not None:
            log.banner("Phase B: 编译 .obj")

        # 收集 .obj 路径
        OBJ_PATHS_SIZE: t.CSizeT = 8192
        obj_paths: bytes = stdlib.malloc(OBJ_PATHS_SIZE)
        if obj_paths is None:
            return 1
        obj_paths[0] = '\0'
        obj_pos: t.CSizeT = 0
        # main_obj_path: 存放定义了用户 main 的 .obj 路径（链接时放在最前面，
        # 避免 --allow-multiple-definition 选择了其他模块的 wrapper main）
        main_obj_path: bytes = stdlib.malloc(512)
        if main_obj_path is None:
            return 1
        main_obj_path[0] = '\0'
        compiled_count: int = 0

        # 组合 IR 缓冲区大小（4MB，足够容纳本地 stub + 所有依赖 stub + 本地 text）
        COMBINED_IR_SIZE: t.CSizeT = 4194304

        for i in range(file_count):
            ea: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
            ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
            if ent is None or ent.Path is None or ent.Sha1 is None:
                continue

            stdio.printf("[Phase B] %s\n", ent.Path)

            # 组合本地 stub + 所有依赖 stub + 本地 text → 完整 IR
            stdio.printf("[Phase B] malloc %d bytes...\n", COMBINED_IR_SIZE)
            combined_ir: bytes = stdlib.malloc(COMBINED_IR_SIZE)
            if combined_ir is None:
                stdio.printf("[Phase B] combined_ir 分配失败: %s\n", ent.Path)
                continue
            stdio.printf("[Phase B] malloc OK, calling BuildCombinedIR...\n")
            combined_len: t.CSizeT = StubMerger.BuildCombinedIR(temp_dir, ent.Sha1, combined_ir, COMBINED_IR_SIZE)
            if combined_len == 0:
                stdio.printf("[Phase B] BuildCombinedIR 失败: %s\n", ent.Path)
                stdlib.free(combined_ir)
                continue

            # 编译为 .obj
            cret: int = BuildPipeline.compile_module_to_obj(
                combined_ir, combined_len, temp_dir, output_dir, ent.Sha1,
                cc_cmd, cc_flags)
            stdlib.free(combined_ir)
            if cret != 0:
                stdio.printf("[FATAL][Phase B] llc 编译失败，终止编译: %s\n", ent.Path)
                sys.exit(1)

            compiled_count += 1

            # 构造 .obj 路径，检测是否是 main 模块（test_main.py 或 main.py）
            od_len: t.CSizeT = string.strlen(output_dir)
            sha1_len: t.CSizeT = string.strlen(ent.Sha1)
            need: t.CSizeT = od_len + 1 + sha1_len + 6
            is_main_mod: int = 0
            if string.strstr(ent.Path, "test_main.py") is not None:
                is_main_mod = 1
            elif string.strstr(ent.Path, "main.py") is not None:
                is_main_mod = 1

            if is_main_mod != 0:
                viperlib.snprintf(main_obj_path, 512, "%s/%s.obj", output_dir, ent.Sha1)
            else:
                if obj_pos + need < OBJ_PATHS_SIZE:
                    if obj_pos > 0:
                        obj_paths[obj_pos] = ' '
                        obj_pos += 1
                    viperlib.snprintf(obj_paths + obj_pos, need, "%s/%s.obj", output_dir, ent.Sha1)
                    obj_pos += od_len + 1 + sha1_len + 4
                    obj_paths[obj_pos] = '\0'
                else:
                    stdio.printf("[Phase B] 警告: .obj 路径缓冲区不足\n")

        stdio.printf("[Phase B] 编译完成: %d/%d\n", compiled_count, file_count)

        if compiled_count == 0:
            stdio.printf("[Phase B] 无成功编译的文件\n")
            stdlib.free(entries)
            return 1

        # === 3.5 编译缺失的 includes 文件 ===
        # includes.binary 可能缺少某些 includes .obj（如 testcheck.py），
        # 这些文件被用户项目导入但未被 TransPyV 自身依赖，Projectrans.py 未编译它们。
        # 检测并编译缺失的 includes 文件到 output_dir，加入链接命令。
        if includes_dir is not None and includes_binary_dir is not None:
            inc_compiled: int = 0
            td_len_mi: t.CSizeT = string.strlen(temp_dir)
            map_path_mi: bytes = stdlib.malloc(td_len_mi + 32)
            if map_path_mi is not None:
                viperlib.snprintf(map_path_mi, td_len_mi + 32, "%s/_sha1_map.txt", temp_dir)
                mapf_mi: fileio.File | t.CPtr = fileio.File(map_path_mi, fileio.MODE.R)
                if not mapf_mi.closed:
                    map_buf_mi: bytes = stdlib.malloc(65536)
                    if map_buf_mi is not None:
                        map_br_mi: t.CInt64T = mapf_mi.read_all(map_buf_mi, 65536)
                        mapf_mi.close()
                        if map_br_mi > 0:
                            if map_br_mi < 65536:
                                map_buf_mi[map_br_mi] = '\0'
                            else:
                                map_buf_mi[65535] = '\0'
                            map_len_mi: t.CSizeT = map_br_mi
                            mpos_mi: t.CSizeT = 0
                            while mpos_mi < map_len_mi:
                                ml_start_mi: t.CSizeT = mpos_mi
                                while mpos_mi < map_len_mi:
                                    if map_buf_mi[mpos_mi] == '\n':
                                        break
                                    mpos_mi += 1
                                ml_len_mi: t.CSizeT = mpos_mi - ml_start_mi
                                mpos_mi += 1

                                # 最小长度: 16(sha1)+1(:)+9(includes/)+1+1+1 = 29
                                if ml_len_mi < 26:
                                    continue
                                if map_buf_mi[ml_start_mi + 16] != ':':
                                    continue
                                rp_start_mi: t.CSizeT = ml_start_mi + 17
                                if string.strncmp(map_buf_mi + rp_start_mi, "includes/", 9) != 0:
                                    continue

                                # 提取 SHA1
                                inc_sha1_mi: str = stdlib.malloc(17)
                                if inc_sha1_mi is None:
                                    continue
                                string.strncpy(inc_sha1_mi, map_buf_mi + ml_start_mi, 16)
                                inc_sha1_mi[16] = '\0'

                                # 检查 .obj 是否已存在于 includes.binary
                                ibd_len_mi: t.CSizeT = string.strlen(includes_binary_dir)
                                check_pat_mi: bytes = stdlib.malloc(ibd_len_mi + 35)
                                if check_pat_mi is None:
                                    continue
                                viperlib.snprintf(check_pat_mi, ibd_len_mi + 35, "%s/%s*.obj", includes_binary_dir, inc_sha1_mi)
                                check_fd_mi: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__())
                                obj_exists_mi: int = 0
                                if check_fd_mi is not None:
                                    string.memset(check_fd_mi, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__())
                                    check_h_mi: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(check_pat_mi, check_fd_mi)
                                    if check_h_mi != w32.win32base.INVALID_HANDLE_VALUE:
                                        w32.win32file.FindClose(check_h_mi)
                                        obj_exists_mi = 1
                                if obj_exists_mi != 0:
                                    continue

                                # .obj 不存在，需要编译
                                # 构造源文件路径: {includes_dir}/{rel_path_without_includes_prefix}
                                rel_path_len_mi: t.CSizeT = ml_len_mi - 26
                                inc_dir_len_mi: t.CSizeT = string.strlen(includes_dir)
                                src_fp_mi: bytes = stdlib.malloc(inc_dir_len_mi + 1 + rel_path_len_mi + 1)
                                if src_fp_mi is None:
                                    continue
                                viperlib.snprintf(src_fp_mi, inc_dir_len_mi + 1 + rel_path_len_mi + 1,
                                                  "%s/%s", includes_dir, map_buf_mi + rp_start_mi + 9)

                                # 检查是否为声明文件（只有 declare 没有实质 define）
                                # 判断方法: text.ll 中若有混淆函数 define（含 @\"）则为实现文件
                                is_decl_mi: int = -1
                                tpath_mi: bytes = stdlib.malloc(td_len_mi + 32)
                                if tpath_mi is not None:
                                    viperlib.snprintf(tpath_mi, td_len_mi + 32, "%s/%s.text.ll", temp_dir, inc_sha1_mi)
                                    tf_mi: fileio.File | t.CPtr = fileio.File(tpath_mi, fileio.MODE.R)
                                    if not tf_mi.closed:
                                        is_decl_mi = 1
                                        tbuf_mi: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
                                        if tbuf_mi is not None:
                                            tbr_mi: t.CInt64T = tf_mi.read_all(tbuf_mi, StubMerger.STUB_READ_BUF_SIZE)
                                            if tbr_mi > 0:
                                                if tbr_mi < StubMerger.STUB_READ_BUF_SIZE:
                                                    tbuf_mi[tbr_mi] = '\0'
                                                else:
                                                    tbuf_mi[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0'
                                                # 逐行扫描: 找 define 行中含 @\" 的（混淆函数名）
                                                tpos_mi: t.CSizeT = 0
                                                while tpos_mi < tbr_mi:
                                                    tls_mi: t.CSizeT = tpos_mi
                                                    while tpos_mi < tbr_mi:
                                                        if tbuf_mi[tpos_mi] == '\n':
                                                            break
                                                        tpos_mi += 1
                                                    tll_mi: t.CSizeT = tpos_mi - tls_mi
                                                    if tpos_mi < tbr_mi:
                                                        tpos_mi += 1
                                                    if tll_mi >= 7 and string.strncmp(tbuf_mi + tls_mi, "define ", 7) == 0:
                                                        # 临时在行尾加 \0 供 strstr 使用
                                                        saved_mi: t.CChar = tbuf_mi[tls_mi + tll_mi]
                                                        tbuf_mi[tls_mi + tll_mi] = '\0'
                                                        if string.strstr(tbuf_mi + tls_mi, "@\"") is not None:
                                                            tbuf_mi[tls_mi + tll_mi] = saved_mi
                                                            is_decl_mi = 0
                                                            break
                                                        tbuf_mi[tls_mi + tll_mi] = saved_mi
                                            stdlib.free(tbuf_mi)
                                        tf_mi.close()
                                stdlib.free(tpath_mi)
                                if is_decl_mi == 1:
                                    stdio.printf("[Phase B+] 跳过(声明文件): %s (sha1=%s)\n", src_fp_mi, inc_sha1_mi)
                                    continue

                                stdio.printf("[Phase B+] 编译缺失 includes: %s (sha1=%s)\n", src_fp_mi, inc_sha1_mi)

                                # 尝试 BuildCombinedIR（stub/text 应已由 Phase1 生成）
                                inc_combined_mi: bytes = stdlib.malloc(COMBINED_IR_SIZE)
                                inc_combined_len_mi: t.CSizeT = 0
                                if inc_combined_mi is not None:
                                    inc_combined_len_mi = StubMerger.BuildCombinedIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE)

                                # 如果 stub/text 不存在，翻译源文件并保存 stub + text，然后重试
                                if inc_combined_len_mi == 0 and inc_combined_mi is not None:
                                    stdio.printf("[Phase B+] stub/text 不存在，翻译: %s\n", src_fp_mi)
                                    # 计算 includes 文件的包名（相对 includes_dir 的目录部分）
                                    inc_pkg_mi: str = None
                                    if string.strlen(src_fp_mi) > inc_dir_len_mi + 1:
                                        inc_rel_mi: str = src_fp_mi + inc_dir_len_mi + 1
                                        inc_pkg_mi = HandlesImports.compute_package_from_relpath(mb, inc_rel_mi)
                                    tr_mi: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, src_fp_mi, inc_sha1_mi, inc_pkg_mi)
                                    if tr_mi is not None:
                                        PBP_IR_SIZE: t.CSizeT = 262144
                                        # 保存 stub.ll
                                        inc_stub_buf: bytes = stdlib.malloc(PBP_IR_SIZE)
                                        if inc_stub_buf is not None:
                                            tr_mi.dump_ir(inc_stub_buf, PBP_IR_SIZE, llvmlite.OUTPUT_STUB)
                                            inc_stub_len: t.CSizeT = string.strlen(inc_stub_buf)
                                            inc_stub_path: bytes = stdlib.malloc(td_len_mi + 32)
                                            if inc_stub_path is not None:
                                                viperlib.snprintf(inc_stub_path, td_len_mi + 32, "%s/%s.stub.ll", temp_dir, inc_sha1_mi)
                                                isf: fileio.File | t.CPtr = fileio.File(inc_stub_path, fileio.MODE.W)
                                                if not isf.closed:
                                                    isf.write(inc_stub_buf, inc_stub_len)
                                                    isf.close()
                                                stdlib.free(inc_stub_path)
                                            stdlib.free(inc_stub_buf)
                                        # 保存 text.ll
                                        inc_text_buf: bytes = stdlib.malloc(PBP_IR_SIZE)
                                        if inc_text_buf is not None:
                                            tr_mi.dump_ir(inc_text_buf, PBP_IR_SIZE, llvmlite.OUTPUT_TEXT)
                                            inc_text_len: t.CSizeT = string.strlen(inc_text_buf)
                                            inc_text_path: bytes = stdlib.malloc(td_len_mi + 32)
                                            if inc_text_path is not None:
                                                viperlib.snprintf(inc_text_path, td_len_mi + 32, "%s/%s.text.ll", temp_dir, inc_sha1_mi)
                                                itf: fileio.File | t.CPtr = fileio.File(inc_text_path, fileio.MODE.W)
                                                if not itf.closed:
                                                    itf.write(inc_text_buf, inc_text_len)
                                                    itf.close()
                                                stdlib.free(inc_text_path)
                                            stdlib.free(inc_text_buf)
                                        # 重试 BuildCombinedIR
                                        inc_combined_len_mi = StubMerger.BuildCombinedIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE)

                                if inc_combined_len_mi == 0:
                                    stdio.printf("[Phase B+] BuildCombinedIR 失败: %s\n", src_fp_mi)
                                    if inc_combined_mi is not None:
                                        stdlib.free(inc_combined_mi)
                                    continue

                                # 编译为 .obj
                                inc_cret_mi: int = BuildPipeline.compile_module_to_obj(
                                    inc_combined_mi, inc_combined_len_mi, temp_dir, output_dir, inc_sha1_mi,
                                    cc_cmd, cc_flags)
                                stdlib.free(inc_combined_mi)
                                if inc_cret_mi != 0:
                                    stdio.printf("[FATAL][Phase B+] llc 编译失败，终止编译: %s\n", src_fp_mi)
                                    sys.exit(1)

                                inc_compiled += 1

                                # 添加到 obj_paths
                                od_len_mi: t.CSizeT = string.strlen(output_dir)
                                sha1_len_mi: t.CSizeT = 16
                                need_mi: t.CSizeT = od_len_mi + 1 + sha1_len_mi + 6
                                if obj_pos + need_mi < OBJ_PATHS_SIZE:
                                    if obj_pos > 0:
                                        obj_paths[obj_pos] = ' '
                                        obj_pos += 1
                                    viperlib.snprintf(obj_paths + obj_pos, need_mi, "%s/%s.obj", output_dir, inc_sha1_mi)
                                    obj_pos += od_len_mi + 1 + sha1_len_mi + 4
                                    obj_paths[obj_pos] = '\0'

                            stdio.printf("[Phase B+] 编译缺失 includes: %d 个\n", inc_compiled)

        # === 4. Phase C: 链接所有 .obj → .exe ===
        if log is not None:
            log.banner("Phase C: 链接")

        od_len2: t.CSizeT = string.strlen(output_dir)
        lo_len: t.CSizeT = string.strlen(linker_output)
        exe_path: bytes = stdlib.malloc(od_len2 + lo_len + 2)
        if exe_path is not None:
            viperlib.snprintf(exe_path, od_len2 + lo_len + 2, "%s/%s", output_dir, linker_output)
        else:
            exe_path = linker_output

        # 构造最终 .obj 路径列表：main_obj_path 在前，其他 .obj 在后
        final_obj_paths: bytes = stdlib.malloc(OBJ_PATHS_SIZE + 512)
        if final_obj_paths is None:
            stdlib.free(entries)
            return 1
        final_obj_paths[0] = '\0'
        fop_pos: t.CSizeT = 0
        # main_obj_path 放在最前面（确保 --allow-multiple-definition 选择用户 main）
        if main_obj_path[0] != '\0':
            mlen: t.CSizeT = string.strlen(main_obj_path)
            string.strcpy(final_obj_paths, main_obj_path)
            fop_pos = mlen
            if obj_paths[0] != '\0':
                final_obj_paths[fop_pos] = ' '
                fop_pos += 1
        # 追加其他 .obj
        if obj_paths[0] != '\0':
            string.strcpy(final_obj_paths + fop_pos, obj_paths)
            fop_pos += string.strlen(obj_paths)
        final_obj_paths[fop_pos] = '\0'

        obj_paths_len: t.CSizeT = fop_pos
        lret: int = BuildPipeline.link_objs_to_exe(
            final_obj_paths, obj_paths_len,
            linker_cmd, linker_flags, exe_path,
            includes_binary_dir)

        if lret == 0:
            stdio.printf("输出: %s\n", exe_path)
            if args.get_bool("run"):
                stdio.printf("[run] 执行: %s\n", exe_path)
                rp: subprocess.CompletedProcess | t.CPtr = subprocess.run(exe_path, False, False)
                if rp is not None:
                    stdio.printf("[run] 退出码: %d\n", rp.returncode)
        else:
            stdio.printf("[Phase C] 链接失败\n")
            stdlib.free(entries)
            return 1

    stdlib.free(entries)
    return 0
