"""Persistent element annotations in the Hermes desktop GUI's in-app browser.

Unlike ``drive_preview``'s self-retiring marks, an annotation outlines an element
(or, with ``hold``, the whole visible field) until removed. Annotations bind to
elements, not coordinates: they ride scrolls and vanish with their element, so
navigation clears them. Same ``preview.act`` bridge as ``drive_preview`` (the
renderer resolves refs and owns the overlay). ``desktop_ui`` toolset only.
"""

import json
from typing import Callable, Optional

from tools.registry import registry, tool_error

ACTIONS = ("add", "hold", "remove", "clear")

# Renderer verbs keyed by ours; `clear` is `unpin` with no target = "all of them".
WIRE = {"add": "pin", "hold": "hold", "remove": "unpin", "clear": "unpin"}


def annotate_preview_tool(
    action: str = "add", ref: Optional[str] = None, selector: Optional[str] = None,
    label: Optional[str] = None, callback: Optional[Callable] = None,
) -> str:
    """Put one annotation up, take one down, or clear them all."""
    if callback is None:
        return tool_error("annotate_preview is only available in the Hermes desktop app.")
    verb = (action or "add").strip().lower()
    if verb not in ACTIONS:
        return tool_error(f"action must be one of: {', '.join(ACTIONS)}.")
    if verb in ("add", "remove") and not (ref or selector):
        return tool_error(f"{verb} needs a ref from drive_preview action='elements' (e.g. 'btn-sign-in') or a CSS selector.")

    targeted = verb not in ("clear", "hold")
    fields = (("action", WIRE[verb]), ("ref", ref if targeted else None), ("selector", selector if targeted else None), ("text", label))
    payload = {name: val for name, val in fields if val is not None}
    try:
        raw = callback(payload)
    except Exception as exc:
        return tool_error(f"Failed to annotate the in-app browser: {exc}")
    if not raw:
        return tool_error(
            "No GUI window answered with a page: no preview tab is open. "
            "Open a page with open_preview first. If the pane IS open, the desktop app "
            "may be older than this backend — its bridge-unavailable error names that case.")
    try:
        return json.dumps(json.loads(raw), ensure_ascii=False)
    except (TypeError, ValueError):
        return json.dumps({"text": str(raw)}, ensure_ascii=False)


ANNOTATE_PREVIEW_SCHEMA = {
    "name": "annotate_preview",
    "description": (
        "Highlight elements on the preview-pane page, lastingly (drive_preview's own "
        "marks fade; annotations stay until removed) — point at findings, "
        "flag what you're about to change, keep your place. Use the refs "
        "from drive_preview action='elements'. add: outline one element "
        "(optional short label — a word or two, drawn on the page). hold: "
        "freeze the whole visible field, every element outlined and named. "
        "remove/clear: take one/all down. Marks follow their element on "
        "scroll; navigation clears them."
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "action": {
                "type": "string",
                "enum": list(ACTIONS),
                "description": "Defaults to 'add'.",
            },
            "ref": {
                "type": "string",
                "description": "Ref from drive_preview elements.",
            },
            "selector": {
                "type": "string",
                "description": "CSS selector fallback. Prefer ref.",
            },
            "label": {
                "type": "string",
                "description": "Optional caption, e.g. 'cheapest'.",
            },
        },
        "required": [],
    },
}


registry.register(
    name="annotate_preview",
    toolset="desktop_ui",
    schema=ANNOTATE_PREVIEW_SCHEMA,
    handler=lambda args, **kw: annotate_preview_tool(
        action=args.get("action", "add"),
        ref=args.get("ref"),
        selector=args.get("selector"),
        label=args.get("label"),
        callback=kw.get("callback"),
    ),
    emoji="🔖",
)
