from collections.abc import Mapping, Sequence
from enum import Enum
from typing import Callable, Iterator, Literal, NamedTuple, Protocol

from docplex.mp.model import Model
from docplex.mp.solution import SolveSolution

class ProgressData(NamedTuple):
    has_incumbent: bool
    current_objective: float | None
    best_bound: float
    mip_gap: float | None
    current_nb_iterations: int
    current_nb_nodes: int
    remaining_nb_nodes: int
    time: float
    det_time: float

class ProgressClock(Enum):
    All = ...
    Solutions = ...
    BestBound = ...
    Objective = ...
    Gap = ...

    @property
    def listens_to_solution(self) -> bool: ...

class _Filter(Protocol):
    def accept(self, data: ProgressData) -> bool: ...
    def reset(self) -> None: ...

class _AbstractProgressListener:
    def abort(self) -> None: ...
    def notify_start(self) -> None: ...

class ProgressListener(_AbstractProgressListener):
    """The base class for progress listeners."""

    def __init__(
        self,
        clock_ar: ProgressClock = ...,
        absdiff: float | None = None,
        reldiff: float | None = None,
    ) -> None: ...
    @property
    def clock(self) -> ProgressClock: ...
    @property
    def abs_diff(self) -> float: ...
    @property
    def relative_diff(self) -> float: ...
    @property
    def current_progress_data(self) -> ProgressData | None: ...
    def accept(self, pdata: ProgressData) -> bool: ...
    def requires_solution(self) -> bool: ...
    def notify_solution(self, s: SolveSolution) -> None: ...
    def notify_jobid(self, jobid: int) -> None: ...
    def notify_end(self, status: int, objective: float | None) -> None: ...
    def notify_progress(self, progress_data: ProgressData) -> None: ...

class FilterAcceptAll:
    def accept(self, pdata: ProgressData) -> bool: ...
    def reset(self) -> None: ...

class FilterAcceptAllSolutions(object):
    def accept(self, pdata: ProgressData) -> bool: ...
    def reset(self) -> None: ...

class Watcher:
    def __init__(
        self,
        name: str,
        absdiff: float,
        reldiff: float,
        update_fn: Callable[[ProgressData], bool],
    ) -> None: ...
    def reset(self) -> None: ...
    def accept(self, progress_data: ProgressData) -> bool: ...
    def sync(self, pdata: ProgressData) -> None: ...

class ClockFilter:
    def __init__(
        self,
        level: ProgressClock,
        obj_absdiff: float,
        bbound_absdiff: float,
        obj_reldiff: float = ...,
        bbound_reldiff: float = ...,
        node_delta: int = ...,
    ) -> None: ...
    def accept(self, progress_data: ProgressData) -> bool: ...
    def peek(self, progress_data: ProgressData) -> Watcher | None: ...
    def reset(self) -> None: ...

class TextProgressListener(ProgressListener):
    def __init__(
        self,
        clock: ProgressClock = ...,
        gap_fmt: str | None = ...,
        obj_fmt: str | None = ...,
        absdiff: float | None = ...,
        reldiff: float | None = ...,
    ) -> None: ...
    def notify_start(self) -> None: ...
    def notify_progress(self, progress_data: ProgressData) -> None: ...

class ProgressDataRecorder(ProgressListener):
    """A specialized class of ProgressListener, which collects all ProgressData it receives."""

    def __init__(
        self,
        clock: ProgressClock = ...,
        absdiff: float | None = ...,
        reldiff: float | None = ...,
    ) -> None: ...
    def notify_start(self) -> None: ...
    def notify_progress(self, progress_data: ProgressData) -> None: ...
    @property
    def number_of_records(self) -> int: ...
    @property
    def iter_recorded(self) -> Iterator[ProgressData]: ...
    @property
    def recorded(self) -> Sequence[ProgressData]: ...

class SolutionListener(ProgressListener):
    def __init__(
        self,
        clock: Literal[
            ProgressClock.Solutions, ProgressClock.Objective, ProgressClock.Gap
        ] = ...,
        absdiff: float | None = ...,
        reldiff: float | None = ...,
    ) -> None: ...
    def requires_solution(self) -> bool: ...
    def notify_solution(self, s: SolveSolution) -> None: ...
    def notify_start(self) -> None: ...
    def accept(self, pdata: ProgressData) -> bool: ...

class SolutionRecorder(SolutionListener):
    def __init__(
        self,
        clock: Literal[
            ProgressClock.Solutions, ProgressClock.Objective, ProgressClock.Gap
        ] = ...,
        absdiff: float | None = ...,
        reldiff: float | None = ...,
    ) -> None: ...
    def notify_start(self) -> None: ...
    def notify_solution(self, s: SolveSolution) -> None: ...
    def iter_solutions(self) -> Iterator[SolveSolution]: ...
    @property
    def number_of_solutions(self) -> int: ...
    @property
    def current_solution(self) -> SolveSolution | None: ...

class FunctionalSolutionListener(SolutionListener):
    def __init__(
        self,
        solution_fn: Callable[[SolveSolution], None],
        clock: Literal[
            ProgressClock.Solutions, ProgressClock.Objective, ProgressClock.Gap
        ] = ...,
        absdiff: float | None = ...,
        reldiff: float | None = ...,
    ) -> None: ...
    def notify_solution(self, s: SolveSolution) -> None: ...

class KpiListener(SolutionListener):
    def __init__(
        self,
        model: Model,
        clock: Literal[
            ProgressClock.Solutions, ProgressClock.Objective, ProgressClock.Gap
        ] = ...,
        absdiff: float | None = ...,
        reldiff: float | None = ...,
    ) -> None: ...
    def publish(self, kpi_dict: Mapping[str, float | str]) -> None: ...
    def notify_solution(self, s: SolveSolution) -> None: ...

class KpiPrinter(KpiListener):
    def __init__(
        self,
        model: Model,
        clock: Literal[
            ProgressClock.Solutions, ProgressClock.Objective, ProgressClock.Gap
        ] = ...,
        absdiff: float | None = ...,
        reldiff: float | None = ...,
        kpi_format: str = ...,
    ) -> None: ...
    def publish(self, kpi_dict: Mapping[str, float | str]) -> None: ...