/*
    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>
*/
#![feature(iter_array_chunks, strip_circumfix)]

mod buffer;
pub mod debug;
#[cfg(feature = "json")]
pub mod json;
mod path;
mod registry;
#[cfg(test)]
mod tests;
mod value;

pub use buffer::*;
pub use path::*;
pub use registry::*;
pub use value::*;

use std::{collections::BTreeSet, fmt::Display, hash::Hash, marker::PhantomData};

#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tag(pub u32);

impl Default for Tag {
    fn default() -> Self {
        Self::new(b"1111")
    }
}
impl Tag {
    pub const fn new(fourcc: &[u8; 4]) -> Self {
        Self(u32::from_le_bytes(*fourcc))
    }
}
impl Display for Tag {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(str::from_utf8(&self.0.to_le_bytes()).unwrap())
    }
}

#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct TypedTag<T>(pub Tag, pub PhantomData<T>);
impl<T> Display for TypedTag<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl<T> Clone for TypedTag<T> {
    fn clone(&self) -> Self {
        Self(self.0, PhantomData)
    }
}
impl<T> Copy for TypedTag<T> {}

#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct Object<'a> {
    tags: &'a [u32],
    offsets: &'a [u32],
    values: &'a [u32],
}

impl<'a> Default for Object<'a> {
    fn default() -> Self {
        Self::EMPTY
    }
}
impl<'a> Object<'a> {
    pub const EMPTY: Object<'static> = Object {
        offsets: &[],
        tags: &[],
        values: &[],
    };

    pub fn load(buf: &'a [u32]) -> Option<Self> {
        let nf = *buf.get(0)? as usize;
        if buf.len() < 1 + nf * 2 {
            return None;
        }
        Some(Self {
            tags: &buf[1..1 + nf],
            offsets: &buf[1 + nf..1 + nf + nf],
            values: &buf[1 + nf + nf..],
        })
    }
    pub fn dump(&self) -> ObjectBuffer {
        let mut out = Vec::new();
        out.push(self.tags.len() as u32);
        out.extend(self.tags);
        out.extend(self.offsets);
        out.extend(self.values);
        ObjectBuffer(out)
    }
    pub fn find_field(&self, tag: Tag) -> Option<usize> {
        // using partition as binary search for the first field (instead of regular binary_search that returns any)
        let first = self.tags.partition_point(|&x| x < tag.0);
        self.tags
            .get(first)
            .is_some_and(|&x| x == tag.0)
            .then_some(first)
    }
    pub fn has(&self, tag: Tag) -> bool {
        self.find_field(tag).is_some()
    }
    #[inline]
    fn offset(&self, i: usize) -> usize {
        self.offsets
            .get(i)
            .map(|&v| v >> 5)
            .unwrap_or(self.values.len() as u32) as usize
    }
    fn offset_type(&self, i: usize) -> ValueType {
        let raw = (self.offsets[i] >> 2) & 0b111;
        ValueType::from_num(raw)
    }
    fn size(&self, i: usize) -> u32 {
        let start_raw = self.offsets[i];
        let end_raw = self
            .offsets
            .get(i + 1)
            .copied()
            .unwrap_or((self.values.len() as u32) << 5);

        let u32_len = (end_raw >> 5) - (start_raw >> 5);
        let padding = start_raw & 0b11;
        u32_len * 4 - padding
    }
    fn get_aligned(&self, index: usize) -> Option<&'a [u32]> {
        let start_raw = self.offsets[index];
        let end_raw = self
            .offsets
            .get(index + 1)
            .copied()
            .unwrap_or((self.values.len() as u32) << 5);

        let start = start_raw >> 5;
        let end = end_raw >> 5;

        Some(&self.values[start as usize..end as usize])
    }

    fn get_unaligned(&self, index: usize) -> Option<&'a [u8]> {
        let start_raw = self.offsets[index];
        let end_raw = self
            .offsets
            .get(index + 1)
            .copied()
            .unwrap_or((self.values.len() as u32) << 5);

        let start = (start_raw >> 5) * 4;
        let padding = start_raw & 0b11;
        let end = (end_raw >> 5) * 4 - padding;

        let values_u8: &[u8] = bytemuck::cast_slice(self.values);
        Some(&values_u8[start as usize..end as usize])
    }
    #[inline]
    pub fn get_typed<T: ValueLoad<'a>>(&self, index: usize) -> Option<T> {
        if T::TYPE.is_aligned() {
            T::load_aligned(self.get_aligned(index)?)
        } else {
            T::load_unaligned(self.get_unaligned(index)?)
        }
    }
    pub fn get<T: ValueLoad<'a>>(&self, tag: TypedTag<T>) -> Option<T> {
        self.get_typed(self.find_field(tag.0)?)
    }
    pub fn keys(&self) -> KeysIter<'a> {
        KeysIter {
            object: *self,
            index: 0,
        }
    }
    pub fn entries<T>(&self) -> EntriesIter<'a, T> {
        EntriesIter {
            object: *self,
            index: 0,
            ty: PhantomData,
        }
    }
    pub fn iter<T>(&self, tag: TypedTag<T>) -> FieldIter<'a, T> {
        FieldIter {
            object: *self,
            index: self.tags.partition_point(|&x| x < tag.0.0),
            tag: tag.0.0,
            ty: PhantomData,
        }
    }
    #[must_use]
    pub fn extend<T: ValueLoad<'a> + Eq + Ord>(
        &self,
        tag: TypedTag<T>,
        values: impl IntoIterator<Item = T>,
    ) -> ObjectBuffer {
        self.insert_multi(
            tag,
            &self
                .iter(tag)
                .chain(values)
                .collect::<BTreeSet<_>>()
                .into_iter()
                .collect::<Vec<_>>(),
        )
    }
    #[must_use]
    pub fn extend_object(
        &self,
        tag: TypedTag<Object<'static>>,
        ident: Tag,
        values: impl IntoIterator<Item = ObjectBuffer>,
    ) -> ObjectBuffer {
        let ident = TypedTag(ident, PhantomData::<&[u8]>);
        let mut new_vals = Vec::new();
        for ob in self.iter(tag) {
            new_vals.push(ob.dump());
        }
        let mut any_new = false;
        for val in values {
            if new_vals
                .iter()
                .all(|rhs| rhs.as_object().get(ident) != val.as_object().get(ident))
            {
                any_new = true;
                new_vals.push(val);
            }
        }
        if any_new {
            self.insert_multi(TypedTag(tag.0, PhantomData), &new_vals)
        } else {
            self.dump()
        }
    }
    #[must_use]
    #[inline]
    pub fn insert<T: ValueStore>(&self, tag: TypedTag<T>, value: T) -> ObjectBuffer {
        self.insert_multi(tag, &[value])
    }
    #[must_use]
    pub fn insert_multi<T: ValueStore>(&self, tag: TypedTag<T>, values: &[T]) -> ObjectBuffer {
        let prefix = self.tags.partition_point(|&x| x < tag.0.0);
        let suffix = self.tags.partition_point(|&x| x <= tag.0.0);
        let values_prefix = self.offset(prefix);
        let values_suffix = self.offset(suffix);

        let mut buf = Vec::new();
        let cut_size = suffix - prefix;
        buf.push((self.tags.len() - cut_size + values.len()) as u32);

        buf.extend(&self.tags[..prefix]);
        buf.extend(values.iter().map(|_| tag.0.0));
        buf.extend(&self.tags[suffix..]);

        buf.extend(&self.offsets[..prefix]);
        let new_offs = buf.len();
        buf.extend(values.iter().map(|_| 0)); // placeholder
        let suffix_offs = buf.len();
        buf.extend(&self.offsets[suffix..]); // need offsetting later
        let suffix_end = buf.len();

        let values_start = buf.len() as u32;
        buf.extend(&self.values[..values_prefix]);
        let mut temp = Vec::new();
        let values_new = buf.len();
        for (i, val) in values.iter().enumerate() {
            let ty = val.get_type();
            let off = (buf.len() as u32 - values_start) << 5 | (ty as u32) << 2;
            if ty.is_aligned() {
                buf[new_offs + i] = off;
                val.store_aligned(&mut buf);
            } else {
                temp.clear();
                val.store_unaligned(&mut temp);
                let pad = pad_vec(&mut temp);
                buf[new_offs + i] = off | pad;
                buf.extend(&*slice_u8_to_u32(&temp));
            }
        }
        let values_insert_size = buf.len() - values_new;
        let values_cut_size = values_suffix - values_prefix;
        let suffix_offset = (values_insert_size as i32 - values_cut_size as i32) << 5;
        if suffix_offset != 0 {
            buf[suffix_offs..suffix_end]
                .iter_mut()
                .for_each(|e| *e = e.strict_add_signed(suffix_offset));
        }
        buf.extend(&self.values[values_suffix..]);

        ObjectBuffer(buf)
    }

    #[must_use]
    pub fn update(
        &self,
        tag: TypedTag<Object<'static>>,
        update: impl FnOnce(Object<'a>) -> ObjectBuffer,
    ) -> ObjectBuffer {
        self.insert(tag, update(self.get(tag).unwrap_or_default()).as_object())
    }
}

pub struct KeysIter<'a> {
    object: Object<'a>,
    index: usize,
}
impl Iterator for KeysIter<'_> {
    type Item = Tag;
    fn next(&mut self) -> Option<Self::Item> {
        if self.index >= self.object.tags.len() {
            return None;
        } else {
            self.index += 1;
            Some(Tag(self.object.tags[self.index - 1]))
        }
    }
}

pub struct EntriesIter<'a, T> {
    object: Object<'a>,
    index: usize,
    ty: PhantomData<T>,
}
impl<'a, T: ValueLoad<'a>> Iterator for EntriesIter<'a, T> {
    type Item = (Tag, T);
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.index >= self.object.tags.len() {
                return None;
            }
            if T::TYPE != self.object.offset_type(self.index) {
                self.index += 1;
                continue;
            }
            let value = self.object.get_typed(self.index)?;
            let tag = self.object.tags[self.index];
            self.index += 1;
            return Some((Tag(tag), value));
        }
    }
}

pub struct FieldIter<'a, T> {
    object: Object<'a>,
    index: usize,
    tag: u32,
    ty: PhantomData<T>,
}
impl<'a, T: ValueLoad<'a>> Iterator for FieldIter<'a, T> {
    type Item = T;
    fn next(&mut self) -> Option<Self::Item> {
        if self.index >= self.object.tags.len() {
            return None;
        }
        if self.object.tags[self.index] != self.tag {
            return None;
        }
        let val = self.object.get_typed(self.index);
        self.index += 1;
        val
    }
}
