//! JPXDecode (JPEG 2000) Filter Implementation
//!
//! ## Encoding Limitation
//!
//! The `jpeg2k` crate (wrapper around OpenJPEG) only supports:
//! - Decoding: `Image::from_bytes` / `Image::from_file`
//! - Encoding: `Image::save_as_file` (requires an existing decoded `Image`)
//!
//! There is no public API to create an `Image` from raw pixel data (`ImageData`)
//! for encoding. Therefore `encode_jpx` returns an error until the underlying
//! crate adds this capability.

use crate::fitz::error::{Error, Result};

/// Decode JPEG 2000 compressed data
#[cfg(feature = "jpeg2000")]
pub fn decode_jpx(data: &[u8]) -> Result<Vec<u8>> {
    use jpeg2k::Image;

    let image = Image::from_bytes(data)
        .map_err(|e| Error::Generic(format!("JPXDecode failed: {:?}", e)))?;

    // Get the decoded image data
    // The jpeg2k crate provides access to image data through its API
    let mut result = Vec::new();

    // Get dimensions
    let width = image.width() as usize;
    let height = image.height() as usize;
    let num_components = image.components().len();

    // Reserve space for the output
    result.reserve(width * height * num_components);

    // Extract data component by component
    // JPEG2000 stores components separately, we need to interleave them
    for y in 0..height {
        for x in 0..width {
            for comp in image.components() {
                let comp_width = comp.width() as usize;
                let idx = y * comp_width + x;
                if let Some(&val) = comp.data().get(idx) {
                    // jpeg2k returns i32 values, convert to u8
                    result.push(val.clamp(0, 255) as u8);
                }
            }
        }
    }

    Ok(result)
}

#[cfg(not(feature = "jpeg2000"))]
pub fn decode_jpx(_data: &[u8]) -> Result<Vec<u8>> {
    Err(Error::Generic(
        "JPEG 2000 support not enabled. Enable 'jpeg2000' feature.".into(),
    ))
}

/// Encode raw image data to JPEG 2000 (JPX) format.
///
/// # Arguments
///
/// * `data` - Raw pixel data, interleaved by component (e.g. RGB: R,G,B,R,G,B,...)
/// * `width` - Image width in pixels
/// * `height` - Image height in pixels
/// * `num_components` - Number of color components (1=grayscale, 3=RGB, 4=RGBA)
///
/// # Limitation
///
/// The `jpeg2k` crate does not expose an API to create an `Image` from raw pixel
/// data. It only supports decoding (from bytes/file) and encoding via
/// `save_as_file` on an already-decoded `Image`. Until the crate adds
/// `Image::from_image_data` or similar, encoding returns an error.
#[cfg(feature = "jpeg2000")]
pub fn encode_jpx(data: &[u8], width: u32, height: u32, num_components: u8) -> Result<Vec<u8>> {
    let expected_len = (width as usize)
        .checked_mul(height as usize)
        .and_then(|n| n.checked_mul(num_components as usize))
        .ok_or_else(|| Error::Generic("Image dimensions overflow".into()))?;

    if data.len() != expected_len {
        return Err(Error::Generic(format!(
            "JPX encode: expected {} bytes ({}x{}x{}), got {}",
            expected_len,
            width,
            height,
            num_components,
            data.len()
        )));
    }

    if width == 0 || height == 0 {
        return Err(Error::Generic(
            "JPX encode: width and height must be non-zero".into(),
        ));
    }

    Err(Error::Generic(
        "JPX encoding from raw pixels is not supported: the jpeg2k crate has no API to create \
         an Image from ImageData. Use decode_jpx for decoding only."
            .into(),
    ))
}

#[cfg(not(feature = "jpeg2000"))]
pub fn encode_jpx(_data: &[u8], _width: u32, _height: u32, _num_components: u8) -> Result<Vec<u8>> {
    Err(Error::Generic(
        "JPEG 2000 support not enabled. Enable 'jpeg2000' feature.".into(),
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[cfg(not(feature = "jpeg2000"))]
    fn test_jpx_decode_disabled() {
        let data = &[0u8; 100];
        let result = decode_jpx(data);
        assert!(result.is_err());
    }

    #[test]
    #[cfg(not(feature = "jpeg2000"))]
    fn test_jpx_encode_disabled() {
        let data = vec![0u8; 12]; // 2x2x3 RGB
        let result = encode_jpx(&data, 2, 2, 3);
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(err_msg.contains("jpeg2000") || err_msg.contains("JPEG 2000"));
    }

    #[test]
    #[cfg(feature = "jpeg2000")]
    fn test_encode_jpx_returns_error_limitation() {
        // When jpeg2000 is enabled, encode still fails due to jpeg2k crate limitation
        let data = vec![255u8, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255]; // 2x2 RGB
        let result = encode_jpx(&data, 2, 2, 3);
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(
            err_msg.contains("jpeg2k") || err_msg.contains("ImageData"),
            "expected limitation message, got: {}",
            err_msg
        );
    }

    #[test]
    #[cfg(feature = "jpeg2000")]
    fn test_encode_jpx_invalid_dimensions() {
        let data = vec![0u8; 10];
        let result = encode_jpx(&data, 2, 2, 3);
        assert!(result.is_err()); // expected 12 bytes
    }

    #[test]
    #[cfg(feature = "jpeg2000")]
    fn test_encode_jpx_zero_dimensions() {
        let data = vec![0u8; 0];
        let result = encode_jpx(&data, 0, 0, 1);
        assert!(result.is_err());
    }

    // When jpeg2k crate adds Image::from_image_data or similar, add encode→decode round-trip test here.
}
