use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use thiserror::Error;

use crate::pipe::{Action, AgentMessage, BrowserPipeTool, CommandOutput, Transport};

const DEFAULT_WAIT_TIMEOUT_MS: u64 = 5_000;

fn default_ensure_loaded() -> bool {
    true
}

fn default_capture_url() -> bool {
    true
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ZhihuNavigateRequest {
    pub page: String,
    #[serde(default = "default_ensure_loaded")]
    pub ensure_loaded: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ZhihuNavigateResult {
    pub summary: String,
    pub page: String,
    pub final_url: String,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZhihuNavigationCatalog {
    pub domains: HashMap<String, String>,
    #[serde(default)]
    pub routes: HashMap<String, ZhihuRouteDefinition>,
    #[serde(default)]
    pub components: HashMap<String, ZhihuComponentDefinition>,
    #[serde(default)]
    pub flows: HashMap<String, ZhihuFlowDefinition>,
    pub targets: HashMap<String, ZhihuTargetDefinition>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZhihuRouteDefinition {
    pub title: String,
    pub domain_ref: String,
    pub url: String,
    #[serde(default)]
    pub aliases: Vec<String>,
    pub wait_selector: Option<String>,
    pub wait_timeout_ms: Option<u64>,
    pub expect_selector: Option<String>,
    pub expect_text: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZhihuComponentDefinition {
    pub title: String,
    pub domain_ref: String,
    pub selector: String,
    #[serde(default)]
    pub aliases: Vec<String>,
    pub entry_route_ref: Option<String>,
    pub result_domain_ref: Option<String>,
    pub wait_selector: Option<String>,
    pub wait_timeout_ms: Option<u64>,
    pub expect_selector: Option<String>,
    pub expect_text: Option<String>,
    pub wait_after_ms: Option<u64>,
    #[serde(default = "default_capture_url")]
    pub capture_url: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ZhihuTargetKind {
    Route,
    Component,
    Flow,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ZhihuSummaryKind {
    Page,
    Entry,
    Menu,
    Navigation,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZhihuTargetDefinition {
    pub title: String,
    pub kind: ZhihuTargetKind,
    pub summary_kind: Option<ZhihuSummaryKind>,
    pub route_ref: Option<String>,
    pub component_ref: Option<String>,
    pub flow_ref: Option<String>,
    #[serde(default)]
    pub aliases: Vec<String>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZhihuFlowDefinition {
    pub steps: Vec<ZhihuFlowStep>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZhihuFlowStep {
    pub name: String,
    pub action: String,
    pub route_ref: Option<String>,
    pub component_ref: Option<String>,
    pub expected_domain: Option<String>,
    pub timeout_ms: Option<u64>,
    pub wait_after_ms: Option<u64>,
    #[serde(default)]
    pub capture_url: bool,
    pub expect_text: Option<String>,
    pub log_message: String,
}

#[derive(Debug, Error)]
pub enum ZhihuNavigationError {
    #[error("page 不能为空")]
    EmptyPage,
    #[error("failed to load zhihu navigation catalog: {0}")]
    CatalogLoad(String),
    #[error("unknown zhihu target: {0}")]
    UnknownTarget(String),
    #[error("missing domain in zhihu navigation catalog: {0}")]
    MissingDomain(String),
    #[error("missing route in zhihu navigation catalog: {0}")]
    MissingRoute(String),
    #[error("missing component in zhihu navigation catalog: {0}")]
    MissingComponent(String),
    #[error("missing flow in zhihu navigation catalog: {0}")]
    MissingFlow(String),
    #[error("invalid target definition in zhihu navigation catalog: {0}")]
    InvalidTargetDefinition(String),
    #[error("missing route ref in zhihu navigation flow step: {0}")]
    MissingRouteRef(String),
    #[error("missing component ref in zhihu navigation flow step: {0}")]
    MissingComponentRef(String),
    #[error("unknown action in zhihu navigation flow: {0}")]
    UnknownAction(String),
    #[error("browser action failed at step {step}: {message}")]
    BrowserActionFailed { step: String, message: String },
    #[error("step {step} expected text containing `{expected}`, got `{actual}`")]
    ExpectedTextMissing {
        step: String,
        expected: String,
        actual: String,
    },
}

#[derive(Debug, Default)]
struct ExecutionState {
    final_url: Option<String>,
}

#[derive(Debug, Clone, Copy)]
struct PostActionChecks<'a> {
    expected_domain: &'a str,
    wait_selector: Option<&'a str>,
    wait_timeout_ms: Option<u64>,
    expect_selector: Option<&'a str>,
    expect_text: Option<&'a str>,
    reset_url_when_absent: bool,
}

pub fn default_catalog_path() -> PathBuf {
    super::default_skill_resource_path("zhihu_navigation_pages.json")
}

pub fn load_catalog() -> Result<ZhihuNavigationCatalog, ZhihuNavigationError> {
    let path = default_catalog_path();
    let contents = fs::read_to_string(&path).map_err(|err| {
        ZhihuNavigationError::CatalogLoad(format!("{} ({})", err, path.display()))
    })?;
    serde_json::from_str(&contents)
        .map_err(|err| ZhihuNavigationError::CatalogLoad(format!("{} ({})", err, path.display())))
}

pub fn try_route_alias(
    instruction: &str,
) -> Result<Option<ZhihuNavigateRequest>, ZhihuNavigationError> {
    let trimmed = instruction.trim();
    if !looks_like_navigation_intent(trimmed) {
        return Ok(None);
    }

    let catalog = load_catalog()?;
    let normalized_instruction = normalize_text(trimmed);
    let mut matches = Vec::new();

    for (target_key, target) in &catalog.targets {
        let score = best_target_match_score(&catalog, target, &normalized_instruction);
        if score > 0 {
            matches.push((target_key.as_str(), score));
        }
    }

    if matches.is_empty() {
        return Ok(None);
    }

    matches.sort_by(|left, right| right.1.cmp(&left.1).then_with(|| left.0.cmp(right.0)));
    if matches.len() > 1 && matches[0].1 == matches[1].1 {
        return Ok(None);
    }

    Ok(Some(ZhihuNavigateRequest {
        page: matches[0].0.to_string(),
        ensure_loaded: true,
    }))
}

pub fn execute<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    req: ZhihuNavigateRequest,
) -> Result<ZhihuNavigateResult, ZhihuNavigationError> {
    validate_request(&req)?;
    let catalog = load_catalog()?;
    let target_key = req.page.trim();
    let target = resolve_target(&catalog, target_key)?;
    let mut state = ExecutionState::default();

    match target.kind {
        ZhihuTargetKind::Route => {
            let route_ref = target.route_ref.as_deref().ok_or_else(|| {
                ZhihuNavigationError::InvalidTargetDefinition(target_key.to_string())
            })?;
            run_route(
                transport,
                browser_tool,
                &catalog,
                route_ref,
                req.ensure_loaded,
                &mut state,
            )?;
        }
        ZhihuTargetKind::Component => {
            let component_ref = target.component_ref.as_deref().ok_or_else(|| {
                ZhihuNavigationError::InvalidTargetDefinition(target_key.to_string())
            })?;
            run_component_target(
                transport,
                browser_tool,
                &catalog,
                component_ref,
                req.ensure_loaded,
                &mut state,
            )?;
        }
        ZhihuTargetKind::Flow => {
            let flow_ref = target.flow_ref.as_deref().ok_or_else(|| {
                ZhihuNavigationError::InvalidTargetDefinition(target_key.to_string())
            })?;
            run_flow(transport, browser_tool, &catalog, flow_ref, &mut state)?;
        }
    }

    let final_url = state.final_url.unwrap_or_default();
    Ok(ZhihuNavigateResult {
        summary: build_summary(target, &final_url),
        page: target_key.to_string(),
        final_url,
    })
}

fn validate_request(req: &ZhihuNavigateRequest) -> Result<(), ZhihuNavigationError> {
    if req.page.trim().is_empty() {
        return Err(ZhihuNavigationError::EmptyPage);
    }
    Ok(())
}

fn run_route<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    catalog: &ZhihuNavigationCatalog,
    route_ref: &str,
    ensure_loaded: bool,
    state: &mut ExecutionState,
) -> Result<(), ZhihuNavigationError> {
    let route = resolve_route(catalog, route_ref)?;
    let expected_domain = resolve_domain(catalog, &route.domain_ref)?;

    send_log(transport, &format!("navigate {}", route.url), "navigate")?;
    let navigate_result = invoke_browser_action(
        browser_tool,
        Action::Navigate,
        json!({ "url": route.url }),
        expected_domain.as_str(),
        "navigate",
    )?;
    state.final_url = Some(extract_url(&navigate_result.data).unwrap_or_else(|| route.url.clone()));

    if ensure_loaded {
        run_post_action_checks(
            transport,
            browser_tool,
            PostActionChecks {
                expected_domain: expected_domain.as_str(),
                wait_selector: route.wait_selector.as_deref(),
                wait_timeout_ms: route.wait_timeout_ms,
                expect_selector: route.expect_selector.as_deref(),
                expect_text: route.expect_text.as_deref(),
                reset_url_when_absent: false,
            },
            state,
        )?;
    }

    Ok(())
}

fn run_component_target<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    catalog: &ZhihuNavigationCatalog,
    component_ref: &str,
    ensure_loaded: bool,
    state: &mut ExecutionState,
) -> Result<(), ZhihuNavigationError> {
    let component = resolve_component(catalog, component_ref)?;

    if let Some(entry_route_ref) = component.entry_route_ref.as_deref() {
        run_route(
            transport,
            browser_tool,
            catalog,
            entry_route_ref,
            false,
            state,
        )?;
    }

    let expected_domain = resolve_domain(catalog, &component.domain_ref)?;
    send_log(
        transport,
        &format!("click {}", component.title),
        component_ref,
    )?;
    let click_result = invoke_browser_action(
        browser_tool,
        Action::Click,
        build_click_params(component.selector.as_str(), component.wait_after_ms),
        expected_domain.as_str(),
        component_ref,
    )?;

    if component.capture_url {
        if let Some(url) = extract_url(&click_result.data) {
            state.final_url = Some(url);
        }
    }

    if ensure_loaded {
        let result_domain_ref = component
            .result_domain_ref
            .as_deref()
            .unwrap_or(component.domain_ref.as_str());
        let result_domain = resolve_domain(catalog, result_domain_ref)?;
        run_post_action_checks(
            transport,
            browser_tool,
            PostActionChecks {
                expected_domain: result_domain.as_str(),
                wait_selector: component.wait_selector.as_deref(),
                wait_timeout_ms: component.wait_timeout_ms,
                expect_selector: component.expect_selector.as_deref(),
                expect_text: component.expect_text.as_deref(),
                reset_url_when_absent: !component.capture_url,
            },
            state,
        )?;
    }

    Ok(())
}

fn run_flow<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    catalog: &ZhihuNavigationCatalog,
    flow_ref: &str,
    state: &mut ExecutionState,
) -> Result<(), ZhihuNavigationError> {
    let flow = resolve_flow(catalog, flow_ref)?;

    for step in &flow.steps {
        send_log(transport, &step.log_message, step.name.as_str())?;
        let action = parse_action(&step.action)?;
        let is_navigate = matches!(action, Action::Navigate);
        let (expected_domain, params, fallback_url) =
            build_flow_step_action(catalog, step, &action)?;
        let result = invoke_browser_action(
            browser_tool,
            action,
            params,
            expected_domain.as_str(),
            step.name.as_str(),
        )?;

        if is_navigate {
            state.final_url = Some(
                extract_url(&result.data)
                    .or(fallback_url.clone())
                    .unwrap_or_default(),
            );
        } else if step.capture_url {
            if let Some(url) = extract_url(&result.data) {
                state.final_url = Some(url);
            }
        }

        if let Some(expected_text) = step.expect_text.as_deref() {
            let actual = extract_content(&result.data);
            if !actual.contains(expected_text) {
                return Err(ZhihuNavigationError::ExpectedTextMissing {
                    step: step.name.clone(),
                    expected: expected_text.to_string(),
                    actual,
                });
            }
        }
    }

    Ok(())
}

fn build_flow_step_action(
    catalog: &ZhihuNavigationCatalog,
    step: &ZhihuFlowStep,
    action: &Action,
) -> Result<(String, Value, Option<String>), ZhihuNavigationError> {
    match action {
        Action::Navigate => {
            let route_ref = step
                .route_ref
                .as_deref()
                .ok_or_else(|| ZhihuNavigationError::MissingRouteRef(step.name.clone()))?;
            let route = resolve_route(catalog, route_ref)?;
            let domain_key = step
                .expected_domain
                .as_deref()
                .unwrap_or(route.domain_ref.as_str());
            let expected_domain = resolve_domain(catalog, domain_key)?;
            Ok((
                expected_domain,
                json!({ "url": route.url }),
                Some(route.url.clone()),
            ))
        }
        Action::Click => {
            let component_ref = step
                .component_ref
                .as_deref()
                .ok_or_else(|| ZhihuNavigationError::MissingComponentRef(step.name.clone()))?;
            let component = resolve_component(catalog, component_ref)?;
            let domain_key = step
                .expected_domain
                .as_deref()
                .unwrap_or(component.domain_ref.as_str());
            let expected_domain = resolve_domain(catalog, domain_key)?;
            let wait_after_ms = step.wait_after_ms.or(component.wait_after_ms);
            Ok((
                expected_domain,
                build_click_params(component.selector.as_str(), wait_after_ms),
                None,
            ))
        }
        Action::WaitForSelector => {
            let component_ref = step
                .component_ref
                .as_deref()
                .ok_or_else(|| ZhihuNavigationError::MissingComponentRef(step.name.clone()))?;
            let component = resolve_component(catalog, component_ref)?;
            let domain_key = step
                .expected_domain
                .as_deref()
                .unwrap_or(component.domain_ref.as_str());
            let expected_domain = resolve_domain(catalog, domain_key)?;
            Ok((
                expected_domain,
                json!({
                    "selector": component.selector,
                    "timeout_ms": step.timeout_ms.unwrap_or(component.wait_timeout_ms.unwrap_or(DEFAULT_WAIT_TIMEOUT_MS)),
                }),
                None,
            ))
        }
        Action::GetText => {
            let component_ref = step
                .component_ref
                .as_deref()
                .ok_or_else(|| ZhihuNavigationError::MissingComponentRef(step.name.clone()))?;
            let component = resolve_component(catalog, component_ref)?;
            let domain_key = step
                .expected_domain
                .as_deref()
                .unwrap_or(component.domain_ref.as_str());
            let expected_domain = resolve_domain(catalog, domain_key)?;
            Ok((
                expected_domain,
                json!({ "selector": component.selector }),
                None,
            ))
        }
        Action::GetHtml => {
            let component_ref = step
                .component_ref
                .as_deref()
                .ok_or_else(|| ZhihuNavigationError::MissingComponentRef(step.name.clone()))?;
            let component = resolve_component(catalog, component_ref)?;
            let domain_key = step
                .expected_domain
                .as_deref()
                .unwrap_or(component.domain_ref.as_str());
            let expected_domain = resolve_domain(catalog, domain_key)?;
            Ok((
                expected_domain,
                json!({ "selector": component.selector, "outer": true }),
                None,
            ))
        }
        Action::ScrollTo => {
            let component_ref = step
                .component_ref
                .as_deref()
                .ok_or_else(|| ZhihuNavigationError::MissingComponentRef(step.name.clone()))?;
            let component = resolve_component(catalog, component_ref)?;
            let domain_key = step
                .expected_domain
                .as_deref()
                .unwrap_or(component.domain_ref.as_str());
            let expected_domain = resolve_domain(catalog, domain_key)?;
            Ok((
                expected_domain,
                json!({ "selector": component.selector }),
                None,
            ))
        }
        other => Err(ZhihuNavigationError::UnknownAction(
            other.as_str().to_string(),
        )),
    }
}

fn run_post_action_checks<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    checks: PostActionChecks<'_>,
    state: &mut ExecutionState,
) -> Result<(), ZhihuNavigationError> {
    if let Some(selector) = checks.wait_selector {
        send_log(
            transport,
            &format!("wait for {selector}"),
            "wait_for_selector",
        )?;
        let wait_result = invoke_browser_action(
            browser_tool,
            Action::WaitForSelector,
            json!({
                "selector": selector,
                "timeout_ms": checks.wait_timeout_ms.unwrap_or(DEFAULT_WAIT_TIMEOUT_MS),
            }),
            checks.expected_domain,
            "wait_for_selector",
        )?;
        if let Some(url) = extract_url(&wait_result.data) {
            state.final_url = Some(url);
        } else if checks.reset_url_when_absent {
            state.final_url = None;
        }
    }

    if let (Some(selector), Some(expected_text)) = (checks.expect_selector, checks.expect_text) {
        send_log(transport, &format!("verify {selector}"), "verify_text")?;
        let text_result = invoke_browser_action(
            browser_tool,
            Action::GetText,
            json!({ "selector": selector }),
            checks.expected_domain,
            "verify_text",
        )?;
        if let Some(url) = extract_url(&text_result.data) {
            state.final_url = Some(url);
        } else if checks.reset_url_when_absent {
            state.final_url = None;
        }
        let actual = extract_content(&text_result.data);
        if !actual.contains(expected_text) {
            return Err(ZhihuNavigationError::ExpectedTextMissing {
                step: "verify_text".to_string(),
                expected: expected_text.to_string(),
                actual,
            });
        }
    }

    Ok(())
}

fn send_log<T: Transport>(
    transport: &T,
    message: &str,
    step: &str,
) -> Result<(), ZhihuNavigationError> {
    transport
        .send(&AgentMessage::LogEntry {
            level: "info".to_string(),
            message: message.to_string(),
        })
        .map_err(|err| ZhihuNavigationError::BrowserActionFailed {
            step: step.to_string(),
            message: err.to_string(),
        })
}

fn invoke_browser_action<T: Transport>(
    browser_tool: &BrowserPipeTool<T>,
    action: Action,
    params: Value,
    expected_domain: &str,
    step: &str,
) -> Result<CommandOutput, ZhihuNavigationError> {
    let result = browser_tool
        .invoke(action, params, expected_domain)
        .map_err(|err| ZhihuNavigationError::BrowserActionFailed {
            step: step.to_string(),
            message: err.to_string(),
        })?;
    if !result.success {
        return Err(ZhihuNavigationError::BrowserActionFailed {
            step: step.to_string(),
            message: result.data.to_string(),
        });
    }
    Ok(result)
}

fn build_click_params(selector: &str, wait_after_ms: Option<u64>) -> Value {
    let mut params = serde_json::Map::new();
    params.insert("selector".to_string(), Value::String(selector.to_string()));
    if let Some(wait_after_ms) = wait_after_ms {
        params.insert("wait_after".to_string(), Value::from(wait_after_ms));
    }
    Value::Object(params)
}

fn parse_action(name: &str) -> Result<Action, ZhihuNavigationError> {
    match name {
        "click" => Ok(Action::Click),
        "navigate" => Ok(Action::Navigate),
        "getText" => Ok(Action::GetText),
        "getHtml" => Ok(Action::GetHtml),
        "waitForSelector" => Ok(Action::WaitForSelector),
        "scrollTo" => Ok(Action::ScrollTo),
        other => Err(ZhihuNavigationError::UnknownAction(other.to_string())),
    }
}

fn resolve_target<'a>(
    catalog: &'a ZhihuNavigationCatalog,
    target_key: &str,
) -> Result<&'a ZhihuTargetDefinition, ZhihuNavigationError> {
    catalog
        .targets
        .get(target_key)
        .ok_or_else(|| ZhihuNavigationError::UnknownTarget(target_key.to_string()))
}

fn resolve_route<'a>(
    catalog: &'a ZhihuNavigationCatalog,
    route_ref: &str,
) -> Result<&'a ZhihuRouteDefinition, ZhihuNavigationError> {
    catalog
        .routes
        .get(route_ref)
        .ok_or_else(|| ZhihuNavigationError::MissingRoute(route_ref.to_string()))
}

fn resolve_component<'a>(
    catalog: &'a ZhihuNavigationCatalog,
    component_ref: &str,
) -> Result<&'a ZhihuComponentDefinition, ZhihuNavigationError> {
    catalog
        .components
        .get(component_ref)
        .ok_or_else(|| ZhihuNavigationError::MissingComponent(component_ref.to_string()))
}

fn resolve_flow<'a>(
    catalog: &'a ZhihuNavigationCatalog,
    flow_ref: &str,
) -> Result<&'a ZhihuFlowDefinition, ZhihuNavigationError> {
    catalog
        .flows
        .get(flow_ref)
        .ok_or_else(|| ZhihuNavigationError::MissingFlow(flow_ref.to_string()))
}

fn resolve_domain(
    catalog: &ZhihuNavigationCatalog,
    key: &str,
) -> Result<String, ZhihuNavigationError> {
    catalog
        .domains
        .get(key)
        .cloned()
        .ok_or_else(|| ZhihuNavigationError::MissingDomain(key.to_string()))
}

fn best_target_match_score(
    catalog: &ZhihuNavigationCatalog,
    target: &ZhihuTargetDefinition,
    normalized_instruction: &str,
) -> usize {
    let best_len = collect_target_aliases(catalog, target)
        .into_iter()
        .map(|alias| normalize_text(alias.as_str()))
        .filter(|alias| !alias.is_empty() && normalized_instruction.contains(alias))
        .map(|alias| alias.len())
        .max()
        .unwrap_or(0);

    if best_len == 0 {
        return 0;
    }

    best_len * 100 + match_bonus(target, normalized_instruction)
}

fn collect_target_aliases(
    catalog: &ZhihuNavigationCatalog,
    target: &ZhihuTargetDefinition,
) -> Vec<String> {
    let mut aliases = Vec::new();
    aliases.push(target.title.clone());
    aliases.extend(target.aliases.iter().cloned());

    if let Some(route_ref) = target.route_ref.as_deref() {
        if let Some(route) = catalog.routes.get(route_ref) {
            aliases.push(route.title.clone());
            aliases.extend(route.aliases.iter().cloned());
        }
    }
    if let Some(component_ref) = target.component_ref.as_deref() {
        if let Some(component) = catalog.components.get(component_ref) {
            aliases.push(component.title.clone());
            aliases.extend(component.aliases.iter().cloned());
        }
    }

    aliases.retain(|alias| !alias.trim().is_empty());
    aliases.sort();
    aliases.dedup();
    aliases
}

fn match_bonus(target: &ZhihuTargetDefinition, normalized_instruction: &str) -> usize {
    let mut bonus = 0;
    let summary_kind = target_summary_kind(target);

    if normalized_instruction.contains("页面") && summary_kind == ZhihuSummaryKind::Page {
        bonus += 20;
    }
    if ["按钮", "入口"]
        .iter()
        .any(|token| normalized_instruction.contains(token))
        && summary_kind == ZhihuSummaryKind::Entry
    {
        bonus += 20;
    }
    if ["菜单", "下拉"]
        .iter()
        .any(|token| normalized_instruction.contains(token))
        && summary_kind == ZhihuSummaryKind::Menu
    {
        bonus += 20;
    }

    bonus
}

fn target_summary_kind(target: &ZhihuTargetDefinition) -> ZhihuSummaryKind {
    target.summary_kind.unwrap_or(match target.kind {
        ZhihuTargetKind::Route => ZhihuSummaryKind::Page,
        ZhihuTargetKind::Component => ZhihuSummaryKind::Entry,
        ZhihuTargetKind::Flow => ZhihuSummaryKind::Navigation,
    })
}

fn build_summary(target: &ZhihuTargetDefinition, final_url: &str) -> String {
    match target_summary_kind(target) {
        ZhihuSummaryKind::Page => {
            format!("知乎页面已打开：{} ({final_url})", target.title)
        }
        ZhihuSummaryKind::Entry => {
            if final_url.is_empty() {
                format!("知乎入口已打开：{}", target.title)
            } else {
                format!("知乎入口已打开：{} ({final_url})", target.title)
            }
        }
        ZhihuSummaryKind::Menu => format!("知乎菜单已打开：{}", target.title),
        ZhihuSummaryKind::Navigation => {
            if final_url.is_empty() {
                format!("知乎导航已完成：{}", target.title)
            } else {
                format!("知乎导航已完成：{} ({final_url})", target.title)
            }
        }
    }
}

fn looks_like_navigation_intent(instruction: &str) -> bool {
    let normalized = normalize_text(instruction);
    let has_platform = ["知乎", "专栏", "创作中心", "创作者中心"]
        .iter()
        .any(|token| normalized.contains(token));
    let has_verb = ["打开", "进入", "跳转", "前往", "去", "点开", "展开", "切到"]
        .iter()
        .any(|token| normalized.contains(token));
    has_platform && has_verb
}

fn normalize_text(text: &str) -> String {
    text.chars()
        .filter(|ch| {
            !ch.is_whitespace()
                && !matches!(
                    ch,
                    '，' | '。'
                        | '：'
                        | '；'
                        | '！'
                        | '？'
                        | '、'
                        | '（'
                        | '）'
                        | '【'
                        | '】'
                        | ','
                        | '.'
                        | ':'
                        | ';'
                        | '!'
                        | '?'
                        | '('
                        | ')'
                        | '['
                        | ']'
                        | '"'
                        | '\''
                        | '/'
                        | '\\'
                        | '-'
                        | '_'
                )
        })
        .flat_map(|ch| ch.to_lowercase())
        .collect()
}

fn extract_content(data: &Value) -> String {
    data.get("text")
        .and_then(Value::as_str)
        .or_else(|| data.get("html").and_then(Value::as_str))
        .unwrap_or_default()
        .trim()
        .to_string()
}

fn extract_url(data: &Value) -> Option<String> {
    data.get("url")
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|url| !url.is_empty())
        .map(ToOwned::to_owned)
}
