use std::time::Duration;

use night_fury_core::{BrowserSession, NightFuryError, Selector};
use serde_json::json;

use crate::output::{output_err, output_ok, OutputMode};
use crate::selector_parse;

pub async fn click(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let rest = parts.join(" ");
    match selector_parse::parse_selector(rest.trim()) {
        Ok(sel) => match session.click(sel).await {
            Ok(msg) => output_ok(mode, &msg, json!({"action": "click"})),
            Err(e) => output_err(mode, &e.to_string()),
        },
        Err(e) => output_err(mode, &e),
    }
}

pub async fn type_text(session: &BrowserSession, line: &str, parts: &[&str], mode: OutputMode) {
    // parts are the args after the command name (e.g. ["@e2", "hello world"])
    // or ["css", "#search", "hello world"] when splitn(4) is used
    if parts.len() < 2 {
        output_err(
            mode,
            "Usage: type @e<N> <text>  or  type css <selector> <text>",
        );
        return;
    }

    let target = parts[0];

    if target.eq_ignore_ascii_case("css") {
        // type css <selector> <text>
        // Re-split from the original line for correct handling
        let raw: Vec<&str> = line.splitn(4, ' ').collect();
        if raw.len() < 4 {
            output_err(mode, "Usage: type css <selector> <text>");
            return;
        }
        let css_sel = raw[2];
        let text = raw[3];
        match session
            .type_text(Selector::Css(css_sel.to_string()), text)
            .await
        {
            Ok(msg) => output_ok(mode, &msg, json!({"action": "type"})),
            Err(e) => output_err(mode, &e.to_string()),
        }
        return;
    }

    // @e<N> form — text is everything after the selector
    // Since we used splitn(4,' ') in dispatch, parts[1] is the text
    let sel = if let Some(rest) = target.strip_prefix("@e") {
        rest.parse::<usize>()
            .map(Selector::Ref)
            .map_err(|_| NightFuryError::InvalidArgument(format!("bad @ref: {target}")))
    } else {
        Err(NightFuryError::InvalidArgument(
            "first arg must be @e<N> or 'css'".to_string(),
        ))
    };

    match sel {
        Ok(sel) => {
            let text = parts[1..].join(" ");
            match session.type_text(sel, &text).await {
                Ok(msg) => output_ok(mode, &msg, json!({"action": "type"})),
                Err(e) => output_err(mode, &e.to_string()),
            }
        }
        Err(e) => output_err(mode, &e.to_string()),
    }
}

pub async fn scroll(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let px: i32 = parts.first().and_then(|s| s.parse().ok()).unwrap_or(600);
    match session.scroll(px).await {
        Ok(msg) => output_ok(mode, &msg, json!({"action": "scroll", "pixels": px})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

pub async fn hover(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let rest = parts.join(" ");
    match selector_parse::parse_selector(rest.trim()) {
        Ok(sel) => match session.hover(sel).await {
            Ok(msg) => output_ok(mode, &msg, json!({"action": "hover"})),
            Err(e) => output_err(mode, &e.to_string()),
        },
        Err(e) => output_err(mode, &e),
    }
}

pub async fn select(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    // select <selector> <value>
    // parts[0] is selector (or "css"/"@e<N>"), last part is value
    if parts.len() < 2 {
        output_err(mode, "Usage: select <selector> <value>");
        return;
    }
    let value = parts[parts.len() - 1];
    let sel_str = parts[..parts.len() - 1].join(" ");
    match selector_parse::parse_selector(sel_str.trim()) {
        Ok(sel) => match session.select(sel, value).await {
            Ok(msg) => output_ok(mode, &msg, json!({"action": "select", "value": value})),
            Err(e) => output_err(mode, &e.to_string()),
        },
        Err(e) => output_err(mode, &e),
    }
}

pub async fn check(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let css = parts.join(" ");
    let css = css.trim();
    if css.is_empty() {
        output_err(mode, "Usage: check <css-selector>");
        return;
    }
    match session.check(css).await {
        Ok(msg) => output_ok(mode, &msg, json!({"action": "check"})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

pub async fn uncheck(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let css = parts.join(" ");
    let css = css.trim();
    if css.is_empty() {
        output_err(mode, "Usage: uncheck <css-selector>");
        return;
    }
    match session.uncheck(css).await {
        Ok(msg) => output_ok(mode, &msg, json!({"action": "uncheck"})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

pub async fn wait(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let ms: u64 = parts.first().and_then(|s| s.parse().ok()).unwrap_or(1000);
    session.wait(Duration::from_millis(ms)).await;
    output_ok(
        mode,
        &format!("Waited {ms}ms"),
        json!({"action": "wait", "ms": ms}),
    );
}
