/*
    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 proc_macro::{token_stream, Delimiter, Span, TokenStream, TokenTree};
use quote::quote;
use syn::{Fields, FieldsUnnamed, Ident, Variant};

struct Tag {
    id: u64,
    global: bool,
    path: Vec<u64>,
    name: Ident,
    r#type: Option<String>, // None -> Master
}

#[proc_macro]
pub fn define_ebml(ts: TokenStream) -> TokenStream {
    let mut ts = ts.into_iter();
    let mut tags = vec![];
    parse_kt(&mut tags, &mut ts, vec![]);

    let enum_variants = tags
        .iter()
        .map(|e| Variant {
            ident: e.name.clone(),
            attrs: vec![],
            fields: Fields::Unnamed(
                syn::parse2::<FieldsUnnamed>(match e.r#type.clone() {
                    None => quote!((Master)),
                    Some(r#type) => match r#type.as_str() {
                        "Int" => quote!((i64)),
                        "Uint" => quote!((u64)),
                        "Float" => quote!((f64)),
                        "Utf8" => quote!((String)),
                        "Binary" => quote!((Vec<u8>)),
                        "Block" => quote!((Block)),
                        _ => panic!("unsupported type {type}"),
                    },
                })
                .expect("parse type"),
            ),
            discriminant: None,
        })
        .collect::<Vec<_>>();

    let path_match = tags
        .iter()
        .map(|e| {
            let name = &e.name;
            let path = e.path.clone();
            if e.global {
                quote! { Self::#name(_) => None }
            } else {
                quote! { Self::#name(_) => Some(&[#(#path),*]) }
            }
        })
        .collect::<Vec<_>>();
    let id_match = tags
        .iter()
        .map(|Tag { id, name, .. }| {
            quote! { Self::#name(_) => #id }
        })
        .collect::<Vec<_>>();

    let parse_match = tags
        .iter()
        .filter_map(
            |Tag {
                 id, name, r#type, ..
             }| {
                if r#type.is_some() {
                    Some(quote! { #id => Self::#name(crate::ReadValue::from_buf(data)?) })
                } else {
                    None
                }
            },
        )
        .collect::<Vec<_>>();
    let write_match = tags
        .iter()
        .map(|Tag { name, .. }| quote! { Self::#name(v) => v.write_to(w) })
        .collect::<Vec<_>>();
    let size_match = tags
        .iter()
        .map(|Tag { name, .. }| quote! { Self::#name(v) => v.size() })
        .collect::<Vec<_>>();
    let cons_master_match = tags
        .iter()
        .filter_map(
            |Tag {
                 id, name, r#type, ..
             }| {
                if r#type.is_none() {
                    Some(quote! { #id => Self::#name(kind) })
                } else {
                    None
                }
            },
        )
        .collect::<Vec<_>>();
    let is_master_match = tags
        .iter()
        .map(|Tag { id, r#type, .. }| match r#type {
            None => quote!(#id => true),
            Some(_) => quote!(#id => false),
        })
        .collect::<Vec<_>>();

    quote! {
        use crate::Master;
        use crate::WriteValue;
        use crate::Block;

        #[derive(Debug, PartialEq, Clone)]
        pub enum MatroskaTag {
            #(#enum_variants),*
        }
        impl MatroskaTag {
            /// returns path in order top-to-immediate-parent order or None if global.
            pub fn path(&self) -> Option<&'static [u64]> {
                match self { #(#path_match),* }
            }
            pub fn id(&self) -> u64 {
                match self { #(#id_match),* }
            }
            pub fn is_master(id: u64) -> crate::Result<bool> {
                Ok(match id { #(#is_master_match),*, _ => return Err(crate::Error::UnknownID) })
            }
            pub fn construct_master(id: u64, kind: Master) -> crate::Result<Self> {
                Ok(match id { #(#cons_master_match),*, _ => return Err(crate::Error::UnknownID) })
            }
            pub fn parse(id: u64, data: &[u8]) -> crate::Result<Self> {
                Ok(match id { #(#parse_match),*, _ => return Err(crate::Error::UnknownID) })
            }
            pub fn write(&self, w: &mut impl std::io::Write) -> crate::Result<()> {
                match self { #(#write_match),* }
            }
            pub fn size(&self) -> usize {
                match self { #(#size_match),* }
            }
        }
    }
    .into()
}

fn parse_kt(tags: &mut Vec<Tag>, ts: &mut token_stream::IntoIter, path: Vec<u64>) {
    let mut next_glob = false;
    loop {
        let global = next_glob;
        next_glob = false;

        let name = if let Some(tt) = ts.next() {
            if let TokenTree::Ident(name) = tt {
                if &name.to_string() == "global" {
                    next_glob = true;
                    continue;
                }
                name.to_string()
            } else {
                panic!("expected ident")
            }
        } else {
            break;
        };

        let id = if let Some(TokenTree::Group(gr)) = ts.next() {
            assert_eq!(gr.delimiter(), Delimiter::Bracket);
            let mut ts = gr.stream().into_iter();
            if let TokenTree::Literal(lit) = ts.next().unwrap() {
                u64::from_str_radix(&lit.to_string()[2..], 16).unwrap()
            } else {
                panic!("literal expected")
            }
        } else {
            panic!("group expected")
        };
        if let Some(TokenTree::Punct(p)) = ts.next() {
            assert_eq!(p.as_char(), ':')
        } else {
            panic!("colon expected")
        }
        match ts.next() {
            Some(TokenTree::Group(gr)) => {
                // eprintln!("entering group");
                let mut ts = gr.stream().into_iter();
                tags.push(Tag {
                    global,
                    id,
                    name: Ident::new(&name, Span::call_site().into()),
                    path: path.clone(),
                    r#type: None,
                });
                let mut path = path.clone();
                path.push(id);
                parse_kt(tags, &mut ts, path);
                // eprintln!("leaving group");
            }
            Some(TokenTree::Ident(r#type)) => {
                let r#type = r#type.to_string();
                // eprintln!("global={global} id={id}, type={}", r#type);
                tags.push(Tag {
                    id,
                    name: Ident::new(&name, Span::call_site().into()),
                    path: path.clone(),
                    global,
                    r#type: Some(r#type),
                })
            }
            _ => panic!("group or ident expected"),
        }
        if let Some(TokenTree::Punct(p)) = ts.next() {
            assert_eq!(p.as_char(), ',')
        } else {
            panic!("colon expected")
        }
    }
}
