/**
 * BrowserSafety — gatekeeper for mutating browser actions.
 *
 * Given an action request (type + target + current page), returns a decision:
 *   - "allow"   : proceed silently
 *   - "confirm" : ask the user (with a reason) before proceeding
 *   - "deny"    : refuse outright (rare — used for blocked origins)
 *
 * The decision is driven by three signals:
 *   1. Current mode: "live" | "confirm-all" | "dry-run"
 *   2. Per-site sensitivity (e.g. instagram.com, gmail.com → confirm more)
 *   3. Destructive-verb detection on the target element's accessible name
 *      (delete, unfollow, archive, remove, block, logout, purchase, send…)
 */
// ── Rules ────────────────────────────────────────────────────────────────
const DESTRUCTIVE_VERBS = [
    "delete",
    "remove",
    "unfollow",
    "block",
    "unfriend",
    "unsubscribe",
    "archive",
    "discard",
    "trash",
    "clear",
    "cancel subscription",
    "close account",
    "deactivate",
    "logout",
    "sign out",
    "log out",
    "pay",
    "purchase",
    "buy now",
    "place order",
    "confirm order",
    "send money",
    "transfer",
    "withdraw",
    "post",
    "publish",
    "tweet",
    "send",
    "reply",
];
const SENSITIVE_DOMAINS = new Set([
    "instagram.com",
    "facebook.com",
    "x.com",
    "twitter.com",
    "tiktok.com",
    "gmail.com",
    "mail.google.com",
    "linkedin.com",
    "paypal.com",
    "venmo.com",
    "bank", // any domain containing "bank"
    "chase.com",
    "wellsfargo.com",
    "stripe.com",
    "amazon.com",
    "github.com",
]);
const BLOCKED_SCHEMES = new Set(["chrome:", "chrome-extension:", "devtools:", "file:"]);
// ── Helpers ──────────────────────────────────────────────────────────────
function hostOf(url) {
    try {
        return new URL(url).hostname.toLowerCase();
    }
    catch {
        return "";
    }
}
function isSensitiveHost(host) {
    if (!host)
        return false;
    if (SENSITIVE_DOMAINS.has(host))
        return true;
    for (const d of SENSITIVE_DOMAINS) {
        if (host === d)
            return true;
        if (host.endsWith(`.${d}`))
            return true;
        if (d === "bank" && host.includes("bank"))
            return true;
    }
    return false;
}
function describeTarget(t, hint) {
    if (!t)
        return hint ? `"${hint}"` : "(unknown target)";
    const name = t.name || hint || "";
    const parts = [`<${t.tag}>`];
    if (t.role)
        parts.push(`role=${t.role}`);
    if (name)
        parts.push(`"${name}"`);
    return parts.join(" ");
}
function findDestructiveHit(text) {
    const lc = text.toLowerCase();
    for (const verb of DESTRUCTIVE_VERBS) {
        // Word-boundary-ish match
        const re = new RegExp(`(^|[^a-z0-9])${verb.replace(/ /g, "\\s+")}([^a-z0-9]|$)`, "i");
        if (re.test(lc))
            return verb;
    }
    return null;
}
export class BrowserSafety {
    constructor() {
        this._mode = "live";
    }
    get mode() {
        return this._mode;
    }
    setMode(m) {
        this._mode = m;
    }
    evaluate(action, ctx) {
        const mode = ctx.mode ?? this._mode;
        // Dry-run: block any mutating action; read-only actions pass.
        if (mode === "dry-run") {
            if (action.type === "scroll" || action.type === "hover")
                return { decision: "allow" };
            return { decision: "deny", reason: "dry-run mode: mutating actions disabled" };
        }
        // Navigate to blocked schemes.
        if (action.type === "navigate") {
            // about:blank and data: navigations are benign; allow without URL parsing.
            if (action.url === "about:blank" || action.url.startsWith("about:")) {
                return { decision: "allow" };
            }
            try {
                const u = new URL(/^[a-z]+:\/\//i.test(action.url) ? action.url : `https://${action.url}`);
                if (BLOCKED_SCHEMES.has(u.protocol)) {
                    return { decision: "deny", reason: `blocked scheme: ${u.protocol}` };
                }
            }
            catch {
                return { decision: "deny", reason: "invalid url" };
            }
        }
        // Read-only actions: always allow.
        if (action.type === "scroll" || action.type === "hover")
            return { decision: "allow" };
        const host = hostOf(ctx.url);
        const sensitive = isSensitiveHost(host);
        // Confirm-all mode forces a prompt for every mutating action.
        if (mode === "confirm-all") {
            return { decision: "confirm", reason: `confirm-all mode — host: ${host || "(unknown)"}` };
        }
        // Click-ish destructive detection.
        if (action.type === "click" || action.type === "dblclick") {
            const t = action.target;
            const label = (t?.name ?? "") + " " + (action.hintedLabel ?? "");
            const verb = findDestructiveHit(label);
            if (verb) {
                return {
                    decision: "confirm",
                    reason: `destructive verb "${verb}" detected in target ${describeTarget(t, action.hintedLabel)} on ${host}`,
                };
            }
            // Generic rule: any click on a sensitive host gets confirmed.
            if (sensitive) {
                return {
                    decision: "confirm",
                    reason: `sensitive host (${host}) — confirm click on ${describeTarget(t, action.hintedLabel)}`,
                };
            }
            return { decision: "allow" };
        }
        if (action.type === "type") {
            // Typing itself is rarely destructive; but `--submit` on a sensitive host is.
            if (action.submit && sensitive) {
                return {
                    decision: "confirm",
                    reason: `submit on ${host} with text: ${JSON.stringify(action.text.slice(0, 80))}`,
                };
            }
            // Typing into a password field on any host → confirm (v1 heuristic: input type=password).
            if (action.target?.type === "password") {
                return { decision: "confirm", reason: `typing into a password field on ${host}` };
            }
            return { decision: "allow" };
        }
        if (action.type === "select") {
            // Selects are almost never destructive on their own.
            return { decision: "allow" };
        }
        if (action.type === "keys") {
            // Enter on sensitive host → confirm.
            if (/\bEnter\b/i.test(action.keys) && sensitive) {
                return { decision: "confirm", reason: `Enter on sensitive host ${host}` };
            }
            return { decision: "allow" };
        }
        if (action.type === "navigate") {
            // Navigations are allowed by default.
            return { decision: "allow" };
        }
        return { decision: "allow" };
    }
    /** Short human-readable summary of an action for confirmation prompts. */
    describeAction(action) {
        switch (action.type) {
            case "click":
                return `Click ${describeTarget(action.target, action.hintedLabel)}`;
            case "dblclick":
                return `Double-click ${describeTarget(action.target, action.hintedLabel)}`;
            case "hover":
                return `Hover ${describeTarget(action.target, action.hintedLabel)}`;
            case "type":
                return `Type ${JSON.stringify(action.text)} into ${describeTarget(action.target, action.hintedLabel)}${action.submit ? " and submit" : ""}`;
            case "select":
                return `Select "${action.option}" in ${describeTarget(action.target, action.hintedLabel)}`;
            case "keys":
                return `Press ${action.keys}`;
            case "navigate":
                return `Navigate to ${action.url}`;
            case "scroll":
                return `Scroll`;
        }
    }
}
//# sourceMappingURL=browser-safety.js.map