#!/usr/bin/env python
"""
Processor functions
"""

from __future__ import annotations

from logging import getLogger
from typing import TYPE_CHECKING, Any

from .rules import RemoveMatch, Rule
from .utils import IdentitySet

if TYPE_CHECKING:
    from collections.abc import Callable

    from .match import Match, Matches

log = getLogger(__name__).log

DEFAULT = "__default__"

POST_PROCESS: int = -2048
PRE_PROCESS: int = 2048


def _default_conflict_solver(match: Match, conflicting_match: Match) -> Match | None:
    """
    Default conflict solver for matches, shorter matches if they conflicts with longer ones

    :param conflicting_match:
    :type conflicting_match:
    :param match:
    :type match:
    :return:
    :rtype:
    """
    if len(conflicting_match.initiator) < len(match.initiator):
        return conflicting_match
    if len(match.initiator) < len(conflicting_match.initiator):
        return match
    return None


class ConflictSolver(Rule):
    """
    Remove conflicting matches.
    """

    priority = PRE_PROCESS

    consequence = RemoveMatch

    @property
    def default_conflict_solver(self) -> Callable[[Match, Match], Match | None]:
        """
        Default conflict solver to use.
        """
        return _default_conflict_solver

    def when(self, matches: Matches, context: dict[str, Any] | None) -> Any:
        to_remove_matches = IdentitySet()

        public_matches = [match for match in matches if not match.private]
        public_matches.sort(key=len)

        for match in public_matches:
            conflicting_matches = matches.conflicting(match)

            if conflicting_matches:
                # keep the match only if it's the longest
                conflicting_matches = [
                    conflicting_match for conflicting_match in conflicting_matches if not conflicting_match.private
                ]
                conflicting_matches.sort(key=len)

                for conflicting_match in conflicting_matches:
                    conflict_solvers = [(self.default_conflict_solver, False)]

                    if match.conflict_solver:
                        conflict_solvers.append((match.conflict_solver, False))
                    if conflicting_match.conflict_solver:
                        conflict_solvers.append((conflicting_match.conflict_solver, True))

                    for conflict_solver, reverse in reversed(conflict_solvers):
                        if reverse:
                            to_remove = conflict_solver(conflicting_match, match)
                        else:
                            to_remove = conflict_solver(match, conflicting_match)
                        if to_remove == DEFAULT:
                            continue
                        if to_remove and to_remove not in to_remove_matches:
                            both_matches = [match, conflicting_match]
                            both_matches.remove(to_remove)
                            to_keep = both_matches[0]

                            if to_keep not in to_remove_matches:
                                log(
                                    self.log_level,
                                    "Conflicting match %s will be removed in favor of match %s",
                                    to_remove,
                                    to_keep,
                                )

                                to_remove_matches.add(to_remove)
                        break
        return to_remove_matches


class PrivateRemover(Rule):
    """
    Removes private matches rule.
    """

    priority = POST_PROCESS

    consequence = RemoveMatch

    def when(self, matches: Matches, context: dict[str, Any] | None) -> Any:
        return [match for match in matches if match.private]
