// WMF (16-bit Windows metafile) record player, plus the WMFC → EMF
// re-assembly used when a memory WMF is only a compatibility wrapper around
// an embedded enhanced metafile (the form Office writes into OlePres000).

use super::raster::{Canvas, Rgba};
use super::{canvas_for_box, colorref, dib, text, Brush, Dc, Font, Obj, Pen};

#[inline]
fn u16_at(d: &[u8], o: usize) -> u16 {
    u16::from_le_bytes(d[o..o + 2].try_into().unwrap())
}
#[inline]
fn i16_at(d: &[u8], o: usize) -> i16 {
    i16::from_le_bytes(d[o..o + 2].try_into().unwrap())
}
#[inline]
fn u32_at(d: &[u8], o: usize) -> u32 {
    u32::from_le_bytes(d[o..o + 4].try_into().unwrap())
}

const PLACEABLE_KEY: u32 = 0x9AC6_CDD7;

/// Strip a placeable header if present. Returns `(records_region, bbox)` where
/// the region starts at the standard WMF header and bbox is the placeable
/// bounds (logical units), if any.
fn strip_placeable(data: &[u8]) -> (&[u8], Option<(f32, f32, f32, f32)>) {
    if data.len() >= 22 && u32_at(data, 0) == PLACEABLE_KEY {
        let l = i16_at(data, 6) as f32;
        let t = i16_at(data, 8) as f32;
        let r = i16_at(data, 10) as f32;
        let b = i16_at(data, 12) as f32;
        (&data[22..], Some((l, t, r, b)))
    } else {
        (data, None)
    }
}

/// Validate the standard WMF header, returning the offset of the first record.
fn header_ok(d: &[u8]) -> Option<usize> {
    if d.len() < 18 {
        return None;
    }
    let ty = u16_at(d, 0);
    let hs = u16_at(d, 2) as usize;
    if (ty != 1 && ty != 2) || hs != 9 {
        return None;
    }
    Some(hs * 2)
}

/// Iterate records as `(function, params)` slices.
struct Records<'a> {
    d: &'a [u8],
    off: usize,
}

impl<'a> Iterator for Records<'a> {
    type Item = (u16, &'a [u8]);
    fn next(&mut self) -> Option<(u16, &'a [u8])> {
        if self.off + 6 > self.d.len() {
            return None;
        }
        let size_w = u32_at(self.d, self.off) as usize;
        let func = u16_at(self.d, self.off + 4);
        let size_b = size_w.checked_mul(2)?;
        if size_b < 6 || self.off + size_b > self.d.len() {
            return None;
        }
        let params = &self.d[self.off + 6..self.off + size_b];
        self.off += size_b;
        if func == 0 {
            return None; // META_EOF
        }
        Some((func, params))
    }
}

struct Player {
    dc: Dc,
    objects: Vec<Option<Obj>>,
}

impl Player {
    /// WMF object slots are assigned to the lowest free index.
    fn create(&mut self, obj: Obj) {
        if let Some(slot) = self.objects.iter_mut().find(|s| s.is_none()) {
            *slot = Some(obj);
        } else {
            self.objects.push(Some(obj));
        }
    }

    fn select(&mut self, idx: u16) {
        match self.objects.get(idx as usize).and_then(|o| o.clone()) {
            Some(Obj::Pen(p)) => self.dc.pen = p,
            Some(Obj::Brush(b)) => self.dc.brush = b,
            Some(Obj::Font(f)) => self.dc.font = f,
            _ => {}
        }
    }

    fn map(&self, pts: &[[f32; 2]]) -> Vec<[f32; 2]> {
        pts.iter().map(|p| self.dc.to_canvas(p[0], p[1])).collect()
    }

    fn fill_and_stroke(&mut self, canvas: &mut Canvas, rings: &[Vec<[f32; 2]>]) {
        let mapped: Vec<Vec<[f32; 2]>> = rings.iter().map(|r| self.map(r)).collect();
        if !self.dc.brush.null {
            canvas.fill_polys(&mapped, self.dc.brush.color);
        }
        if !self.dc.pen.null {
            let w = self.dc.pen_px();
            for r in &mapped {
                if r.len() >= 2 {
                    let mut closed = r.clone();
                    closed.push(r[0]);
                    canvas.stroke_polyline(&closed, w, self.dc.pen.color);
                }
            }
        }
    }
}

/// Blit a packed DIB from a blit record. `dest`/`src` rectangles are in
/// logical / source-pixel units respectively.
#[allow(clippy::too_many_arguments)]
fn blit_dib(
    canvas: &mut Canvas,
    dc: &Dc,
    dibdata: &[u8],
    xd: f32,
    yd: f32,
    wd: f32,
    hd: f32,
    sx: f32,
    sy: f32,
    sw: f32,
    sh: f32,
) {
    let Some((px, w, h)) = dib::decode_packed(dibdata) else {
        return;
    };
    let a = dc.to_canvas(xd, yd);
    let b = dc.to_canvas(xd + wd, yd + hd);
    let (sw, sh) = if sw <= 0.0 || sh <= 0.0 {
        (w as f32, h as f32)
    } else {
        (sw, sh)
    };
    canvas.blit(
        &px, w as usize, h as usize, sx, sy, sw, sh, a[0], a[1], b[0], b[1],
    );
}

pub fn render(data: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
    let (data, placeable_box) = strip_placeable(data);
    let first = header_ok(data)?;

    // Establish the logical picture box: the first window org/ext pair wins,
    // falling back to the placeable bounds.
    let mut org: Option<(f32, f32)> = None;
    let mut ext: Option<(f32, f32)> = None;
    for (func, p) in (Records {
        d: data,
        off: first,
    }) {
        match func {
            0x020B if p.len() >= 4 && org.is_none() => {
                org = Some((i16_at(p, 2) as f32, i16_at(p, 0) as f32));
            }
            0x020C if p.len() >= 4 && ext.is_none() => {
                ext = Some((i16_at(p, 2) as f32, i16_at(p, 0) as f32));
            }
            _ => {}
        }
        if org.is_some() && ext.is_some() {
            break;
        }
    }
    let (wx, wy, we, he) = match (org, ext, placeable_box) {
        (Some((x, y)), Some((w, h)), _) => (x, y, w, h),
        (_, _, Some((l, t, r, b))) => (l, t, r - l, b - t),
        _ => return None,
    };
    if we == 0.0 || he == 0.0 {
        return None;
    }

    let (mut canvas, _) = canvas_for_box(we, he)?;
    let mut p = Player {
        dc: Dc::new(),
        objects: Vec::new(),
    };
    // Window → viewport maps straight onto the canvas; the device→canvas
    // stage stays identity.
    p.dc.win_org = (wx, wy);
    p.dc.win_ext = (we, he);
    p.dc.vp_org = (0.0, 0.0);
    p.dc.vp_ext = (canvas.w as f32 * we.signum(), canvas.h as f32 * he.signum());
    if we < 0.0 {
        p.dc.vp_org.0 = canvas.w as f32;
    }
    if he < 0.0 {
        p.dc.vp_org.1 = canvas.h as f32;
    }

    for (func, prm) in (Records {
        d: data,
        off: first,
    }) {
        match func {
            0x020B if prm.len() >= 4 => {
                p.dc.win_org = (i16_at(prm, 2) as f32, i16_at(prm, 0) as f32)
            }
            0x020C if prm.len() >= 4 => {
                let (cx, cy) = (i16_at(prm, 2) as f32, i16_at(prm, 0) as f32);
                if cx != 0.0 && cy != 0.0 {
                    p.dc.win_ext = (cx, cy);
                }
            }
            0x0201 if prm.len() >= 4 => p.dc.bk_color = colorref(u32_at(prm, 0)),
            0x0102 if prm.len() >= 2 => p.dc.bk_mode = u16_at(prm, 0) as u32,
            0x0209 if prm.len() >= 4 => p.dc.text_color = colorref(u32_at(prm, 0)),
            0x012E if prm.len() >= 2 => p.dc.text_align = u16_at(prm, 0) as u32,
            0x001E => p.dc.save(&canvas),
            0x0127 => p.dc.restore(&mut canvas),
            0x0416 if prm.len() >= 8 => {
                let a = p.dc.to_canvas(i16_at(prm, 6) as f32, i16_at(prm, 4) as f32);
                let b = p.dc.to_canvas(i16_at(prm, 2) as f32, i16_at(prm, 0) as f32);
                let c = &mut canvas.clip;
                c.0 = c.0.max(a[0].min(b[0]));
                c.1 = c.1.max(a[1].min(b[1]));
                c.2 = c.2.min(a[0].max(b[0]));
                c.3 = c.3.min(a[1].max(b[1]));
            }
            0x0214 if prm.len() >= 4 => p.dc.pos = (i16_at(prm, 2) as f32, i16_at(prm, 0) as f32),
            0x0213 if prm.len() >= 4 => {
                let to = (i16_at(prm, 2) as f32, i16_at(prm, 0) as f32);
                if !p.dc.pen.null {
                    let seg = p.map(&[[p.dc.pos.0, p.dc.pos.1], [to.0, to.1]]);
                    let w = p.dc.pen_px();
                    canvas.stroke_polyline(&seg, w, p.dc.pen.color);
                }
                p.dc.pos = to;
            }
            0x0324 | 0x0325 if prm.len() >= 2 => {
                let n = u16_at(prm, 0) as usize;
                if n == 0 || 2 + n * 4 > prm.len() {
                    continue;
                }
                let pts: Vec<[f32; 2]> = (0..n)
                    .map(|i| [i16_at(prm, 2 + i * 4) as f32, i16_at(prm, 4 + i * 4) as f32])
                    .collect();
                if func == 0x0324 {
                    p.fill_and_stroke(&mut canvas, std::slice::from_ref(&pts));
                } else if !p.dc.pen.null {
                    let mapped = p.map(&pts);
                    let w = p.dc.pen_px();
                    canvas.stroke_polyline(&mapped, w, p.dc.pen.color);
                }
            }
            0x0538 if prm.len() >= 2 => {
                let n_polys = u16_at(prm, 0) as usize;
                if n_polys == 0 || 2 + n_polys * 2 > prm.len() {
                    continue;
                }
                let mut rings = Vec::with_capacity(n_polys);
                let mut pt_off = 2 + n_polys * 2;
                for i in 0..n_polys {
                    let c = u16_at(prm, 2 + i * 2) as usize;
                    if pt_off + c * 4 > prm.len() {
                        break;
                    }
                    rings.push(
                        (0..c)
                            .map(|j| {
                                [
                                    i16_at(prm, pt_off + j * 4) as f32,
                                    i16_at(prm, pt_off + 2 + j * 4) as f32,
                                ]
                            })
                            .collect::<Vec<_>>(),
                    );
                    pt_off += c * 4;
                }
                p.fill_and_stroke(&mut canvas, &rings);
            }
            0x041B | 0x0418 if prm.len() >= 8 => {
                let (b, r, t, l) = (
                    i16_at(prm, 0) as f32,
                    i16_at(prm, 2) as f32,
                    i16_at(prm, 4) as f32,
                    i16_at(prm, 6) as f32,
                );
                let ring: Vec<[f32; 2]> = if func == 0x041B {
                    vec![[l, t], [r, t], [r, b], [l, b]]
                } else {
                    let (cx, cy) = ((l + r) * 0.5, (t + b) * 0.5);
                    let (rx, ry) = ((r - l) * 0.5, (b - t) * 0.5);
                    (0..64)
                        .map(|i| {
                            let a = i as f32 / 64.0 * std::f32::consts::TAU;
                            [cx + rx * a.cos(), cy + ry * a.sin()]
                        })
                        .collect()
                };
                p.fill_and_stroke(&mut canvas, std::slice::from_ref(&ring));
            }
            0x061D if prm.len() >= 12 => {
                // META_PATBLT: brush fill of a rectangle.
                let rop = u32_at(prm, 0);
                let (h, w, y, x) = (
                    i16_at(prm, 4) as f32,
                    i16_at(prm, 6) as f32,
                    i16_at(prm, 8) as f32,
                    i16_at(prm, 10) as f32,
                );
                let color = match rop {
                    0x00F0_0021 | 0x005A_0049 => Some(p.dc.brush.color),
                    0x0000_0042 => Some(Rgba([0, 0, 0, 255])),
                    0x00FF_0062 => Some(Rgba([255, 255, 255, 255])),
                    _ => None,
                };
                if let Some(c) = color {
                    let a = p.dc.to_canvas(x, y);
                    let b = p.dc.to_canvas(x + w, y + h);
                    canvas.fill_rect(
                        a[0].min(b[0]),
                        a[1].min(b[1]),
                        a[0].max(b[0]),
                        a[1].max(b[1]),
                        c,
                    );
                }
            }
            0x02FA if prm.len() >= 10 => {
                let style = u16_at(prm, 0);
                p.create(Obj::Pen(Pen {
                    color: colorref(u32_at(prm, 6)),
                    width: i16_at(prm, 2) as f32,
                    null: style & 0xF == 5,
                }));
            }
            0x02FC if prm.len() >= 6 => {
                let style = u16_at(prm, 0);
                p.create(Obj::Brush(Brush {
                    color: colorref(u32_at(prm, 2)),
                    null: style == 1,
                }));
            }
            0x02FB if prm.len() >= 18 => {
                let mut name = String::new();
                for &b in prm[18..].iter().take(32) {
                    if b == 0 {
                        break;
                    }
                    name.push(b as char);
                }
                p.create(Obj::Font(Font {
                    height: i16_at(prm, 0) as f32,
                    escapement: i16_at(prm, 4) as f32,
                    facename: name,
                    charset: prm[13],
                }));
            }
            // Other object-creating records occupy a slot even when unused.
            0x00F7 | 0x01F9 | 0x0142 | 0x06FF => p.create(Obj::Other),
            0x012D if prm.len() >= 2 => p.select(u16_at(prm, 0)),
            0x01F0 if prm.len() >= 2 => {
                let i = u16_at(prm, 0) as usize;
                if i < p.objects.len() {
                    p.objects[i] = None;
                }
            }
            0x0521 if prm.len() >= 2 => {
                let n = u16_at(prm, 0) as usize;
                let str_end = 2 + n;
                let padded = str_end + (str_end & 1);
                if n == 0 || padded + 4 > prm.len() {
                    continue;
                }
                let s = text::decode_ansi(&prm[2..str_end], &p.dc.font);
                let y = i16_at(prm, padded) as f32;
                let x = i16_at(prm, padded + 2) as f32;
                text::draw_text(&mut canvas, &mut p.dc, x, y, &s, None, None);
            }
            0x0A32 if prm.len() >= 8 => {
                let y = i16_at(prm, 0) as f32;
                let x = i16_at(prm, 2) as f32;
                let n = u16_at(prm, 4) as usize;
                let opts = u16_at(prm, 6);
                let mut off = 8;
                let mut opaque = None;
                if opts & 0x0006 != 0 && prm.len() >= off + 8 {
                    let rect = [
                        i16_at(prm, off) as f32,
                        i16_at(prm, off + 2) as f32,
                        i16_at(prm, off + 4) as f32,
                        i16_at(prm, off + 6) as f32,
                    ];
                    if opts & 0x0002 != 0 {
                        opaque = Some(rect);
                    }
                    off += 8;
                }
                if n == 0 || off + n > prm.len() {
                    continue;
                }
                let s = text::decode_ansi(&prm[off..off + n], &p.dc.font);
                let str_pad = n + (n & 1);
                let dx_off = off + str_pad;
                let dx: Option<Vec<f32>> = if dx_off + n * 2 <= prm.len() {
                    Some((0..n).map(|i| i16_at(prm, dx_off + i * 2) as f32).collect())
                } else {
                    None
                };
                text::draw_text(&mut canvas, &mut p.dc, x, y, &s, dx.as_deref(), opaque);
            }
            0x0F43 if prm.len() >= 22 => {
                let (sh, sw, sy, sx) = (
                    i16_at(prm, 6) as f32,
                    i16_at(prm, 8) as f32,
                    i16_at(prm, 10) as f32,
                    i16_at(prm, 12) as f32,
                );
                let (dh, dw, dy, dx) = (
                    i16_at(prm, 14) as f32,
                    i16_at(prm, 16) as f32,
                    i16_at(prm, 18) as f32,
                    i16_at(prm, 20) as f32,
                );
                blit_dib(
                    &mut canvas,
                    &p.dc,
                    &prm[22..],
                    dx,
                    dy,
                    dw,
                    dh,
                    sx,
                    sy,
                    sw,
                    sh,
                );
            }
            0x0B41 if prm.len() >= 20 => {
                let (sh, sw, sy, sx) = (
                    i16_at(prm, 4) as f32,
                    i16_at(prm, 6) as f32,
                    i16_at(prm, 8) as f32,
                    i16_at(prm, 10) as f32,
                );
                let (dh, dw, dy, dx) = (
                    i16_at(prm, 12) as f32,
                    i16_at(prm, 14) as f32,
                    i16_at(prm, 16) as f32,
                    i16_at(prm, 18) as f32,
                );
                blit_dib(
                    &mut canvas,
                    &p.dc,
                    &prm[20..],
                    dx,
                    dy,
                    dw,
                    dh,
                    sx,
                    sy,
                    sw,
                    sh,
                );
            }
            0x0940 if prm.len() >= 16 => {
                let (sy, sx) = (i16_at(prm, 4) as f32, i16_at(prm, 6) as f32);
                let (dh, dw, dy, dx) = (
                    i16_at(prm, 8) as f32,
                    i16_at(prm, 10) as f32,
                    i16_at(prm, 12) as f32,
                    i16_at(prm, 14) as f32,
                );
                blit_dib(
                    &mut canvas,
                    &p.dc,
                    &prm[16..],
                    dx,
                    dy,
                    dw,
                    dh,
                    sx,
                    sy,
                    dw,
                    dh,
                );
            }
            _ => {}
        }
    }

    let (px, w, h) = canvas.downsample(super::SS);
    Some((px, w, h))
}
