"""Prepare the inventory header's left-boundary wheel plan from source units.

Horizontal ScrollSlot consumes the same vertical mouse-wheel event as other
source lists. Its axis does not invert the delta: positive wheel moves content
anchoredPosition.x toward the left-view boundary. Every raw event is clamped
to one unit before ScrollSpeed is applied; the existing native input adapter
therefore expands the prepared total into individual 120-unit wheel events.
No runtime capture, current offset estimate or extra screen approval is used.
"""
import math

from .ui_layout import LayoutError, Rect


def _number(value, name, *, positive=False):
    if (type(value) not in (int, float) or not math.isfinite(value)
            or value < 0 or (positive and value == 0)):
        raise LayoutError("invalid bag category scroll " + name)
    return float(value)


def category_scroll_reset(viewport, widths_ui, gap_ui, scale, rule):
    """Return a physical wheel-recipient Rect and immutable-plan metadata.

    Enough positive notches are prepared to cover the entire legal scroll
    range, so every initial offset (including zero) reaches the same boundary.
    The serialized Elastic/inertia settings do not add overshoot: mouse-wheel
    dispatch uses checkBoundary=True and clears velocity before/after input.
    """
    if not isinstance(viewport, Rect) or viewport.width <= 0 or viewport.height <= 0:
        raise LayoutError("bag category scroll viewport required")
    scale = _number(scale, "scale", positive=True)
    gap_ui = _number(gap_ui, "gap")
    if not isinstance(widths_ui, (tuple, list)) or not widths_ui:
        raise LayoutError("bag category ordered widths required")
    widths = [_number(width, "category width", positive=True) for width in widths_ui]
    speed = _number(rule["scroll_speed"], "speed", positive=True)
    quantum = rule["wheel_quantum"]
    if (type(quantum) is not int or quantum != 120
            or rule["direction_constraint"] != 2
            or tuple(rule["raw_event_clamp"]) != (-1, 1)):
        raise LayoutError("bag category horizontal source wheel contract required")
    content_width = sum(widths) + gap_ui * (len(widths) - 1)
    max_offset = max(0., content_width - viewport.width / scale)
    notches = math.ceil(max_offset / speed)
    # Stay inside the actual header ScrollSlot, never over Search/Sort controls.
    recipient = Rect(viewport.left + viewport.width / 4,
                     viewport.top + viewport.height / 4,
                     viewport.width / 2, viewport.height / 2)
    metadata = dict(
        geometry_kind="source-scroll-parent-wheel-recipient",
        input_kind="scroll-only", source_parent_bounds=viewport.bounds,
        source_scroll_slot=rule["source_scroll_slot"],
        source_layout_rule=rule["source_layout_rule"],
        content_width_ui=content_width, viewport_width_ui=viewport.width / scale,
        max_scroll_x=max_offset * scale, max_scroll_x_ui=max_offset,
        target_scroll_x=0., reset_boundary="left", category_label_widths=widths,
        wheel_quantum=quantum, wheel_step_ui=speed, source_scroll_speed=speed,
        wheel_event_clamp=tuple(rule["raw_event_clamp"]),
        wheel_boundary_clamp=True, wheel_inertia=False,
        wheel_input_mode="notches", wheel_source=rule["wheel_source"],
        scroll_steps=([dict(delta=quantum * notches, notches=notches, direction="left")]
                      if notches else []))
    return recipient, metadata
