#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
最终 SDK 包 demo 工程添加器

用途：
1. 将最终导出的 SDK 文件夹加入 demo 工程
2. 自动创建 SDK 分组结构
3. 自动补充 Frameworks / Resources / Sources 构建引用
4. 自动补充搜索路径

说明：
- framework / bundle 按整体添加
- 建议配合 custom_pbxproj_clean.py 先清理旧引用再重新添加
"""

import os
import re
import shutil
import sys
import uuid


class FinalSDKAdder:
    def __init__(self, pbxproj_path: str):
        self.pbxproj_path = pbxproj_path
        if not os.path.exists(pbxproj_path):
            raise FileNotFoundError(f"项目文件不存在: {pbxproj_path}")

    def add_sdk_folder(self, sdk_folder_path: str, group_name: str = "SDK"):
        print(f"📁 开始添加 SDK 文件夹: {sdk_folder_path}")
        print(f"📂 目标组名: {group_name}")

        if not os.path.exists(sdk_folder_path):
            print(f"❌ SDK 文件夹不存在: {sdk_folder_path}")
            return False

        backup_path = f"{self.pbxproj_path}.backup_final_sdk_add"
        shutil.copy2(self.pbxproj_path, backup_path)
        print(f"✅ 已创建备份: {backup_path}")

        try:
            with open(self.pbxproj_path, "r", encoding="utf-8") as f:
                content = f.read()

            sdk_items = self._scan_sdk_folder(sdk_folder_path)
            print(f"📊 扫描到 {len(sdk_items)} 个项目")

            content = self._add_file_references(content, sdk_items)
            content = self._create_group_structure(content, sdk_items, sdk_folder_path, group_name)
            content = self._add_build_files(content, sdk_items)
            content = self._add_to_build_phases(content, sdk_items)
            content = self._add_search_paths(content, sdk_items, sdk_folder_path)

            if not self._validate(content):
                raise RuntimeError("添加后的 pbxproj 结构校验失败")

            with open(self.pbxproj_path, "w", encoding="utf-8") as f:
                f.write(content)

            print("✅ SDK 文件夹添加完成")
            return True
        except Exception as e:
            print(f"❌ 添加失败: {e}")
            shutil.copy2(backup_path, self.pbxproj_path)
            print("✅ 已恢复备份文件")
            return False

    def _scan_sdk_folder(self, sdk_folder_path: str):
        print("🔍 扫描 SDK 文件夹...")
        sdk_items = []
        processed_paths = set()

        for root, dirs, files in os.walk(sdk_folder_path):
            relative_root = os.path.relpath(root, sdk_folder_path)
            if relative_root == ".":
                relative_root = ""

            if root.endswith(".framework"):
                if root not in processed_paths:
                    sdk_items.append(
                        {
                            "type": "framework",
                            "name": os.path.basename(root),
                            "path": root,
                            "relative_path": os.path.relpath(root, sdk_folder_path),
                            "parent_dir": os.path.dirname(relative_root) if relative_root else "",
                        }
                    )
                    processed_paths.add(root)
                    print(f"  📦 {os.path.relpath(root, sdk_folder_path)} (framework)")
                for sub_root, _, _ in os.walk(root):
                    processed_paths.add(sub_root)
                continue

            if root.endswith(".bundle"):
                if root not in processed_paths:
                    sdk_items.append(
                        {
                            "type": "bundle",
                            "name": os.path.basename(root),
                            "path": root,
                            "relative_path": os.path.relpath(root, sdk_folder_path),
                            "parent_dir": os.path.dirname(relative_root) if relative_root else "",
                        }
                    )
                    processed_paths.add(root)
                    print(f"  📦 {os.path.relpath(root, sdk_folder_path)} (bundle)")
                for sub_root, _, _ in os.walk(root):
                    processed_paths.add(sub_root)
                continue

            if root in processed_paths:
                continue

            for file_name in files:
                file_path = os.path.join(root, file_name)
                if any(file_path.startswith(done) for done in processed_paths):
                    continue
                if self._should_skip_file(file_name):
                    continue

                file_type = self._get_file_type(file_name)
                relative_path = os.path.join(relative_root, file_name) if relative_root else file_name
                sdk_items.append(
                    {
                        "type": file_type,
                        "name": file_name,
                        "path": file_path,
                        "relative_path": relative_path,
                        "parent_dir": relative_root,
                        "extension": os.path.splitext(file_name)[1].lower(),
                    }
                )
                print(f"  📄 {relative_path} ({file_type})")

        return sdk_items

    def _should_skip_file(self, file_name: str):
        if file_name.startswith("."):
            return True
        if file_name in ["CodeResources", "CodeDirectory", "CodeRequirements", "CodeRequirements-1", "CodeSignature"]:
            return True
        return False

    def _get_file_type(self, file_name: str):
        ext = os.path.splitext(file_name)[1].lower()
        if ext == ".a":
            return "library"
        if ext == ".framework":
            return "framework"
        if ext == ".bundle":
            return "bundle"
        if ext == ".h":
            return "header"
        if ext in [".m", ".mm", ".c", ".cpp", ".swift"]:
            return "source"
        return "resource"

    def _generate_uuid(self):
        return str(uuid.uuid4()).replace("-", "").upper()[:24]

    def _pbx_value(self, value: str):
        if re.fullmatch(r"[A-Za-z0-9_./$()+\-]+", value):
            return value
        escaped = value.replace('"', '\\"')
        return f'"{escaped}"'

    def _validate(self, content: str):
        print("🔍 校验 pbxproj 结构...")
        required_sections = [
            "/* Begin PBXFileReference section */",
            "/* End PBXFileReference section */",
            "/* Begin PBXGroup section */",
            "/* End PBXGroup section */",
            "/* Begin XCBuildConfiguration section */",
            "/* End XCBuildConfiguration section */",
        ]
        for section in required_sections:
            if section not in content:
                print(f"❌ 缺少必要节: {section}")
                return False

        if content.count("{") != content.count("}"):
            print("❌ 花括号数量不匹配")
            return False

        if not re.search(r"rootObject = [A-F0-9]{24}", content):
            print("❌ 缺少 rootObject")
            return False

        print("✅ pbxproj 结构校验通过")
        return True

    def _add_file_references(self, content: str, sdk_items):
        print("📄 添加文件引用...")
        file_ref_end = content.find("/* End PBXFileReference section */")
        if file_ref_end == -1:
            raise RuntimeError("找不到 PBXFileReference section")

        new_file_refs = []
        for item in sdk_items:
            file_id = self._generate_uuid()
            item["file_id"] = file_id
            file_name = item["name"]
            pbx_path = self._pbx_value(file_name)

            if item["type"] == "framework":
                line = f'\t\t{file_id} /* {file_name} */ = {{isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = {pbx_path}; sourceTree = "<group>"; }};'
            elif item["type"] == "bundle":
                line = f'\t\t{file_id} /* {file_name} */ = {{isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = {pbx_path}; sourceTree = "<group>"; }};'
            elif item["type"] == "library":
                line = f'\t\t{file_id} /* {file_name} */ = {{isa = PBXFileReference; lastKnownFileType = archive.ar; path = {pbx_path}; sourceTree = "<group>"; }};'
            elif item["type"] == "header":
                line = f'\t\t{file_id} /* {file_name} */ = {{isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = {pbx_path}; sourceTree = "<group>"; }};'
            elif item["type"] == "source":
                if file_name.endswith(".m"):
                    file_type = "sourcecode.c.objc"
                elif file_name.endswith(".mm"):
                    file_type = "sourcecode.cpp.objcpp"
                elif file_name.endswith(".swift"):
                    file_type = "sourcecode.swift"
                elif file_name.endswith(".cpp"):
                    file_type = "sourcecode.cpp.cpp"
                else:
                    file_type = "sourcecode.c.c"
                line = f'\t\t{file_id} /* {file_name} */ = {{isa = PBXFileReference; lastKnownFileType = {file_type}; path = {pbx_path}; sourceTree = "<group>"; }};'
            else:
                if file_name.endswith(".plist"):
                    file_type = "text.plist.xml"
                elif file_name.endswith(".xcprivacy"):
                    file_type = "text.xml"
                else:
                    file_type = "file"
                line = f'\t\t{file_id} /* {file_name} */ = {{isa = PBXFileReference; lastKnownFileType = {file_type}; path = {pbx_path}; sourceTree = "<group>"; }};'

            new_file_refs.append(line)
            print(f"  - 文件引用: {file_name}")

        insert_content = "\n" + "\n".join(new_file_refs) + "\n"
        return content[:file_ref_end] + insert_content + content[file_ref_end:]

    def _create_group_structure(self, content: str, sdk_items, sdk_folder_path: str, group_name: str):
        print("📂 创建组结构...")
        group_end = content.find("/* End PBXGroup section */")
        if group_end == -1:
            raise RuntimeError("找不到 PBXGroup section")

        sdk_folder_name = os.path.basename(os.path.normpath(sdk_folder_path))
        groups = {
            "root": {
                "id": self._generate_uuid(),
                "name": group_name,
                "path": sdk_folder_name,
                "children": [],
                "files": [],
            }
        }

        for item in sdk_items:
            parent_dir = item["parent_dir"]
            if parent_dir:
                current_path = ""
                for part in parent_dir.split(os.sep):
                    current_path = os.path.join(current_path, part) if current_path else part
                    if current_path not in groups:
                        groups[current_path] = {
                            "id": self._generate_uuid(),
                            "name": part,
                            "path": part,
                            "children": [],
                            "files": [],
                        }

        for item in sdk_items:
            parent_dir = item["parent_dir"]
            key = parent_dir if parent_dir else "root"
            groups[key]["files"].append(item)

        for path, info in list(groups.items()):
            if path == "root":
                continue
            parent = os.path.dirname(path)
            parent_key = parent if parent else "root"
            groups[parent_key]["children"].append(info["id"])

        blocks = []
        for key, info in groups.items():
            children_lines = []
            for child_id in info["children"]:
                child_name = self._group_name_by_id(groups, child_id)
                children_lines.append(f"\t\t\t\t{child_id} /* {child_name} */, ".rstrip())
            for item in info["files"]:
                children_lines.append(f"\t\t\t\t{item['file_id']} /* {item['name']} */, ".rstrip())
            children_content = "\n".join(children_lines)

            path_value = self._pbx_value(info["path"])
            if key == "root" and info["name"] != info["path"]:
                block = (
                    f"\t\t{info['id']} /* {info['name']} */ = {{\n"
                    f"\t\t\tisa = PBXGroup;\n"
                    f"\t\t\tchildren = (\n{children_content}\n\t\t\t);\n"
                    f"\t\t\tname = {self._pbx_value(info['name'])};\n"
                    f"\t\t\tpath = {path_value};\n"
                    f"\t\t\tsourceTree = \"<group>\";\n"
                    f"\t\t}};"
                )
            else:
                block = (
                    f"\t\t{info['id']} /* {info['name']} */ = {{\n"
                    f"\t\t\tisa = PBXGroup;\n"
                    f"\t\t\tchildren = (\n{children_content}\n\t\t\t);\n"
                    f"\t\t\tpath = {path_value};\n"
                    f"\t\t\tsourceTree = \"<group>\";\n"
                    f"\t\t}};"
                )
            blocks.append(block)
            print(f"  - 组: {info['name']}")

        content = content[:group_end] + "\n" + "\n".join(blocks) + "\n" + content[group_end:]
        content = self._add_to_main_group(content, groups["root"]["id"], group_name)
        return content

    def _group_name_by_id(self, groups, group_id: str):
        for info in groups.values():
            if info["id"] == group_id:
                return info["name"]
        return "Unknown"

    def _add_to_main_group(self, content: str, sdk_group_id: str, group_name: str):
        print("📌 将 SDK 组挂到 mainGroup...")
        project_match = re.search(r"mainGroup = ([A-F0-9]{24})(?: /\* .*? \*/)?;", content)
        if not project_match:
            raise RuntimeError("找不到 mainGroup")

        main_group_id = project_match.group(1)
        group_pattern = re.compile(
            rf"({main_group_id}(?: /\* .*? \*/)? = \{{\s*isa = PBXGroup;\s*children = \()(.*?)(\)\s*;\s*(?:name = .*?;\s*)?(?:path = .*?;\s*)?sourceTree = \"<group>\";\s*\}};)",
            re.DOTALL,
        )
        match = group_pattern.search(content)
        if not match:
            raise RuntimeError("找不到 mainGroup 对应的 PBXGroup 定义")

        existing_children = match.group(2).rstrip()
        new_line = f"\n\t\t\t\t{sdk_group_id} /* {group_name} */,"
        new_children = existing_children + new_line if existing_children else new_line
        new_block = match.group(1) + new_children + "\n\t\t\t" + match.group(3)
        return content[: match.start()] + new_block + content[match.end() :]

    def _add_build_files(self, content: str, sdk_items):
        print("🔧 添加构建文件...")
        build_file_end = content.find("/* End PBXBuildFile section */")
        if build_file_end == -1:
            raise RuntimeError("找不到 PBXBuildFile section")

        lines = []
        for item in sdk_items:
            build_file_id = self._generate_uuid()
            item["build_file_id"] = build_file_id
            file_name = item["name"]
            file_id = item["file_id"]
            if item["type"] in ["framework", "library"]:
                line = f"\t\t{build_file_id} /* {file_name} in Frameworks */ = {{isa = PBXBuildFile; fileRef = {file_id} /* {file_name} */; }};"
            elif item["type"] in ["bundle", "resource"]:
                line = f"\t\t{build_file_id} /* {file_name} in Resources */ = {{isa = PBXBuildFile; fileRef = {file_id} /* {file_name} */; }};"
            elif item["type"] == "source":
                line = f"\t\t{build_file_id} /* {file_name} in Sources */ = {{isa = PBXBuildFile; fileRef = {file_id} /* {file_name} */; }};"
            else:
                continue
            lines.append(line)
            print(f"  - 构建文件: {file_name}")

        return content[:build_file_end] + "\n" + "\n".join(lines) + "\n" + content[build_file_end:]

    def _add_to_build_phases(self, content: str, sdk_items):
        content = self._append_to_phase(content, sdk_items, "PBXFrameworksBuildPhase", ["framework", "library"], "Frameworks")
        content = self._append_to_phase(content, sdk_items, "PBXResourcesBuildPhase", ["bundle", "resource"], "Resources")
        content = self._append_to_phase(content, sdk_items, "PBXSourcesBuildPhase", ["source"], "Sources")
        return content

    def _append_to_phase(self, content: str, sdk_items, isa_name: str, item_types, phase_name: str):
        selected = [item for item in sdk_items if item["type"] in item_types and item.get("build_file_id")]
        if not selected:
            return content

        print(f"⚙️ 添加到 {phase_name} 构建阶段...")
        phase_pattern = re.compile(
            rf"([A-F0-9]{{24}} /\* {phase_name} \*/ = \{{\s*isa = {isa_name};\s*buildActionMask = .*?;\s*files = \()(.*?)(\)\s*;\s*runOnlyForDeploymentPostprocessing = 0;\s*\}};)",
            re.DOTALL,
        )
        match = phase_pattern.search(content)
        if not match:
            print(f"  - 未找到 {phase_name} 阶段，跳过")
            return content

        existing = match.group(2).rstrip()
        lines = [f"\t\t\t\t{item['build_file_id']} /* {item['name']} in {phase_name} */," for item in selected]
        appended = existing + ("\n" if existing else "") + "\n".join(lines)
        new_block = match.group(1) + appended + "\n\t\t\t" + match.group(3)
        return content[: match.start()] + new_block + content[match.end() :]

    def _add_search_paths(self, content: str, sdk_items, sdk_folder_path: str):
        print("🔍 添加搜索路径...")
        sdk_folder_name = os.path.basename(os.path.normpath(sdk_folder_path))
        framework_paths = {f'"$(PROJECT_DIR)/{sdk_folder_name}"'}
        library_paths = {f'"$(PROJECT_DIR)/{sdk_folder_name}"'}
        header_paths = {f'"$(PROJECT_DIR)/{sdk_folder_name}"'}

        for item in sdk_items:
            if not item["parent_dir"]:
                continue
            sub_path = f'"$(PROJECT_DIR)/{sdk_folder_name}/{item["parent_dir"].replace(os.sep, "/")}"'
            if item["type"] == "framework":
                framework_paths.add(sub_path)
            elif item["type"] == "library":
                library_paths.add(sub_path)
            elif item["type"] == "header":
                header_paths.add(sub_path)

        content = self._merge_search_paths(content, "FRAMEWORK_SEARCH_PATHS", framework_paths)
        content = self._merge_search_paths(content, "LIBRARY_SEARCH_PATHS", library_paths)
        content = self._merge_search_paths(content, "HEADER_SEARCH_PATHS", header_paths)
        return content

    def _merge_search_paths(self, content: str, key: str, paths):
        if not paths:
            return content

        print(f"  - 处理 {key}...")
        config_pattern = re.compile(
            r"([A-F0-9]{24} /\* (Debug|Release) \*/ = \{\s*isa = XCBuildConfiguration;\s*buildSettings = \{)(.*?)(\s*\};\s*name = (Debug|Release);\s*\};)",
            re.DOTALL,
        )

        for match in list(config_pattern.finditer(content)):
            prefix = match.group(1)
            body = match.group(3)
            suffix = match.group(4)
            config_name = match.group(2)

            key_pattern = re.compile(rf"({key} = \()(.*?)(\);)", re.DOTALL)
            key_match = key_pattern.search(body)
            if key_match:
                existing = key_match.group(2)
                new_lines = [f"\t\t\t\t{path}," for path in sorted(paths) if path not in existing]
                if new_lines:
                    replacement = key_match.group(1) + existing.rstrip() + "\n" + "\n".join(new_lines) + "\n\t\t\t" + key_match.group(3)
                    body = body.replace(key_match.group(0), replacement)
            else:
                lines = ['\t\t\t\t"$(inherited)",'] + [f"\t\t\t\t{path}," for path in sorted(paths)]
                block = f"{key} = (\n" + "\n".join(lines) + "\n\t\t\t);"
                body = body.rstrip() + "\n\t\t\t" + block + "\n"

            new_config = prefix + body + suffix
            content = content.replace(match.group(0), new_config)
            print(f"    ✅ 已更新 {config_name} 配置")

        return content


def main():
    if len(sys.argv) < 3:
        print("用法: python custom_pbxproj_add.py <project.pbxproj路径> <SDK文件夹路径> [组名]")
        print("示例: python custom_pbxproj_add.py project.pbxproj SDK SDK")
        sys.exit(1)

    pbxproj_path = sys.argv[1]
    sdk_folder_path = sys.argv[2]
    group_name = sys.argv[3] if len(sys.argv) > 3 else "SDK"

    adder = FinalSDKAdder(pbxproj_path)
    ok = adder.add_sdk_folder(sdk_folder_path, group_name)
    sys.exit(0 if ok else 1)


if __name__ == "__main__":
    main()
