use std::fmt::Display;

/*
    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::{
    MultiBehaviour, Sort,
    kv::{
        T_INDICES,
        binning::{Binning, BinningComponent},
    },
};
use jellyobject::{Path, Tag};

#[derive(Debug, PartialEq, Eq)]
pub struct IndexKey(pub Binning, pub SortKey);

#[derive(Debug, Hash, PartialEq, Eq)]
pub enum SortKey {
    None,
    Count,
    Random,
    Value(Path, MultiBehaviour),
    Text(Path),
}

impl Sort {
    pub fn key(&self) -> SortKey {
        match self {
            Sort::None => SortKey::None,
            Sort::Random(_) => SortKey::Random,
            Sort::Value(vs) => SortKey::Value(vs.path.clone(), vs.multi),
            Sort::TextSearch(p, _) => SortKey::Text(p.to_owned()),
        }
    }
}

impl IndexKey {
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut out = Vec::new();
        out.extend(T_INDICES.to_be_bytes());
        self.0.write(&mut out);
        self.1.write(&mut out);
        out
    }
    pub fn from_bytes(mut b: &[u8]) -> Self {
        b = &b[4..]; // remove subtree prefix
        let binning = Binning::read(&mut b);
        let sort = SortKey::read(&mut b);
        assert!(b.is_empty(), "index key deser left-over bytes");
        Self(binning, sort)
    }
}
impl Binning {
    fn read(b: &mut &[u8]) -> Self {
        let len = b[0];
        *b = &b[1..];
        let mut o = Vec::new();
        for _ in 0..len {
            let ty = b[0];
            *b = &b[1..];
            o.push(match ty {
                0 => BinningComponent::Has(read_path(b)),
                1 => BinningComponent::Match(read_path(b)),
                _ => unreachable!(),
            });
        }
        Self(o)
    }
    fn write(&self, out: &mut Vec<u8>) {
        assert!(out.len() < 256);
        out.push(self.0.len() as u8);
        for b in &self.0 {
            match b {
                BinningComponent::Has(path) => {
                    out.push(0);
                    write_path(path, out);
                }
                BinningComponent::Match(path) => {
                    out.push(1);
                    write_path(path, out);
                }
            }
        }
    }
}
impl SortKey {
    fn read(b: &mut &[u8]) -> Self {
        let ty = b[0];
        *b = &b[1..];
        match ty {
            0 => SortKey::None,
            4 => SortKey::Random,
            1 => SortKey::Count,
            2 => SortKey::Value(read_path(b), {
                let mb = b[0];
                *b = &b[1..];
                match mb {
                    0 => MultiBehaviour::First,
                    1 => MultiBehaviour::ForEach,
                    2 => MultiBehaviour::Max,
                    3 => MultiBehaviour::Min,
                    4 => MultiBehaviour::Count,
                    _ => unreachable!(),
                }
            }),
            3 => SortKey::Text(read_path(b)),
            _ => unreachable!(),
        }
    }
    fn write(&self, out: &mut Vec<u8>) {
        match self {
            SortKey::None => out.push(0),
            SortKey::Random => out.push(4),
            SortKey::Count => out.push(1),
            SortKey::Value(path, multi_behaviour) => {
                out.push(2);
                write_path(path, out);
                out.push(match multi_behaviour {
                    MultiBehaviour::First => 0,
                    MultiBehaviour::ForEach => 1,
                    MultiBehaviour::Max => 2,
                    MultiBehaviour::Min => 3,
                    MultiBehaviour::Count => 4,
                });
            }
            SortKey::Text(path) => {
                out.push(3);
                write_path(path, out);
            }
        }
    }
}

impl Display for IndexKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} | {}", self.0, self.1)
    }
}
impl Display for Binning {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (i, b) in self.0.iter().enumerate() {
            if i > 0 {
                write!(f, " ")?;
            }
            write!(f, "{b}")?;
        }
        Ok(())
    }
}
impl Display for BinningComponent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BinningComponent::Has(path) => write!(f, "H({path})"),
            BinningComponent::Match(path) => write!(f, "M({path})"),
        }
    }
}
impl Display for SortKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SortKey::None => write!(f, "none"),
            SortKey::Random => write!(f, "random"),
            SortKey::Count => write!(f, "count"),
            SortKey::Value(path, multi) => write!(f, "value({path}, {multi})"),
            SortKey::Text(path) => write!(f, "text({path})"),
        }
    }
}
impl Display for MultiBehaviour {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            MultiBehaviour::First => "first",
            MultiBehaviour::ForEach => "for_each",
            MultiBehaviour::Max => "max",
            MultiBehaviour::Min => "min",
            MultiBehaviour::Count => "count",
        })
    }
}

fn write_path(path: &Path, out: &mut Vec<u8>) {
    assert!(path.0.len() < 256);
    out.push(path.0.len() as u8);
    for c in &path.0 {
        out.extend(c.0.to_be_bytes());
    }
}
fn read_path(b: &mut &[u8]) -> Path {
    let len = b[0];
    *b = &b[1..];
    let mut o = Vec::new();
    for _ in 0..len {
        o.push(Tag(u32::from_be_bytes([b[0], b[1], b[2], b[3]])));
        *b = &b[4..];
    }
    Path(o)
}

#[cfg(test)]
mod test {
    use jellyobject::{Path, Tag};

    use crate::kv::{
        binning::{Binning, BinningComponent},
        index_key::{IndexKey, SortKey},
    };

    #[test]
    fn serialize() {
        let t = |k: IndexKey| {
            let b = k.to_bytes();
            let k2 = IndexKey::from_bytes(&b);
            assert_eq!(k, k2);
        };

        t(IndexKey(
            Binning(vec![BinningComponent::Match(Path(vec![Tag(1001)]))]),
            SortKey::None,
        ));
        t(IndexKey(
            Binning(vec![BinningComponent::Has(Path(vec![Tag(123), Tag(456)]))]),
            SortKey::Text(Path(vec![Tag(789)])),
        ));
    }
}
