//! Project-owned representation of the mapped `SkiaSharp.SKBitmap` boundary.

use crate::Error;
use libremetaverse_imaging::DEFAULT_MAX_PIXELS;

/// Pixel formats used by the pinned `SkiaTextureCodec` implementation.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SKColorType {
    Rgb565,
    Bgra8888,
    Rgba8888,
    Rgba1010102,
    Bgra1010102,
    Gray8,
    Alpha8,
    /// Another Skia format handled by the reference's byte-width fallback.
    Other {
        bytes_per_pixel: usize,
    },
}

impl SKColorType {
    const fn storage_bytes(self) -> usize {
        match self {
            Self::Rgb565 => 2,
            Self::Bgra8888 | Self::Rgba8888 | Self::Rgba1010102 | Self::Bgra1010102 => 4,
            Self::Gray8 | Self::Alpha8 => 1,
            Self::Other { bytes_per_pixel } => bytes_per_pixel,
        }
    }
}

/// Alpha representation attached to bitmap samples.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SKAlphaType {
    Opaque,
    Premul,
    Unpremul,
    Unknown,
}

/// Owned, checked bitmap used by the fixed mapped signature.
///
/// This deliberately does not expose a Skia allocation or pointer. Feature-
/// enabled decoding copies the native pixmap into this bounded Rust buffer
/// before conversion to the core imaging abstraction.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SKBitmap {
    width: i32,
    height: i32,
    row_bytes: usize,
    color_type: SKColorType,
    alpha_type: SKAlphaType,
    pixels: Vec<u8>,
}

impl SKBitmap {
    /// Creates a bitmap with an explicit row stride and owned pixels.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Argument`] for invalid dimensions, formats, strides,
    /// lengths, or over-limit storage.
    pub fn new(
        width: i32,
        height: i32,
        row_bytes: usize,
        color_type: SKColorType,
        alpha_type: SKAlphaType,
        pixels: Vec<u8>,
    ) -> Result<Self, Error> {
        let width_usize = usize::try_from(width).map_err(|_| Error::Argument)?;
        let height_usize = usize::try_from(height).map_err(|_| Error::Argument)?;
        let pixel_count = width_usize
            .checked_mul(height_usize)
            .filter(|count| *count > 0 && *count <= DEFAULT_MAX_PIXELS)
            .ok_or(Error::Argument)?;
        let storage_bytes = color_type.storage_bytes();
        if storage_bytes == 0 || storage_bytes > 16 {
            return Err(Error::Argument);
        }
        let packed_row = width_usize
            .checked_mul(storage_bytes)
            .ok_or(Error::Argument)?;
        if row_bytes < packed_row {
            return Err(Error::Argument);
        }
        let required = row_bytes
            .checked_mul(height_usize.saturating_sub(1))
            .and_then(|prefix| prefix.checked_add(packed_row))
            .ok_or(Error::Argument)?;
        if pixels.len() < required
            || pixels.len()
                > pixel_count
                    .checked_mul(16)
                    .and_then(|size| size.checked_add(row_bytes))
                    .ok_or(Error::Argument)?
        {
            return Err(Error::Argument);
        }
        Ok(Self {
            width,
            height,
            row_bytes,
            color_type,
            alpha_type,
            pixels,
        })
    }

    pub(crate) const fn width(&self) -> i32 {
        self.width
    }

    pub(crate) const fn height(&self) -> i32 {
        self.height
    }

    pub(crate) const fn row_bytes(&self) -> usize {
        self.row_bytes
    }

    pub(crate) const fn color_type(&self) -> SKColorType {
        self.color_type
    }

    pub(crate) const fn alpha_type(&self) -> SKAlphaType {
        self.alpha_type
    }

    pub(crate) fn pixels(&self) -> &[u8] {
        &self.pixels
    }
}
