package ai.platon.pulsar.api

import ai.platon.pulsar.chrome.dom.model.AriaSnapshotOptions
import ai.platon.pulsar.api.model.JsEvaluation
import ai.platon.pulsar.api.model.NavigateEntry
import ai.platon.pulsar.api.model.NavigateHistory
import ai.platon.pulsar.api.model.WebDriverException
import ai.platon.pulsar.api.model.NetworkResourceResponse
import ai.platon.pulsar.api.model.NodeRef
import ai.platon.pulsar.api.model.BrowserUseState
import ai.platon.pulsar.api.model.NanoDOMTree
import ai.platon.pulsar.api.model.PageTarget
import ai.platon.pulsar.api.model.SnapshotOptions
import ai.platon.pulsar.common.CheckState
import ai.platon.pulsar.common.ai.llm.MCP
import ai.platon.pulsar.common.browser.BrowserType
import ai.platon.pulsar.common.math.geometric.PointD
import ai.platon.pulsar.common.math.geometric.RectD
import ai.platon.pulsar.common.serialize.json.Pson
import ai.platon.pulsar.common.serialize.json.pulsarObjectMapper
import ai.platon.pulsar.common.urls.Hyperlink

import org.jsoup.Connection
import java.io.Closeable
import java.time.Duration
import kotlin.time.Duration.Companion.milliseconds

/**
 * [WebDriver] defines a concise interface to visit and manipulate webpages. @mcp
 *
 * The webpage is rendered to a Document Object Model (DOM) in a real browser, and the interface provides methods to
 * control the browser, select textContent and attributes of Elements, and interact with the webpage.
 *
 * All actions and behaviors are optimized to mimic real people as closely as possible, such as scrolling, clicking,
 * typing text, dragging and dropping, etc.
 *
 * The term `document` here refers to a Document Object Model (DOM) within a browser.
 *
 * The methods in this interface fall into three categories:
 *
 * * Control of the browser itself
 * * Selection of textContent and attributes of Elements
 * * Interact with the webpage
 *
 * Key methods:
 * * [navigate]: navigate to a URL.
 * * [currentUrl]: get the current URL displayed in the address bar.
 * * [scrollDown]: scroll down on a webpage to fully load the page. Most modern webpages support lazy loading
 * using ajax tech, where the page content only starts to load when it is scrolled into view.
 * * [pageSource]: retrieve the source code of the webpage.
 *
 * For each document, there are several properties that represent the URL of the document:
 * * `driver.currentUrl()`: Returns the URL displayed in the address bar, it can be either navigated or not.
 * * `driver.url()`, `document.URL`: Returns the URL of the document.
 * * `driver.documentURI()`, `document.documentURI`: Returns the URI of the document.
 * * `driver.baseURI()`, `document.baseURI`: Returns the base URI of the document.
 * * `document.location`: Represents the location (URL) of the current page and allows you to manipulate the URL.
 *
 * In the Document Object Model (DOM), the relationship between `document.URL`, `document.documentURI`,
 * `document.location`, and the URL displayed in the browser's address bar is as follows:
 * * `driver.currentUrl()`:
 *    - This read-only property displayed in the browser's address bar is what users see and can edit directly.
 *    - This read-only property can be either navigated or not.
 *    - When the page is loaded or when `document.location` is modified, the address bar is updated to reflect the new URL.
 *    - It is typically synchronized with `document.URL` and `document.location.href` (a property of `document.location`).
 * * `driver.url()`, `document.URL`:
 *    - This property returns the URL of the document as a string.
 *    - It is a read-only property and reflects the current URL of the document.
 *    - Changes to `document.location` will also update `document.URL`.
 * * `driver.documentURI()`, `document.documentURI`:
 *    - This property returns the URI of the document.
 *    - It is also a read-only property and typically contains the same value as `document.URL`.
 *    - However, `document.documentURI` is defined to be the URI that was provided to the parser, which could
 *      potentially differ from `document.URL` in certain cases, although in practice, this is rare.
 * * `driver.baseURI()`, `document.baseURI`:
 *    - This property returns the base URI of the document.
 *    - The base URI is used to resolve relative URLs within the document.
 *    - It is a read-only property and is typically the URL of the document, unless a `<base>` element is present
 *    in the document, in which case the value of the `href` attribute of the `<base>` element is used.
 *    - If no `<base>` element is present, the base URI is the same as `document.URL`.
 * * `document.location`:
 *    - This property represents the location (URL) of the current page and allows you to manipulate the URL.
 *    - It is a read-write property, which means you can change it to navigate to a different page or to manipulate
 *      query strings, fragments, etc.
 *    - Changes to `document.location` will cause the browser to navigate to the new URL, updating both `document.URL`
 *      and the URL displayed in the address bar.
 *
 * In summary, `document.URL` and `document.documentURI` are read-only properties that reflect the current URL of the
 * document, while `document.location` is a read-write property that not only reflects the current URL but also allows
 * you to navigate to a new one. The URL displayed in the address bar is a user-facing representation of the current
 * document's URL, which is usually in sync with `document.location`.
 *
 * In addition to the above properties, The method `driver.referrer()` returns the document's referrer.
 * The `document.referrer` property returns the URI of the page that linked to the current page. If the user navigated
 * directly to the page (e.g., via a bookmark), the value is an empty string. Inside an `<iframe>`, the referrer is
 * initially set to the same value as the `href` of the parent window's `Window.location`.
 *
 * The following example demonstrates how to use the WebDriver interface to visit a webpage and interact with it:
 *
 * ```kotlin
 *  fun visit() {
 *      val url = "https://twitter.com/home"
 *      val args = "-refresh"
 *      val options = session.options(args)
 *
 *      options.eventHandlers.browseEventHandlers.onDocumentSteady.addLast { page, driver ->
 *          interact(page, driver)
 *      }
 *
 *      session.load(url, options)
 *  }
 *
 *  // interact with the page
 *  private suspend fun interact(driver: WebDriver) {
 *      val selector = "input[placeholder*=搜索], input[placeholder*=Search]"
 *      driver.waitForSelector(selector)
 *      driver.fill(selector, "Facebook")
 *      driver.press("Space", selector)
 *      "Email".forEach { driver.press("$it", selector) }
 *      driver.press("Enter", selector)
 *  }
 * ```
 *
 * @see [Document ](https://developer.mozilla.org/en-US/docs/Web/API/Document)
 * @see [Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)
 * @see [Document: URL property](https://developer.mozilla.org/en-US/docs/Web/API/Document/URL)
 * @see [Document: documentURI property](https://developer.mozilla.org/en-US/docs/Web/API/Document/documentURI)
 * @see [Document: baseURI property](https://developer.mozilla.org/en-US/docs/Web/API/Document/baseURI)
 * @see [Document: referrer property](https://developer.mozilla.org/en-US/docs/Web/API/Document/referrer)
 * @see [Document: location property](https://developer.mozilla.org/en-US/docs/Web/API/Document/location)
 *
 * @see ai.platon.pulsar.api.model.BrowserSettings
 */
interface WebDriver : Closeable {
    /**
     * The driver id, starts with 1. The id is unique in process scope.
     * */
    val id: Int

    /**
     * The parent driver id. The id is unique in process scope.
     * */
    val parentSid: Int

    /**
     * The GUID of the driver.
     * */
    val guid: String

    /**
     * The readable state
     * */
    val readableState: String

    /**
     * The browser of the driver.
     * The browser defines methods and events to manipulate a real browser.
     * */
    val browser: Browser

    /**
     * Web pages for the page open from the current page, via window.open(), link click, form submission, etc.
     *
     * TODO: NOT IMPLEMENTED
     * */
    val frames: List<WebDriver>

    /**
     * The driver from whom opens the current page.
     * */
    val opener: WebDriver?

    /**
     * Web pages for the page open from the current page, via window.open(), link click, form submission, etc.
     * */
    val outgoingPages: Set<WebDriver>

    /**
     * The browser type.
     * BrowserType.PULSAR_CHROME is the only fully supported browser type currently.
     * BrowserType.PLAYWRIGHT_CHROME is a partially supported browser type since Playwright is not thread safe.
     * */
    val browserType: BrowserType

    /**
     * The current navigation entry.
     * */
    var navigateEntry: NavigateEntry

    /**
     * The navigation history.
     * */
    val navigateHistory: NavigateHistory

    /**
     * The associated data of the driver.
     * */
    val data: MutableMap<String, Any?>

    /**
     * The delay policy of the driver. The delay policy is a map of delay ranges in milliseconds for different actions.
     *
     * The delay policy is used to simulate human behaviors, such as typing, clicking, scrolling, etc.
     *
     * ```kotlin
     * delayPolicy["click"] == 500..1000
     * ```
     * */
    val delayPolicy: Map<String, IntRange>

    /**
     * The timeout policy of the driver. The timeout policy is a map of timeout durations for different actions.
     *
     * The timeout policy is used to set the maximum time to wait for an action to complete.
     *
     * ```kotlin
     * timeoutPolicy["click"] == Duration.ofSeconds(30)
     * ```
     * */
    val timeoutPolicy: Map<String, Duration>

    /**
     * Quick check if the connection to the backend tab is open.
     * */
    val isOpen: Boolean

    /**
     * Check if this driver is healthy.
     *
     * This is a heavy operation and should be called with low frequency.
     * */
    suspend fun healthy(): CheckState

    /**
     * Re-establishes the CDP connection to the backend tab after the link was
     * lost (e.g. machine sleep killed the websocket, or the backend tab was
     * closed). Keeps the same tab and browser — cookies and login state are
     * preserved. Implementations that cannot reconnect return false.
     */
    suspend fun reconnect(): Boolean = false

    /**
     * Adds a script which would be evaluated whenever the page is navigated. @mcp
     *
     * The script is evaluated after the document was created but before any of
     * its scripts were run. This is useful to amend the JavaScript environment, e.g.
     * to seed [Math.random].
     *
     * This function should be invoked before a navigation, usually in an onWillNavigate
     * event handler.
     *
     * @param script JavaScript source code to add.
     * */
    @Throws(WebDriverException::class)
    suspend fun addInitScript(script: String)

    /**
     * Blocks resource URLs from loading. @mcp
     *
     * @param urlPatterns URL patterns to block. Wildcards ('*') are allowed.
     */
    @Throws(WebDriverException::class)
    suspend fun addBlockedURLs(urlPatterns: List<String>)

    /**
     * Opens the specified URL in the web driver. @mcp
     *
     * This function navigates the web driver to the provided URL and waits for the navigation to complete.
     * It is a suspend function, meaning it can be used within coroutines for asynchronous execution.
     *
     * Example usage:
     * ```kotlin
     * driver.open("https://www.example.com")
     * ```
     *
     * @param url The URL to which the web driver should navigate. Must be a valid URL string.
     * @throws WebDriverException If an error occurs during navigation or waiting for the navigation to complete.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun open(url: String) {
        // Navigates the web driver to the specified URL.
        navigate(url)
        // Waits for the navigation to complete before proceeding.
        waitForNavigation()
    }

    /**
     * Navigates current page to the given URL. @mcp
     *
     * ```kotlin
     * driver.navigate("https://www.example.com")
     * driver.waitForNavigation()
     * ```
     *
     * @param userTypedUrl URL to navigate page to.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun navigate(userTypedUrl: String)

    @Deprecated("Use navigate(userTypedUrl: String) instead", ReplaceWith("navigate(url)"))
    @Throws(WebDriverException::class)
    suspend fun navigateTo(url: String) = navigate(url)

    /**
     * Navigates current page to the given URL. @mcp
     *
     * ```kotlin
     * val entry = NavigateEntry("https://www.example.com?timestamp=11712067353", pageUrl = "https://www.example.com")
     * driver.navigate(entry)
     * driver.waitForNavigation()
     * ```
     *
     * @param entry NavigateEntry to navigate page to.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun navigate(entry: NavigateEntry)

    /**
     * Reloads the current page. @mcp
     *
     * This method should trigger a reload of the current page, similar to how a user would refresh the page in a browser.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun reload()

    /**
     * Navigates the browser to the previous page in the navigation history. @mcp
     *
     * This method is expected to use the browser's navigation history to move back to the previous page.
     * It should handle any exceptions that may occur during the navigation process.
     *
     * @throws WebDriverException If an error occurs while navigating back.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun goBack()

    /**
     * Navigates the browser to the next page in the navigation history. @mcp
     *
     * This method is expected to use the browser's navigation history to move forward to the next page.
     * It should handle any exceptions that may occur during the navigation process.
     *
     * @throws WebDriverException If an error occurs while navigating forward.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun goForward()

    /**
     * Returns the user typed url in the address bar. @mcp
     *
     * @return The user typed url in the address bar.
     * */
    fun userTypedUrl(): String

    /**
     * Returns a string representing the current URL that the browser is looking at. @mcp
     *
     * The current url is always the main frame's `document.documentURI` if the browser succeed to return it, and is
     * displayed in the browser's address bar.
     *
     * If the browser failed to return a proper url, returns the passed in url to navigate, just like a real user enter
     * a url in the address bar but the browser failed to load the page.
     *
     * NOTE: the url can be non-standard, for example:
     *
     * about:blank
     * chrome://newtab
     * chrome://settings
     *
     * see [ai.platon.cdt.kt.protocol.types.page.NavigationEntry.userTypedURL]
     *
     * @return A string containing the URL of the document, or the passed in url to navigate.
     */
    @MCP
    suspend fun currentUrl(): String

    /**
     * The URL read-only property of the Document interface returns the document location as a string. @mcp
     *
     * This property equals to javascript `document.URL`.
     * The `document.URL` property returns the same value. The `document.documentURI` property can be used on
     * any document types, while the `document.URL` property can only be used on HTML documents.
     *
     * @see [Document: URL property](https://developer.mozilla.org/en-US/docs/Web/API/Document/URL)
     *
     * @return A string containing the URL of the document
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun url(): String

    /**
     * Returns the document location as a string. @mcp
     *
     * This property equals to javascript `document.documentURI`.
     *
     * The `document.URL` property which returns the same value. The `document.documentURI` property can be used on
     * any document types, while the `document.URL` property can only be used on HTML documents.
     *
     * @see [Document: documentURI property](https://developer.mozilla.org/en-US/docs/Web/API/Document/documentURI)
     *
     * @return The document's documentURI.
     * */
    @MCP
    suspend fun documentURI(): String

    /**
     * Returns the document's baseURI. @mcp
     *
     * The baseURI is a property of Node, it's the absolute base URL of the
     * document containing the node. A baseURI is used to resolve relative URLs.
     *
     * The base URL is determined as follows:
     * 1. By default, the base URL is the location of the document
     *    (as determined by window.location).
     * 2. If the document has an `<base>` element, its href attribute is used.
     *
     * @return The document's baseURI.
     * */
    @MCP
    suspend fun baseURI(): String

    /**
     * The referrer property returns the URI of the page that linked to this page. @mcp
     *
     * The value is an empty string if the user navigated to the page directly (not through a link, but, for example,
     * by using a bookmark).
     *
     * Inside an <iframe>, the referrer will initially be set to the same value as the href of the parent
     * window's Window.location.
     *
     * @return The document's referrer.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun referrer(): String

    /**
     * Returns the source of the last loaded page. @mcp
     *
     * If the page has been modified after loading (for
     * example, by JavaScript) there is no guarantee that the returned text is that of the modified
     * page.
     *
     * PageSource and outerHTML:
     *
     * - pageSource: returns document HTML markup, will support non-HTML document
     * - outerHTML: returns document HTML markup, for HTML document only
     *
     * ```kotlin
     * val pageSource = driver.pageSource()
     * ```
     *
     * @return The source of the current page
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun pageSource(): String?

    /**
     * Returns the title of the current page. @mcp
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun title(): String

    /**
     * Retrieves a lightweight version of the DOM tree (NanoDOMTree). @mcp
     *
     * The NanoDOMTree is based on the accessibility tree and enhanced with DOM and document snapshot information.
     * It is designed to be a compact representation suitable for AI processing and analysis.
     *
     * @return A [NanoDOMTree] representing the current page state, or null if retrieval fails.
     * */
    @MCP
    @Throws(WebDriverException::class)
    suspend fun nanoDOMTree(): NanoDOMTree?

    @Throws(WebDriverException::class)
    suspend fun browserUseState(
        target: PageTarget = PageTarget(),
        snapshotOptions: SnapshotOptions = SnapshotOptions()
    ): BrowserUseState

    /**
     * Returns the cookies of the current page. @mcp
     *
     * ```kotlin
     * val cookies = driver.getCookies()
     * ```
     *
     * @return The cookies of the current page.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun getCookies(): List<Map<String, String>>

    /**
     * Deletes browser cookies with matching name and url or domain/path pair. @mcp
     *
     * ```kotlin
     * driver.deleteCookies("name", "https://www.example.com")
     * ```
     *
     * > NOTE: At least one of the url and domain needs to be specified
     *
     * @param name Name of the cookies to remove.
     * @param url If specified, deletes all the cookies with the given name where domain and path
     * match provided URL.
     * @param domain If specified, deletes only cookies with the exact domain.
     * @param path If specified, deletes only cookies with the exact path.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun deleteCookies(name: String, url: String? = null, domain: String? = null, path: String? = null)

    /**
     * Clears browser cookies. @mcp
     *
     * ```kotlin
     * driver.clearBrowserCookies()
     * ```
     *
     * @see Browser.clearCookies
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun clearBrowserCookies()

    /**
     * Saves the current browser storage state, including cookies and the active origin's localStorage, as JSON. @mcp
     *
     * @return A JSON string that can later be passed to [loadStorageState].
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun saveStorageState(): String

    /**
     * Loads a previously saved browser storage state JSON, restoring cookies and localStorage. @mcp
     *
     * @param state A JSON string produced by [saveStorageState].
     * @return A JSON summary of the restored cookies, origins, and localStorage entries.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun loadStorageState(state: String): String

    /**
     * Wait until the element identified by the selector becomes present in the DOM or timeout. @mcp
     *
     * ```kotlin
     * val remainingTime = driver.waitForSelector("h2.title")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is waited for until it becomes present.
     * @return The remaining time until timeout when the element becomes present.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForSelector(selector: String): Duration = waitForSelector(selector) {}

    /**
     * Wait until the element identified by the selector becomes present in the DOM or timeout. @mcp
     *
     * ```kotlin
     * val remainingTime = driver.waitForSelector("h2.title", 30000)
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is waited for until it becomes present.
     * @param timeoutMillis The maximum time to wait for the element to become present.
     * @return The remaining time until timeout when the element becomes present.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForSelector(selector: String, timeoutMillis: Long): Long =
        waitForSelector(selector, timeoutMillis) {}

    /**
     * Wait for the element identified by the selector to become present in the DOM, or until timeout. @mcp
     *
     * ```kotlin
     * val remainingTime = driver.waitForSelector("h2.title", Duration.ofSeconds(30))
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is waited for until it becomes present.
     * @param timeout The maximum time to wait for the element to become present.
     * @return The remaining time until timeout when the element becomes present.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForSelector(selector: String, timeout: Duration): Duration = waitForSelector(selector, timeout) {}

    /**
     * Wait for the element identified by the selector to become present in the DOM, or until timeout. @mcp
     *
     * This method periodically checks for the existence of the element. If the element is not found during a check,
     * the action will be executed, such as scrolling the page down.
     *
     * ```kotlin
     * val remainingTime = driver.waitForSelector("h2.title") {
     *  driver.scrollDown()
     * }
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is waited for until it becomes present.
     * @param action The action to execute when the element is not found.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForSelector(selector: String, action: suspend () -> Unit): Duration

    /**
     * Wait for the element identified by the selector to become present in the DOM, or until timeout. @mcp
     *
     * This method periodically checks for the existence of the element. If the element is not found during a check,
     * the action will be executed, such as scrolling the page down.
     *
     * ```kotlin
     * val remainingTime = driver.waitForSelector("h2.title", 30000) {
     *  driver.scrollDown()
     * }
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is waited for until it becomes present.
     * @param timeoutMillis The maximum time to wait for the element to become present.
     * @param action The action to execute when the element is not found.
     * @return The remaining time until timeout when the element becomes present.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForSelector(selector: String, timeoutMillis: Long, action: suspend () -> Unit): Long =
        waitForSelector(selector, Duration.ofMillis(timeoutMillis), action).toMillis()

    /**
     * Wait for the element identified by the selector to become present in the DOM, or until timeout. @mcp
     *
     * This method periodically checks for the existence of the element. If the element is not found during a check,
     * the action will be executed, such as scrolling the page down.
     *
     * ```kotlin
     * val remainingTime = driver.waitForSelector("h2.title", Duration.ofSeconds(30)) {
     *  driver.scrollDown()
     * }
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is waited for until it becomes present.
     * @param timeout The maximum time to wait for the element to become present.
     * @param action The action to execute when the element is not found.
     * @return The remaining time until timeout when the element becomes present.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForSelector(selector: String, timeout: Duration, action: suspend () -> Unit): Duration

    /**
     * Wait until the current url changes or timeout. @mcp
     *
     * ```kotlin
     * val url = "https://www.example.com"
     * driver.navigate(url)
     * var remainingTime = driver.waitForNavigation()
     * if (remainingTime > 0) {
     *   driver.click("a[href='/next']")
     *   remainingTime = driver.waitForNavigation(url)
     * }
     * ```
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForNavigation(oldUrl: String = ""): Duration

    /**
     * Wait until the current url changes or timeout. @mcp
     *
     * ```kotlin
     * val url = "https://www.example.com"
     * driver.navigate(url)
     * var remainingTime = driver.waitForNavigation(1000)
     * if (remainingTime > 0) {
     *   driver.click("a[href='/next']")
     *   remainingTime = driver.waitForNavigation(url, 1000)
     * }
     * ```
     *
     * @param timeoutMillis The maximum time to wait for the url to change.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForNavigation(oldUrl: String = "", timeoutMillis: Long): Long =
        waitForNavigation(oldUrl, Duration.ofMillis(timeoutMillis)).toMillis()

    /**
     * Wait until the current url changes or timeout. @mcp
     *
     * ```kotlin
     * val timeout = Duration.ofSeconds(30)
     * val url = "https://www.example.com"
     * driver.navigate(url)
     * var remainingTime = driver.waitForNavigation(timeout)
     * if (remainingTime > 0) {
     *   driver.click("a[href='/next']")
     *   remainingTime = driver.waitForNavigation(url, timeout)
     * }
     * ```
     *
     * @param timeout The maximum time to wait for the url to change.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForNavigation(oldUrl: String = "", timeout: Duration): Duration

    /**
     * Await navigation to the specified URL page or timeout if necessary. @mcp
     *
     * ```kotlin
     * val newDriver = driver.waitForPage("https://www.example.com", Duration.ofSeconds(30))
     * ```
     *
     * TODO: check if waitForPage and waitForNavigation can be merged into one method.
     *
     * @param url The URL to navigate to.
     * @return The remaining time until timeout when the predicate returns true.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForPage(url: String, timeout: Duration): WebDriver?

    /**
     * Returns when the pageFunction returns a truthy value. @mcp
     *
     * @param pageFunction A JavaScript function to be evaluated in the page context.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun waitForFunction(pageFunction: String, timeout: Duration): WebDriver?

    /**
     * Wait until the predicate returns true. @mcp
     *
     * ```kotlin
     * val remainingTime = driver.waitUntil {
     *    driver.exists("h2.title")
     * }
     * ```
     *
     * @param predicate The predicate to check.
     * @return The remaining time until timeout when the predicate returns true.
     * */
    @Throws(WebDriverException::class)
    suspend fun waitUntil(predicate: suspend () -> Boolean): Duration

    /**
     * Wait until the predicate returns true. @mcp
     *
     * ```kotlin
     * val remainingTime = driver.waitUntil(10000) {
     *   driver.exists("h2.title")
     * }
     * ```
     *
     * @param timeoutMillis The maximum time to wait for the predicate to return true.
     * @param predicate The predicate to check.
     * @return The remaining time until timeout when the predicate returns true.
     * */
    @Throws(WebDriverException::class)
    suspend fun waitUntil(timeoutMillis: Long, predicate: suspend () -> Boolean): Long =
        waitUntil(Duration.ofMillis(timeoutMillis), predicate).toMillis()

    /**
     * Wait until the predicate returns true. @mcp
     *
     * ```kotlin
     * val remainingTime = driver.waitUntil(Duration.ofSeconds(10)) {
     *    driver.exists("h2.title")
     * }
     * ```
     *
     * @param timeout The maximum time to wait for the predicate to return true.
     * @param predicate The predicate to check.
     * @return The remaining time until timeout when the predicate returns true.
     * */
    @Throws(WebDriverException::class)
    suspend fun waitUntil(timeout: Duration, predicate: suspend () -> Boolean): Duration

    ///////////////////////////////////////////////////////////////////
    // Status checking
    //

    /**
     * Returns whether the element exists. @mcp
     *
     * ```kotlin
     * driver.exists("h2.title")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is checked for existence.
     * @return Whether the element exists.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun exists(selector: String): Boolean

    /**
     * Returns whether the element is hidden. @mcp
     *
     * ```kotlin
     * driver.isHidden("input[name='q']")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is checked for visibility state.
     * @return Whether the element is hidden.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun isHidden(selector: String): Boolean = !isVisible(selector)

    /**
     * Returns whether the element is visible. @mcp
     *
     * ```kotlin
     * driver.isVisible("input[name='q']")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is checked for visibility state.
     * @return Whether the element is visible.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun isVisible(selector: String): Boolean

    /**
     * Returns whether the element is checked. @mcp
     *
     * ```kotlin
     * driver.isChecked("input[name='agree']")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The element is checked for checked state.
     * @return Whether the element is checked.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun isChecked(selector: String): Boolean

    /////////////////////////////////////////////////
    // Interacts with the Webpage

    /**
     * Brings the browser window to the front. @mcp
     *
     * ```kotlin
     * driver.bringToFront()
     * ```
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun bringToFront()

    /**
     * This method hovers over the element. @mcp
     *
     * 1. Scroll the element into view if needed.
     * 2. Move mouse to hover over a random position near the center of the element.
     *
     * ```kotlin
     * driver.hover("h1")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun hover(selector: String)

    /**
     * This method fetches an element with `selector` and focuses it. @mcp
     *
     * If there's no element matching `selector`, nothing to do.
     *
     * ```kotlin
     * driver.focus("input[name='q']")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. If there are multiple matching
     * elements, the first will be focused.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun focus(selector: String)

    /**
     * This method emulates inserting text that doesn't come from a key press. @mcp
     *
     * ```kotlin
     * driver.type("Hello, World!")
     * driver.type("Hello, World!", "input[name='q']")
     * ```
     *
     * When [selector] is omitted, text is typed into the currently focused element.
     * When [selector] is provided, the matching element is focused first and then receives the text.
     *
     * @param text The text to insert.
     * @param selector The selector of the element, multiple formats supported. Optional. If provided, the matching
     * element is focused before typing. If there are multiple matching elements, the first is used.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun type(text: String, selector: String? = null)

    /**
     * Shortcut for keyboard down and keyboard up. @mcp
     *
     * The key is specified as a string, which can be a single character, a key name, or a combination of both.
     * For example, 'a', 'A', 'KeyA', 'Enter', 'Shift+A', and 'Control+Shift+Tab' are all valid keys.
     *
     * ```kotlin
     * driver.press("Enter")
     * driver.press("Enter", "input[name='q']")
     * ```
     *
     * When [selector] is omitted, the key is pressed on the currently focused element.
     * When [selector] is provided, the matching element is focused first and then receives the key press.
     *
     * @param key A key to press. The key can be a single character, a key name, or a combination of both.
     *      See [Code values for keyboard events](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values)
     * @param selector The selector of the element, multiple formats supported. Optional. If provided, the matching
     * element is focused before pressing the key. If there are multiple matching elements, the first is used.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun press(key: String, selector: String? = null)

    /**
     * This method emulates inserting text that doesn't come from a key press. @mcp
     *
     * Unlike [type], this method clears the existing value before typing.
     *
     * ```kotlin
     * driver.fill("input[name='q']", "Hello, World!")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. The matching element is focused and
     * then filled with text. If there are multiple matching elements, the first will be focused.
     * @param text The text to fill.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun fill(selector: String, text: String)

    /**
     * Presses and holds a keyboard key on the currently focused element. @mcp
     *
     * ```kotlin
     * driver.keyDown("Shift")
     * driver.keyDown("Control")
     * ```
     *
     * @param key A key to press and hold. The key can be a single character, a key name, or a combination of both.
     *      See [Code values for keyboard events](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values)
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun keyDown(key: String)

    /**
     * Releases a previously pressed keyboard key on the currently focused element. @mcp
     *
     * ```kotlin
     * driver.keyUp("Shift")
     * driver.keyUp("Control")
     * ```
     *
     * @param key A key to release. The key can be a single character, a key name, or a combination of both.
     *      See [Code values for keyboard events](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values)
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun keyUp(key: String)

    /**
     * Focus on an element with [selector] and click it. @mcp
     *
     * If there's no element matching `selector`, nothing to do.
     *
     * ```kotlin
     * driver.click("button[type='submit']")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. If there are multiple matching
     * elements, the first will be focused.
     * @param count The number of times to click.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun click(selector: String, count: Int = 1)

    /**
     * Focus on an element with [selector] and click it with [modifier] pressed. @mcp
     *
     * @param selector The selector of the element, multiple formats supported. The matching element is clicked.
     * @param modifier The keyboard modifier to press while clicking (e.g., "Shift", "Control", "Alt").
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun click(selector: String, modifier: String)

    /**
     * Focus on an element with [selector] and double-click it. @mcp
     *
     * @param selector The selector of the element, multiple formats supported. The matching element is double-clicked.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun dblclick(selector: String)

    /**
     * Focus on an element with [selector] and double-click it with [modifier] pressed. @mcp
     *
     * @param selector The selector of the element, multiple formats supported. The matching element is double-clicked.
     * @param modifier The keyboard modifier to press while double-clicking.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun dblclick(selector: String, modifier: String)

    /**
     * Resizes the viewport to the specified width and height. @mcp
     * */
    @MCP
    suspend fun resize(width: Int, height: Int)

    /**
     * Accepts the dialog. @mcp
     * */
    @MCP
    suspend fun dialogAccept(promptText: String? = null)

    /**
     * Dismisses the dialog. @mcp
     * */
    @MCP
    suspend fun dialogDismiss()

    /**
     * This method clicks an element with [selector] whose text content matches [pattern], and then focuses it. @mcp
     *
     * If there's no element matching [selector], or the element's text content doesn't match [pattern], nothing to do.
     *
     * ```kotlin
     * driver.clickTextMatches("button", "submit")
     * ```
     *
     * TODO: use playwright style selector which supports text matching, for example: `button:has-text("submit")`
     *
     * @param selector The selector of the element, multiple formats supported. If there are multiple matching
     * elements, the first will be focused.
     * @param pattern The pattern to match the text content.
     * @param count The number of times to click.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun clickTextMatches(selector: String, pattern: String, count: Int = 1)

    /**
     * This method clicks an element with [selector] whose attribute name is [attrName] and value matches [pattern], and then focuses it. @mcp
     *
     * If there's no element matching [selector], or the element has no attribute [attrName],
     * or the element's attribute value doesn't match [pattern], nothing to do.
     *
     * ```kotlin
     * driver.clickMatches("button", "type", "submit")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. If there are multiple matching
     * elements, the first will be focused.
     * @param attrName The attribute name to match.
     * @param pattern The pattern to match the attribute value.
     * @param count The number of times to click.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun clickMatches(selector: String, attrName: String, pattern: String, count: Int = 1)

    /**
     * Selects one or more options in a <select> element. @mcp
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param values The values or labels of the options to select
     * @return The list of selected option values
     */
    @MCP
    suspend fun selectOption(selector: String, values: List<String>): List<String>

    /**
     * Selects an option in a <select> element. @mcp
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param value The value or label of the option to select
     * @return The selected option value, or null if not found
     */
    @MCP
    suspend fun selectOption(selector: String, value: String): String? {
        val selected = selectOption(selector, listOf(value))
        return selected.firstOrNull()
    }

    /**
     * This method check an element with [selector]. If there's no element matching [selector], nothing to do. @mcp
     *
     * @param selector The selector of the element, multiple formats supported. If there are multiple matching
     * elements, the first will be checked.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun check(selector: String)

    /**
     * This method uncheck an element with [selector]. If there's no element matching [selector], nothing to do. @mcp
     *
     * @param selector The selector of the element, multiple formats supported. If there are multiple matching
     * elements, the first will be unchecked.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun uncheck(selector: String)

    /**
     * This method fetches an element with [selector], scrolls it into view if needed. @mcp
     *
     * If there's no element matching [selector], the method does nothing.
     *
     * ```kotlin
     * driver.scrollTo("h2.title")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. If there are multiple matching
     * elements, the first will be scrolled into view.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollTo(selector: String): Double

    /**
     * The current page frame scrolls down for [count] times. @mcp
     *
     * ```kotlin
     * driver.scrollDown(3)
     * ```
     *
     * NOTE: This uses JS scrollBy for fast, direct scroll control. For realistic
     * user-like scrolling that triggers all event handlers, use [mouseWheelDown].
     *
     * @param count The times to scroll down.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollDown(count: Int = 1): Double

    /**
     * The current page frame scrolls up for [count] times. @mcp
     *
     * ```kotlin
     * driver.scrollUp(3)
     * ```
     *
     * NOTE: This uses JS scrollBy for fast, direct scroll control. For realistic
     * user-like scrolling that triggers all event handlers, use [mouseWheelUp].
     *
     * @param count The times to scroll up.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollUp(count: Int = 1): Double

    /**
     * Scrolls the current page frame vertically by the given number of pixels. @mcp
     *
     * Positive values scroll down, negative values scroll up. The target position is clamped to the
     * scrollable range [0, scrollHeight - viewportHeight].
     *
     * When [smooth] is true a deterministic, step‑wise scrolling algorithm is used (instead of relying
     * on the browser's native smooth scrolling) for higher stability: it issues incremental
     * `window.scrollTo()` calls and waits for the viewport to settle after each step. This reduces race
     * conditions with lazy‑loaded content or async layout changes and yields more predictable final
     * positions.
     *
     * Typical usage:
     * ```kotlin
     * // Scroll down 200px smoothly (default)
     * driver.scrollBy()
     * // Scroll up 500px immediately
     * driver.scrollBy(-500.0, smooth = false)
     * // Scroll down one viewport height, smooth
     * val y = driver.scrollBy(driver.viewportHeight(), smooth = true)
     * ```
     *
     * @param pixels The delta in pixels to scroll; positive scrolls down, negative scrolls up. Default 200.0.
     * @param smooth Whether to perform a deterministic smooth scrolling sequence. If false a single
     *               `window.scrollTo()` is executed.
     * @return The final vertical scroll offset (window.scrollY) after scrolling.
     * @throws WebDriverException If the underlying browser interaction fails.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollBy(pixels: Double = 200.0, smooth: Boolean = true): Double

    /**
     * The current page frame scrolls to the top. @mcp
     *
     * ```kotlin
     * driver.scrollToTop()
     * ```
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollToTop(): Double

    /**
     * The current page frame scrolls to the bottom. @mcp
     *
     * ```kotlin
     * driver.scrollToBottom()
     * ```
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollToBottom(): Double

    /**
     * The current page frame scrolls to the middle. @mcp
     *
     * ```kotlin
     * driver.scrollToMiddle(0.2)
     * driver.scrollToMiddle(0.5)
     * driver.scrollToMiddle(0.8)
     * ```
     *
     * @param ratio The ratio of the page to scroll to, 0.0 means the top, 1.0 means the bottom.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollToMiddle(ratio: Double): Double

    /**
     * Scroll to the given 0-based viewport position. @mcp
     *
     * ```kotlin
     * driver.scrollToViewport(0.0)
     * driver.scrollToViewport(0.5)
     * driver.scrollToViewport(1.5)
     * driver.scrollToViewport(2.0)
     * ```
     *
     * @param n The viewport number of the page to scroll to (0-based).
     * 0.00 means at the top of the first screen, 1.50 means halfway through the second screen.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun scrollToViewport(n: Double, smooth: Boolean = true): Double

    /**
     * The mouse wheels down for [count] times. @mcp
     *
     * ```kotlin
     * driver.mouseWheelDown(3)
     * ```
     *
     * @param count The times to wheel down.
     * @param deltaX The distance to wheel horizontally.
     * @param deltaY The distance to wheel vertically.
     * @param delayMillis The delay time in milliseconds between ticks.
     *        Use 0 for no delay, -1 (default) for the policy-based random delay,
     *        or a positive value for a fixed delay.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun mouseWheelDown(count: Int = 1, deltaX: Double = 0.0, deltaY: Double = 150.0, delayMillis: Long = -1)

    /**
     * The mouse wheels up for [count] times. @mcp
     *
     * ```kotlin
     * driver.mouseWheelUp(3)
     * ```
     *
     * @param count The times to wheel up.
     * @param deltaX The distance to wheel horizontally.
     * @param deltaY The distance to wheel vertically.
     * @param delayMillis The delay time in milliseconds between ticks.
     *        Use 0 for no delay, -1 (default) for the policy-based random delay,
     *        or a positive value for a fixed delay.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun mouseWheelUp(count: Int = 1, deltaX: Double = 0.0, deltaY: Double = -150.0, delayMillis: Long = -1)

    /**
     * Scrolls the mouse wheel by the provided deltas. @mcp
     *
     * Positive [deltaY] scrolls down and negative [deltaY] scrolls up.
     *
     * ```kotlin
     * driver.mouseWheel(0.0, 150.0)
     * driver.mouseWheel(0.0, -150.0)
     * ```
     *
     * @param deltaX The distance to wheel horizontally.
     * @param deltaY The distance to wheel vertically.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun mouseWheel(deltaX: Double = 0.0, deltaY: Double = 150.0)

    /**
     * Scrolls the mouse wheel on a specific element identified by [selector]. @mcp
     *
     * The element is scrolled into view if needed, the mouse is moved to a random
     * position near the element's center, and then the wheel event is dispatched.
     *
     * ```kotlin
     * driver.mouseWheel(".scrollable-panel", 0.0, 200.0)
     * ```
     *
     * @param selector The selector of the element to wheel on.
     * @param deltaX The distance to wheel horizontally.
     * @param deltaY The distance to wheel vertically.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun mouseWheel(selector: String, deltaX: Double = 0.0, deltaY: Double = 150.0)

    /**
     * Moves the mouse to the position specified by [x] and [y]. @mcp
     *
     * ```kotlin
     * driver.mouseMove(100.0, 200.0)
     * ```
     *
     * @param x The x coordinate to move to.
     * @param y The y coordinate to move to.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun mouseMove(x: Double, y: Double)

    /**
     * Presses a mouse button at the current mouse position. @mcp
     *
     * ```kotlin
     * driver.mouseDown()
     * driver.mouseDown("right")
     * ```
     *
     * @param button The mouse button to press. Supported values are `left`, `middle`, and `right`.
     * @param clickCount The click count associated with the press event.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun mouseDown(button: String = "left", clickCount: Int = 1)

//    @Throws(WebDriverException::class)
//    @MCP
//    suspend fun mouseDown(x: Double, y: Double, button: String = "left", modifier: String? = null)

    /**
     * Releases a mouse button at the current mouse position. @mcp
     *
     * ```kotlin
     * driver.mouseUp()
     * driver.mouseUp("right")
     * ```
     *
     * @param button The mouse button to release. Supported values are `left`, `middle`, and `right`.
     * @param clickCount The click count associated with the release event.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun mouseUp(button: String = "left", clickCount: Int = 1)

//    @Throws(WebDriverException::class)
//    @MCP
//    suspend fun mouseUp(x: Double, y: Double, button: String = "left", modifier: String? = null)


    /**
     * The mouse moves to the element with [selector]. @mcp
     *
     * ```kotlin
     * driver.moveMouseTo("h2.title")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param deltaX The distance to the left of the element.
     * @param deltaY The distance to the top of the element.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun moveMouseTo(selector: String, deltaX: Int, deltaY: Int = 0)

    /**
     * Performs a drag, dragenter, dragover, and drop in sequence. @mcp
     *
     * @param selector The selector of the element, multiple formats supported. The matching element is dragged from.
     * @param deltaX The distance to drag horizontally.
     * @param deltaY The distance to drag vertically.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun dragAndDrop(selector: String, deltaX: Int, deltaY: Int = 0)


    /**
     * Drags the element identified by [sourceSelector] onto the element identified by [targetSelector]. @mcp
     *
     * The implementation dispatches an HTML5 drag sequence between the source and target elements using a shared
     * `DataTransfer` payload so selector-to-selector drag flows can be asserted reliably by automation clients.
     *
     * @param sourceSelector The selector of the element, multiple formats supported. Identifies the drag source.
     * @param targetSelector The selector of the element, multiple formats supported. Identifies the drop target.
     */
    @MCP
    suspend fun drag(sourceSelector: String, targetSelector: String) {
        val encodedSource = Pson.toJson(sourceSelector)
        val encodedTarget = Pson.toJson(targetSelector)
        val script = """
            (() => {
                const source = document.querySelector($encodedSource);
                const target = document.querySelector($encodedTarget);
                if (!source || !target) {
                    return JSON.stringify({
                        ok: false,
                        error: !source && !target
                            ? 'Source and target elements were not found'
                            : !source
                                ? 'Source element was not found'
                                : 'Target element was not found'
                    });
                }
                if (typeof DataTransfer === 'undefined' || typeof DragEvent === 'undefined') {
                    return JSON.stringify({
                        ok: false,
                        error: 'HTML5 drag-and-drop APIs are not available in the current page context'
                    });
                }

                const sourceRect = source.getBoundingClientRect();
                const targetRect = target.getBoundingClientRect();
                const sourceX = Math.round(sourceRect.left + sourceRect.width / 2);
                const sourceY = Math.round(sourceRect.top + sourceRect.height / 2);
                const targetX = Math.round(targetRect.left + targetRect.width / 2);
                const targetY = Math.round(targetRect.top + targetRect.height / 2);
                const dataTransfer = new DataTransfer();

                const fire = (element, type, clientX, clientY) => {
                    const event = new DragEvent(type, {
                        bubbles: true,
                        cancelable: true,
                        composed: true,
                        dataTransfer,
                        clientX,
                        clientY
                    });
                    element.dispatchEvent(event);
                };

                fire(source, 'dragstart', sourceX, sourceY);
                fire(target, 'dragenter', targetX, targetY);
                fire(target, 'dragover', targetX, targetY);
                fire(target, 'drop', targetX, targetY);
                fire(source, 'dragend', targetX, targetY);

                return JSON.stringify({ ok: true });
            })()
        """.trimIndent()
        val result = evaluate(script) as? String
            ?: """{"ok":false,"error":"Failed to execute drag script"}"""
        val parsed = pulsarObjectMapper().readTree(result)
        if (!parsed.path("ok").asBoolean(false)) {
            val error = parsed.path("error").asText("Unknown drag failure")
            throw WebDriverException(
                "Failed to drag '$sourceSelector' to '$targetSelector': $error",
                driver = this
            )
        }
    }

    /**
     * Returns the document's HTML markup. @mcp
     *
     * ```kotlin
     * val html = driver.outerHTML()
     * ```
     *
     * If the document does not exist, returns null.
     *
     * @return The HTML markup of the document.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun outerHTML(): String?

    /**
     * Returns the node's HTML markup, the node is located by [selector]. @mcp
     *
     * If the node does not exist, returns null.
     *
     * ```kotlin
     * val html = driver.outerHTML("h2.title")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @return The HTML markup of the node.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun outerHTML(selector: String): String?

    /**
     * Returns the text content of the document or a specific element. @mcp
     *
     * If [selector] is null, returns the text content of the entire document (usually `document.body.innerText`).
     * If [selector] is provided, returns the text content of the first matching element.
     *
     * If the document or element does not exist, returns null.
     *
     * ```kotlin
     * val bodyText = driver.textContent()
     * val titleText = driver.textContent("h1.title")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported. If null, targets the document body.
     * @return The text content, or null if not found.
     * */
    @MCP
    suspend fun textContent(selector: String? = null): String?

    /**
     * Extracts multiple fields from the page using CSS selectors. @mcp
     *
     * @param fields A map where keys are field names and values are CSS selectors.
     * @return A map containing the extracted text content for each field. Values are null if the selector matches nothing.
     * */
    suspend fun extract(fields: Map<String, String>): Map<String, String?>

    /**
     * Use querySelectorAll to get all elements matched by [selector], and then return their NodeRefs. @mcp
     *
     * Note that the NodeRefs may become stale after certain operations, so they should be used immediately after selection.
     *
     * @param selector The selector of the element, multiple formats supported. Supported selector types include CSS
     * selector, XPath, and backend node id. The type is determined by the selector prefix, e.g. "css:div" for CSS
     * selector, "xpath://div" for XPath, and "e123" for backendNodeId. If no prefix is provided, CSS selector is
     * used by default.
     * @return a list of NodeRefs for the matched elements, or an empty list if no elements are matched or an error occurs.
     * */
    @Throws(WebDriverException::class)
    suspend fun querySelectorAll(selector: String): List<NodeRef>

    /**
     * Returns the node's text content, the node is located by [selector]. @mcp
     *
     * If the node does not exist, returns null.
     *
     * ```kotlin
     * val text = driver.selectFirstTextOrNull("h2.title")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @return The text content of the node.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun selectFirstTextOrNull(selector: String): String?

    /**
     * Returns a list of text contents of all the elements matching the specified selector within the page. @mcp
     *
     * If no elements match the selector, returns an empty list.
     *
     * ```kotlin
     * val texts = driver.selectTextAll("h2")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @return The text contents of the nodes.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun selectTextAll(selector: String): List<String>

    /**
     * Returns the node's attribute value, the node is located by [selector], the attribute is [attrName]. @mcp
     *
     * If the node does not exist, or the attribute does not exist, returns null.
     *
     * ```kotlin
     * val classes = driver.selectFirstAttributeOrNull("h2.title", "class")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param attrName The attribute name to retrieve.
     * @return The attribute value of the node.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun selectFirstAttributeOrNull(selector: String, attrName: String): String?

    /**
     * Returns the node's attribute values, the node is located by [selector]. @mcp
     *
     * If the node do not exist, or the attribute does not exist, returns an empty list.
     *
     * ```kotlin
     * val classes = driver.selectAttributes("h2.title", "class")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @return The attribute pairs of the nodes.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun selectAttributes(selector: String): Map<String, String>

    /**
     * Returns the nodes' attribute values, the nodes are located by [selector], the attribute is [attrName]. @mcp
     *
     * If the nodes do not exist, or the attribute does not exist, returns an empty list.
     *
     * ```kotlin
     * val classes = driver.selectAttributeAll("h2.title", "class")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param attrName The attribute name to retrieve.
     * @param start The offset of the first node to select.
     * @param limit The maximum number of nodes to select.
     * @return The attribute values of the nodes.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun selectAttributeAll(selector: String, attrName: String, start: Int = 0, limit: Int = 10000): List<String>

    /**
     * Set the attribute of an element located by [selector]. @mcp
     *
     * ```kotlin
     * driver.setAttribute("h2.title", "class", "header")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param attrName The attribute name to set.
     * @param attrValue The attribute value to set.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun setAttribute(selector: String, attrName: String, attrValue: String)

    /**
     * Set the attribute of all elements matching [selector]. @mcp
     *
     * ```kotlin
     * driver.setAttributeAll("h2.title", "class", "header")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param attrName The attribute name to set.
     * @param attrValue The attribute value to set.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun setAttributeAll(selector: String, attrName: String, attrValue: String)

    /**
     * Returns the node's property value, the node is located by [selector], the property is [propName]. @mcp
     *
     * If the node does not exist, or the property does not exist, returns null.
     *
     * ```kotlin
     * val classes = driver.selectFirstPropertyOrNull("input#input", "value")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param propName The property name to retrieve.
     * @return The property value of the node.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun selectFirstPropertyValueOrNull(selector: String, propName: String): String?

    /**
     * Returns the nodes' property values, the nodes are located by [selector], the property is [propName]. @mcp
     *
     * If the nodes do not exist, or the property does not exist, returns an empty list.
     *
     * ```kotlin
     * val classes = driver.selectPropertyAll("input#input", "value")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param propName The property name to retrieve.
     * @param start The offset of the first node to select.
     * @param limit The maximum number of nodes to select.
     * @return The property values of the nodes.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun selectPropertyValueAll(
        selector: String,
        propName: String,
        start: Int = 0,
        limit: Int = 10000
    ): List<String>

    /**
     * Set the property of an element located by [selector]. @mcp
     *
     * ```kotlin
     * driver.setProperty("input#input", "value")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param propName The property name to set.
     * @param propValue The property value to set.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun setProperty(selector: String, propName: String, propValue: String)

    /**
     * Set the property of all elements matching [selector]. @mcp
     *
     * ```kotlin
     * driver.setPropertyAll("input#input", "value")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param propName The property name to set.
     * @param propValue The property value to set.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun setPropertyAll(selector: String, propName: String, propValue: String)

    /**
     * Find hyperlinks in elements matching [selector].
     *
     * ```kotlin
     * val hyperlinks = driver.selectHyperlinks("a.product-link")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param offset The offset of the first element to select.
     * @param limit The maximum number of elements to select.
     * @return The hyperlinks in the elements.
     * */
    @Throws(WebDriverException::class)
    suspend fun selectHyperlinks(selector: String, offset: Int = 1, limit: Int = Int.MAX_VALUE): List<Hyperlink>

    /**
     * Find anchor elements matching [selector].
     *
     * ```kotlin
     * val anchors = driver.selectAnchors("a.product-link")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param offset The offset of the first element to select.
     * @param limit The maximum number of elements to select.
     * @return The anchors.
     * */
//    @Throws(WebDriverException::class)
//    suspend fun selectAnchors(selector: String, offset: Int = 1, limit: Int = Int.MAX_VALUE): List<GeoAnchor>

    /**
     * Find image elements matching [selector].
     *
     * ```kotlin
     * val images = driver.selectImages("img.product-image")
     * ```
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param offset The offset of the first element to select.
     * @param limit The maximum number of elements to select.
     * @return The image URLs.
     * */
    @Throws(WebDriverException::class)
    suspend fun selectImages(selector: String, offset: Int = 1, limit: Int = Int.MAX_VALUE): List<String>

    /**
     * Executes JavaScript in the context of the currently selected frame or window. @mcp
     *
     * If the result is not JavaScript object, it is not returned.
     *
     * If you want to execute a function, convert it to IIFE (Immediately Invoked Function Expression).
     *
     * ```kotlin
     * val title = driver.evaluate("document.title")
     * ```
     *
     * Multi-line JavaScript code:
     *
     * ```kotlin
     * val code = """
     * () => {
     *   const a = 10;
     *   const b = 20;
     *   return a * b;
     * }
     * """.trimIndent()
     *
     * val result = driver.evaluate(code)
     * ```
     *
     * ### 🔍 Notes:
     * * **Wrap the code in an IIFE (Immediately Invoked Function Expression)** to return a value.
     * * **Escape line breaks** with `\n`.
     *
     * @param expression JavaScript expression to evaluate
     * @return Remote object value in case of primitive values or null.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun evaluate(expression: String): Any?

    /**
     * Executes JavaScript and returns the result, or [defaultValue] if the result is null or incompatible. @mcp
     *
     * @param expression The JavaScript expression to evaluate.
     * @param defaultValue The value to return if evaluation fails or returns null.
     * @return The evaluation result or [defaultValue].
     */
    @Throws(WebDriverException::class)
    suspend fun <T> evaluate(expression: String, defaultValue: T): T

    /**
     * returns detailed evaluation metadata (beta). @mcp
     *
     * @param expression The JavaScript expression to evaluate.
     * @return A [JsEvaluation] object containing the result and metadata.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun evaluateDetail(expression: String): JsEvaluation?

    /**
     * Executes JavaScript and returns the result as a JSON-serializable value. @mcp
     *
     * If the result is an object, it is serialized to a Map or List.
     * If the result is a primitive, it is returned as is.
     * If the result is null or undefined, null is returned.
     *
     * @param expression The JavaScript expression to evaluate.
     * @return The result as a JSON-compatible object (Map, List, String, Number, Boolean, or null).
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun evaluateValue(expression: String): Any?

    /**
     * Executes JavaScript and returns the result as a JSON-serializable value, or [defaultValue] if null/incompatible. @mcp
     *
     * @param expression The JavaScript expression to evaluate.
     * @param defaultValue The value to return if evaluation fails or returns null.
     * @return The evaluation result or [defaultValue].
     * */
    @Throws(WebDriverException::class)
    suspend fun <T> evaluateValue(expression: String, defaultValue: T): T

    /**
     * Returns detailed value evaluation metadata (beta). @mcp
     *
     * @param expression The JavaScript expression to evaluate.
     * @return A [JsEvaluation] object containing the result value and metadata.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun evaluateValueDetail(expression: String): JsEvaluation?

    /**
     * Returns detailed value evaluation metadata with optional async await support.
     *
     * @param expression The JavaScript expression to evaluate.
     * @param awaitPromise Whether to wait for the evaluated expression's Promise to resolve.
     * @return A [JsEvaluation] object containing the result value and metadata.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun evaluateValueDetail(expression: String, awaitPromise: Boolean): JsEvaluation?

    /**
     * Executes JavaScript for the element located by [selector] and returns the result as a JSON-serializable value. @mcp
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param functionDeclaration The JavaScript function declaration to execute against the matched element.
     * @return The result as a JSON-compatible object (Map, List, String, Number, Boolean, or null).
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun evaluateValue(selector: String, functionDeclaration: String): Any?

    /**
     * Returns detailed value evaluation metadata for the element located by [selector]. @mcp
     *
     * @param selector The selector of the element, multiple formats supported.
     * @param functionDeclaration The JavaScript function declaration to execute against the matched element.
     * @return A [JsEvaluation] object containing the result value and metadata.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun evaluateValueDetail(selector: String, functionDeclaration: String): JsEvaluation?

    /**
     * Execute a Chrome DevTools Protocol (CDP) command and return the deserialized result. @mcp
     *
     * This is an extension point for advanced scenarios that are not yet covered by
     * the high-level WebDriver API. Use it to issue arbitrary CDP commands directly
     * against the current page target.
     *
     * The return value is the deserialized CDP response (Map, List, String, Number, Boolean, or null).
     *
     * Example:
     * ```kotlin
     * // Screenshot the page as JPEG
     * val result = driver.executeCdpCommand("Page.captureScreenshot", mapOf("format" to "jpeg", "quality" to 80))
     *
     * // Get all cookies
     * val cookies = driver.executeCdpCommand("Network.getCookies")
     * ```
     *
     * @param method The full CDP method name (e.g. "Page.captureScreenshot").
     * @param params Optional parameters for the CDP command.
     * @return The deserialized result (Map for objects, List for arrays, or primitive), or null.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun executeCdpCommand(method: String, params: Map<String, Any?>? = null): Any?

    /**
     * Generates a unique CSS selector path for the element located by [selector]. @mcp
     *
     * Walks up the DOM from the target element, building a CSS selector segment for
     * each ancestor using the best available strategy: `#id`, `tag.stable-classes`,
     * or `tag:nth-of-type(n)`. The resulting path is joined with `>` separators,
     * producing a concise, human-readable selector suitable for use in other commands.
     *
     * @param selector CSS selector or element reference (e5, backend:15).
     * @return A CSS selector path string such as `#main > div.content > a:nth-of-type(3)`, or null if not found.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun generateLocator(selector: String): String?

    /**
     * Capture a screenshot of the current viewport (or primary browsing context) after ensuring any pending layout. @mcp
     *
     * If the backend supports element-centric capture this may represent the full page; implementation specific.
     *
     * The target element (if any) is scrolled into view before capture.
     *
     * ```kotlin
     * val base64 = driver.screenshot()
     * val bytes = Base64.getDecoder().decode(base64)
     * ```
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun screenshot(fullPage: Boolean = false): String?

    /**
     * Scroll the element matched by [selector] into view (if needed) then take a screenshot of that element's bounding box. @mcp
     *
     * Returns a Base64 encoded image (implementation usually PNG/JPEG), PNG by default.
     *
     * @param selector The selector of the element, multiple formats supported.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun screenshot(selector: String): String?

    /**
     * Take a screenshot of the rectangle specified by [rect] in the current page coordinate space. @mcp
     *
     * Caller is responsible for ensuring the rectangle is visible or scrolled into view if the implementation requires it.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun screenshot(rect: RectD): String?

    @Deprecated("Use screenshot(fullPage = true) instead", ReplaceWith("screenshot(fullPage = true)"))
    suspend fun captureScreenshot(fullPage: Boolean = false): String? = screenshot(fullPage)

    @Deprecated("Use screenshot(selector) instead", ReplaceWith("screenshot(selector)"))
    suspend fun captureScreenshot(selector: String): String? = screenshot(selector)

    @Deprecated("Use screenshot(rect) instead", ReplaceWith("screenshot(rect)"))
    suspend fun captureScreenshot(rect: RectD): String? = screenshot(rect)

    /**
     * Print the current page as PDF. @mcp
     *
     * Returns a Base64 encoded PDF document generated via Chrome DevTools Protocol
     * Page.printToPDF. Uses A4 paper size, portrait orientation, and prints background
     * graphics by default.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun pdf(): String?

    @Throws(WebDriverException::class)
    @MCP
    suspend fun ariaSnapshot(boxes: Boolean = false): String

    /**
     * Return the ARIA snapshot (accessibility tree in YAML format) for the specified viewports. @mcp
     *
     * The [viewports] parameter controls which viewport(s) to include:
     * - `"all"` — return the full-page snapshot (default).
     * - `"2"` — return only viewport 2.
     * - `"0,2,4"` — return viewports 0, 2 and 4.
     * - `"1-3"` — return viewports 1, 2 and 3.
     *
     * Viewport indices are 0-based. The viewport height is determined by the browser's
     * current viewport DimIs.
     *
     * @param viewports A viewport specification string (e.g., `"2"`, `"0,2,4"`, `"1-3"`, `"all"`).
     * @param boxes When true, includes each element's bounding box as [box=x,y,width,height].
     * @return The ARIA snapshot YAML covering only the requested viewports.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun ariaSnapshot(viewports: String, boxes: Boolean = false): String = ariaSnapshot(boxes = boxes)

    /**
     * Return the ARIA snapshot (accessibility tree in YAML format) with filtering
     * [options] applied. @mcp
     *
     * Supports interactive-only mode, URL inclusion, compact mode, depth limiting,
     * CSS selector scoping, and viewport filtering. All options compose.
     *
     * @param options The filtering and rendering options.
     * @return The ARIA snapshot YAML with options applied.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun ariaSnapshot(options: AriaSnapshotOptions): String = ariaSnapshot()

    /**
     * Calculate the clickable point of an element located by [selector]. @mcp
     *
     * If the element does not exist, or is not clickable, returns null.
     *
     * @param selector The selector of the element, multiple formats supported. Used to calculate the clickable point.
     * @return The clickable point of the element.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun clickablePoint(selector: String): PointD?

    /**
     * Return the bounding box of an element located by [selector]. @mcp
     *
     * If the element does not exist, returns null.
     *
     * @param selector The selector of the element, multiple formats supported. Used to calculate the bounding box.
     * @return The bounding box of the element.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun boundingBox(selector: String): RectD?

    /**
     * Create a new Jsoup session with the last page's context, which means, the same headers and cookies. @mcp
     *
     * @return The Jsoup session.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun newJsoupSession(): Connection

    /**
     * Load the url as a resource with Jsoup rather than browser rendering, with the last page's context. @mcp
     *
     * This means, the same headers and cookies.
     *
     * @param url The URL to load.
     * @return The Jsoup response.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun loadJsoupResource(url: String): Connection.Response

    /**
     * Load the url as a resource without browser rendering, with the last page's context. @mcp
     *
     * This means, the same headers and cookies.
     *
     * @param url The URL to load.
     * @return The network resource response.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun loadResource(url: String): NetworkResourceResponse

    /**
     * Delay for a given amount of time. @mcp
     *
     * @param millis The amount of time to delay, in milliseconds.
     * */
    @MCP
    suspend fun delay(millis: Long = 1000) = kotlinx.coroutines.delay(millis.milliseconds)

    /**
     * Delay for a given amount of time. @mcp
     *
     * @param duration The amount of time to delay.
     * */
    @MCP
    suspend fun delay(duration: Duration) = kotlinx.coroutines.delay(duration.toMillis().milliseconds)

    /**
     * Delay for a given amount of time. @mcp
     *
     * @param duration The amount of time to delay.
     * */
    @MCP
    suspend fun delay(duration: kotlin.time.Duration) =
        kotlinx.coroutines.delay(duration.inWholeMilliseconds.milliseconds)

    /**
     * Upload files to the element located by [selector]. @mcp
     *
     * @param selector The selector of the element, multiple formats supported. It should resolve to the file input element.
     * @param paths The list of file paths to upload.
     */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun upload(selector: String, paths: List<String>)

    /**
     * Force the page pauses all navigations and PENDING resource fetches. @mcp
     *
     * If the page loading pauses, the user can still interact with the page,
     * and therefore resources can continue to load.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun pause()

    /**
     * Force the page stop all navigations and RELEASES all resources. @mcp
     *
     * Interaction with the stop page results in undefined behavior and the results should not be trusted.
     *
     * If a web driver stops, it can later be used to visit other pages.
     * */
    @Throws(WebDriverException::class)
    @MCP
    suspend fun stop()
}
