"""PBX object model for .pbxproj generation.

Each class represents a PBX/XC object type. Objects store cross-references
as plain 24-char hex strings (matching the on-disk format).
"""

from typing import Any


class PBXObject:
    """Base class for all PBX objects."""

    isa: str = "PBXObject"

    def __init__(self, object_key: str) -> None:
        """Create a PBX object with a unique 24-char hex key."""
        self.object_key = object_key

    @property
    def comment(self) -> str | None:
        """Human-readable comment for this object (used in serializer)."""
        return None

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict with Xcode camelCase keys."""
        return {"isa": self.isa}


class PBXBuildFile(PBXObject):
    """A file reference used in a build phase."""

    isa = "PBXBuildFile"

    def __init__(
        self,
        object_key: str,
        file_ref: str,
        *,
        file_ref_comment: str | None = None,
        settings: dict[str, Any] | None = None,
    ) -> None:
        """Create a build file linking to a file reference with optional compiler settings."""
        super().__init__(object_key)
        self.file_ref = file_ref
        self.file_ref_comment = file_ref_comment
        self.settings = settings

    @property
    def comment(self) -> str | None:
        """Return the file reference comment suffixed with 'in Sources'."""
        if self.file_ref_comment:
            return f"{self.file_ref_comment} in Sources"
        return None

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["fileRef"] = self.file_ref
        if self.settings:
            d["settings"] = self.settings
        return d


class PBXFileReference(PBXObject):
    """A reference to an on-disk file."""

    isa = "PBXFileReference"

    def __init__(
        self,
        object_key: str,
        path: str,
        source_tree: str = "<group>",
        *,
        last_known_file_type: str | None = None,
        explicit_file_type: str | None = None,
        name: str | None = None,
        include_in_index: str | None = None,
    ) -> None:
        """Create a file reference with path, type, and source tree info."""
        super().__init__(object_key)
        self.path = path
        self.source_tree = source_tree
        self.last_known_file_type = last_known_file_type
        self.explicit_file_type = explicit_file_type
        self.name = name
        self.include_in_index = include_in_index

    @property
    def comment(self) -> str | None:
        """Return the file name or path."""
        return self.name or self.path

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        if self.explicit_file_type:
            d["explicitFileType"] = self.explicit_file_type
        if self.include_in_index is not None:
            d["includeInIndex"] = self.include_in_index
        if self.last_known_file_type:
            d["lastKnownFileType"] = self.last_known_file_type
        if self.name:
            d["name"] = self.name
        d["path"] = self.path
        d["sourceTree"] = self.source_tree
        return d


class PBXGroup(PBXObject):
    """A logical group of files and subgroups in the project navigator."""

    isa = "PBXGroup"

    def __init__(
        self,
        object_key: str,
        children: list[str] | None = None,
        *,
        name: str | None = None,
        path: str | None = None,
        source_tree: str = "<group>",
    ) -> None:
        """Create a group with optional children, name, and path."""
        super().__init__(object_key)
        self.children: list[str] = children or []
        self.name = name
        self.path = path
        self.source_tree = source_tree

    @property
    def comment(self) -> str | None:
        """Return the group name or path."""
        return self.name or self.path

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["children"] = self.children
        if self.name:
            d["name"] = self.name
        if self.path:
            d["path"] = self.path
        d["sourceTree"] = self.source_tree
        return d


class PBXFileSystemSynchronizedRootGroup(PBXObject):
    """A root group that auto-syncs files from disk (objectVersion 77+)."""

    isa = "PBXFileSystemSynchronizedRootGroup"

    def __init__(
        self,
        object_key: str,
        *,
        path: str,
        source_tree: str = "<group>",
        exceptions: list[str] | None = None,
    ) -> None:
        """Create a file-system-synchronized root group.

        Args:
            object_key: Unique 24-char hex key.
            path: On-disk directory path relative to the project.
            source_tree: Source tree setting (default "<group>").
            exceptions: Optional list of PBXFileSystemSynchronizedBuildFileExceptionSet keys.

        """
        super().__init__(object_key)
        self.path = path
        self.source_tree = source_tree
        self.exceptions: list[str] = exceptions or []

    @property
    def comment(self) -> str | None:
        """Return the path."""
        return self.path

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        if self.exceptions:
            d["exceptions"] = self.exceptions
        d["path"] = self.path
        d["sourceTree"] = self.source_tree
        return d


class PBXSourcesBuildPhase(PBXObject):
    """Build phase that compiles source files."""

    isa = "PBXSourcesBuildPhase"

    def __init__(self, object_key: str, files: list[str] | None = None) -> None:
        """Create a sources build phase with optional file references."""
        super().__init__(object_key)
        self.files: list[str] = files or []
        self.build_action_mask = "2147483647"
        self.run_only_for_deployment_postprocessing = "0"

    @property
    def comment(self) -> str | None:
        """Return 'Sources'."""
        return "Sources"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildActionMask"] = self.build_action_mask
        d["files"] = self.files
        d["runOnlyForDeploymentPostprocessing"] = self.run_only_for_deployment_postprocessing
        return d


class PBXResourcesBuildPhase(PBXObject):
    """Build phase that copies resource files into the bundle."""

    isa = "PBXResourcesBuildPhase"

    def __init__(self, object_key: str, files: list[str] | None = None) -> None:
        """Create a resources build phase with optional file references."""
        super().__init__(object_key)
        self.files: list[str] = files or []
        self.build_action_mask = "2147483647"
        self.run_only_for_deployment_postprocessing = "0"

    @property
    def comment(self) -> str | None:
        """Return 'Resources'."""
        return "Resources"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildActionMask"] = self.build_action_mask
        d["files"] = self.files
        d["runOnlyForDeploymentPostprocessing"] = self.run_only_for_deployment_postprocessing
        return d


class PBXFrameworksBuildPhase(PBXObject):
    """Build phase that links binary with frameworks."""

    isa = "PBXFrameworksBuildPhase"

    def __init__(self, object_key: str, files: list[str] | None = None) -> None:
        """Create a frameworks build phase with optional file references."""
        super().__init__(object_key)
        self.files: list[str] = files or []
        self.build_action_mask = "2147483647"
        self.run_only_for_deployment_postprocessing = "0"

    @property
    def comment(self) -> str | None:
        """Return 'Frameworks'."""
        return "Frameworks"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildActionMask"] = self.build_action_mask
        d["files"] = self.files
        d["runOnlyForDeploymentPostprocessing"] = self.run_only_for_deployment_postprocessing
        return d


class PBXCopyFilesBuildPhase(PBXObject):
    """Build phase that copies files to a destination folder."""

    isa = "PBXCopyFilesBuildPhase"

    def __init__(
        self,
        object_key: str,
        *,
        dst_path: str = "",
        dst_subfolder_spec: int = 16,
        name: str | None = None,
        files: list[str] | None = None,
    ) -> None:
        """Create a copy-files build phase with destination path and subfolder spec."""
        super().__init__(object_key)
        self.dst_path = dst_path
        self.dst_subfolder_spec = dst_subfolder_spec
        self.name = name
        self.files: list[str] = files or []
        self.build_action_mask = "2147483647"
        self.run_only_for_deployment_postprocessing = "0"

    @property
    def comment(self) -> str | None:
        """Return the phase name or 'CopyFiles'."""
        return self.name or "CopyFiles"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildActionMask"] = self.build_action_mask
        d["dstPath"] = self.dst_path
        d["dstSubfolderSpec"] = str(self.dst_subfolder_spec)
        d["files"] = self.files
        if self.name:
            d["name"] = self.name
        d["runOnlyForDeploymentPostprocessing"] = self.run_only_for_deployment_postprocessing
        return d


class PBXShellScriptBuildPhase(PBXObject):
    """Build phase that runs a shell script."""

    isa = "PBXShellScriptBuildPhase"

    def __init__(
        self,
        object_key: str,
        *,
        name: str = "Run Script",
        shell_path: str = "/bin/sh",
        shell_script: str = "",
        input_paths: list[str] | None = None,
        output_paths: list[str] | None = None,
    ) -> None:
        """Create a shell script build phase with script content and I/O paths."""
        super().__init__(object_key)
        self.name = name
        self.shell_path = shell_path
        self.shell_script = shell_script
        self.input_paths: list[str] = input_paths or []
        self.output_paths: list[str] = output_paths or []
        self.files: list[str] = []
        self.build_action_mask = "2147483647"
        self.run_only_for_deployment_postprocessing = "0"

    @property
    def comment(self) -> str | None:
        """Return the script phase name."""
        return self.name

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildActionMask"] = self.build_action_mask
        d["files"] = self.files
        d["inputPaths"] = self.input_paths
        d["name"] = self.name
        d["outputPaths"] = self.output_paths
        d["runOnlyForDeploymentPostprocessing"] = self.run_only_for_deployment_postprocessing
        d["shellPath"] = self.shell_path
        d["shellScript"] = self.shell_script
        return d


class PBXNativeTarget(PBXObject):
    """A target that builds a native product (app, framework, etc.)."""

    isa = "PBXNativeTarget"

    def __init__(
        self,
        object_key: str,
        *,
        name: str,
        product_name: str,
        product_type: str,
        product_reference: str,
        build_configuration_list: str,
        build_phases: list[str] | None = None,
        build_rules: list[str] | None = None,
        dependencies: list[str] | None = None,
        package_product_dependencies: list[str] | None = None,
        file_system_synchronized_groups: list[str] | None = None,
    ) -> None:
        """Create a native target with name, product info, phases, and dependencies."""
        super().__init__(object_key)
        self.name = name
        self.product_name = product_name
        self.product_type = product_type
        self.product_reference = product_reference
        self.build_configuration_list = build_configuration_list
        self.build_phases: list[str] = build_phases or []
        self.build_rules: list[str] = build_rules or []
        self.dependencies: list[str] = dependencies or []
        self.package_product_dependencies: list[str] | None = package_product_dependencies
        self.file_system_synchronized_groups: list[str] | None = file_system_synchronized_groups

    @property
    def comment(self) -> str | None:
        """Return the target name."""
        return self.name

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildConfigurationList"] = self.build_configuration_list
        d["buildPhases"] = self.build_phases
        d["buildRules"] = self.build_rules
        d["dependencies"] = self.dependencies
        if self.file_system_synchronized_groups:
            d["fileSystemSynchronizedGroups"] = self.file_system_synchronized_groups
        d["name"] = self.name
        if self.package_product_dependencies:
            d["packageProductDependencies"] = self.package_product_dependencies
        d["productName"] = self.product_name
        d["productReference"] = self.product_reference
        d["productType"] = self.product_type
        return d


class PBXAggregateTarget(PBXObject):
    """A target that runs scripts or aggregates other targets without building."""

    isa = "PBXAggregateTarget"

    def __init__(
        self,
        object_key: str,
        *,
        name: str,
        build_configuration_list: str,
        build_phases: list[str] | None = None,
        dependencies: list[str] | None = None,
    ) -> None:
        """Create an aggregate target with name, build config, phases, and dependencies."""
        super().__init__(object_key)
        self.name = name
        self.build_configuration_list = build_configuration_list
        self.build_phases: list[str] = build_phases or []
        self.dependencies: list[str] = dependencies or []

    @property
    def comment(self) -> str | None:
        """Return the target name."""
        return self.name

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildConfigurationList"] = self.build_configuration_list
        d["buildPhases"] = self.build_phases
        d["dependencies"] = self.dependencies
        d["name"] = self.name
        return d


class PBXProject(PBXObject):
    """The root project object in a .pbxproj file."""

    isa = "PBXProject"

    def __init__(
        self,
        object_key: str,
        *,
        build_configuration_list: str,
        main_group: str,
        targets: list[str] | None = None,
        attributes: dict[str, Any] | None = None,
        compatibility_version: str | None = "Xcode 14.0",
        development_region: str = "en",
        known_regions: list[str] | None = None,
        product_ref_group: str | None = None,
        project_dir_path: str = "",
        project_root: str = "",
        minimized_project_reference_proxies: str | None = None,
        preferred_project_object_version: str | None = None,
    ) -> None:
        """Create a project with build config, main group, targets, and attributes."""
        super().__init__(object_key)
        self.build_configuration_list = build_configuration_list
        self.main_group = main_group
        self.targets: list[str] = targets or []
        self.attributes: dict[str, Any] = attributes or {}
        self.compatibility_version = compatibility_version
        self.development_region = development_region
        self.known_regions: list[str] = known_regions or ["en", "Base"]
        self.product_ref_group = product_ref_group
        self.project_dir_path = project_dir_path
        self.project_root = project_root
        self.minimized_project_reference_proxies = minimized_project_reference_proxies
        self.preferred_project_object_version = preferred_project_object_version

    @property
    def comment(self) -> str | None:
        """Return 'Project object'."""
        return "Project object"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["attributes"] = self.attributes
        d["buildConfigurationList"] = self.build_configuration_list
        if self.compatibility_version is not None:
            d["compatibilityVersion"] = self.compatibility_version
        d["developmentRegion"] = self.development_region
        d["hasScannedForEncodings"] = "0"
        d["knownRegions"] = self.known_regions
        d["mainGroup"] = self.main_group
        if self.minimized_project_reference_proxies is not None:
            d["minimizedProjectReferenceProxies"] = self.minimized_project_reference_proxies
        if self.preferred_project_object_version is not None:
            d["preferredProjectObjectVersion"] = self.preferred_project_object_version
        if self.product_ref_group:
            d["productRefGroup"] = self.product_ref_group
        d["projectDirPath"] = self.project_dir_path
        d["projectRoot"] = self.project_root
        d["targets"] = self.targets
        return d


class PBXTargetDependency(PBXObject):
    """A dependency relationship between two targets."""

    isa = "PBXTargetDependency"

    def __init__(
        self,
        object_key: str,
        *,
        target: str,
        target_proxy: str,
    ) -> None:
        """Create a target dependency linking a target and its proxy."""
        super().__init__(object_key)
        self.target = target
        self.target_proxy = target_proxy

    @property
    def comment(self) -> str | None:
        """Return 'PBXTargetDependency'."""
        return "PBXTargetDependency"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["target"] = self.target
        d["targetProxy"] = self.target_proxy
        return d


class PBXContainerItemProxy(PBXObject):
    """A proxy for a target in another container (or the same project)."""

    isa = "PBXContainerItemProxy"

    def __init__(
        self,
        object_key: str,
        *,
        container_portal: str,
        proxy_type: str = "1",
        remote_global_id_string: str,
        remote_info: str,
    ) -> None:
        """Create a container item proxy with portal, type, and remote target info."""
        super().__init__(object_key)
        self.container_portal = container_portal
        self.proxy_type = proxy_type
        self.remote_global_id_string = remote_global_id_string
        self.remote_info = remote_info

    @property
    def comment(self) -> str | None:
        """Return 'PBXContainerItemProxy'."""
        return "PBXContainerItemProxy"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["containerPortal"] = self.container_portal
        d["proxyType"] = self.proxy_type
        d["remoteGlobalIDString"] = self.remote_global_id_string
        d["remoteInfo"] = self.remote_info
        return d


class XCBuildConfiguration(PBXObject):
    """A named build configuration (e.g., Debug or Release) with settings."""

    isa = "XCBuildConfiguration"

    def __init__(
        self,
        object_key: str,
        *,
        name: str,
        build_settings: dict[str, Any] | None = None,
    ) -> None:
        """Create a build configuration with a name and settings dict."""
        super().__init__(object_key)
        self.name = name
        self.build_settings: dict[str, Any] = build_settings or {}

    @property
    def comment(self) -> str | None:
        """Return the configuration name."""
        return self.name

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildSettings"] = self.build_settings
        d["name"] = self.name
        return d


class XCConfigurationList(PBXObject):
    """An ordered list of build configurations for a target or project."""

    isa = "XCConfigurationList"

    def __init__(
        self,
        object_key: str,
        *,
        build_configurations: list[str],
        default_configuration_name: str = "Release",
        default_configuration_is_visible: str = "0",
        comment_name: str = "",
    ) -> None:
        """Create a configuration list with build configs and a default name."""
        super().__init__(object_key)
        self.build_configurations = build_configurations
        self.default_configuration_name = default_configuration_name
        self.default_configuration_is_visible = default_configuration_is_visible
        self.comment_name = comment_name

    @property
    def comment(self) -> str | None:
        """Return a descriptive comment for the configuration list."""
        if self.comment_name:
            return f'Build configuration list for PBXNativeTarget "{self.comment_name}"'
        return "Build configuration list for PBXProject"

    def to_dict(self) -> dict[str, Any]:
        """Serialize to plist-ready dict."""
        d = super().to_dict()
        d["buildConfigurations"] = self.build_configurations
        d["defaultConfigurationIsVisible"] = self.default_configuration_is_visible
        d["defaultConfigurationName"] = self.default_configuration_name
        return d
