"""Shared execution of canonical operator words across a network.

The executor owns the network-stage schedule used by SDK and physics probes.
Validated words reject blocked requests and use immutable all-target proposals.
Recursivity commits only its deduplicated node-level advisory; explicit delayed
EPI mixing remains a separate network operation. The standalone stage dispatcher
can instead receive a context allowing grammar replacement; that route retains
its operator-major Gauss--Seidel schedule inside a failure-atomic transaction.
"""

from __future__ import annotations

import warnings
from collections.abc import Sequence
from dataclasses import replace
from operator import index as integer_index
from typing import TYPE_CHECKING, Any, Callable

import networkx as nx

from ..errors import TNFRValueError
from ._reception_kernel import RECEPTION_NO_SOURCES_WARNING_PATTERN

if TYPE_CHECKING:
    from .network_stage import GraphTransactionSnapshot, NetworkStageResult


def _validate_cycles(cycles: Any) -> int:
    """Return a nonnegative integer cycle count without boolean coercion."""

    if isinstance(cycles, bool):
        raise TNFRValueError(
            "cycles must be a nonnegative integer",
            context={"cycles": repr(cycles), "reason": "boolean_not_allowed"},
        )
    try:
        count = integer_index(cycles)
    except (OverflowError, TypeError, ValueError) as exc:
        raise TNFRValueError(
            "cycles must be a nonnegative integer",
            context={"cycles": repr(cycles), "reason": "not_an_integer"},
        ) from exc
    if count < 0:
        raise TNFRValueError(
            "cycles must be a nonnegative integer",
            context={"cycles": count, "reason": "negative"},
        )
    return count


def preflight_network_mutation_sequence(
    graph: nx.Graph,
    names: list[str],
    *,
    cycles: int,
) -> None:
    """Reject stale Mutation evidence after validating a nonnegative cycle count."""

    cycles = _validate_cycles(cycles)
    if cycles == 0:
        return

    from ._mutation_gate import validate_mutation_runtime_gate
    from .operator_contracts import StateChannel, contract_for
    from .preconditions import OperatorPreconditionError

    try:
        contracts = [contract_for(name) for name in names]
    except KeyError:
        # Preserve the registry diagnostic for an unknown operator.
        return
    mutation_positions = [
        index for index, contract in enumerate(contracts) if contract.glyph == "ZHIR"
    ]
    if not mutation_positions:
        return

    for node in list(graph.nodes()):
        node_data = graph.nodes[node]
        if node_data.get("epi_time_history") is not None:
            for mutation_index in mutation_positions:
                prior_contracts = contracts[:mutation_index]
                if cycles > 1:
                    prior_contracts = contracts + prior_contracts
                epi_writers = [
                    contract.english_name
                    for contract in prior_contracts
                    if contract.primary_channel is StateChannel.EPI
                ]
                if epi_writers:
                    raise TNFRValueError(
                        "Mutation sequence preflight failed.",
                        context={
                            "node": node,
                            "reason": "physical_mutation_evidence_would_be_stale",
                            "mutation_index": mutation_index,
                            "prior_epi_channel_operators": epi_writers,
                        },
                        suggestion=(
                            "Advance and record physical EPI time evidence after the "
                            "preceding EPI-channel operations, or use explicitly "
                            "legacy unit-step evidence."
                        ),
                    )
        try:
            validate_mutation_runtime_gate(node_data, graph.graph)
        except OperatorPreconditionError as exc:
            raise TNFRValueError(
                "Mutation sequence preflight failed.",
                context={
                    "node": node,
                    "reason": exc.reason,
                    "sequence": names,
                },
                suggestion=(
                    "Provide valid current Mutation evidence that strictly crosses "
                    "the configured threshold and an active structural frequency."
                ),
            ) from exc


def execute_network_operator_stage(
    graph: nx.Graph,
    operator: Any,
    targets: Sequence[Any],
    *,
    sequence_context: Any = None,
    compute_delta_nfr: Any = None,
    include_epi_jump_certificate: bool = False,
    transaction_snapshot: "GraphTransactionSnapshot | None" = None,
) -> "NetworkStageResult":
    """Dispatch one fixed-target stage through the canonical network route.

    Callers own whole-word grammar validation and supply its per-step context.
    This helper centralizes stage selection only; every routed implementation
    retains its own preconditions, immutable proposal boundary and rollback.
    Recursivity remains advisory-only here. Delayed EPI mixing is exposed
    separately by apply_network_remesh.
    """

    if type(include_epi_jump_certificate) is not bool:
        raise TypeError("include_epi_jump_certificate must be a bool")

    from .network_stage import (
        POINTWISE_EPI_JUMP_GLYPHS,
        POINTWISE_TWO_PHASE_GLYPHS,
        execute_coupling_stage,
        execute_dissonance_stage,
        execute_neighbor_stage,
        execute_operator_major_stage,
        execute_pointwise_stage,
        execute_recursivity_stage,
        execute_self_organization_stage,
    )

    kwargs = {
        "sequence_context": sequence_context,
        "compute_delta_nfr": compute_delta_nfr,
    }
    if transaction_snapshot is not None:
        kwargs["transaction_snapshot"] = transaction_snapshot
    if operator.name in {"reception", "resonance"}:
        return execute_neighbor_stage(
            graph,
            operator,
            targets,
            include_epi_jump_certificate=include_epi_jump_certificate,
            **kwargs,
        )

    def unsupported(result: "NetworkStageResult") -> "NetworkStageResult":
        if not include_epi_jump_certificate:
            return result
        if result.epi_jump_certificate is not None:
            return result
        if result.epi_jump_certificate_abstention_reason is not None:
            return result
        return replace(
            result,
            epi_jump_certificate_abstention_reason=(
                "epi_jump_certificate_unavailable_for_glyph:" f"{operator.glyph.value}"
            ),
        )

    if operator.name == "coupling":
        return unsupported(execute_coupling_stage(graph, operator, targets, **kwargs))
    if operator.name == "dissonance":
        return unsupported(execute_dissonance_stage(graph, operator, targets, **kwargs))
    if operator.name == "self_organization":
        return unsupported(
            execute_self_organization_stage(
                graph,
                operator,
                targets,
                **kwargs,
            )
        )
    if operator.name == "recursivity":
        return unsupported(
            execute_recursivity_stage(graph, operator, targets, **kwargs)
        )
    if operator.glyph in POINTWISE_TWO_PHASE_GLYPHS:
        if include_epi_jump_certificate and operator.glyph in POINTWISE_EPI_JUMP_GLYPHS:
            return execute_pointwise_stage(
                graph,
                operator,
                targets,
                epi_jump_fixed_support_declared=True,
                _allow_epi_jump_certificate_abstention=True,
                **kwargs,
            )
        return unsupported(execute_pointwise_stage(graph, operator, targets, **kwargs))
    return unsupported(execute_operator_major_stage(graph, operator, targets, **kwargs))


def run_network_sequence(
    graph: nx.Graph,
    operator_names: list[str],
    *,
    cycles: int = 1,
    validate: bool = True,
    suppress_birth_warnings: bool = False,
    context: dict[str, Any] | None = None,
    on_step: Callable[[str], None] | None = None,
) -> None:
    """Evolve the invocation-time targets with the operator-major stage schedule.

    The graph's node set is captured once and retained across every stage and
    cycle in this call. Children created by THOL are not added to that target
    set; a later invocation captures its own current node set.

    When every target retains the requested glyph, all thirteen operators read one
    immutable stage snapshot, propose every target, validate every proposal,
    and commit the stage atomically. EN, IL and RA read neighbours; UM merges
    phase/topology proposals; OZ reduces local and propagated pressure; THOL
    merges child support and hierarchy; AL, SHA, VAL, NUL, ZHIR and NAV use
    pointwise proposals; and REMESH deduplicates its graph advisory. Grammar
    replacements update in graph iteration order inside a stage-level rollback
    boundary.
    Complete-word grammar validation is optional for compatibility; live
    operator preconditions remain active in both modes.
    ``cycles`` must be a non-boolean nonnegative integer; zero validates the
    declared word without applying a stage.
    """

    from .grammar_execution import ValidatedSequence
    from .registry import get_operator_class

    cycles = _validate_cycles(cycles)
    names = list(operator_names)
    if not names:
        return
    ops = [get_operator_class(name)() for name in names]
    execution_word = ValidatedSequence(ops, context=context) if validate else None
    preflight_network_mutation_sequence(graph, names, cycles=cycles)
    compute = graph.graph.get("compute_delta_nfr")
    nodes = list(graph.nodes())
    with warnings.catch_warnings():
        if suppress_birth_warnings:
            warnings.filterwarnings(
                "ignore",
                message=RECEPTION_NO_SOURCES_WARNING_PATTERN,
            )
        for _ in range(cycles):
            for index, operator in enumerate(ops):
                sequence_step = (
                    None if execution_word is None else execution_word.step(index)
                )
                execute_network_operator_stage(
                    graph,
                    operator,
                    nodes,
                    sequence_context=sequence_step,
                    compute_delta_nfr=compute,
                )
                if on_step is not None:
                    on_step(operator.name)


__all__ = [
    "execute_network_operator_stage",
    "preflight_network_mutation_sequence",
    "run_network_sequence",
]
