"""Scroll collection state and target generation.

The actual scroll state machine runs in JavaScript (wheel events are
client-side). This module handles server-side target generation and
trace persistence.
"""
from __future__ import annotations

import logging
import random

from tools.config import SCROLL_MODES

logger = logging.getLogger(__name__)


class ScrollCollector:
    """Manages scroll collection sessions."""

    def __init__(
        self,
        mode: str,
        count: int,
        page_height: int = 10000,
        viewport_height: int = 900,
    ):
        if mode not in SCROLL_MODES:
            raise ValueError(f"Unknown mode: {mode}. Use: {', '.join(SCROLL_MODES)}")
        self.mode = mode
        self.count = count
        self.page_height = page_height
        self.viewport_height = viewport_height
        self.collected = 0

        cfg = SCROLL_MODES[mode]
        self.dist_min = cfg.dist_min
        self.dist_max = cfg.dist_max
        self.success_radius = cfg.success_radius

    def next_target(self, current_scrollY: int) -> dict:
        """Generate next target scroll position.

        The target must be reachable — i.e. the user must be able to scroll
        such that the target band enters the viewport's success zone (centered).

        Reachability constraint:
            To hit a target at T, the user needs scrollTop ≈ T - viewportCenter + bandHeight/2.
            scrollTop must be in [0, pageHeight - viewportHeight].

        So valid target range is:
            T_min = viewportCenter - successRadius  (scrollTop=0 → target at top of success zone)
            T_max = maxScrollTop + viewportCenter + successRadius

        Returns:
            {"target_scrollY": int, "direction": "up"|"down"}
        """
        viewport_center = self.viewport_height // 2
        max_scroll_top = self.page_height - self.viewport_height

        # Valid range for targetScrollY so it's always reachable
        target_min = viewport_center - self.success_radius
        target_max = max_scroll_top + viewport_center + self.success_radius

        # Clamp to ensure sane bounds
        target_min = max(0, target_min)
        target_max = min(self.page_height, target_max)

        max_down = min(self.page_height - current_scrollY, target_max - current_scrollY)
        max_up = min(current_scrollY, current_scrollY - target_min)

        for _ in range(100):
            dist = random.randint(self.dist_min, self.dist_max)
            direction = random.choice(["up", "down"])

            if direction == "down" and 0 < dist <= max_down:
                candidate = current_scrollY + dist
                if target_min <= candidate <= target_max:
                    return {"target_scrollY": candidate, "direction": "down"}
            elif direction == "up" and 0 < dist <= max_up:
                candidate = current_scrollY - dist
                if target_min <= candidate <= target_max:
                    return {"target_scrollY": candidate, "direction": "up"}

        # Fallback: generate a valid target within bounds
        valid_down = min(max_down, self.dist_max)
        valid_up = min(max_up, self.dist_max)

        if valid_down >= self.dist_min:
            dist = random.randint(self.dist_min, max(self.dist_min, valid_down))
            return {"target_scrollY": current_scrollY + dist, "direction": "down"}
        elif valid_up >= self.dist_min:
            dist = random.randint(self.dist_min, max(self.dist_min, valid_up))
            return {"target_scrollY": current_scrollY - dist, "direction": "up"}
        else:
            # Edge case: move to center of valid range
            target = max(target_min, min(target_max, (target_min + target_max) // 2))
            direction = "down" if target > current_scrollY else "up"
            return {"target_scrollY": target, "direction": direction}

