from __future__ import annotations

from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, cast

from attr import define

if TYPE_CHECKING:
    from ..enums.ClassIDType import ClassIDType
    from ..files.ObjectReader import ObjectReader
    from ..files.SerializedFile import SerializedFile

T = TypeVar("T")


@define(slots=True, kw_only=True)
class PPtr(Generic[T]):
    m_FileID: int
    m_PathID: int
    assetsfile: Optional[SerializedFile] = None

    @property
    def file_id(self) -> int:
        # backwards compatibility
        return self.m_FileID

    @property
    def path_id(self) -> int:
        # backwards compatibility
        return self.m_PathID

    @property
    def type(self) -> ClassIDType:
        return self.deref().type

    # backwards compatibility - to be removed in UnityPy 2
    def read(self):
        return self.deref_parse_as_object()

    # backwards compatibility - to be removed in UnityPy 2
    def read_typetree(self):
        return self.deref_parse_as_dict()

    def deref(self, assetsfile: Optional[SerializedFile] = None) -> ObjectReader[T]:
        assetsfile = assetsfile or self.assetsfile

        if assetsfile is None:
            raise ValueError("PPtr can't deref without an assetsfile!")

        if self.m_PathID == 0:
            raise ValueError("PPtr can't deref with m_PathID == 0!")

        assetsfile_dst = None
        if self.m_FileID == 0:
            assetsfile_dst = assetsfile or self.assetsfile
        else:
            # resolve file id to external name
            external_id = self.m_FileID - 1
            if external_id >= len(assetsfile.externals):
                raise FileNotFoundError("Failed to resolve pointer - invalid m_FileID!")
            external = assetsfile.externals[external_id]

            # resolve external name to assetsfile
            container = assetsfile.parent
            if container is None:
                # TODO - use default fs
                raise FileNotFoundError(f"PPtr points to {external.path} but no container is set!")

            external_clean_path = external.path
            if external_clean_path.startswith("archive:/"):
                external_clean_path = external_clean_path[9:]
            if external_clean_path.startswith("assets/"):
                external_clean_path = external_clean_path[7:]
            external_clean_path = external_clean_path.rsplit("/")[-1].lower()

            for key, file in container.files.items():
                if key.lower() == external_clean_path:
                    assetsfile_dst = file
                    break
            else:
                env = assetsfile.environment
                cab = env.find_file(external_clean_path)
                if cab:
                    assetsfile_dst = cab
                else:
                    raise FileNotFoundError(f"Failed to resolve pointer - {external.path} not found!")

        if assetsfile_dst is None:
            raise FileNotFoundError(f"Failed to resolve pointer - {self.m_FileID} not found!")

        return cast("ObjectReader[T]", assetsfile_dst.objects[self.m_PathID])

    def deref_parse_as_object(self, assetsfile: Optional[SerializedFile] = None) -> T:
        return self.deref(assetsfile).parse_as_object()

    def deref_parse_as_dict(self, assetsfile: Optional[SerializedFile] = None) -> dict[str, Any]:
        return self.deref(assetsfile).parse_as_dict()

    def __bool__(self):
        return self.m_PathID != 0

    def __hash__(self) -> int:
        return hash((self.m_FileID, self.m_PathID))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, PPtr):
            return False
        return self.m_FileID == other.m_FileID and self.m_PathID == other.m_PathID


__all__ = [
    "PPtr",
]
