import sys
import os
from pbxproj import XcodeProject
from pbxproj.pbxextensions import ProjectFiles
import traceback # 引入 traceback 模块
import re

def delete_target_using_pbxproj(pbx_path, target_name):
    try:
        # 1. 加载项目文件
        project = XcodeProject.load(pbx_path)

        # 2. 按名称查找目标 Target 对象
        target_to_delete = project.get_target_by_name(target_name)

        if not target_to_delete:
            print(f"错误: 在项目中未找到名为 '{target_name}' 的 Target。")
            return False

        target_uuid = target_to_delete.get_id()

        # --- 在删除 Target 前，获取关联对象的 UUID ---
        build_phase_uuids = list(target_to_delete.buildPhases) if hasattr(target_to_delete, 'buildPhases') else []
        build_configuration_list_uuid = target_to_delete.buildConfigurationList if hasattr(target_to_delete, 'buildConfigurationList') else None
        product_reference_uuid = target_to_delete.productReference if hasattr(target_to_delete, 'productReference') else None


        # 3. 从项目中删除 Target

        # 3.1 从主项目的 targets 列表中移除引用
        project_object = project.get_object(project.rootObject) # 修正：使用 rootObject
        if project_object and hasattr(project_object, 'targets'):
            if target_uuid in project_object.targets:
                project_object.targets.remove(target_uuid)
            else:
                print(f"警告: Target UUID {target_uuid} 不在项目 targets 列表中。")
        else:
             print(f"警告: 无法获取主项目对象或其 targets 列表。")

        # 3.2 从 project.objects 字典中直接移除 Target 对象
        if target_uuid in project.objects:
            del project.objects[target_uuid]
        else:
            print(f"警告: Target UUID {target_uuid} 不在 project.objects 字典中。")

        # --- 新增：清理关联的 Build Phases ---
        for phase_uuid in build_phase_uuids:
            if phase_uuid in project.objects:
                # 清理 Build Phase 中的 buildFiles (如果存在)
                phase_obj = project.get_object(phase_uuid)
                if phase_obj and hasattr(phase_obj, 'files'):
                    build_file_uuids = list(phase_obj.files)
                    for bf_uuid in build_file_uuids:
                        if bf_uuid in project.objects:
                            del project.objects[bf_uuid]
                # 删除 Build Phase 对象本身
                del project.objects[phase_uuid]
            else:
                print(f"警告: Build Phase UUID {phase_uuid} 不在 project.objects 中。")

        # --- 新增：清理关联的 Build Configuration List 和 Configurations ---
        if build_configuration_list_uuid and build_configuration_list_uuid in project.objects:
            config_list_obj = project.get_object(build_configuration_list_uuid)
            if config_list_obj and hasattr(config_list_obj, 'buildConfigurations'):
                build_config_uuids = list(config_list_obj.buildConfigurations)
                for config_uuid in build_config_uuids:
                    if config_uuid in project.objects:
                        del project.objects[config_uuid]
                    else:
                        print(f"警告: Build Configuration UUID {config_uuid} 不在 project.objects 中。")
            # 删除 XCConfigurationList 对象本身
            del project.objects[build_configuration_list_uuid]
        else:
            print(f"警告: Build Configuration List UUID {build_configuration_list_uuid} 不存在或已清理。")

        # --- 新增：清理关联的 Product File Reference ---
        if product_reference_uuid and product_reference_uuid in project.objects:
             # 同时尝试从 Products Group 中移除引用
             products_group = project.get_groups_by_name('Products')
             if products_group:
                 products_group[0].remove_child(project.get_object(product_reference_uuid))

             del project.objects[product_reference_uuid]
        else:
            print(f"警告: Product File Reference UUID {product_reference_uuid} 不存在或已清理。")


        # 4. 保存修改后的项目文件
        project.save()
        return True

    except Exception as e:
        print(f"处理项目时发生错误: {e}")
        print("-" * 60)
        traceback.print_exc(file=sys.stdout) # 打印详细的错误堆栈信息
        print("-" * 60)
        return False

def modify_target_build_setting(pbx_path, target_name, setting_key, new_value=None, delete=False):
    """
    修改或删除 Target 的构建设置
    
    参数:
        pbx_path (str): project.pbxproj 文件的路径
        target_name (str): 目标 Target 的名称
        setting_key (str): 要修改的设置键名，例如 'GCC_PREPROCESSOR_DEFINITIONS'
        new_value (any, optional): 新的设置值，如果 delete=True 则忽略此参数
        delete (bool, optional): 如果为 True，则删除该设置项，默认为 False
        
    返回:
        bool: 操作成功返回 True，否则返回 False
    """
    try:
        # 1. 加载项目文件
        project = XcodeProject.load(pbx_path)
        
        # 2. 按名称查找目标 Target 对象
        target = project.get_target_by_name(target_name)
        
        if not target:
            print(f"错误: 在项目中未找到名为 '{target_name}' 的 Target。")
            return False
            
        # 3. 获取 Target 的构建配置列表
        build_config_list_id = target.buildConfigurationList
        build_config_list = project.get_object(build_config_list_id)
        
        if not build_config_list:
            print(f"错误: 无法获取 Target '{target_name}' 的构建配置列表。")
            return False
            
        # 4. 遍历所有构建配置（Debug, Release 等）
        modified = False
        for config_id in build_config_list.buildConfigurations:
            config = project.get_object(config_id)
            if not config or not hasattr(config, 'buildSettings'):
                continue
                
            # 5. 修改或删除构建设置
            if delete:
                if setting_key in config.buildSettings:
                    del config.buildSettings[setting_key]
                    modified = True
            else:
                # 如果设置不存在，则添加；如果存在，则修改
                config.buildSettings[setting_key] = new_value
                modified = True
                
        # 6. 保存修改后的项目文件
        if modified:
            project.save()
            return True
        else:
            return False
            
    except Exception as e:
        print(f"处理项目时发生错误: {e}")
        print("-" * 60)
        traceback.print_exc(file=sys.stdout)
        print("-" * 60)
        return False

def clear_target_preprocessor_definitions(pbx_path, target_name):
    """
    清空 Target 的预处理器定义 (GCC_PREPROCESSOR_DEFINITIONS)
    
    参数:
        pbx_path (str): project.pbxproj 文件的路径
        target_name (str): 目标 Target 的名称
        
    返回:
        bool: 操作成功返回 True，否则返回 False
    """
    return modify_target_build_setting(pbx_path, target_name, 'GCC_PREPROCESSOR_DEFINITIONS', [], False)

def remove_files_from_project(pbx_path, file_paths):
    """
    从项目中删除文件或文件夹及其所有相关引用
    
    参数:
        pbx_path (str): project.pbxproj 文件的路径
        file_paths (list): 要删除的文件或文件夹路径列表
        
    返回:
        bool: 操作成功返回 True，否则返回 False
    """
    try:
        # 1. 加载项目文件
        project = XcodeProject.load(pbx_path)
        
        # 用于存储已处理的对象ID，避免重复处理
        processed_ids = set()
        
        # 跟踪删除的文件引用ID，用于后续清理孤立的构建文件
        all_removed_file_refs = set()
        
        # 提取纯框架名称，用于更精确的路径匹配
        framework_names = []
        for file_path in file_paths:
            # 提取文件或文件夹名称
            file_name = os.path.basename(file_path)
            framework_names.append(file_name)
            
            # 如果路径包含父目录，也尝试提取父目录名称（常见于OSFrameworks/XXX格式）
            parent_dir = os.path.dirname(file_path)
            if parent_dir and os.path.basename(parent_dir):
                parent_name = os.path.basename(parent_dir)
                if parent_name not in ['OSFrameworks', 'CNFrameworks']:
                    framework_names.append(parent_name)
        
        # 2. 处理每个文件路径
        for file_path in file_paths:
            try:
                file_name = os.path.basename(file_path)
                
                # 记录要删除的文件引用和构建文件引用
                file_refs_to_remove = []
                build_files_to_remove = []
                groups_to_clean = []
                
                # 第一步：识别所有匹配的文件引用
                for file_ref in project.objects.get_objects_in_section('PBXFileReference'):
                    if file_ref.get_id() in processed_ids:
                        continue
                    
                    should_remove = False
                    
                    # 检查文件名匹配
                    if hasattr(file_ref, 'name'):
                        if file_name == file_ref.name or file_name in file_ref.name:
                            should_remove = True
                    
                    # 检查路径匹配
                    if not should_remove and hasattr(file_ref, 'path'):
                        path_basename = os.path.basename(file_ref.path)
                        if file_name == path_basename or file_name in file_ref.path:
                            should_remove = True
                    
                    if should_remove:
                        file_refs_to_remove.append(file_ref)
                        processed_ids.add(file_ref.get_id())
                
                # 第二步：查找与这些文件引用相关的构建文件
                file_ref_ids = [ref.get_id() for ref in file_refs_to_remove]
                all_removed_file_refs.update(file_ref_ids)
                
                for build_file in project.objects.get_objects_in_section('PBXBuildFile'):
                    if build_file.get_id() in processed_ids:
                        continue
                    
                    if hasattr(build_file, 'fileRef') and build_file.fileRef in file_ref_ids:
                        build_files_to_remove.append(build_file)
                        processed_ids.add(build_file.get_id())
                
                # 特别处理：搜索名称或路径中包含关键字的构建文件
                # 这对库文件特别有用
                for build_file in project.objects.get_objects_in_section('PBXBuildFile'):
                    if build_file.get_id() in processed_ids:
                        continue
                    
                    if hasattr(build_file, 'fileRef') and build_file.fileRef in project.objects:
                        file_ref = project.objects[build_file.fileRef]
                        
                        should_remove = False
                        
                        # 检查文件名匹配
                        if hasattr(file_ref, 'name') and file_name in file_ref.name:
                            should_remove = True
                        
                        # 检查路径匹配
                        if not should_remove and hasattr(file_ref, 'path') and file_name in file_ref.path:
                            should_remove = True
                        
                        if should_remove:
                            build_files_to_remove.append(build_file)
                            if file_ref.get_id() not in file_ref_ids:
                                file_refs_to_remove.append(file_ref)
                                file_ref_ids.append(file_ref.get_id())
                                all_removed_file_refs.add(file_ref.get_id())
                            
                            processed_ids.add(build_file.get_id())
                            processed_ids.add(file_ref.get_id())
                
                # 第三步：识别和清理组 - 更彻底的组结构处理
                
                # 函数: 递归查找组内所有子对象
                def collect_group_contents(group, target_name):
                    if group.get_id() in processed_ids:
                        return
                    
                    # 检查组名或路径是否匹配
                    should_remove = False
                    if hasattr(group, 'name') and (group.name == target_name or target_name in group.name):
                        should_remove = True
                    elif hasattr(group, 'path') and (group.path == target_name or target_name in group.path):
                        should_remove = True
                    
                    if should_remove and group not in groups_to_clean:
                        groups_to_clean.append(group)
                        processed_ids.add(group.get_id())
                    
                    # 处理子项
                    if hasattr(group, 'children'):
                        for child_id in list(group.children):  # 使用副本避免修改时的问题
                            if child_id in project.objects and child_id not in processed_ids:
                                child = project.objects[child_id]
                                
                                if hasattr(child, 'isa'):
                                    # 处理子文件
                                    if child.isa == 'PBXFileReference':
                                        # 检查文件是否应该被移除
                                        child_should_remove = False
                                        if hasattr(child, 'name') and (target_name == child.name or target_name in child.name):
                                            child_should_remove = True
                                        elif hasattr(child, 'path') and (target_name == child.path or target_name in child.path):
                                            child_should_remove = True
                                        
                                        if child_should_remove or should_remove:  # 如果父组要删除，子项也删除
                                            if child.get_id() not in file_ref_ids:
                                                file_refs_to_remove.append(child)
                                                file_ref_ids.append(child.get_id())
                                                all_removed_file_refs.add(child.get_id())
                                            processed_ids.add(child.get_id())
                                    
                                    # 递归处理子组
                                    elif child.isa == 'PBXGroup':
                                        # 无论如何都递归检查子组
                                        collect_group_contents(child, target_name)
                                        
                                        # 如果父组被标记为删除，子组也删除
                                        if should_remove and child not in groups_to_clean:
                                            groups_to_clean.append(child)
                                            processed_ids.add(child.get_id())
                
                # 检查所有顶级组
                for group in project.objects.get_objects_in_section('PBXGroup'):
                    collect_group_contents(group, file_name)
                    
                # 第四步：从构建阶段中删除构建文件引用
                build_file_ids = [bf.get_id() for bf in build_files_to_remove]
                
                # 处理所有构建阶段
                for phase_type in ['PBXFrameworksBuildPhase', 'PBXResourcesBuildPhase', 'PBXSourcesBuildPhase']:
                    for phase in project.objects.get_objects_in_section(phase_type):
                        if hasattr(phase, 'files'):
                            original_count = len(phase.files)
                            # 过滤掉要删除的构建文件
                            phase.files = [f for f in phase.files if f not in build_file_ids]
                
                # 第五步：从组中移除文件引用和子组
                for group in project.objects.get_objects_in_section('PBXGroup'):
                    if not hasattr(group, 'children'):
                        continue
                    
                    original_count = len(group.children)
                    # 过滤掉要删除的文件引用和子组
                    group.children = [c for c in group.children if c not in file_ref_ids and 
                                     (c not in [g.get_id() for g in groups_to_clean])]
                
                # 第六步：清理构建设置中的搜索路径
                modified_settings = False  # 跟踪是否修改了构建设置
                
                # 获取所有目标
                targets = list(project.objects.get_targets())
                
                for target in targets:
                    # 安全获取target名称
                    target_name = getattr(target, 'name', target.get_id()) if hasattr(target, 'name') else target.get_id()
                    
                    # 检查是否有构建配置列表
                    if not hasattr(target, 'buildConfigurationList'):
                        continue
                    
                    # 获取构建配置列表ID
                    build_config_list_id = target.buildConfigurationList
                    
                    # 检查构建配置列表ID是否在项目对象中
                    if build_config_list_id not in project.objects:
                        continue
                    
                    # 获取构建配置列表对象
                    build_config_list = project.objects[build_config_list_id]
                    
                    # 检查构建配置列表是否有buildConfigurations属性
                    if not hasattr(build_config_list, 'buildConfigurations'):
                        continue
                    
                    # 获取目标上的配置ID列表
                    config_ids = build_config_list.buildConfigurations
                    
                    # 手动获取每个配置
                    for config_id in config_ids:
                        if config_id not in project.objects:
                            continue
                        
                        config = project.objects[config_id]
                        config_name = getattr(config, 'name', '未命名配置') if hasattr(config, 'name') else '未命名配置'
                        
                        # 检查配置是否有buildSettings属性
                        if not hasattr(config, 'buildSettings'):
                            continue
                        
                        build_settings = config.buildSettings
                        
                        # 处理所有搜索路径设置
                        for key in ['FRAMEWORK_SEARCH_PATHS', 'LIBRARY_SEARCH_PATHS', 'HEADER_SEARCH_PATHS']:
                            # 安全检查key是否在build_settings中
                            has_key = False
                            try:
                                has_key = key in build_settings or hasattr(build_settings, key)
                            except Exception as e:
                                continue
                            
                            if has_key:
                                # 安全获取原始值
                                original_value = None
                                try:
                                    original_value = build_settings[key] if key in build_settings else getattr(build_settings, key, None)
                                except Exception as e:
                                    continue
                                
                                if original_value is None:
                                    continue
                                
                                # 处理列表形式的路径
                                if isinstance(original_value, list):
                                    new_paths = []
                                    for path in original_value:
                                        keep_path = True
                                        # 检查路径是否包含要删除的文件名
                                        for fn in framework_names:
                                            # 更严格的匹配条件，避免部分匹配误删
                                            if fn in path and (f"/{fn}" in path or f"/{fn}." in path or f"\\{fn}" in path or f"\\{fn}." in path or path.endswith(fn)):
                                                keep_path = False
                                                break
                                        
                                        # 保留继承路径和不包含要删除文件的路径
                                        if keep_path:
                                            new_paths.append(path)
                                            
                                    if len(new_paths) != len(original_value):
                                        if new_paths:
                                            # 安全设置新值
                                            try:
                                                build_settings[key] = new_paths
                                            except Exception as e:
                                                try:
                                                    setattr(build_settings, key, new_paths)
                                                except Exception as e2:
                                                    pass
                                        else:
                                            # 安全删除键
                                            try:
                                                del build_settings[key]
                                            except Exception as e:
                                                try:
                                                    delattr(build_settings, key)
                                                except Exception as e2:
                                                    # 最后尝试设置为None
                                                    try:
                                                        build_settings[key] = None
                                                    except:
                                                        pass
                                        modified_settings = True
                                
                                # 处理字符串形式的路径
                                elif isinstance(original_value, str):
                                    # 构建要删除的路径模式
                                    patterns = []
                                    for fn in framework_names:
                                        # 基本路径模式
                                        base_patterns = [
                                            f'"$(SRCROOT)/../{fn}"', 
                                            f'"$(SRCROOT)/../*/{fn}"',
                                            f'$(SRCROOT)/../{fn}',
                                            f'$(SRCROOT)/../*/{fn}',
                                            f'"$(SRCROOT)/../{fn}/"',
                                            f'"$(SRCROOT)/../*/{fn}/"',
                                            f'$(SRCROOT)/../{fn}/',
                                            f'$(SRCROOT)/../*/{fn}/',
                                            # 特定于CNFrameworks/OSFrameworks的处理
                                            f'"$(SRCROOT)/../CNFrameworks/{fn}"',
                                            f'$(SRCROOT)/../CNFrameworks/{fn}',
                                            f'"$(SRCROOT)/../CNFrameworks/{fn}/"',
                                            f'$(SRCROOT)/../CNFrameworks/{fn}/',
                                            f'"$(SRCROOT)/../OSFrameworks/{fn}"',
                                            f'$(SRCROOT)/../OSFrameworks/{fn}',
                                            f'"$(SRCROOT)/../OSFrameworks/{fn}/"',
                                            f'$(SRCROOT)/../OSFrameworks/{fn}/',
                                        ]
                                        
                                        # 处理嵌套引号的特殊模式
                                        nested_quote_patterns = [
                                            f'"\\\"$(SRCROOT)/../OSFrameworks/{fn}\\\""',  # "\"$(SRCROOT)/../OSFrameworks/XXX\""
                                            f'"\\\"$(SRCROOT)/../CNFrameworks/{fn}\\\""',  # "\"$(SRCROOT)/../CNFrameworks/XXX\""
                                            f'"\\\"$(SRCROOT)/../{fn}\\\""',  # "\"$(SRCROOT)/../XXX\""
                                            f'\\\"$(SRCROOT)/../OSFrameworks/{fn}\\\"',
                                            f'\\\"$(SRCROOT)/../CNFrameworks/{fn}\\\"',
                                            f'\\\"$(SRCROOT)/../{fn}\\\"',
                                        ]
                                        
                                        # 绝对路径模式
                                        absolute_patterns = [
                                            f'"/Users/*/OSFrameworks/{fn}"',
                                            f'"/Users/*/CNFrameworks/{fn}"',
                                            f'"/Users/*/{fn}"'
                                        ]
                                        
                                        current_patterns = base_patterns + nested_quote_patterns + absolute_patterns
                                        patterns.extend(current_patterns)
                                    
                                    # 替换所有匹配的模式
                                    new_value = original_value
                                    modified = False
                                    
                                    # 首先尝试精确的模式匹配
                                    for pattern in patterns:
                                        if pattern in new_value:
                                            new_value = new_value.replace(pattern, '')
                                            modified = True
                                    
                                    # 然后尝试基于正则表达式的更灵活匹配
                                    for fn in framework_names:
                                        # 匹配带有框架名称的路径片段
                                        framework_regex_patterns = [
                                            r'["\s,](.*?/OSFrameworks/' + re.escape(fn) + r'/?)["\s,]',
                                            r'["\s,](.*?/CNFrameworks/' + re.escape(fn) + r'/?)["\s,]',
                                            r'["\s,](.*?/' + re.escape(fn) + r'/?)["\s,]'
                                        ]
                                        
                                        for regex_pattern in framework_regex_patterns:
                                            regex_matches = re.findall(regex_pattern, new_value)
                                            if regex_matches:
                                                for match in regex_matches:
                                                    # 安全替换，确保不删除路径中间部分
                                                    new_value = new_value.replace(f'"{match}"', '')
                                                    new_value = new_value.replace(f'{match}', '')
                                                    modified = True
                                    
                                    # 如果路径有变化，更新设置
                                    if modified:
                                        # 清理多余空格和分隔符
                                        new_value = new_value.replace('  ', ' ').strip()
                                        while ',,' in new_value:
                                            new_value = new_value.replace(',,', ',')
                                        new_value = new_value.strip(' ,')
                                        
                                        if new_value:
                                            # 安全设置新值
                                            try:
                                                build_settings[key] = new_value
                                            except Exception as e:
                                                try:
                                                    setattr(build_settings, key, new_value)
                                                except Exception as e2:
                                                    pass
                                        else:
                                            # 安全删除键
                                            try:
                                                del build_settings[key]
                                            except Exception as e:
                                                try:
                                                    delattr(build_settings, key)
                                                except Exception as e2:
                                                    # 最后尝试设置为None
                                                    try:
                                                        build_settings[key] = None
                                                    except:
                                                        pass
                                        modified_settings = True
                    
                # 第七步：删除实际对象
                
                # 删除构建文件
                for build_file in build_files_to_remove:
                    if build_file.get_id() in project.objects:
                        del project.objects[build_file.get_id()]
        
                # 删除文件引用
                for file_ref in file_refs_to_remove:
                    if file_ref.get_id() in project.objects:
                        del project.objects[file_ref.get_id()]
                
                # 删除组
                for group in reversed(groups_to_clean):  # 反向删除，先删子组
                    if group.get_id() in project.objects:
                        del project.objects[group.get_id()]
            except Exception as e:
                print(f"❌ 处理文件 {file_path} 时出错: {e}")
                print("-" * 60)
                traceback.print_exc(file=sys.stdout)
                print("-" * 60)
                # 继续处理下一个文件
                continue
        
        # 第八步：清理项目中的孤立引用
        try:
            orphaned_build_files = []
            
            # 检查所有构建文件，寻找那些指向已删除文件引用的构建文件
            for build_file in project.objects.get_objects_in_section('PBXBuildFile'):
                if hasattr(build_file, 'fileRef'):
                    # 如果引用的文件已不存在，或者在已删除列表中
                    if build_file.fileRef not in project.objects or build_file.fileRef in all_removed_file_refs:
                        orphaned_build_files.append(build_file)
            
            # 从所有构建阶段中移除孤立的构建文件
            if orphaned_build_files:
                orphaned_ids = [bf.get_id() for bf in orphaned_build_files]
                for phase_type in ['PBXFrameworksBuildPhase', 'PBXResourcesBuildPhase', 'PBXSourcesBuildPhase']:
                    for phase in project.objects.get_objects_in_section(phase_type):
                        if hasattr(phase, 'files'):
                            original_count = len(phase.files)
                            phase.files = [f for f in phase.files if f not in orphaned_ids]
            
                # 删除孤立的构建文件对象
                for build_file in orphaned_build_files:
                    if build_file.get_id() in project.objects:
                        del project.objects[build_file.get_id()]
        
        except Exception as e:
            print(f"❌ 清理孤立引用时出错: {e}")
            print("-" * 60)
            traceback.print_exc(file=sys.stdout)
            print("-" * 60)
        
        # 保存当前处理的结果
        try:
            project.save()
        except Exception as e:
            print(f"❌ 保存项目文件时出错: {e}")
            print("-" * 60)
            traceback.print_exc(file=sys.stdout)
            print("-" * 60)
        
        return True
        
    except Exception as e:
        print(f"\n❌ 处理项目时发生错误: {e}")
        print("-" * 60)
        traceback.print_exc(file=sys.stdout)
        print("-" * 60)
        return False
