# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""
Types used by lookups.

These are mainly used by :py:mod:`debusine.server.collections.lookup`, but
are defined here to reduce import cycles.
"""

from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Literal, assert_never

from debusine.artifacts.models import CollectionCategory
from debusine.client.models import LookupChildType, LookupResultType
from debusine.db.models.artifacts import Artifact
from debusine.db.models.collections import Collection, CollectionItem
from debusine.db.models.permissions import PermissionUser
from debusine.db.models.work_requests import WorkRequest
from debusine.db.models.workspaces import Workspace
from debusine.tasks.models import LookupDict, LookupSingle


@dataclass(frozen=True, kw_only=True)
class LookupResult:
    """
    The result of a collection item lookup.

    None of :py:class:`Artifact`, :py:class:`Collection`, or
    :py:class:`CollectionItem` are entirely suitable return types: a lookup
    that returns a bare item needs to return the collection item, but in the
    case of a single-segment lookup there's no collection item available,
    only an artifact or collection.

    The least bad option seems to be to define a custom return type
    returning all the different things callers might need.
    """

    result_type: LookupResultType
    parent_collection_lookup: LookupSingle | None = None
    collection_item: CollectionItem | None = None
    artifact: Artifact | None = None
    collection: Collection | None = None

    @classmethod
    def create(
        cls,
        result_type: LookupResultType,
        parent_collection_lookup: LookupSingle | None = None,
        collection_item: CollectionItem | None = None,
        artifact: Artifact | None = None,
        collection: Collection | None = None,
    ) -> "LookupResult":
        """Instantiate the right LookupResult subclass based on result type."""
        match result_type:
            case LookupResultType.ARTIFACT:
                return LookupResultArtifact(
                    result_type=result_type,
                    parent_collection_lookup=parent_collection_lookup,
                    collection_item=collection_item,
                    artifact=artifact,
                )
            case LookupResultType.BARE:
                return LookupResultBare(
                    result_type=result_type,
                    parent_collection_lookup=parent_collection_lookup,
                    collection_item=collection_item,
                )
            case LookupResultType.COLLECTION:
                return LookupResultCollection(
                    result_type=result_type,
                    parent_collection_lookup=parent_collection_lookup,
                    collection_item=collection_item,
                    collection=collection,
                )
            case _ as unreachable:
                assert_never(unreachable)


class LookupResultBare(LookupResult):
    """
    A collection item lookup containing bare data.

    Used to assist type annotations.
    """

    result_type: Literal[LookupResultType.BARE]
    parent_collection_lookup: LookupSingle
    collection_item: CollectionItem
    artifact: None
    collection: None


class LookupResultArtifact(LookupResult):
    """
    A collection item lookup containing an artifact.

    Used to assist type annotations.
    """

    result_type: Literal[LookupResultType.ARTIFACT]
    artifact: Artifact
    collection: None


class LookupResultCollection(LookupResult):
    """
    A collection item lookup containing a collection.

    Used to assist type annotations.
    """

    result_type: Literal[LookupResultType.COLLECTION]
    artifact: None
    collection: Collection


class LookupCacheGroup[Lookup, Result](
    dict[
        tuple[
            Lookup,
            Workspace,
            PermissionUser,
            CollectionCategory | None,
            WorkRequest | None,
            LookupChildType,
        ],
        Result,
    ]
):
    """A cache for a particular kind of lookup."""


@dataclass(slots=True)
class LookupCache:
    """A cache of lookups that have been performed in the current context."""

    single: LookupCacheGroup[LookupSingle, LookupResult] = field(
        default_factory=LookupCacheGroup
    )
    dictionary: LookupCacheGroup[LookupDict, Sequence[LookupResult]] = field(
        default_factory=LookupCacheGroup
    )
