//! Custom Bevy asset loaders for Project Sylpheed's file formats.
//!
//! Bevy's asset system is extended via `AssetLoader` implementations.
//! Each loader converts a game-specific binary format into a Bevy `Image`
//! or `Mesh` that can be used directly in the scene.
//!
//! ## How Bevy asset loading works
//!
//! 1. You request an asset: `asset_server.load("textures/ship01.xpr")`
//! 2. Bevy looks for an `AssetLoader` that handles `.xpr` files
//! 3. Our loader reads the bytes, de-tiles the texture, returns a `Image`
//! 4. Bevy uploads it to the GPU automatically

use bevy::asset::io::Reader;
use bevy::asset::{AssetLoader, LoadContext};
use bevy::image::ImageSampler;
use bevy::prelude::*;
use bevy::render::render_asset::RenderAssetUsages;
use bevy::render::render_resource::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
use sylpheed_formats::texture::{X360Texture, X360TextureFormat};

// ── Plugin ────────────────────────────────────────────────────────────────────

/// Registers all custom asset loaders with Bevy.
pub struct SylpheedAssetPlugin;

impl Plugin for SylpheedAssetPlugin {
    fn build(&self, app: &mut App) {
        app.init_asset_loader::<Xpr2TextureLoader>();
        // TODO: app.init_asset_loader::<SylpheedMeshLoader>();
        // TODO: app.init_asset_loader::<SylpheedAudioLoader>();
        info!("Sylpheed asset loaders registered");
    }
}

// ── XPR2 Texture Loader ───────────────────────────────────────────────────────

/// Loads Xbox 360 XPR2 texture files (`.xpr`) as Bevy `Image` assets.
///
/// The loader:
///   1. Reads the raw XPR2 bytes
///   2. Parses the XPR2 header to find the texture descriptor
///   3. De-tiles the GPU memory layout using Morton-order de-interleaving
///   4. Returns a `bevy::render::texture::Image` with the correct `TextureFormat`
#[derive(Default)]
pub struct Xpr2TextureLoader;

#[derive(Debug, thiserror::Error)]
pub enum Xpr2LoadError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Texture parse error: {0}")]
    Texture(#[from] sylpheed_formats::texture::TextureError),
    #[error("Unsupported texture format for Bevy upload")]
    UnsupportedForBevy,
}

impl AssetLoader for Xpr2TextureLoader {
    type Asset = Image;
    type Settings = ();
    type Error = Xpr2LoadError;

    async fn load(
        &self,
        reader: &mut dyn Reader,
        _settings: &Self::Settings,
        _load_context: &mut LoadContext<'_>,
    ) -> Result<Self::Asset, Self::Error> {
        // Read all bytes from the asset source
        let mut bytes = Vec::new();
        reader.read_to_end(&mut bytes).await?;

        // Parse the XPR2 container and de-tile the texture
        let x360_tex = X360Texture::from_xpr2(&bytes)?;

        // Convert to Bevy's Image type
        x360_texture_to_bevy_image(x360_tex)
    }

    fn extensions(&self) -> &[&str] {
        // Register for both .xpr and .xpr2 extensions
        &["xpr", "xpr2"]
    }
}

// ── Conversion: X360Texture → bevy::Image ─────────────────────────────────────

/// Convert a decoded `X360Texture` into a Bevy-compatible `Image`.
///
/// Constructs the `Image` struct directly rather than using `Image::new()`,
/// because `Image::new()` calls `pixel_size()` which panics for BCn
/// block-compressed formats.
pub fn x360_texture_to_bevy_image(tex: X360Texture) -> Result<Image, Xpr2LoadError> {
    let format = x360_format_to_wgpu(&tex.format)?;

    // Uncompressed k_8_8_8_8: after `from_xpr2`'s k8in32 endian swap the bytes
    // are in [A,R,G,B] order (verified against the retail Acheron backdrop).
    // wgpu has no ARGB format, so reorder to [R,G,B,A] and upload as Rgba8
    // (see `x360_format_to_wgpu`). BCn data is already GPU-ready.
    let data = match tex.format {
        X360TextureFormat::A8R8G8B8 | X360TextureFormat::X8R8G8B8 => {
            let opaque = matches!(tex.format, X360TextureFormat::X8R8G8B8);
            let mut out = tex.data;
            for px in out.chunks_exact_mut(4) {
                let (a, r, g, b) = (px[0], px[1], px[2], px[3]);
                px[0] = r;
                px[1] = g;
                px[2] = b;
                px[3] = if opaque { 0xFF } else { a };
            }
            out
        }
        _ => tex.data,
    };

    Ok(Image {
        data,
        texture_descriptor: TextureDescriptor {
            label: None,
            size: Extent3d {
                width: tex.width,
                height: tex.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: TextureDimension::D2,
            format,
            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
            view_formats: &[],
        },
        sampler: ImageSampler::Default,
        texture_view_descriptor: None,
        asset_usage: RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
    })
}

/// Map an `X360TextureFormat` to the corresponding `wgpu::TextureFormat`.
///
/// Bevy fully supports BCn (DXT) block compression — the GPU handles
/// decompression in hardware, so we pass DXT data directly without
/// software decompression.
fn x360_format_to_wgpu(format: &X360TextureFormat) -> Result<TextureFormat, Xpr2LoadError> {
    Ok(match format {
        // DXT1 / BC1
        X360TextureFormat::Dxt1 => TextureFormat::Bc1RgbaUnormSrgb,
        // DXT3 / BC2
        X360TextureFormat::Dxt3 => TextureFormat::Bc2RgbaUnormSrgb,
        // DXT5 / BC3
        X360TextureFormat::Dxt5 => TextureFormat::Bc3RgbaUnormSrgb,
        // DXN / BC5 — two-channel normal maps (RG, not sRGB)
        X360TextureFormat::Dxn => TextureFormat::Bc5RgUnorm,
        // DXT5A / BC4 — single-channel (gloss, specular, luminance maps)
        X360TextureFormat::Dxt5A => TextureFormat::Bc4RUnorm,
        // Uncompressed — reordered to [R,G,B,A] by `x360_texture_to_bevy_image`.
        X360TextureFormat::A8R8G8B8 | X360TextureFormat::X8R8G8B8 => {
            TextureFormat::Rgba8UnormSrgb
        }
    })
}

// ── Fallback: load a raw DDS file ─────────────────────────────────────────────

/// Loads a standard DDS file (if you've pre-converted game textures using
/// a tool like Noesis). This is the fast path for early Milestone 1 work
/// before the XPR2 loader is fully validated.
///
/// Bevy has built-in DDS support via the `dds` feature.
/// Just use: asset_server.load("textures/ship01.dds")
/// No custom loader needed for DDS!
pub struct DdsTextureInfo;
impl DdsTextureInfo {
    /// Use this path in Bevy for standard DDS files:
    /// ```rust,no_run
    /// # use bevy::prelude::*;
    /// # fn example(asset_server: Res<AssetServer>) {
    /// let handle: Handle<Image> = asset_server.load("textures/ship.dds");
    /// # }
    /// ```
    pub fn note() {}
}
