//! MCP BrowsingService: tool implementations

use browsing::{Browser, config::Config};
use rmcp::{
    ServerHandler,
    handler::server::{router::tool::ToolRouter, wrapper::Parameters},
    model::{Content, ErrorData as McpError, *},
    tool, tool_handler, tool_router,
};
use std::sync::Arc;
use tokio::sync::RwLock;

use super::params::*;
use super::sitemap;

#[derive(Clone)]
pub struct BrowsingService {
    /// Shared browser instance; cloned for shutdown handler in main
    pub browser: Arc<RwLock<Option<Browser>>>,
    pub tool_router: ToolRouter<Self>,
}

#[tool_router]
impl BrowsingService {
    pub fn new() -> Self {
        Self {
            browser: Arc::new(RwLock::new(None)),
            tool_router: Self::tool_router(),
        }
    }

    async fn ensure_browser(&self) -> Result<(), McpError> {
        let mut g = self.browser.write().await;
        if g.is_none() {
            let profile = Config::from_env().browser_profile;
            let mut browser = Browser::new(profile);
            browser.start().await.map_err(|e| {
                McpError::internal_error(format!("Browser start failed: {}", e), None)
            })?;
            *g = Some(browser);
        }
        Ok(())
    }

    #[tool(description = "Navigate to a URL")]
    async fn navigate(
        &self,
        Parameters(p): Parameters<NavigateParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let mut g = self.browser.write().await;
        let browser = g
            .as_mut()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        browser
            .navigate(&p.url)
            .await
            .map_err(|e| McpError::internal_error(format!("Navigate failed: {}", e), None))?;
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "url": p.url
        })))
    }

    #[tool(description = "Get all links on the current page (index, href, text)")]
    async fn get_links(&self) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let script = r#"
            (function() {
                const links = Array.from(document.querySelectorAll('a[href]'))
                    .filter(a => a.href && !a.href.startsWith('javascript:'))
                    .map((a, i) => ({
                        index: i,
                        href: a.href,
                        text: (a.textContent || '').trim().slice(0, 150)
                    }));
                return JSON.stringify(links);
            })()
        "#;
        let result = page
            .evaluate(script)
            .await
            .unwrap_or_else(|_| "[]".to_string());
        let links: Vec<serde_json::Value> = serde_json::from_str(&result).unwrap_or_default();
        let url = browser.get_current_url().await.unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "url": url,
            "links": links,
            "count": links.len()
        })))
    }

    #[tool(description = "Follow a link by index (from get_links) or by URL")]
    async fn follow_link(
        &self,
        Parameters(p): Parameters<FollowLinkParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let url = if let Some(u) = p.url {
            u
        } else if let Some(idx) = p.index {
            let g = self.browser.read().await;
            let browser = g
                .as_ref()
                .ok_or_else(|| McpError::internal_error("No browser", None))?;
            let page = browser
                .get_page()
                .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
            let script = r#"
                (function() {
                    const links = Array.from(document.querySelectorAll('a[href]'))
                        .filter(a => a.href && !a.href.startsWith('javascript:'));
                    return links.length > 0 ? JSON.stringify(links.map(a => a.href)) : '[]';
                })()
            "#;
            let result = page
                .evaluate(script)
                .await
                .unwrap_or_else(|_| "[]".to_string());
            let hrefs: Vec<String> = serde_json::from_str(&result).unwrap_or_default();
            drop(g);
            hrefs.get(idx as usize).cloned().ok_or_else(|| {
                McpError::invalid_params(
                    format!("Link index {} out of range ({} links)", idx, hrefs.len()),
                    None,
                )
            })?
        } else {
            return Err(McpError::invalid_params("Provide 'index' or 'url'", None));
        };

        let mut g = self.browser.write().await;
        let browser = g
            .as_mut()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        browser
            .navigate(&url)
            .await
            .map_err(|e| McpError::internal_error(format!("Navigate failed: {}", e), None))?;
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "url": url
        })))
    }

    #[tool(description = "List available content: links and images with indices")]
    async fn list_content(&self) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let script = r#"
            (function() {
                const links = Array.from(document.querySelectorAll('a[href]'))
                    .filter(a => a.href && !a.href.startsWith('javascript:'))
                    .map((a, i) => ({ index: i, href: a.href, text: (a.textContent||'').trim().slice(0, 100) }));
                const images = Array.from(document.querySelectorAll('img[src]'))
                    .map((img, i) => ({ index: i, src: img.src, alt: (img.alt||'').slice(0, 80) }));
                return JSON.stringify({ links, images });
            })()
        "#;
        let result = page
            .evaluate(script)
            .await
            .unwrap_or_else(|_| "{\"links\":[],\"images\":[]}".to_string());
        let content: serde_json::Value =
            serde_json::from_str(&result).unwrap_or(serde_json::json!({"links":[],"images":[]}));
        let url = browser.get_current_url().await.unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "url": url,
            "links": content.get("links").cloned().unwrap_or_default(),
            "images": content.get("images").cloned().unwrap_or_default()
        })))
    }

    #[tool(description = "Get page text content")]
    async fn get_content(
        &self,
        Parameters(p): Parameters<GetContentParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let url = browser.get_current_url().await.unwrap_or_default();
        let max_chars = p.max_chars.unwrap_or(100_000) as usize;
        let expr = format!(
            "(document.body?.innerText||document.body?.textContent||'').slice(0,{})",
            max_chars
        );
        let text = page.evaluate(&expr).await.unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "url": url,
            "text": text,
            "length": text.len()
        })))
    }

    #[tool(
        description = "Get or save image by index from list_content.images (captures visible element as screenshot)"
    )]
    async fn get_image(
        &self,
        Parameters(p): Parameters<GetImageParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let idx = p.index.unwrap_or(0);
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let elements = page
            .get_elements_by_css_selector("img[src]")
            .await
            .map_err(|e| McpError::internal_error(format!("Get elements failed: {}", e), None))?;
        let element = elements.get(idx as usize).ok_or_else(|| {
            McpError::invalid_params(
                format!(
                    "Image index {} out of range ({} images)",
                    idx,
                    elements.len()
                ),
                None,
            )
        })?;
        let b64 = element
            .screenshot(Some("png"), None)
            .await
            .map_err(|e| McpError::internal_error(format!("Screenshot failed: {}", e), None))?;
        drop(g);
        Ok(CallToolResult::success(vec![Content::image(
            b64,
            "image/png",
        )]))
    }

    #[tool(description = "Save text content or image (by index) to a file")]
    async fn save_content(
        &self,
        Parameters(p): Parameters<SaveContentParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let path = p.path;
        match p.content_type.to_lowercase().as_str() {
            "text" => {
                let g = self.browser.read().await;
                let browser = g
                    .as_ref()
                    .ok_or_else(|| McpError::internal_error("No browser", None))?;
                let page = browser.get_page().map_err(|e| {
                    McpError::internal_error(format!("Get page failed: {}", e), None)
                })?;
                let text = page
                    .evaluate("(document.body?.innerText||document.body?.textContent||'')")
                    .await
                    .unwrap_or_default();
                drop(g);
                tokio::fs::write(&path, &text)
                    .await
                    .map_err(|e| McpError::internal_error(format!("Write failed: {}", e), None))?;
            }
            "image" => {
                let idx = p.image_index.unwrap_or(0);
                let g = self.browser.read().await;
                let browser = g
                    .as_ref()
                    .ok_or_else(|| McpError::internal_error("No browser", None))?;
                let page = browser.get_page().map_err(|e| {
                    McpError::internal_error(format!("Get page failed: {}", e), None)
                })?;
                let elements = page
                    .get_elements_by_css_selector("img[src]")
                    .await
                    .map_err(|e| {
                        McpError::internal_error(format!("Get elements failed: {}", e), None)
                    })?;
                let element = elements.get(idx as usize).ok_or_else(|| {
                    McpError::invalid_params(format!("Image index {} out of range", idx), None)
                })?;
                let b64 = element.screenshot(Some("png"), None).await.map_err(|e| {
                    McpError::internal_error(format!("Screenshot failed: {}", e), None)
                })?;
                drop(g);
                let bytes =
                    base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
                        .map_err(|e| {
                            McpError::internal_error(format!("Base64 decode failed: {}", e), None)
                        })?;
                tokio::fs::write(&path, &bytes)
                    .await
                    .map_err(|e| McpError::internal_error(format!("Write failed: {}", e), None))?;
            }
            _ => {
                return Err(McpError::invalid_params(
                    "content_type must be 'text' or 'image'",
                    None,
                ));
            }
        }
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "path": path
        })))
    }

    #[tool(description = "Take screenshot: full page, or a specific element by CSS selector")]
    async fn screenshot(
        &self,
        Parameters(p): Parameters<ScreenshotParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;

        let bytes = if let Some(selector) = p.selector {
            let page = browser
                .get_page()
                .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
            let elements = page
                .get_elements_by_css_selector(&selector)
                .await
                .map_err(|e| McpError::internal_error(format!("Selector failed: {}", e), None))?;
            let idx = p.element_index.unwrap_or(0) as usize;
            let element = elements.get(idx).ok_or_else(|| {
                McpError::invalid_params(
                    format!(
                        "Element index {} out of range ({} matches for '{}')",
                        idx,
                        elements.len(),
                        selector
                    ),
                    None,
                )
            })?;
            let b64 = element.screenshot(Some("png"), None).await.map_err(|e| {
                McpError::internal_error(format!("Element screenshot failed: {}", e), None)
            })?;
            base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
                .map_err(|e| McpError::internal_error(format!("Base64 decode: {}", e), None))?
        } else {
            browser
                .take_screenshot(None, p.full_page.unwrap_or(false), None, None)
                .await
                .map_err(|e| McpError::internal_error(format!("Screenshot failed: {}", e), None))?
        };
        drop(g);
        let b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &bytes);
        Ok(CallToolResult::success(vec![Content::image(
            b64,
            "image/png",
        )]))
    }

    #[tool(
        description = "Generate sitemap by crawling from URL: navigate, capture title and content preview, discover links. Returns structured sitemap (optionally save to file)."
    )]
    async fn generate_sitemap(
        &self,
        Parameters(p): Parameters<GenerateSitemapParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let sitemap = sitemap::run_sitemap_crawl(self.browser.clone(), p.clone()).await?;

        if let Some(path) = &p.save_path {
            let s = serde_json::to_string_pretty(&sitemap)
                .map_err(|e| McpError::internal_error(format!("Serialize failed: {}", e), None))?;
            tokio::fs::write(path, s)
                .await
                .map_err(|e| McpError::internal_error(format!("Write failed: {}", e), None))?;
        }

        let total = sitemap
            .get("total_pages")
            .and_then(|v| v.as_u64())
            .unwrap_or(0);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "total_pages": total,
            "sitemap": sitemap,
            "saved_to": p.save_path
        })))
    }

    // ── Core browser automation tools (no AI required) ──

    #[tool(description = "Click an element by CSS selector")]
    async fn click(
        &self,
        Parameters(p): Parameters<ClickParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let elements = page
            .get_elements_by_css_selector(&p.selector)
            .await
            .map_err(|e| McpError::internal_error(format!("Selector failed: {}", e), None))?;
        let idx = p.element_index.unwrap_or(0) as usize;
        let element = elements.get(idx).ok_or_else(|| {
            McpError::invalid_params(
                format!(
                    "Element index {} out of range ({} matches for '{}')",
                    idx,
                    elements.len(),
                    p.selector
                ),
                None,
            )
        })?;
        element
            .click(browsing::actor::mouse::MouseButton::Left, 1, None)
            .await
            .map_err(|e| McpError::internal_error(format!("Click failed: {}", e), None))?;
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "selector": p.selector,
            "index": idx
        })))
    }

    #[tool(description = "Type text into an input field by CSS selector")]
    async fn input(
        &self,
        Parameters(p): Parameters<InputParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let elements = page
            .get_elements_by_css_selector(&p.selector)
            .await
            .map_err(|e| McpError::internal_error(format!("Selector failed: {}", e), None))?;
        let idx = p.element_index.unwrap_or(0) as usize;
        let element = elements.get(idx).ok_or_else(|| {
            McpError::invalid_params(
                format!(
                    "Element index {} out of range ({} matches for '{}')",
                    idx,
                    elements.len(),
                    p.selector
                ),
                None,
            )
        })?;
        element
            .fill(&p.text)
            .await
            .map_err(|e| McpError::internal_error(format!("Input failed: {}", e), None))?;
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "selector": p.selector,
            "index": idx,
            "text": p.text
        })))
    }

    #[tool(description = "Send keyboard keys to the page: Enter, Escape, Tab, ArrowDown, etc.")]
    async fn send_keys(
        &self,
        Parameters(p): Parameters<SendKeysParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        page.press(&p.keys)
            .await
            .map_err(|e| McpError::internal_error(format!("send_keys failed: {}", e), None))?;
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "keys": p.keys
        })))
    }

    #[tool(description = "Scroll the page: up, down, left, right")]
    async fn scroll(
        &self,
        Parameters(p): Parameters<ScrollParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let amount = p.amount.unwrap_or(0);
        let amount_expr: String = if amount == 0 {
            match p.direction.to_lowercase().as_str() {
                "up" | "down" => "window.innerHeight * 3".to_string(),
                "left" | "right" => "window.innerWidth".to_string(),
                _ => "0".to_string(),
            }
        } else {
            amount.to_string()
        };
        let script = match p.direction.to_lowercase().as_str() {
            "up" => format!("window.scrollBy(0, -{})", amount_expr),
            "down" => format!("window.scrollBy(0, {})", amount_expr),
            "left" => format!("window.scrollBy(-{}, 0)", amount_expr),
            "right" => format!("window.scrollBy({}, 0)", amount_expr),
            _ => {
                return Err(McpError::invalid_params(
                    "direction must be 'up', 'down', 'left', or 'right'",
                    None,
                ));
            }
        };
        page.evaluate(&script)
            .await
            .map_err(|e| McpError::internal_error(format!("Scroll failed: {}", e), None))?;
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "direction": p.direction,
            "amount": amount
        })))
    }

    #[tool(description = "Wait for a condition (element present, text, url) or fixed seconds")]
    async fn wait(
        &self,
        Parameters(p): Parameters<WaitParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;

        let condition = if let Some(selector) = p.wait_for_element {
            browsing::tools::smart_wait::WaitCondition::ElementPresent(selector)
        } else if let Some(text) = p.wait_for_text {
            browsing::tools::smart_wait::WaitCondition::TextPresent(text)
        } else if let Some(url) = p.wait_for_url {
            browsing::tools::smart_wait::WaitCondition::UrlContains(url)
        } else if let Some(ms) = p.wait_for_stable_ms {
            browsing::tools::smart_wait::WaitCondition::PageStable { stable_ms: ms }
        } else {
            browsing::tools::smart_wait::WaitCondition::FixedSeconds(p.seconds.unwrap_or(3).min(30))
        };

        let waiter = browsing::tools::smart_wait::SmartWaiter::new();
        let result = waiter
            .wait(&condition, &page)
            .await
            .map_err(|e| McpError::internal_error(format!("Wait failed: {}", e), None))?;
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "satisfied": result.satisfied,
            "elapsed_ms": result.elapsed_ms,
            "description": result.description
        })))
    }

    #[tool(description = "Execute JavaScript on the page and return the result")]
    async fn evaluate(
        &self,
        Parameters(p): Parameters<EvaluateParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let result = page
            .evaluate(&p.expression)
            .await
            .map_err(|e| McpError::internal_error(format!("Evaluate failed: {}", e), None))?;
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "result": result
        })))
    }

    #[tool(description = "Go back in browser history")]
    async fn go_back(&self) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        page.go_back()
            .await
            .map_err(|e| McpError::internal_error(format!("go_back failed: {}", e), None))?;
        let url = browser.get_current_url().await.unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "url": url
        })))
    }

    #[tool(description = "Go forward in browser history")]
    async fn go_forward(&self) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        page.go_forward()
            .await
            .map_err(|e| McpError::internal_error(format!("go_forward failed: {}", e), None))?;
        let url = browser.get_current_url().await.unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "url": url
        })))
    }

    #[tool(description = "Reload the current page")]
    async fn reload(&self) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        page.reload()
            .await
            .map_err(|e| McpError::internal_error(format!("reload failed: {}", e), None))?;
        let url = browser.get_current_url().await.unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": true,
            "url": url
        })))
    }

    #[tool(description = "Get options from a dropdown/select element")]
    async fn dropdown_options(
        &self,
        Parameters(p): Parameters<DropdownOptionsParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let esc = p.selector.replace('\\', "\\\\").replace('"', "\\\"").replace('\'', "\\'");
        let script = format!(
            "(() => {{ const el = document.querySelector('{}'); \
             return el ? JSON.stringify(Array.from(el.options).map(o => ({{ value: o.value, text: o.text }}))) : '[]'; }})()",
            esc
        );
        let result = page
            .evaluate(&script)
            .await
            .unwrap_or_else(|_| "[]".to_string());
        let options: Vec<serde_json::Value> = serde_json::from_str(&result).unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "selector": p.selector,
            "options": options,
            "count": options.len()
        })))
    }

    #[tool(description = "Select options in a dropdown/select element by values")]
    async fn select_dropdown(
        &self,
        Parameters(p): Parameters<SelectDropdownParams>,
    ) -> Result<CallToolResult, McpError> {
        self.ensure_browser().await?;
        let g = self.browser.read().await;
        let browser = g
            .as_ref()
            .ok_or_else(|| McpError::internal_error("No browser", None))?;
        let page = browser
            .get_page()
            .map_err(|e| McpError::internal_error(format!("Get page failed: {}", e), None))?;
        let esc = p.selector.replace('\\', "\\\\").replace('"', "\\\"").replace('\'', "\\'");
        let values_json = serde_json::to_string(&p.values).unwrap_or_default();
        let script = format!(
            "(() => {{ const el = document.querySelector('{}'); \
             if (!el) return 'not_found'; \
             const values = {}; \
             Array.from(el.options).forEach(o => o.selected = values.includes(o.value)); \
             el.dispatchEvent(new Event('change', {{ bubbles: true }})); \
             return 'selected'; }})()",
            esc,
            values_json
        );
        let result = page
            .evaluate(&script)
            .await
            .unwrap_or_default();
        drop(g);
        Ok(CallToolResult::structured(serde_json::json!({
            "success": result == "selected",
            "selector": p.selector,
            "values": p.values,
            "result": result
        })))
    }
}

#[tool_handler]
impl ServerHandler for BrowsingService {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::V_2024_11_05,
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::from_build_env(),
            instructions: Some(
                "Browser automation MCP server. No AI required — pure CDP-based browser control.\n\n\
                 Navigation: navigate(url), go_back(), go_forward(), reload()\n\
                 Content: get_content(), get_links(), get_image(index), list_content(), \
                 screenshot(selector?, full_page?), save_content(path, type)\n\
                 Interaction: click(selector), input(selector, text), send_keys(keys), \
                 scroll(direction, amount?), dropdown_options(selector), select_dropdown(selector, values)\n\
                 Utility: wait(seconds | wait_for_element | wait_for_text | wait_for_url | wait_for_stable_ms), \
                 evaluate(js_expression), follow_link(index|url), generate_sitemap(url)\n\n\
                 All tools operate on the same shared browser instance."
                    .into(),
            ),
        }
    }
}
