

import argparse
import os
import sys
import json
from pathlib import Path

# Add the utils directory to the Python path
# This allows importing modules from the 'utils' subfolder
utils_path = Path(__file__).parent / 'utils'
sys.path.append(str(utils_path))

from yaml_utils import load_unity_yaml, convert_to_plain_python_types
from file_utils import find_files_by_extension, create_guid_to_path_map
from json_utils import write_json

def parse_physics_settings(input_dir, project_mode):
    """
    Parses the appropriate physics settings file based on the project mode.
    """
    physics_data = {}
    if project_mode == "3D":
        asset_path = input_dir / "ProjectSettings" / "DynamicsManager.asset"
        if asset_path.is_file():
            docs = load_unity_yaml(str(asset_path))
            if docs:
                settings = convert_to_plain_python_types(docs[0]).get('PhysicsManager', {})
                physics_data['gravity'] = settings.get('m_Gravity')
                physics_data['sleepThreshold'] = settings.get('m_SleepThreshold')
                physics_data['solverType'] = settings.get('m_SolverType')
                physics_data['layerCollisionMatrix'] = settings.get('m_LayerCollisionMatrix')
                physics_data['autoSimulation'] = settings.get('m_AutoSimulation')
                physics_data['autoSyncTransforms'] = settings.get('m_AutoSyncTransforms')
    else: # 2D
        asset_path = input_dir / "ProjectSettings" / "Physics2DSettings.asset"
        if asset_path.is_file():
            docs = load_unity_yaml(str(asset_path))
            if docs:
                settings = convert_to_plain_python_types(docs[0]).get('Physics2DSettings', {})
                physics_data['gravity'] = settings.get('m_Gravity')
                physics_data['velocityIterations'] = settings.get('m_VelocityIterations')
                physics_data['positionIterations'] = settings.get('m_PositionIterations')
                physics_data['layerCollisionMatrix'] = settings.get('m_LayerCollisionMatrix')
                physics_data['autoSimulation'] = settings.get('m_AutoSimulation')
                physics_data['autoSyncTransforms'] = settings.get('m_AutoSyncTransforms')
    
    return physics_data

def parse_project_settings(input_dir, output_dir, indent=None):
    """
    Parses various project settings files to create a comprehensive manifest.
    """
    print("\n--- Starting Task 2: Comprehensive Project Settings Parser ---")
    
    manifest_data = {}
    guid_map = create_guid_to_path_map(str(input_dir))

    # --- ProjectSettings.asset ---
    project_settings_path = input_dir / "ProjectSettings" / "ProjectSettings.asset"
    if project_settings_path.is_file():
        docs = load_unity_yaml(str(project_settings_path))
        if docs:
            player_settings = convert_to_plain_python_types(docs[0]).get('PlayerSettings', {})
            manifest_data['productName'] = player_settings.get('productName')
            manifest_data['companyName'] = player_settings.get('companyName')
            manifest_data['bundleVersion'] = player_settings.get('bundleVersion')
            manifest_data['activeColorSpace'] = player_settings.get('m_ActiveColorSpace')
            
            # --- Mappers for human-readable values ---
            scripting_backend_map = {0: "Mono", 1: "IL2CPP"}
            api_compatibility_map = {3: ".NET Framework", 6: ".NET Standard 2.1"}

            # --- Extract and map platform-specific settings ---
            scripting_backends = player_settings.get('scriptingBackend', {})
            manifest_data['scriptingBackend'] = {
                platform: scripting_backend_map.get(val, f"Unknown ({val})")
                for platform, val in scripting_backends.items()
            }

            api_levels = player_settings.get('apiCompatibilityLevelPerPlatform', {})
            manifest_data['apiCompatibilityLevel'] = {
                platform: api_compatibility_map.get(val, f"Unknown ({val})")
                for platform, val in api_levels.items()
            }
            
            # Fallback for older Unity versions that use a single key
            if not api_levels and 'apiCompatibilityLevel' in player_settings:
                val = player_settings.get('apiCompatibilityLevel')
                manifest_data['apiCompatibilityLevel']['Standalone'] = api_compatibility_map.get(val, f"Unknown ({val})")

            manifest_data['activeInputHandler'] = player_settings.get('activeInputHandler')
            manifest_data['allowUnsafeCode'] = player_settings.get('allowUnsafeCode')
            manifest_data['managedStrippingLevel'] = player_settings.get('managedStrippingLevel')
            manifest_data['scriptingDefineSymbols'] = player_settings.get('scriptingDefineSymbols')

            # --- Deduce configured platforms from various settings ---
            configured_platforms = set()
            if 'applicationIdentifier' in player_settings:
                configured_platforms.update(player_settings['applicationIdentifier'].keys())
            if 'scriptingBackend' in player_settings:
                configured_platforms.update(player_settings['scriptingBackend'].keys())
            manifest_data['configuredPlatforms'] = sorted(list(configured_platforms))

            # --- Filter managedStrippingLevel based on configured platforms ---
            managed_stripping_level = player_settings.get('managedStrippingLevel', {})
            manifest_data['managedStrippingLevel'] = {
                platform: level
                for platform, level in managed_stripping_level.items()
                if platform in manifest_data['configuredPlatforms']
            }

            # --- Populate all configured platforms for scripting settings ---
            default_api_level = player_settings.get('apiCompatibilityLevel')
            
            final_scripting_backends = {}
            final_api_levels = {}

            for platform in manifest_data['configuredPlatforms']:
                # Scripting Backend (Default to Mono if not specified)
                backend_val = scripting_backends.get(platform, 0)
                final_scripting_backends[platform] = scripting_backend_map.get(backend_val, f"Unknown ({backend_val})")

                # API Compatibility Level (Default to project's global setting if not specified)
                level_val = api_levels.get(platform, default_api_level)
                final_api_levels[platform] = api_compatibility_map.get(level_val, f"Unknown ({level_val})")
            
            manifest_data['scriptingBackend'] = final_scripting_backends
            manifest_data['apiCompatibilityLevel'] = final_api_levels

    # --- EditorSettings.asset for 2D/3D Mode ---
    editor_settings_path = input_dir / "ProjectSettings" / "EditorSettings.asset"
    if editor_settings_path.is_file():
        docs = load_unity_yaml(str(editor_settings_path))
        if docs:
            editor_settings = convert_to_plain_python_types(docs[0]).get('EditorSettings', {})
            manifest_data['projectMode'] = "2D" if editor_settings.get('m_DefaultBehaviorMode') == 1 else "3D"

    # --- GraphicsSettings.asset for Render Pipeline ---
    graphics_settings_path = input_dir / "ProjectSettings" / "GraphicsSettings.asset"
    manifest_data['renderPipeline'] = 'Built-in'
    if graphics_settings_path.is_file():
        docs = load_unity_yaml(str(graphics_settings_path))
        if docs:
            graphics_settings = convert_to_plain_python_types(docs[0]).get('GraphicsSettings', {})
            pipeline_ref = graphics_settings.get('m_CustomRenderPipeline') or graphics_settings.get('m_SRPDefaultSettings', {}).get('UnityEngine.Rendering.Universal.UniversalRenderPipeline')
            if pipeline_ref and pipeline_ref.get('guid'):
                guid = pipeline_ref['guid']
                if guid in guid_map:
                    asset_path = Path(guid_map[guid]).name.upper()
                    if "URP" in asset_path: manifest_data['renderPipeline'] = 'URP'
                    elif "HDRP" in asset_path: manifest_data['renderPipeline'] = 'HDRP'
                    else: manifest_data['renderPipeline'] = 'Scriptable'

    # --- TagManager.asset ---
    tag_manager_path = input_dir / "ProjectSettings" / "TagManager.asset"
    if tag_manager_path.is_file():
        docs = load_unity_yaml(str(tag_manager_path))
        if docs:
            tag_manager = convert_to_plain_python_types(docs[0]).get('TagManager', {})
            manifest_data['tags'] = tag_manager.get('tags')
            layers_list = tag_manager.get('layers', [])
            # Only include layers that have a name, preserving their index
            manifest_data['layers'] = {i: name for i, name in enumerate(layers_list) if name}

    # --- EditorBuildSettings.asset ---
    build_settings_path = input_dir / "ProjectSettings" / "EditorBuildSettings.asset"
    if build_settings_path.is_file():
        docs = load_unity_yaml(str(build_settings_path))
        if docs:
            build_settings = convert_to_plain_python_types(docs[0]).get('EditorBuildSettings', {})
            manifest_data['buildScenes'] = [
                {'path': scene.get('path'), 'enabled': scene.get('enabled') == 1}
                for scene in build_settings.get('m_Scenes', [])
            ]

    # --- TimeManager.asset ---
    time_manager_path = input_dir / "ProjectSettings" / "TimeManager.asset"
    if time_manager_path.is_file():
        docs = load_unity_yaml(str(time_manager_path))
        if docs:
            time_manager = convert_to_plain_python_types(docs[0]).get('TimeManager', {})
            # Cherry-pick only the useful time settings
            manifest_data['timeSettings'] = {
                'Fixed Timestep': time_manager.get('Fixed Timestep'),
                'Maximum Allowed Timestep': time_manager.get('Maximum Allowed Timestep'),
                'm_TimeScale': time_manager.get('m_TimeScale'),
                'Maximum Particle Timestep': time_manager.get('Maximum Particle Timestep')
            }

    # --- Physics Settings ---
    manifest_data['physicsSettings'] = parse_physics_settings(input_dir, manifest_data.get('projectMode', '3D'))

    # --- Write manifest.json ---
    manifest_output_path = output_dir / "manifest.json"
    try:
        write_json(manifest_data, manifest_output_path, indent=indent)
        print(f"Successfully created manifest.json at {manifest_output_path}")
    except Exception as e:
        print(f"Error writing to {manifest_output_path}. {e}", file=sys.stderr)


def parse_package_manifests(input_dir, output_dir, indent=None):
    """
    Parses the primary package manifest and creates a clean packages.json file.
    """
    print("\n--- Starting Task 3: Package Manifest Extractor ---")
    
    manifest_path = input_dir / "Packages" / "manifest.json"
    
    if manifest_path.is_file():
        try:
            with open(manifest_path, 'r', encoding='utf-8') as f:
                packages_data = json.load(f)
            
            packages_output_path = output_dir / "packages.json"
            write_json(packages_data, packages_output_path, indent=indent)
            print(f"Successfully created packages.json at {packages_output_path}")

        except (IOError, json.JSONDecodeError) as e:
            print(f"Error processing {manifest_path}: {e}", file=sys.stderr)
    else:
        print(f"Warning: {manifest_path} not found.")

def main():
    """
    Main function to run the high-level data extraction process.
    """
    parser = argparse.ArgumentParser(
        description="Extracts high-level summary data from a Unity project."
    )
    parser.add_argument("--input", type=str, required=True, help="The root directory of the target Unity project.")
    parser.add_argument("--output", type=str, required=True, help="The directory where the generated output folder will be saved.")
    parser.add_argument("--indent", type=int, default=None, help="Indentation level for JSON output. Defaults to None (compact).")
    args = parser.parse_args()

    input_dir = Path(args.input)
    output_dir = Path(args.output)

    if not input_dir.is_dir():
        print(f"Error: Input path '{input_dir}' is not a valid directory.", file=sys.stderr)
        sys.exit(1)

    high_level_output_dir = output_dir / "HighLevel"
    try:
        high_level_output_dir.mkdir(parents=True, exist_ok=True)
        print(f"Output will be saved to: {high_level_output_dir}")
    except OSError as e:
        print(f"Error: Could not create output directory '{high_level_output_dir}'. {e}", file=sys.stderr)
        sys.exit(1)

    parse_project_settings(input_dir, high_level_output_dir, indent=args.indent)
    parse_package_manifests(input_dir, high_level_output_dir, indent=args.indent)

    print("\nHigh-level extraction complete.")

if __name__ == "__main__":
    main()

