import type { Page } from "playwright-core";
import type { BrowserManager } from "./manager.js";
import { type SnapshotMode, type SnapshotOptions } from "./snapshot.js";
import type { ExecutionLogger } from "../logging/logger.js";
export interface ActionDelayConfig {
    /** Minimum delay in ms between browser actions (default: 800) */
    min: number;
    /** Maximum delay in ms between browser actions (default: 3000) */
    max: number;
}
export interface BrowserActionsOptions {
    /**
     * Opt-in compatibility switch for JS/DOM interaction fallbacks.
     * Defaults to false because these paths emit untrusted DOM events or mutate
     * page state directly and are easier for sites to distinguish from user input.
     */
    allowEvaluateFallbacks?: boolean;
}
export interface ClickOptions {
    doubleClick?: boolean;
    button?: "left" | "right" | "middle";
    modifiers?: Array<"Alt" | "Control" | "Meta" | "Shift">;
    dispatch?: "mouse" | "dom";
}
export interface TypeOptions {
    clear?: boolean;
    submit?: boolean;
    method?: "keyboard" | "paste";
    /**
     * If the first insert + verify fails, try the other insertion method once.
     * Default true even when `method` is set explicitly — `method` is a
     * preference, not an opt-out from the safety net. Pass false to disable
     * (e.g. when the caller wants to diagnose a specific method's behavior).
     */
    fallback?: boolean;
}
export interface WaitOptions {
    selector?: string;
    text?: string;
    textGone?: string;
    url?: string;
    loadState?: string;
    fn?: string;
    timeMs?: number;
    timeoutMs?: number;
    /** Wait until selector resolves and is visible (preferred over `selector` for visibility-sensitive waits). */
    selectorVisible?: string;
    /** Wait until selector is missing or hidden. */
    selectorHidden?: string;
    /** Wait until selector resolves to an enabled control. */
    selectorEnabled?: string;
    /** Wait until selector resolves to a disabled control. */
    selectorDisabled?: string;
    /** Wait until location.href matches this regex. */
    urlMatches?: string;
    /** Wait for DOM mutations to stop firing for this many milliseconds. */
    domQuietMs?: number;
    /**
     * Wait until selector's bounding box has been stable (same position+size) for this duration.
     * Useful for letting in-flight animations settle before screenshot/click.
     * Pair with `selector` or `selectorVisible`.
     */
    locatorStableMs?: number;
    /** Wait until at least N elements match a selector (e.g. result cells). */
    textCountAtLeast?: {
        selector: string;
        count: number;
    };
}
export interface ScrollOptions {
    direction?: "up" | "down" | "left" | "right";
    amount?: number;
    deltaX?: number;
    deltaY?: number;
}
export interface FillField {
    ref: string;
    value: string;
    type?: string;
}
/**
 * Status from {@link verifyInsertedText}.
 *
 * - `ok`              — body text confirmed; every expected URL also confirmed
 *                       (via raw text or matching <a> in the editable).
 * - `ok_url_warning`  — body text confirmed but at least one expected URL is
 *                       not visible in text and no matching <a> was found.
 *                       Common cause: site rewraps URLs (link shortener,
 *                       link card) into a host the editable doesn't expose
 *                       under <a href>. Treated as success — body landed —
 *                       but callers should surface the warning so the LLM
 *                       knows not to "fix" it by deleting the URL.
 * - `missing_text`    — non-URL portion of expected text not found.
 * - `duplicated`      — expected body appears two or more times (clear+retry
 *                       race left old content in place).
 * - `error`           — read failure (handle gone, navigation, etc.).
 */
type VerifyStatus = "ok" | "ok_url_warning" | "missing_text" | "duplicated" | "error";
interface VerifyResult {
    ok: boolean;
    status: VerifyStatus;
    /** Latest readback of the editable, post-normalization NOT applied. */
    lastValue: string;
    /** Non-URL chunks expected but not found in `lastValue`. Diagnostic. */
    missingChunks?: string[];
    /** URLs extracted from expected text. */
    expectedUrls?: string[];
    /** Of `expectedUrls`, which were satisfied (raw text or matching <a>). */
    foundUrls?: string[];
    /** Of `expectedUrls`, which were NOT satisfied. */
    missingUrls?: string[];
    /** Free-form error message (status="error" only). */
    message?: string;
}
/**
 * Normalize text for comparison. Deliberately narrow: only changes that the
 * browser/site reliably normalizes regardless of content. Smart quotes,
 * em-dashes, ellipses are content-meaningful and intentionally preserved
 * — if the user typed them, the verifier should detect when they got
 * dropped, not silently paper over the difference.
 */
export declare function normalizeForCompare(s: string): string;
export declare function extractUrls(s: string): string[];
interface UrlKey {
    host: string;
    pathKey: string;
    raw: string;
}
export declare function urlKey(u: string): UrlKey | null;
export declare function shouldForceClearBeforeTyping(snapshot: {
    value: string;
    anchors: string[];
}): boolean;
export declare function shouldAllowPasteMethod(input: {
    isRichEditor: boolean;
    allowEvaluateFallbacks: boolean;
}): boolean;
/**
 * One-shot evaluation of expected vs actual. Pure function over the
 * already-read snapshot (no DOM access), so the polling loop can iterate
 * cheaply.
 */
export declare function evaluateMatch(expectedText: string, expectedUrls: UrlKey[], value: string, anchorHrefs: string[]): VerifyResult;
/**
 * URL match check for nav recovery. Conservative — must satisfy ALL of:
 *
 *   1. Same origin (protocol + host + port).
 *   2. (When `beforeUrl` is provided) the URL must have CHANGED from
 *      beforeUrl. If they're identical the goto never committed, no real
 *      navigation happened, and the page is whatever was there before —
 *      regardless of how nice the title and body look.
 *   3. Pathname equals target's, OR extends it (target "/foo" → current
 *      "/foo/bar"). Going SHORTER (target "/profile/me" → current
 *      "/login") is rejected — the redirect-to-login case.
 *      Special case: target pathname "/" (origin-only) accepts any path,
 *      because sites routinely redirect their root URL to /home, /login,
 *      /dashboard etc.
 *   4. (When target has a query string) every target query parameter must
 *      be present in current with the same value. Extra params on current
 *      are allowed (utm_*, tracking suffixes). This catches "goto
 *      /search?q=new timed out before commit and we're still on
 *      /search?q=old" — same path, different query, definitely not the
 *      page the caller asked for.
 *
 * Hash differences are ignored — they don't constitute a navigation.
 *
 * Note on edge case: if the caller navigates to the SAME url they're
 * already on and goto times out, recovery will fail (rule 2). That's
 * intentional — we genuinely can't tell whether the page reloaded or not,
 * so reporting "navigation succeeded" would be a guess.
 */
export declare function urlMatchesTarget(current: string, target: string, beforeUrl?: string): boolean;
export declare class BrowserActions {
    private browser;
    private logger;
    private actionDelay;
    private lastActionTime;
    private dialogArmId;
    private uploadArmId;
    private attachmentsDir;
    private allowEvaluateFallbacksEnabled;
    constructor(browser: BrowserManager, actionDelay?: ActionDelayConfig, attachmentsDir?: string, options?: BrowserActionsOptions);
    /**
     * Auto-inject a random delay between consecutive browser actions
     * to simulate human pacing. Skips if enough time has already passed.
     */
    private interActionDelay;
    /** Expose the active page for direct Playwright locator usage (e.g. CSS selectors in skill replay) */
    getPage(): Promise<Page>;
    allowEvaluateFallbacks(): boolean;
    setLogger(logger: ExecutionLogger | null): void;
    clearLogger(logger: ExecutionLogger): void;
    navigate(url: string): Promise<string>;
    snapshot(modeOrOptions?: SnapshotMode | SnapshotOptions): Promise<string>;
    screenshot(opts?: {
        fullPage?: boolean;
        ref?: string;
        element?: string;
        type?: "png" | "jpeg";
        /** When true, write the screenshot into workspace/attachments/ with an auto-generated filename. */
        save?: boolean;
        /** Explicit filename (basename only) for saving into attachments/. Implies save=true. */
        saveAs?: string;
    }): Promise<{
        base64: string;
        text: string;
        savedAs?: string;
    }>;
    console(level?: string): Promise<string>;
    dialog(accept: boolean, promptText?: string, timeoutMs?: number): Promise<string>;
    /**
     * Resolve a path the LLM passed to `upload` into an absolute filesystem
     * path. Playwright's setInputFiles/fileChooser.setFiles resolve relative
     * strings against process.cwd(), which isn't the workspace — so a bare
     * "attachments/foo.jpg" silently fails to upload. We normalize here:
     *  1. Absolute and exists → use as-is.
     *  2. Exists under workspace/attachments/ (with or without the
     *     "attachments/" prefix) → resolve there.
     *  3. Exists relative to cwd → keep (preserves prior behavior).
     *  4. Otherwise → throw so the error reaches the LLM.
     */
    private resolveUploadPath;
    upload(paths: string[], ref?: string, timeoutMs?: number, selector?: string): Promise<string>;
    click(ref: string | undefined, options?: ClickOptions, selector?: string): Promise<string>;
    type(ref: string | undefined, text: string, clearOrOptions?: boolean | TypeOptions, selector?: string): Promise<string>;
    pressKey(key: string, delayMs?: number): Promise<string>;
    hover(ref?: string, selector?: string): Promise<string>;
    drag(startRef?: string, endRef?: string, startSelector?: string, endSelector?: string): Promise<string>;
    selectOption(ref: string | undefined, values: string | string[], selector?: string): Promise<string>;
    fill(fields: FillField[]): Promise<string>;
    resize(width: number, height: number): Promise<string>;
    scroll(ref: string | undefined, options?: ScrollOptions, selector?: string): Promise<string>;
    wait(opts: WaitOptions): Promise<string>;
    evaluate(script: string): Promise<string>;
    openTab(url: string): Promise<string>;
    listTabs(): Promise<string>;
    closeTab(index: number): Promise<string>;
    focusTab(index: number): Promise<string>;
    closeActivePage(): Promise<string>;
    sleep(ms: number): Promise<string>;
}
export {};
