from __future__ import annotations

import random

from .focus_types import FocusCandidate

VALID_FOCUS_POLICIES = {
    "energy_first",
    "random_candidate",
    "sequential_scan",
    "novelty_first",
    "familiarity_first",
}


def normalize_focus_policy_name(policy_name: str | None) -> str:
    normalized_name = str(policy_name or "energy_first").strip().lower()
    if normalized_name not in VALID_FOCUS_POLICIES:
        raise ValueError(f"Unsupported focus policy: {policy_name}")
    return normalized_name


def select_candidate(
    candidates: list[FocusCandidate],
    policy_name: str,
    rng: random.Random,
) -> FocusCandidate:
    normalized_name = normalize_focus_policy_name(policy_name)
    if not candidates:
        raise ValueError("Focus selection requires at least one candidate.")

    ordered_candidates = sorted(candidates, key=_stable_candidate_key)
    if normalized_name == "random_candidate":
        return ordered_candidates[rng.randrange(len(ordered_candidates))]
    if normalized_name == "sequential_scan":
        return min(ordered_candidates, key=_sequential_key)
    if normalized_name == "novelty_first":
        return min(ordered_candidates, key=_novelty_key)
    if normalized_name == "familiarity_first":
        return min(ordered_candidates, key=_familiarity_key)
    return min(ordered_candidates, key=_energy_key)


def _stable_candidate_key(candidate: FocusCandidate) -> tuple[int, int, float]:
    if candidate.point is not None:
        return (int(candidate.point[0]), int(candidate.point[1]), -float(candidate.energy))
    if candidate.scalar_value is not None:
        return (0, 0, -float(candidate.scalar_value))
    return (0, 0, -float(candidate.energy))


def _energy_key(candidate: FocusCandidate) -> tuple[float, int, int]:
    point_value = candidate.point or (0, 0)
    return (-float(candidate.energy), int(point_value[0]), int(point_value[1]))


def _sequential_key(candidate: FocusCandidate) -> tuple[int, int, float]:
    point_value = candidate.point or (0, 0)
    return (int(point_value[0]), int(point_value[1]), -float(candidate.energy))


def _novelty_key(candidate: FocusCandidate) -> tuple[float, float, int, int]:
    point_value = candidate.point or (0, 0)
    return (
        -float(candidate.novelty_score),
        -float(candidate.energy),
        int(point_value[0]),
        int(point_value[1]),
    )


def _familiarity_key(candidate: FocusCandidate) -> tuple[float, float, int, int]:
    point_value = candidate.point or (0, 0)
    return (
        -float(candidate.familiarity_score),
        -float(candidate.energy),
        int(point_value[0]),
        int(point_value[1]),
    )
