use crate::db::DbState;
use crate::ipc::sidecar::SidecarState;
use rusqlite::params;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tauri::State;

// ── Shared data shapes ────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClassSummary {
    pub name: String,
    pub namespace: String,
    pub file_path: String,
    pub base_class: Option<String>,
    pub interfaces: Vec<String>,
    pub is_gump: bool,
    pub is_mobile: bool,
    pub is_item: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MethodSummary {
    pub id: i64,
    pub class_name: String,
    pub name: String,
    pub return_type: String,
    pub parameters: Vec<ParameterInfo>,
    pub is_override: bool,
    pub is_virtual: bool,
    pub calls_gump: bool,
    pub gump_class: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParameterInfo {
    pub name: String,
    #[serde(rename = "type")]
    pub ty: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClassDetail {
    #[serde(rename = "class")]
    pub class_info: ClassSummary,
    pub methods: Vec<MethodSummary>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamespaceTree {
    pub namespace: String,
    pub classes: Vec<ClassSummary>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlowNode {
    pub id: String,
    #[serde(rename = "type")]
    pub node_type: String,
    pub label: String,
    pub children: Vec<FlowNode>,
    pub fake_input_key: Option<String>,
    pub resolved_gump: Option<String>,
    pub asset_ref: Option<i64>,
}

// ── Commands ──────────────────────────────────────────────────────────────────

/// Walk the ServUO Scripts directory, build the index via the C# sidecar,
/// and persist class/method summaries to SQLite.
#[tauri::command]
pub fn index_scripts(
    scripts_path: String,
    db: State<DbState>,
    sidecar: State<SidecarState>,
) -> Result<u32, String> {
    // Ask sidecar to index
    let result = sidecar
        .send("index_scripts", json!({ "path": scripts_path }))
        .map_err(|e| e.to_string())?;

    let classes: Vec<Value> = result["classes"]
        .as_array()
        .cloned()
        .unwrap_or_default();

    let count = classes.len() as u32;

    // Persist to SQLite
    let conn = db.0.lock().map_err(|e| e.to_string())?;
    conn.execute("DELETE FROM script_methods", []).map_err(|e| e.to_string())?;
    conn.execute("DELETE FROM script_classes", []).map_err(|e| e.to_string())?;

    for cls in &classes {
        let name = cls["name"].as_str().unwrap_or("");
        let namespace = cls["namespace"].as_str().unwrap_or("");
        let file_path = cls["file_path"].as_str().unwrap_or("");
        let base_class = cls["base_class"].as_str();
        let interfaces = cls["interfaces"]
            .as_array()
            .and_then(|a| serde_json::to_string(a).ok())
            .unwrap_or_else(|| "[]".to_string());
        let attributes = cls["attributes"]
            .as_array()
            .and_then(|a| serde_json::to_string(a).ok())
            .unwrap_or_else(|| "[]".to_string());
        let is_gump = cls["is_gump"].as_bool().unwrap_or(false) as i32;
        let is_mobile = cls["is_mobile"].as_bool().unwrap_or(false) as i32;
        let is_item = cls["is_item"].as_bool().unwrap_or(false) as i32;

        conn.execute(
            "INSERT OR REPLACE INTO script_classes
             (name, namespace, file_path, base_class, interfaces, attributes, is_gump, is_mobile, is_item)
             VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9)",
            params![name, namespace, file_path, base_class, interfaces, attributes,
                    is_gump, is_mobile, is_item],
        ).map_err(|e| e.to_string())?;
    }

    Ok(count)
}

/// Return the full script namespace tree (namespaces with their class summaries).
#[tauri::command]
pub fn get_script_tree(db: State<DbState>) -> Result<Vec<NamespaceTree>, String> {
    let conn = db.0.lock().map_err(|e| e.to_string())?;

    let mut stmt = conn
        .prepare(
            "SELECT name, namespace, file_path, base_class, interfaces, is_gump, is_mobile, is_item
             FROM script_classes ORDER BY namespace, name",
        )
        .map_err(|e| e.to_string())?;

    let rows = stmt
        .query_map([], |row| {
            let interfaces_json: String = row.get(4)?;
            let interfaces: Vec<String> =
                serde_json::from_str(&interfaces_json).unwrap_or_default();
            Ok(ClassSummary {
                name: row.get(0)?,
                namespace: row.get(1)?,
                file_path: row.get(2)?,
                base_class: row.get(3)?,
                interfaces,
                is_gump: row.get::<_, i32>(5)? != 0,
                is_mobile: row.get::<_, i32>(6)? != 0,
                is_item: row.get::<_, i32>(7)? != 0,
            })
        })
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;

    // Group by namespace
    let mut map: std::collections::BTreeMap<String, Vec<ClassSummary>> =
        std::collections::BTreeMap::new();
    for cls in rows {
        map.entry(cls.namespace.clone()).or_default().push(cls);
    }

    Ok(map
        .into_iter()
        .map(|(namespace, classes)| NamespaceTree { namespace, classes })
        .collect())
}

/// Ask the sidecar for full ClassInfo (with methods) and return it.
/// Also caches methods to SQLite on first fetch.
#[tauri::command]
pub fn get_class_detail(
    class_name: String,
    db: State<DbState>,
    sidecar: State<SidecarState>,
) -> Result<ClassDetail, String> {
    let result = sidecar
        .send("get_class", json!({ "class": class_name }))
        .map_err(|e| e.to_string())?;

    let cls_val = &result;
    let conn = db.0.lock().map_err(|e| e.to_string())?;

    // Parse class summary from sidecar response
    let interfaces: Vec<String> = cls_val["interfaces"]
        .as_array()
        .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
        .unwrap_or_default();

    let class = ClassSummary {
        name: cls_val["name"].as_str().unwrap_or("").to_string(),
        namespace: cls_val["namespace"].as_str().unwrap_or("").to_string(),
        file_path: cls_val["file_path"].as_str().unwrap_or("").to_string(),
        base_class: cls_val["base_class"].as_str().map(String::from),
        interfaces: interfaces.clone(),
        is_gump: cls_val["is_gump"].as_bool().unwrap_or(false),
        is_mobile: cls_val["is_mobile"].as_bool().unwrap_or(false),
        is_item: cls_val["is_item"].as_bool().unwrap_or(false),
    };

    // Parse methods
    let methods_val = cls_val["methods"].as_array().cloned().unwrap_or_default();
    let mut methods = Vec::new();

    // Clear existing methods for this class and re-insert
    conn.execute("DELETE FROM script_methods WHERE class_name = ?1", params![class.name])
        .map_err(|e| e.to_string())?;

    for m in &methods_val {
        let params_json = serde_json::to_string(&m["parameters"]).unwrap_or_default();
        let method_name = m["name"].as_str().unwrap_or("").to_string();
        let return_type = m["return_type"].as_str().unwrap_or("void").to_string();
        let is_override = m["is_override"].as_bool().unwrap_or(false);
        let is_virtual = m["is_virtual"].as_bool().unwrap_or(false);
        let calls_gump = m["calls_gump"].as_bool().unwrap_or(false);
        let gump_class = m["gump_class"].as_str().map(String::from);

        conn.execute(
            "INSERT INTO script_methods
             (class_name, name, return_type, parameters, is_override, is_virtual, calls_gump, gump_class)
             VALUES (?1,?2,?3,?4,?5,?6,?7,?8)",
            params![
                class.name,
                method_name,
                return_type,
                params_json,
                is_override as i32,
                is_virtual as i32,
                calls_gump as i32,
                gump_class,
            ],
        )
        .map_err(|e| e.to_string())?;
        let row_id = conn.last_insert_rowid();

        let parameters: Vec<ParameterInfo> = m["parameters"]
            .as_array()
            .map(|a| {
                a.iter()
                    .map(|p| ParameterInfo {
                        name: p["name"].as_str().unwrap_or("").to_string(),
                        ty: p["type"].as_str().unwrap_or("").to_string(),
                    })
                    .collect()
            })
            .unwrap_or_default();

        methods.push(MethodSummary {
            id: row_id,
            class_name: class.name.clone(),
            name: m["name"].as_str().unwrap_or("").to_string(),
            return_type: m["return_type"].as_str().unwrap_or("void").to_string(),
            parameters,
            is_override,
            is_virtual,
            calls_gump,
            gump_class: m["gump_class"].as_str().map(String::from),
        });
    }

    Ok(ClassDetail { class_info: class, methods })
}

/// Full-text search across indexed class and method names.
#[tauri::command]
pub fn search_scripts(query: String, db: State<DbState>) -> Result<Vec<ClassSummary>, String> {
    let conn = db.0.lock().map_err(|e| e.to_string())?;
    let pattern = format!("%{}%", query);

    let mut stmt = conn
        .prepare(
            "SELECT DISTINCT c.name, c.namespace, c.file_path, c.base_class,
                    c.interfaces, c.is_gump, c.is_mobile, c.is_item
             FROM script_classes c
             LEFT JOIN script_methods m ON m.class_name = c.name
             WHERE c.name LIKE ?1 OR c.namespace LIKE ?1 OR m.name LIKE ?1
             ORDER BY c.name LIMIT 100",
        )
        .map_err(|e| e.to_string())?;

    let rows = stmt
        .query_map(params![pattern], |row| {
            let interfaces_json: String = row.get(4)?;
            let interfaces: Vec<String> =
                serde_json::from_str(&interfaces_json).unwrap_or_default();
            Ok(ClassSummary {
                name: row.get(0)?,
                namespace: row.get(1)?,
                file_path: row.get(2)?,
                base_class: row.get(3)?,
                interfaces,
                is_gump: row.get::<_, i32>(5)? != 0,
                is_mobile: row.get::<_, i32>(6)? != 0,
                is_item: row.get::<_, i32>(7)? != 0,
            })
        })
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;

    Ok(rows)
}

/// Trace the call chain for a method via the C# sidecar and return a FlowNode tree.
#[tauri::command]
pub fn trace_method(
    class_name: String,
    method_name: String,
    sidecar: State<SidecarState>,
) -> Result<FlowNode, String> {
    let result = sidecar
        .send(
            "trace_method",
            json!({ "class": class_name, "method": method_name }),
        )
        .map_err(|e| e.to_string())?;

    serde_json::from_value::<FlowNode>(result).map_err(|e| e.to_string())
}
