# -*- coding: utf-8 -*-

"""Core components for storing and executing issue solvers."""

from __future__ import annotations

__all__ = [
    'SolvedError', 'SolverCollection',
    'ConfigurationContext', 'CONFIGURATION_POOL',
    'ConflictContext', 'CONFLICT_POOL',
]

import typing
from anaconda_navigator.utils import solvers as common_solvers

if typing.TYPE_CHECKING:
    import typing_extensions
    from anaconda_navigator.api import anaconda_api
    from anaconda_navigator.config import user as user_config

    TContext_contra = typing.TypeVar('TContext_contra', contravariant=True)

    class Solver(typing_extensions.Protocol[TContext_contra]):  # pylint: disable=too-few-public-methods
        """
        Common interface for issue solvers.

        Each solver should:

        - check if there is an issue
        - perform corresponding actions to fix the issue
        - report what was wrong and what was changed
        """

        def __call__(self, context: TContext_contra) -> typing.Union[None, SolvedError, typing.Iterable[SolvedError]]:
            """
            Solve an issue if it is detected.

            :param context: Data structure with data that should be checked for issue and updated if one is found.
            :return: Message(s) about what was fixed.
            """


TInvContext = typing.TypeVar('TInvContext')


class SolvedError(typing.NamedTuple):
    """Details on solved issue."""

    caption: str
    message: str
    tags: typing.Sequence[str]


class SolverCollection(common_solvers.SolverCollection['Solver[TInvContext]'], typing.Generic[TInvContext]):
    """Collection of issue solvers."""

    __slots__ = ()

    def solve(
            self,
            context: TInvContext,
            *,
            tags: typing.Union[None, str, typing.Iterable[str]] = None,
    ) -> typing.Iterator[SolvedError]:
        """
        Detect and solve issues for the `context`.

        :param context: Context to check for issues and fix them.
        :param tags: Limit to issue solvers with specific tags.
        :return: Iterator of details about solved issues.
        """
        solver_record: common_solvers.SolverRecord['Solver[TInvContext]']
        for solver_record in self.only(tags=tags):
            result: typing.Union[None, SolvedError, typing.Iterable[SolvedError]] = solver_record.solver(context)
            if result is None:
                continue
            if isinstance(result, SolvedError):
                yield result
            else:
                yield from result


class ConfigurationContext(typing.NamedTuple):
    """Context for fixing a general configuration issues."""

    api: 'anaconda_api._AnacondaAPI'
    config: 'user_config.UserConfig'


CONFIGURATION_POOL: 'typing_extensions.Final[SolverCollection[ConfigurationContext]]' = SolverCollection()


class ConflictContext(typing.NamedTuple):
    """Context for fixing issues with conflicting Navigator and Conda configurations."""

    api: 'anaconda_api._AnacondaAPI'
    config: 'user_config.UserConfig'
    conda_info: typing.Any


CONFLICT_POOL: 'typing_extensions.Final[SolverCollection[ConflictContext]]' = SolverCollection()
