from __future__ import annotations

from typing import cast

import msgspec

from ..replay.types import PackedPlayerInput
from .lockstep_protocol import INPUT_DELAY_TICKS, InputBatch, PauseState, TickFrame
from .lockstep_state import ClientLockstepState, HostLockstepState, HostReadyTick


class ResyncFailureTracker(msgspec.Struct):
    max_failures_per_match: int = 2
    failures: int = 0

    def note_failure(self) -> bool:
        self.failures += 1
        return int(self.failures) >= int(self.max_failures_per_match)

    def reset(self) -> None:
        self.failures = 0


class HostLanAdapter(msgspec.Struct):
    player_count: int
    input_delay_ticks: int = INPUT_DELAY_TICKS
    input_stall_timeout_ms: int = 250
    local_slot_index: int = 0
    lockstep: HostLockstepState = cast(HostLockstepState, None)
    resync_failures: ResyncFailureTracker = msgspec.field(default_factory=ResyncFailureTracker)

    def __post_init__(self) -> None:
        self.lockstep = HostLockstepState(
            player_count=int(self.player_count),
            input_delay_ticks=int(self.input_delay_ticks),
            input_stall_timeout_ms=int(self.input_stall_timeout_ms),
        )

    def submit_local_input(self, *, tick_index: int, packed_input: PackedPlayerInput) -> None:
        self.lockstep.submit_input_sample(
            slot_index=int(self.local_slot_index),
            tick_index=int(tick_index),
            packed_input=list(packed_input),
        )

    def submit_remote_batch(self, batch: InputBatch) -> None:
        self.lockstep.submit_input_batch(batch)

    def emit_ready_frames(
        self,
        *,
        now_ms: int,
    ) -> list[HostReadyTick]:
        return self.lockstep.pop_ready_frames(
            now_ms=int(now_ms),
        )

    def update_pause_state(self, *, now_ms: int) -> PauseState | None:
        return self.lockstep.update_pause_state(now_ms=int(now_ms))


class ClientLanAdapter(msgspec.Struct):
    local_slot_index: int
    input_delay_ticks: int = INPUT_DELAY_TICKS
    input_stall_timeout_ms: int = 250
    lockstep: ClientLockstepState = cast(ClientLockstepState, None)
    resync_failures: ResyncFailureTracker = msgspec.field(default_factory=ResyncFailureTracker)

    def __post_init__(self) -> None:
        self.lockstep = ClientLockstepState(
            local_slot_index=int(self.local_slot_index),
            input_delay_ticks=int(self.input_delay_ticks),
            input_stall_timeout_ms=int(self.input_stall_timeout_ms),
        )

    def queue_local_input(self, packed_input: PackedPlayerInput) -> InputBatch:
        return self.lockstep.queue_local_input(list(packed_input))

    def ingest_tick_frame(self, frame: TickFrame, *, now_ms: int) -> None:
        self.lockstep.ingest_tick_frame(
            frame,
            now_ms=int(now_ms),
        )

    def pop_tick_frame(self) -> TickFrame | None:
        return self.lockstep.pop_canonical_frame()

    def update_pause_state(self, *, now_ms: int) -> PauseState | None:
        return self.lockstep.update_pause_state(now_ms=int(now_ms))
