from typing import Optional

from .dependency import Composer
from .scope import Scope
from .scope_context import ScopeContext
from .scope_type import ScopeType, RootType
from .resolver import Resolver


class ScopeManager:
    def __init__(self):
        self._composers: set[Composer] = set()
        self._scopes: dict[str, Scope] = {}

    def add_composer(self, composer: Composer) -> None:
        if composer in self._composers:
            return
        existing_ids = {composer.id for composer in self._composers}
        if composer.id in existing_ids:
            raise Exception(f'Duplicate composer with id {composer.id}')

        self._composers.add(composer)

    def get_resolver(self, scope_id: str) -> Resolver:
        return self._get_scope(scope_id).resolver()

    def _get_scope(self, scope_id: str) -> Scope:
        if scope_id == 'root':
            self.ensure_scope(scope_id='root', scope_type=RootType())

        return self._scopes[scope_id]

    def ensure_scope(
        self,
        scope_id: str,
        scope_type: ScopeType,
        parent_id: Optional[str] = None,
        context: Optional[ScopeContext] = None
    ) -> None:
        scope = self._create_scope(scope_id, scope_type, parent_id, context or ScopeContext.empty())
        if scope_id not in self._scopes:
            self._scopes[scope_id] = scope
            return

        existing_scope = self._scopes[scope_id]
        if existing_scope.get_type() != scope_type:
            raise Exception
        if existing_scope.get_parent() is None and parent_id is not None:
            raise Exception
        if existing_scope.get_parent() is not None and existing_scope.get_parent().get_id() != parent_id:
            raise Exception
        if context is not None and existing_scope.get_context() != context:
            raise Exception

    def _create_scope(
        self,
        scope_id: str,
        scope_type: ScopeType,
        parent_id: Optional[str],
        context: ScopeContext
    ) -> Scope:
        parent = self._get_scope(parent_id) if parent_id is not None else None
        to_add = {
            composer
            for composer in self._composers
            if not composer.scope_type.supports_storing() or composer.scope_type == scope_type
        }
        scope = Scope(
            scope_id=scope_id,
            parent=parent,
            scope_type=scope_type,
            composers=to_add
        )
        scope.add_context(context.values())

        return scope

    def add_context(self, scope_id: str, values: dict[str, str]):
        self._get_scope(scope_id).add_context(values)

    def clear_scope(self, scope_id: str) -> None:
        if scope_id not in self._scopes:
            return
        deleted_scope = self._scopes.pop(scope_id)
        child_ids = {scope.get_id() for scope in self._scopes.values() if scope.get_parent() == deleted_scope}
        for child_id in child_ids:
            self.clear_scope(child_id)

    def reset(self) -> None:
        self.__init__()


scope_manager = ScopeManager()
