use crate::assets::gumpart;
use crate::assets::art::ArtImage;
use crate::commands::asset_commands::ArtImageResult;
use serde::{Deserialize, Serialize};
use std::path::Path;

/// A single entry from gumps.xml (art gump with numeric ID).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GumpEntry {
    pub id: u32,
    pub name: String,
    pub tags: Vec<String>,
}

/// A single entry from script_gumps.xml (C# Gump class from ServUO Scripts).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScriptGumpEntry {
    pub class_name: String,
    pub file_path: String,
    pub tags: Vec<String>,
}

/// Parse gumps.xml from the "UO Gumps" folder and return all entries.
/// Supports both decimal IDs and 0x-prefixed hex IDs.
#[tauri::command]
pub fn list_gumps(app: tauri::AppHandle) -> Result<Vec<GumpEntry>, String> {
    let gump_dir = gumpart::find_gump_dir(&app)
        .ok_or_else(|| "UO Gumps folder not found".to_string())?;

    let xml_path = gump_dir.join("gumps.xml");
    if !xml_path.exists() {
        return Err("gumps.xml not found in UO Gumps folder".to_string());
    }

    let content = std::fs::read_to_string(&xml_path)
        .map_err(|e| format!("Failed to read gumps.xml: {}", e))?;

    let mut entries = Vec::new();

    for line in content.lines() {
        let trimmed = line.trim();
        if !trimmed.starts_with("<Gump ") {
            continue;
        }

        let id = parse_attr(trimmed, "id").and_then(|s| parse_gump_id(&s));
        let name = parse_attr(trimmed, "name");
        let tags = parse_attr(trimmed, "tags");

        if let (Some(id), Some(name)) = (id, name) {
            let tags_vec: Vec<String> = if let Some(t) = tags {
                t.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
            } else {
                Vec::new()
            };
            entries.push(GumpEntry { id, name, tags: tags_vec });
        }
    }

    Ok(entries)
}

fn parse_attr(line: &str, attr: &str) -> Option<String> {
    let needle = format!("{}=\"", attr);
    let start = line.find(&needle)? + needle.len();
    let rest = &line[start..];
    let end = rest.find('"')?;
    Some(rest[..end].to_string())
}

fn parse_gump_id(s: &str) -> Option<u32> {
    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
        u32::from_str_radix(hex, 16).ok()
    } else {
        s.parse::<u32>().ok()
    }
}

/// Get a gump image by gump ID.
/// Priority: bundled BMP → gumpart.mul → gumpartLegacyMUL.uop
#[tauri::command]
pub fn get_gump_art(
    app: tauri::AppHandle,
    gump_id: u32,
    uo_root: String,
) -> Result<ArtImageResult, String> {
    // 1. Bundled BMP
    if let Some(gump_dir) = gumpart::find_gump_dir(&app) {
        if let Ok(img) = gumpart::decode_gump_bmp(&gump_dir, gump_id as usize) {
            return Ok(art_image_to_result(img));
        }
    }

    // 2. Fall back to MUL / UOP client files
    let root = Path::new(&uo_root);
    let mul_path = root.join("gumpart.mul");
    let idx_path = root.join("gumpartidx.mul");
    let uop_path = root.join("gumpartLegacyMUL.uop");

    let img = if mul_path.exists() && idx_path.exists() {
        gumpart::decode_gump(&mul_path, &idx_path, gump_id as usize)
            .map_err(|e| e.to_string())?
    } else if uop_path.exists() {
        gumpart::decode_gump_uop(&uop_path, gump_id as usize)
            .map_err(|e| e.to_string())?
    } else {
        return Err("No gump art source found".to_string());
    };

    Ok(art_image_to_result(img))
}

/// Parse script_gumps.xml from the "UO Gumps" folder and return all script gump entries.
#[tauri::command]
pub fn list_script_gumps(app: tauri::AppHandle) -> Result<Vec<ScriptGumpEntry>, String> {
    let gump_dir = gumpart::find_gump_dir_for_scripts(&app)
        .ok_or_else(|| "UO Gumps folder not found".to_string())?;

    let xml_path = gump_dir.join("script_gumps.xml");
    if !xml_path.exists() {
        return Err(format!("script_gumps.xml not found — searched: {}", gump_dir.display()));
    }

    let content = std::fs::read_to_string(&xml_path)
        .map_err(|e| format!("Failed to read script_gumps.xml: {}", e))?;

    let mut entries = Vec::new();

    for line in content.lines() {
        let trimmed = line.trim();
        if !trimmed.starts_with("<Gump ") {
            continue;
        }

        let class_name = parse_attr(trimmed, "class");
        let file_path = parse_attr(trimmed, "file");
        let tags = parse_attr(trimmed, "tags");

        if let (Some(class_name), Some(file_path)) = (class_name, file_path) {
            let tags_vec: Vec<String> = if let Some(t) = tags {
                t.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
            } else {
                Vec::new()
            };
            entries.push(ScriptGumpEntry { class_name, file_path, tags: tags_vec });
        }
    }

    Ok(entries)
}

fn art_image_to_result(img: ArtImage) -> ArtImageResult {
    ArtImageResult {
        width: img.width,
        height: img.height,
        pixels: img.pixels,
    }
}
