//! Tier 2 codec: SVG (and SVGZ) via `usvg` + `resvg` + `tiny-skia`.
//!
//! SVG is the only vector format we support. We rasterize on decode so the
//! rest of the pipeline can treat it as RGBA8 — same as any bitmap. This
//! keeps the viewer's compositing, rotation, and thumbnail code uniform.
//!
//! Notes:
//! - **SVGZ** is detected by gzip magic bytes (0x1f 0x8b) rather than the
//!   file extension. The `Codec::decode` trait method takes only bytes, so
//!   we can't see the extension here. Magic-byte detection is more robust
//!   anyway (it catches `.svg.gz` and mis-named files too).
//! - **Render size**: if the SVG declares explicit `width`/`height` (or a
//!   `viewBox`), we render at that exact pixel size. If neither is present,
//!   we default to 1024×768 — a sane viewport for unconstrained vector art.
//! - **Premultiplied alpha**: tiny-skia renders premultiplied; we call
//!   `take_demultiplied()` to get straight RGBA8 for downstream consumers.

use std::io::Read;

use flate2::read::GzDecoder;
use resvg::tiny_skia::{Pixmap, Transform};
use resvg::usvg::{Options, Tree};

use super::{Codec, DecodeError, DecodedImage};

/// Default render size when an SVG declares neither width/height nor viewBox.
const FALLBACK_SIZE: (u32, u32) = (1024, 768);

/// SVG codec. Rasterizes on decode.
pub struct SvgCodec;

impl Codec for SvgCodec {
    fn name(&self) -> &'static str {
        "SVG"
    }

    fn extensions(&self) -> &'static [&'static str] {
        &["svg", "svgz"]
    }

    fn decode(&self, bytes: &[u8]) -> Result<DecodedImage, DecodeError> {
        // SVGZ detection: gzip magic is 0x1f 0x8b. We decompress transparently
        // so .svgz files "just work" without the user renaming them.
        let svg_bytes: Vec<u8> = if bytes.starts_with(&[0x1f, 0x8b]) {
            let mut decoder = GzDecoder::new(bytes);
            let mut out = Vec::new();
            decoder
                .read_to_end(&mut out)
                .map_err(|e| DecodeError::Io(e.to_string()))?;
            out
        } else {
            bytes.to_vec()
        };

        // Parse the SVG into a usvg tree. `Options::default()` uses the
        // current directory for resolving relative URLs (none, in our case)
        // and default font config.
        let tree = Tree::from_data(&svg_bytes, &Options::default())
            .map_err(|e| DecodeError::Decode(e.to_string()))?;

        // Determine render dimensions. `tree.size()` is a `tiny_skia::Size`
        // (f32, guaranteed non-zero by usvg). If for any reason the size
        // rounds down to 0×0, fall back to the default.
        let (width, height) = {
            let size = tree.size();
            let w = size.width().round() as u32;
            let h = size.height().round() as u32;
            if w == 0 || h == 0 {
                FALLBACK_SIZE
            } else {
                (w, h)
            }
        };

        // Allocate the destination pixmap. tiny-skia's `Pixmap::new` returns
        // `None` on overflow; we surface that as a decode error.
        let mut pixmap = Pixmap::new(width, height)
            .ok_or_else(|| DecodeError::Decode(format!("pixmap alloc failed: {width}x{height}")))?;

        // Render. `Transform::identity()` places the SVG at (0,0) — which is
        // correct because usvg already resolved any viewBox / preserveAspectRatio.
        resvg::render(&tree, Transform::identity(), &mut pixmap.as_mut());

        // tiny-skia stores premultiplied RGBA; `take_demultiplied` reverses
        // that so downstream consumers see straight RGBA8.
        let pixels = pixmap.take_demultiplied();

        Ok(DecodedImage {
            width,
            height,
            pixels,
            format: "SVG",
        })
    }
}

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

    const RED_SVG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect width="10" height="10" fill="red"/></svg>"#;

    #[test]
    fn decodes_simple_svg() {
        let codec = SvgCodec;
        let decoded = codec.decode(RED_SVG.as_bytes()).unwrap();
        assert_eq!(decoded.width, 10);
        assert_eq!(decoded.height, 10);
        assert_eq!(decoded.format, "SVG");
        assert_eq!(decoded.pixels.len(), 10 * 10 * 4);

        // Top-left pixel should be red (#ff0000ff).
        assert_eq!(&decoded.pixels[0..4], &[255, 0, 0, 255]);
    }

    #[test]
    fn rejects_garbage_bytes() {
        // Not a valid SVG — usvg should reject it.
        let codec = SvgCodec;
        let result = codec.decode(&[0xff, 0xfe, 0xfd]);
        assert!(matches!(result, Err(DecodeError::Decode(_))));
    }
}
