use crate::assets::{art, gumpart, tiledata};
use crate::db::DbState;
use rusqlite::{params, OptionalExtension};
use serde::{Deserialize, Serialize};
use std::path::Path;
use tauri::State;

#[derive(Debug, Serialize, Deserialize)]
pub struct TileRow {
    pub id: u32,
    pub name: String,
    pub flags: i64,
    pub weight: u8,
    pub quality: u8,
    pub height: u8,
    pub hue: u8,
    pub anim_id: u16,
}

/// List static tiles from the SQLite index, paginated.
#[tauri::command]
pub fn list_static_tiles(
    offset: u32,
    limit: u32,
    search: Option<String>,
    state: State<DbState>,
) -> Result<Vec<TileRow>, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;

    let rows = if let Some(q) = search {
        let pattern = format!("%{}%", q);
        let mut stmt = conn
            .prepare(
                "SELECT id, name, flags, weight, quality, height, hue, anim_id
                 FROM static_tiles WHERE name LIKE ?1 ORDER BY id LIMIT ?2 OFFSET ?3",
            )
            .map_err(|e| e.to_string())?;
        let rows: Vec<TileRow> = stmt
            .query_map(params![pattern, limit, offset], row_to_tile)
            .map_err(|e| e.to_string())?
            .collect::<Result<Vec<_>, _>>()
            .map_err(|e| e.to_string())?;
        rows
    } else {
        let mut stmt = conn
            .prepare(
                "SELECT id, name, flags, weight, quality, height, hue, anim_id
                 FROM static_tiles ORDER BY id LIMIT ?1 OFFSET ?2",
            )
            .map_err(|e| e.to_string())?;
        let rows: Vec<TileRow> = stmt
            .query_map(params![limit, offset], row_to_tile)
            .map_err(|e| e.to_string())?
            .collect::<Result<Vec<_>, _>>()
            .map_err(|e| e.to_string())?;
        rows
    };

    Ok(rows)
}

fn row_to_tile(row: &rusqlite::Row) -> rusqlite::Result<TileRow> {
    Ok(TileRow {
        id: row.get(0)?,
        name: row.get(1)?,
        flags: row.get(2)?,
        weight: row.get(3)?,
        quality: row.get(4)?,
        height: row.get(5)?,
        hue: row.get(6)?,
        anim_id: row.get(7)?,
    })
}

/// Get a single static tile by ID.
#[tauri::command]
pub fn get_static_tile(id: u32, state: State<DbState>) -> Result<Option<TileRow>, String> {
    let conn = state.0.lock().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare(
            "SELECT id, name, flags, weight, quality, height, hue, anim_id
             FROM static_tiles WHERE id = ?1",
        )
        .map_err(|e| e.to_string())?;

    let result = stmt
        .query_row(params![id], row_to_tile)
        .optional()
        .map_err(|e| e.to_string())?;

    Ok(result)
}

/// Get art image pixels for a static tile by item ID.
/// Priority: bundled BMP artwork folder → art.mul → artLegacyMUL.uop → tileart.uop
#[tauri::command]
pub fn get_tile_art(
    app: tauri::AppHandle,
    item_id: u32,
    uo_root: String,
    _state: State<DbState>,
) -> Result<ArtImageResult, String> {
    // 1. Bundled BMP artwork (highest quality, always try first)
    if let Some(artwork_dir) = art::find_artwork_dir(&app) {
        if let Ok(img) = art::decode_static_bmp(&artwork_dir, item_id as usize) {
            return Ok(ArtImageResult {
                width: img.width,
                height: img.height,
                pixels: img.pixels,
            });
        }
    }

    // 2. Fall back to MUL / UOP client files
    let root = Path::new(&uo_root);
    let img = art::decode_static_any(root, item_id as usize)
        .map_err(|e| e.to_string())?;

    Ok(ArtImageResult {
        width: img.width,
        height: img.height,
        pixels: img.pixels,
    })
}

/// Get a gump image by gump ID.
/// Tries gumpart.mul (Classic), then gumpartLegacyMUL.uop (EC / Classic UOP).
#[tauri::command]
pub fn get_gump_image(
    gump_id: u32,
    uo_root: String,
) -> Result<ArtImageResult, String> {
    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. Requires gumpart.mul+gumpartidx.mul \
             or gumpartLegacyMUL.uop."
                .to_string(),
        );
    };

    Ok(ArtImageResult {
        width: img.width,
        height: img.height,
        pixels: img.pixels,
    })
}

/// Walk tiledata.mul and populate the static_tiles table.
#[tauri::command]
pub fn index_assets(uo_root: String, state: State<DbState>) -> Result<u32, String> {
    let root = Path::new(&uo_root);
    let tiledata_path = root.join("tiledata.mul");

    if !tiledata_path.exists() {
        return Err("tiledata.mul not found in UO root".to_string());
    }

    let tiles = tiledata::read_item_tiles(&tiledata_path).map_err(|e| e.to_string())?;
    let count = tiles.len() as u32;

    let mut conn = state.0.lock().map_err(|e| e.to_string())?;

    // Wrap everything in a single transaction — without this, 39k+ individual
    // auto-committed inserts each flush to disk and take minutes to complete.
    let tx = conn.transaction().map_err(|e| e.to_string())?;

    tx.execute("DELETE FROM static_tiles", [])
        .map_err(|e| e.to_string())?;

    {
        let mut stmt = tx
            .prepare(
                "INSERT INTO static_tiles (id, name, flags, weight, quality, height, hue, anim_id)
                 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
            )
            .map_err(|e| e.to_string())?;

        for tile in &tiles {
            stmt.execute(params![
                tile.id,
                tile.name,
                tile.flags as i64,
                tile.weight,
                tile.quality,
                tile.height,
                tile.hue,
                tile.anim_id,
            ])
            .map_err(|e| e.to_string())?;
        }
    }

    tx.commit().map_err(|e| e.to_string())?;

    Ok(count)
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ArtImageResult {
    pub width: u32,
    pub height: u32,
    pub pixels: Vec<u8>,
}
