use anyhow::{anyhow, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
use std::path::Path;

use super::art::ArtImage;
use super::art::argb16_to_rgba32;
use super::mul_reader;
use super::uop_reader;

// ── Gump BMP folder ───────────────────────────────────────────────────────────

/// Collect all candidate "UO Gumps" directories: resource_dir + walk-up from exe.
fn gump_dir_candidates(app: &tauri::AppHandle) -> Vec<std::path::PathBuf> {
    use tauri::Manager;
    let mut candidates = Vec::new();

    if let Ok(resource_dir) = app.path().resource_dir() {
        let c = resource_dir.join("UO Gumps");
        if c.is_dir() { candidates.push(c); }
    }

    if let Ok(exe) = std::env::current_exe() {
        let mut dir = exe.parent().map(|p| p.to_path_buf());
        for _ in 0..10 {
            if let Some(ref d) = dir {
                let c = d.join("UO Gumps");
                if c.is_dir() && !candidates.contains(&c) { candidates.push(c); }
                dir = d.parent().map(|p| p.to_path_buf());
            } else { break; }
        }
    }

    candidates
}

/// Find the "UO Gumps" folder containing gumps.xml.
pub fn find_gump_dir(app: &tauri::AppHandle) -> Option<std::path::PathBuf> {
    // Prefer any candidate that has gumps.xml
    let candidates = gump_dir_candidates(app);
    candidates.into_iter().find(|c| c.join("gumps.xml").exists())
}

/// Find the "UO Gumps" folder containing script_gumps.xml.
/// Falls back to any UO Gumps dir if the file isn't found anywhere.
pub fn find_gump_dir_for_scripts(app: &tauri::AppHandle) -> Option<std::path::PathBuf> {
    let candidates = gump_dir_candidates(app);
    // Prefer a candidate that actually has script_gumps.xml
    if let Some(hit) = candidates.iter().find(|c| c.join("script_gumps.xml").exists()) {
        return Some(hit.clone());
    }
    // Fallback: return first candidate at all
    candidates.into_iter().next()
}

/// Decode a BMP from the "UO Gumps" folder by gump_id.
/// Filename pattern: "Gump 0x{id:04X}.bmp"
/// Black (0,0,0) pixels are made transparent.
pub fn decode_gump_bmp(gump_dir: &Path, gump_id: usize) -> Result<ArtImage> {
    let filename = format!("Gump 0x{:04X}.bmp", gump_id);
    let path = gump_dir.join(&filename);
    if !path.exists() {
        return Err(anyhow!("no gump data"));
    }
    decode_gump_bmp_file(&path)
}

fn decode_gump_bmp_file(path: &Path) -> Result<ArtImage> {
    let img = image::open(path)
        .map_err(|e| anyhow!("BMP decode: {}", e))?;
    let mut rgba = img.to_rgba8();
    for pixel in rgba.pixels_mut() {
        if pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0 {
            pixel[3] = 0;
        }
    }
    Ok(ArtImage {
        width: rgba.width(),
        height: rgba.height(),
        pixels: rgba.into_raw(),
    })
}

/// Decode a gump image by ID from gumpart.mul.
pub fn decode_gump(mul_path: &Path, idx_path: &Path, gump_id: usize) -> Result<ArtImage> {
    let entries = mul_reader::read_index(idx_path)?;

    let entry = entries
        .get(gump_id)
        .ok_or_else(|| anyhow!("Gump ID {} out of range", gump_id))?;

    let raw = mul_reader::read_entry(mul_path, entry)?
        .ok_or_else(|| anyhow!("Gump ID {} has no data", gump_id))?;

    // Entry extra field: width in high 16 bits, height in low 16 bits
    let width = ((entry.extra >> 16) & 0xFFFF) as u32;
    let height = (entry.extra & 0xFFFF) as u32;

    decode_gump_data(&raw, width, height)
}

/// Decode a gump image from gumpartLegacyMUL.uop (Enhanced Client).
/// Hash pattern: "build/gumpartlegacymul/{id:08}.tga"
/// Width and height are encoded in the UOP entry's decompressed data header.
pub fn decode_gump_uop(uop_path: &Path, gump_id: usize) -> Result<ArtImage> {
    let pattern = format!("build/gumpartlegacymul/{:08}.tga", gump_id);
    let hash = uop_reader::uop_hash(&pattern);

    let index = uop_reader::read_uop_index(uop_path)?;
    let entry = index
        .get(&hash)
        .ok_or_else(|| anyhow!("Gump ID {} not found in gumpartLegacyMUL.uop", gump_id))?
        .clone();

    let raw = uop_reader::read_uop_entry(uop_path, &entry)?;

    // In the UOP format the width/height are encoded in the extra field of the
    // index, but in gumpartLegacyMUL.uop they are stored as the first 8 bytes
    // of the decompressed data: u32 width, u32 height.
    if raw.len() < 8 {
        return Err(anyhow!("Gump UOP entry too small: {} bytes", raw.len()));
    }
    let width  = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
    let height = u32::from_le_bytes([raw[4], raw[5], raw[6], raw[7]]);
    decode_gump_data(&raw[8..], width, height)
}

fn decode_gump_data(data: &[u8], width: u32, height: u32) -> Result<ArtImage> {
    if width == 0 || height == 0 {
        return Err(anyhow!("Invalid gump dimensions: {}x{}", width, height));
    }

    let mut cursor = Cursor::new(data);
    let mut pixels = vec![0u8; (width * height * 4) as usize];

    // Row lookup: one u32 per row, offset in u32 units from start of data
    let mut row_offsets = vec![0u32; height as usize];
    for o in row_offsets.iter_mut() {
        *o = cursor.read_u32::<LittleEndian>()?;
    }

    for y in 0..height as usize {
        let pos = (row_offsets[y] as usize) * 4;
        let mut x = 0usize;
        let mut p = pos;

        loop {
            if p + 4 > data.len() {
                break;
            }
            let color = u16::from_le_bytes([data[p], data[p + 1]]);
            let count = u16::from_le_bytes([data[p + 2], data[p + 3]]);
            p += 4;

            if count == 0 {
                break;
            }

            let rgba = argb16_to_rgba32(color);
            for _ in 0..count as usize {
                if x >= width as usize {
                    break;
                }
                let idx = (y * width as usize + x) * 4;
                pixels[idx..idx + 4].copy_from_slice(&rgba);
                x += 1;
            }
        }
    }

    Ok(ArtImage { width, height, pixels })
}
