"""Authored tapes -> precomputed coordinates -> references -> offline input port.

No Win32, process enumeration, capture, CLI, or input transport is imported.
"""
from dataclasses import dataclass
from .vendor.coordinate_store import CoordinateStore
from .vendor.ui_layout import Rect, Viewport, CanvasProfile, resolve_canvas
from .vendor.source_menu_layouts import gathering_category


class GeometryUnavailable(ValueError):
    pass


@dataclass(frozen=True)
class Coordinate:
    x: float
    y: float
    source: str
    status: str


class RecordingGameWindow:
    """Fake GameWindow boundary: records calls, cannot control any window."""
    def __init__(self, signature):
        self.signature = signature
        self.calls = []

    def record(self, kind, payload, delay):
        self.calls.append({"kind": kind, "payload": payload, "delay_before_seconds": delay,
                           "transport": "recording-only", "sent_to_game": False})


class AutomationService:
    def __init__(self, store, window):
        self.store, self.window = store, window
        self.cancelled = False

    def input(self, kind, payload, delay):
        if self.cancelled:
            raise GeometryUnavailable("cancelled")
        if kind == "click":
            value = self.store.resolve(payload, current_signature=self.window.signature)
            payload = [value.x, value.y]
        elif kind != "key":
            raise GeometryUnavailable("Unconnected input kind")
        self.window.record(kind, payload, delay)


class FeaturePort:
    def __init__(self, catalog):
        self.catalog, self.store = catalog, CoordinateStore()
        self.window = None

    def run_tape(self, batch_id, width=1920, height=1080, ui_percent=70):
        if type(width) is not int or type(height) is not int or width <= 0 or height <= 0:
            raise GeometryUnavailable("Invalid viewport")
        if ui_percent != 70 or width * 9 != height * 16:
            self.store.invalidate()
            raise GeometryUnavailable("Full tape has no confirmed source-parent binding for this aspect/UI scale")
        batch = self.catalog.routes["batches"][batch_id]
        signature = (width, height, ui_percent, batch_id, self.catalog.fingerprint)
        records = {}
        for step in batch["steps"]:
            if step["kind"] == "click":
                if "control" not in step:
                    raise GeometryUnavailable("Dynamic binding required; no static substitute")
                name = step["control"]
                control = self.catalog.routes["controls"][name]
                if control["profile"] != "pc70_wide" or control["space"] != "client_content":
                    raise GeometryUnavailable("Unknown coordinate space")
                x, y = control["point"]
                if not 0 < x < 1 or not 0 < y < 1:
                    raise GeometryUnavailable("Coordinate outside client")
                records[name] = Coordinate(x * width, y * height, "action-paths", "historical; not live verified")
            elif step["kind"] != "key":
                raise GeometryUnavailable("Tape input not connected")
        # The entire fixed batch is prepared before its first input consumer.
        self.store.publish(records, signature=signature)
        prepared = [(s["kind"], self.store.ref(s["control"]) if s["kind"] == "click" else s["key"],
                     s["delay_before_seconds"]) for s in batch["steps"]]
        self.window = RecordingGameWindow(signature)
        service = AutomationService(self.store, self.window)
        for kind, target, delay in prepared:
            service.input(kind, target, delay)
        return self.window.calls

    def category_candidate(self, *, width, height, area, flow_left, index=0):
        """Consume reused client rule with explicit parent, no aspect stretching.

        Constructor/profile and parent are synthetic inputs in dev.1, not live
        evidence. Other-aspect support is limited to this calculated candidate.
        """
        context = resolve_canvas(Viewport(width, height), "pc", 70, orientation="landscape",
            profile=CanvasProfile(1280, 720, 1280, "synthetic explicit landscape profile", False))
        candidate = gathering_category(Rect(*area), scale=context.scale, is_portrait=False,
            index=index, scroll_offset=0, flow_left=flow_left)["gather.category"]
        if not candidate.metadata["fully_visible"]:
            raise GeometryUnavailable("Candidate is clipped")
        signature = (width, height, tuple(area), flow_left, index, self.catalog.fingerprint)
        x, y = candidate.rect.center
        self.store.publish({"gather.category": Coordinate(x, y, "source_menu_layouts", "synthetic parent; candidate only")}, signature=signature)
        window = RecordingGameWindow(signature)
        AutomationService(self.store, window).input("click", self.store.ref("gather.category"), 0)
        return {"rect": candidate.rect.bounds, "metadata": dict(candidate.metadata), "consumed": window.calls}
