/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/

use crate::{Object, Tag, TypedTag, ValueMarker, value::ValueSer};
use bytemuck::{try_cast_slice, try_cast_vec};
use core::marker::Sized;
use log::trace;
use std::borrow::Cow;

pub type OBB = ObjectBufferBuilder;

#[derive(Default)]
pub struct ObjectBufferBuilder(Vec<(Tag, u32, Vec<u32>)>);
impl ObjectBufferBuilder {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn push<'a, T: ValueMarker<'a> + ?Sized>(&mut self, tag: TypedTag<T>, value: T::Inner) {
        let tyb = (T::Inner::TYPE as u32) << 2;
        if T::Inner::TYPE.is_aligned() {
            let mut buf = Vec::new();
            value.store_aligned(&mut buf);
            self.0.push((tag.0, tyb, buf));
        } else {
            let mut buf = Vec::new();
            value.store_unaligned(&mut buf);
            let pad = pad_vec(&mut buf);
            self.0.push((tag.0, tyb | pad, vec_u8_to_u32(buf)));
        }
    }
    pub fn with<'a, T: ValueMarker<'a> + ?Sized>(
        mut self,
        tag: TypedTag<T>,
        value: T::Inner,
    ) -> Self {
        self.push(tag, value);
        self
    }
    pub fn finish(mut self) -> Box<Object> {
        let mut tags = Vec::new();
        let mut offsets = Vec::new();
        let mut values = Vec::new();
        self.0.sort_by_key(|(t, _, _)| t.0);
        for (tag, mut off, buf) in self.0 {
            tags.push(tag.0);
            off |= (values.len() as u32) << 5;
            offsets.push(off);
            values.extend(buf);
        }
        vec_to_ob(
            [tags.len() as u32]
                .into_iter()
                .chain(tags)
                .chain(offsets)
                .chain(values)
                .collect(),
        )
    }
}

pub fn vec_to_ob(v: Vec<u32>) -> Box<Object> {
    //? safe way to do this?
    unsafe { std::mem::transmute(v.into_boxed_slice()) }
}
pub const fn slice_to_ob<'a>(v: &'a [u32]) -> &'a Object {
    //? safe way to do this?
    unsafe { std::mem::transmute(v) }
}

#[inline]
pub(super) fn pad_vec(temp: &mut Vec<u8>) -> u32 {
    let mut pad = 0;
    while temp.len() % 4 != 0 {
        pad += 1;
        temp.push(0);
    }
    pad
}

pub fn vec_u8_to_u32(value: Vec<u8>) -> Vec<u32> {
    try_cast_vec(value).unwrap_or_else(|(_, v)| {
        trace!("encountered unalined vec");
        v.into_iter()
            .array_chunks()
            .map(u32::from_ne_bytes)
            .collect()
    })
}
pub fn slice_u8_to_u32<'a>(value: &'a [u8]) -> Cow<'a, [u32]> {
    try_cast_slice(value)
        .map(Cow::Borrowed)
        .unwrap_or_else(|_| {
            trace!("encountered unalined slice");
            Cow::Owned(
                value
                    .into_iter()
                    .copied()
                    .array_chunks()
                    .map(u32::from_ne_bytes)
                    .collect(),
            )
        })
}
