"""Proactive chat delay sampling and burst helpers — pure functions (PC11-M2)."""

from __future__ import annotations

import random
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Literal

from niannian.chat.proactive_scope import ProactivePersonaPolicy

Scenario = Literal["empty", "post_chat"]
BurstCfg = Literal["once", "few", "unlimited"]

DELAY_MIN_SEC = 12.0
DELAY_MAX_SEC = 600.0
COMPANION_BURST_RESET_SEC = 900
UNLIMITED_BURST_CAP = 999

MODE_FACTORS: dict[str, float] = {
    "companion": 0.82,
    "default": 1.0,
    "normal": 1.0,
    "auto": 1.0,
    "education": 1.08,
}

SCENE_RANGES: dict[str, tuple[float, float]] = {
    "empty": (70.0, 150.0),
    "post_chat_light": (25.0, 55.0),
    "post_chat_heavy": (60.0, 140.0),
}

POST_CHAT_HEAVY_THRESHOLD = 1.2


@dataclass(frozen=True)
class ProactiveSchedulerInput:
    scenario: Scenario
    recent_chars: int
    recent_turns: int
    last_user_gap_sec: float | None
    chat_mode: str
    profile: dict[str, Any]
    vp_policy: ProactivePersonaPolicy | None = None
    disturb_lock: str = "auto"


def compute_conv_load(
    recent_chars: int,
    recent_turns: int,
    last_user_gap_sec: float | None,
) -> float:
    """Conversation load: longer chats raise load; a fresh gap lowers it."""
    chars = max(0, int(recent_chars))
    turns = max(0, int(recent_turns))
    volume = 1.0 + (chars / 2500.0) + (turns * 0.07)

    if last_user_gap_sec is None:
        gap_factor = 1.0
    else:
        gap = max(0.0, float(last_user_gap_sec))
        if gap < 60.0:
            gap_factor = max(0.6, 1.0 - (60.0 - gap) / 150.0)
        elif gap < 300.0:
            gap_factor = 1.0
        else:
            gap_factor = min(1.25, 1.0 + (gap - 300.0) / 1200.0)

    return max(0.5, min(volume * gap_factor, 3.0))


def _mode_factor(chat_mode: str) -> float:
    key = str(chat_mode or "default").strip().lower()
    return MODE_FACTORS.get(key, 1.0)


def _user_factor(profile: dict[str, Any], disturb_lock: str) -> float:
    interval = float(profile.get("interval_multiplier", 1.0))
    bias = float(profile.get("explicit_bias", 0))
    factor = interval * (1.0 + bias * 0.12)
    if str(disturb_lock).strip().lower() == "quiet":
        factor = max(factor, 1.8)
    return max(0.25, factor)


def _vp_delay_factor(vp_policy: ProactivePersonaPolicy | None) -> float:
    if vp_policy is None:
        return 1.0
    return 2.0 - float(vp_policy.interruptiveness)


def _scene_range(scenario: Scenario, conv_load: float) -> tuple[float, float]:
    if scenario == "empty":
        return SCENE_RANGES["empty"]
    if conv_load > POST_CHAT_HEAVY_THRESHOLD:
        return SCENE_RANGES["post_chat_heavy"]
    return SCENE_RANGES["post_chat_light"]


def _random_jitter(rng: random.Random) -> float:
    return rng.uniform(0.9, 1.1)


def sample_delay_seconds(input: ProactiveSchedulerInput, rng: random.Random) -> float:
    conv_load = compute_conv_load(
        input.recent_chars,
        input.recent_turns,
        input.last_user_gap_sec,
    )
    scene_min, scene_max = _scene_range(input.scenario, conv_load)
    delay_base = rng.uniform(scene_min, scene_max) * _random_jitter(rng)
    delay = (
        delay_base
        * conv_load
        * _user_factor(input.profile, input.disturb_lock)
        * _mode_factor(input.chat_mode)
        * _vp_delay_factor(input.vp_policy)
    )
    return max(DELAY_MIN_SEC, min(delay, DELAY_MAX_SEC))


def _base_burst_count(burst_cfg: str, rng: random.Random | None) -> int:
    key = str(burst_cfg or "few").strip().lower()
    if key == "once":
        return 1
    if key == "unlimited":
        return UNLIMITED_BURST_CAP
    picker = rng if rng is not None else random.Random()
    return picker.randint(2, 3)


def initial_burst_count(
    burst_cfg: str,
    chat_mode: str,
    vp_policy: ProactivePersonaPolicy | None,
    rng: random.Random | None = None,
) -> int:
    """Initial burst allowance for a quiet period."""
    _ = chat_mode  # reserved for mode-specific burst curves in later tasks
    base = _base_burst_count(burst_cfg, rng)
    if vp_policy is None or base >= UNLIMITED_BURST_CAP:
        return base
    scaled = round(base * float(vp_policy.interruptiveness))
    return max(1, scaled)


def should_reset_burst(
    last_user_at: datetime | None,
    now: datetime,
    chat_mode: str,
) -> bool:
    """Companion-only: reset burst after 15 minutes without a user reply."""
    if str(chat_mode or "").strip().lower() != "companion":
        return False
    if last_user_at is None:
        return False
    gap_sec = (now - last_user_at).total_seconds()
    return gap_sec >= COMPANION_BURST_RESET_SEC
