# retoor <retoor@molodetz.nl>

from __future__ import annotations

from .spec import Action, Param


def arg(
    name: str, description: str, required: bool = False, kind: str = "string"
) -> Param:
    return Param(
        name=name,
        location="body",
        description=description,
        required=required,
        type=kind,
    )


CLIENT = (
    "Runs in the user's own browser through the web terminal. Only effective in the web "
    "interface with a live connection; elsewhere it reports that no browser is attached."
)

CLIENT_ACTIONS: tuple[Action, ...] = (
    Action(
        name="get_page_context",
        method="LOCAL",
        path="",
        summary="Read the user's current page: URL, title, viewport, scroll, selected text, visible headings, and whether they are signed in",
        description=CLIENT
        + " Use this to understand where the user is and what they are looking at before acting or guiding them.",
        handler="client",
        requires_auth=False,
        read_only=True,
    ),
    Action(
        name="run_js",
        method="LOCAL",
        path="",
        summary="Execute JavaScript in the user's browser and return its result",
        description=(
            CLIENT
            + " The code is the body of an async function; use 'return value' to return a "
            "JSON-serializable result. You have full access to window and document. Use this for "
            "anything not covered by the dedicated tools: read or change the DOM, drive a live "
            "demo, inspect state, or update the screen. Prefer the dedicated tools "
            "(highlight_element, show_toast, scroll_to_element, navigate_to, reload_page) when they fit."
        ),
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "code",
                "JavaScript to run as an async function body. Return a JSON-serializable value.",
                required=True,
            ),
        ),
    ),
    Action(
        name="highlight_element",
        method="LOCAL",
        path="",
        summary="Highlight an element on screen with an outline and an optional callout label, for live tutorials",
        description=CLIENT
        + " Scrolls the element into view and draws an attention outline. Call clear_highlights to remove it.",
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "selector",
                "CSS selector, or the element's exact visible text (e.g. a heading or link label).",
                required=True,
            ),
            arg("label", "Optional callout text shown next to the element."),
        ),
    ),
    Action(
        name="clear_highlights",
        method="LOCAL",
        path="",
        summary="Remove all highlights and callouts placed by highlight_element",
        description=CLIENT,
        handler="client",
        requires_auth=False,
    ),
    Action(
        name="show_toast",
        method="LOCAL",
        path="",
        summary="Show a brief on-screen message (toast) to the user",
        description=CLIENT,
        handler="client",
        requires_auth=False,
        params=(
            arg("text", "Message to display.", required=True),
            arg(
                "duration_ms",
                "How long to show it, in milliseconds (default 4000).",
                kind="integer",
            ),
        ),
    ),
    Action(
        name="scroll_to_element",
        method="LOCAL",
        path="",
        summary="Smoothly scroll an element into view",
        description=CLIENT,
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "selector",
                "CSS selector, or the element's exact visible text.",
                required=True,
            ),
        ),
    ),
    Action(
        name="navigate_to",
        method="LOCAL",
        path="",
        summary="Send the user's browser to a URL",
        description=(
            CLIENT
            + " Use a same-origin path like /feed or /docs/index.html, or a full URL. The "
            "page reloads; the user's Devii session and conversation persist and reconnect automatically."
        ),
        handler="client",
        requires_auth=False,
        params=(arg("url", "Path or URL to navigate to.", required=True),),
    ),
    Action(
        name="reload_page",
        method="LOCAL",
        path="",
        summary="Refresh the user's current page, e.g. after something changed",
        description=CLIENT
        + " The Devii session and conversation persist and reconnect automatically.",
        handler="client",
        requires_auth=False,
    ),
    Action(
        name="discover_elements",
        method="LOCAL",
        path="",
        summary="List the actionable elements on the user's screen in reading order, each with a stable ref, role, label, selector, and state",
        description=(
            CLIENT
            + " This is how you SEE the page before acting: it returns buttons, links, inputs, "
            "textboxes, checkboxes, selects, tabs, and menu items in document (logical) order, each "
            "with a `ref` (like e3) you can pass to click_element/fill_field/etc, plus its role, "
            "accessible label, a robust CSS selector, current state (visible, enabled, checked, value), "
            "and which form or modal it belongs to. Call this first, then act on the refs. Use `query` "
            "to filter by label, `within` to scope to a region (a ref/selector, e.g. an open modal), and "
            "`kind` to keep only one type."
        ),
        handler="client",
        requires_auth=False,
        read_only=True,
        params=(
            arg("query", "Only return elements whose label contains this text (case-insensitive)."),
            arg(
                "within",
                "Scope discovery to inside this element (a ref, CSS selector, or exact text), e.g. an open modal or form.",
            ),
            arg(
                "kind",
                "Keep only one type: button, link, input, textbox, checkbox, radio, select, tab, or menuitem.",
            ),
            arg("limit", "Maximum number of elements to return (default 40).", kind="integer"),
        ),
    ),
    Action(
        name="read_element",
        method="LOCAL",
        path="",
        summary="Read one element in detail: text, value, attributes, state, position, and visibility",
        description=CLIENT
        + " Use this to investigate a specific element you found with discover_elements, "
        "or to confirm the result of an action (its value, whether it is checked, any error text near it).",
        handler="client",
        requires_auth=False,
        read_only=True,
        params=(
            arg(
                "target",
                "The element to read: a ref (e3), a CSS selector, or its exact visible text.",
                required=True,
            ),
        ),
    ),
    Action(
        name="click_element",
        method="LOCAL",
        path="",
        summary="Click an element on the user's screen and report what changed",
        description=(
            CLIENT
            + " Scrolls the element into view, waits briefly for it to be visible and enabled, "
            "dispatches a real click, and returns a delta (did the URL change, did a modal open or close, "
            "any new toast or validation error, is the element still there). Prefer this over run_js for clicking. "
            "Set `button` to 'right' for a context menu or 'double' for a double-click."
        ),
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "target",
                "The element to click: a ref (e3), a CSS selector, or its exact visible text.",
                required=True,
            ),
            arg("button", "left (default), right, or double."),
        ),
    ),
    Action(
        name="fill_field",
        method="LOCAL",
        path="",
        summary="Type a value into an input, textarea, contenteditable, or code editor and fire the right events",
        description=(
            CLIENT
            + " Sets the value through the native setter and dispatches input and change events, so "
            "framework and validation handlers actually run (plain run_js value assignment does not). "
            "Supports text inputs, textareas, contenteditable elements, and CodeMirror editors. "
            "Returns the resulting value and any validation message shown near the field."
        ),
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "target",
                "The field to fill: a ref (e3), a CSS selector, or its label/placeholder text.",
                required=True,
            ),
            arg("value", "The value to type into the field.", required=True),
        ),
    ),
    Action(
        name="set_control",
        method="LOCAL",
        path="",
        summary="Toggle a checkbox or radio, or choose an option in a select",
        description=CLIENT
        + " For a checkbox or radio pass `checked` true/false; for a select pass `option` "
        "(matched against option value or visible label). Dispatches a change event.",
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "target",
                "The control: a ref (e3), a CSS selector, or its label text.",
                required=True,
            ),
            arg("checked", "For a checkbox or radio: true to check, false to uncheck.", kind="boolean"),
            arg("option", "For a select: the option value or visible label to choose."),
        ),
    ),
    Action(
        name="submit_form",
        method="LOCAL",
        path="",
        summary="Optionally fill a form's fields, then submit it, and report what changed",
        description=(
            CLIENT
            + " Resolves the form that contains `target` (a ref/selector/text for the form, a field in it, "
            "or its submit button). If `fields` is given (a map of field name/label to value) each is filled "
            "first with the proper events, then the form is submitted by clicking its submit button (so app "
            "handlers run) or calling requestSubmit. Returns a delta (URL change, modal open/close, toast, errors). "
            "This is the primary tool for completing a mutation such as creating a post or saving a profile."
        ),
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "target",
                "The form, one of its fields, or its submit button: a ref (e3), CSS selector, or exact text.",
                required=True,
            ),
            arg(
                "fields",
                "Optional map of field name or label to the value to fill before submitting.",
                kind="object",
            ),
        ),
    ),
    Action(
        name="wait_for",
        method="LOCAL",
        path="",
        summary="Wait until an element becomes visible, hidden, or contains text, before continuing",
        description=(
            CLIENT
            + " Polls the page until the condition holds or it times out, so sequences after a navigation, "
            "click, or fetch do not race. `condition` is visible (default), hidden, or text_contains "
            "(supply `text`). Returns whether it was satisfied and how long it waited."
        ),
        handler="client",
        requires_auth=False,
        read_only=True,
        params=(
            arg(
                "target",
                "The element to wait on: a ref (e3), a CSS selector, or exact visible text.",
                required=True,
            ),
            arg("condition", "visible (default), hidden, or text_contains."),
            arg("text", "For text_contains: the text the element should contain."),
            arg("timeout_ms", "Maximum wait in milliseconds (default 8000, capped at 55000).", kind="integer"),
        ),
    ),
    Action(
        name="press_key",
        method="LOCAL",
        path="",
        summary="Send a keypress to an element or the page (Enter, Escape, Tab, arrows, etc.)",
        description=CLIENT
        + " Dispatches keydown/keypress/keyup for the named key to `target` (or the focused element "
        "if omitted). Use Enter to submit, Escape to close a modal, Tab to move focus.",
        handler="client",
        requires_auth=False,
        params=(
            arg("key", "The key name: Enter, Escape, Tab, ArrowDown, a, etc.", required=True),
            arg("target", "Optional element to send the key to: a ref (e3), selector, or exact text."),
        ),
    ),
    Action(
        name="run_sequence",
        method="LOCAL",
        path="",
        summary="Run an ordered list of client steps in one round-trip, stopping on the first failure",
        description=(
            CLIENT
            + " Each step is an object with an `action` (click_element, fill_field, set_control, submit_form, "
            "wait_for, press_key, scroll_to_element, read_element, discover_elements, highlight_element, show_toast) "
            "and its arguments. Steps run in order with the page settling between them; execution stops at the first "
            "error and returns every step's result plus the final page context. Use this to perform a whole UI flow "
            "(open a modal, fill it, submit) reliably without a round-trip per step. run_js is not allowed inside a sequence."
        ),
        handler="client",
        requires_auth=False,
        params=(
            arg(
                "steps",
                "Ordered list of step objects, each with an `action` field and that action's arguments.",
                required=True,
                kind="array",
            ),
        ),
    ),
    Action(
        name="open_terminal",
        method="LOCAL",
        path="",
        summary="Open a floating interactive terminal window attached to a running container instance",
        description=(
            CLIENT
            + " Opens a new xterm.js window in the user's browser connected to the container's "
            "interactive shell (admin only). Resolve the instance first with container_list_instances, "
            "then pass the project slug and the instance slug or uid."
        ),
        handler="client",
        requires_admin=True,
        params=(
            arg(
                "project_slug",
                "Project slug or uid that owns the container.",
                required=True,
            ),
            arg("instance", "Container instance slug or uid.", required=True),
            arg("label", "Optional window title (defaults to the instance name)."),
        ),
    ),
)
