use anyhow::{anyhow, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;

/// A single entry from a .idx file.
#[derive(Debug, Clone, Copy)]
pub struct IndexEntry {
    pub offset: i32,
    pub length: i32,
    pub extra: i32,
}

/// Read all index entries from a .idx file.
pub fn read_index(idx_path: &Path) -> Result<Vec<IndexEntry>> {
    let file = File::open(idx_path)?;
    let mut reader = BufReader::new(file);
    let mut entries = Vec::new();

    loop {
        let offset = match reader.read_i32::<LittleEndian>() {
            Ok(v) => v,
            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
            Err(e) => return Err(e.into()),
        };
        let length = reader.read_i32::<LittleEndian>()?;
        let extra = reader.read_i32::<LittleEndian>()?;
        entries.push(IndexEntry { offset, length, extra });
    }

    Ok(entries)
}

/// Read the raw bytes for a given index entry from the .mul data file.
/// Returns `None` if the entry is marked as non-existent or invalid.
pub fn read_entry(mul_path: &Path, entry: &IndexEntry) -> Result<Option<Vec<u8>>> {
    // offset < 0 or length <= 0 means "no entry" per UO convention.
    // Also guard against absurdly large lengths (corrupt/sparse entries).
    if entry.offset < 0 || entry.length <= 0 || entry.length > 0x100_0000 {
        return Ok(None);
    }

    let file_len = std::fs::metadata(mul_path)?.len();
    let start = entry.offset as u64;
    let len = entry.length as usize;

    // Entry extends past EOF — treat as empty rather than hard error.
    if start >= file_len || start + len as u64 > file_len {
        return Ok(None);
    }

    let mut file = File::open(mul_path)?;
    file.seek(SeekFrom::Start(start))?;

    let mut buf = vec![0u8; len];
    file.read_exact(&mut buf)?;
    Ok(Some(buf))
}

/// Convenience: open a mul/idx pair and return the index entries.
pub fn open_mul_pair(root: &Path, base_name: &str) -> Result<(Vec<IndexEntry>, std::path::PathBuf)> {
    let idx_path = root.join(format!("{}idx.mul", base_name));
    let mul_path = root.join(format!("{}.mul", base_name));

    if !idx_path.exists() {
        return Err(anyhow!("Index file not found: {:?}", idx_path));
    }
    if !mul_path.exists() {
        return Err(anyhow!("Data file not found: {:?}", mul_path));
    }

    let entries = read_index(&idx_path)?;
    Ok((entries, mul_path))
}
