use syn::punctuated::Punctuated;
use syn::{token, ExprPath, PathSegment};
use typst::model::Content;
use typst_library::layout::{EnumElem, EnumItem, ListElem, ListItem, ParbreakElem, TableElem};
use typst_library::meta::HeadingElem;
use typst_library::text::{SpaceElem, TextElem};

use crate::literal_string;

#[derive(Debug)]
pub enum SupportedContent<'a> {
    Sequence(Vec<SupportedContent<'a>>),
    Heading(&'a HeadingElem),
    Text(&'a TextElem),
    Space(&'a SpaceElem),
    Table(&'a TableElem),
    UnorderedList(&'a ListElem),
    UnorderedListItem(&'a ListItem),
    ParagraphBreak(&'a ParbreakElem),
    OrderedList(&'a EnumElem),
    OrderedListItem(&'a EnumItem),
}

impl<'a> SupportedContent<'a> {
    pub fn downcast(value: &'a Content) -> Self {
        if let Some(sequence) = value.to_sequence() {
            let children = sequence
                .map(|child| SupportedContent::downcast(child))
                .collect::<Vec<_>>();
            Self::Sequence(children)
        } else if let Some(heading) = value.to::<HeadingElem>() {
            Self::Heading(heading)
        } else if let Some(text) = value.to::<TextElem>() {
            Self::Text(text)
        } else if let Some(space) = value.to::<SpaceElem>() {
            Self::Space(space)
        } else if let Some(table) = value.to::<TableElem>() {
            Self::Table(table)
        } else if let Some(list) = value.to::<ListElem>() {
            Self::UnorderedList(list)
        } else if let Some(list) = value.to::<EnumElem>() {
            Self::OrderedList(list)
        } else if let Some(item) = value.to::<ListItem>() {
            Self::UnorderedListItem(item)
        } else if let Some(item) = value.to::<EnumItem>() {
            Self::OrderedListItem(item)
        } else if let Some(pragraph_break) = value.to::<ParbreakElem>() {
            Self::ParagraphBreak(pragraph_break)
        } else {
            dbg!(value);
            todo!()
        }
    }

    pub fn to_xilem(&self) -> Box<dyn Iterator<Item = syn::Expr> + '_> {
        match self {
            SupportedContent::Sequence(sequence) => {
                Box::new(sequence.iter().map(SupportedContent::to_xilem).flatten())
            }
            SupportedContent::Heading(heading) => {
                let body_text = Self::downcast(heading.body());
                Box::new(Some(xilem_html_element("h1", body_text.to_xilem())).into_iter())
            }
            SupportedContent::Text(text) => {
                Box::new(Some(syn::Expr::Lit(literal_string(text.text().as_str()))).into_iter())
            }
            SupportedContent::Space(_space) => {
                Box::new(Some(syn::Expr::Lit(literal_string(" "))).into_iter())
            }
            SupportedContent::Table(table) => Box::new(
                Some(xilem_html_element(
                    "table",
                    Some(syn::Expr::Tuple(syn::ExprTuple {
                        attrs: Vec::new(),
                        paren_token: token::Paren::default(),
                        elems: Punctuated::from_iter(
                            table
                                .children()
                                .into_iter()
                                .map(|content| SupportedContent::downcast(content))
                                .map(|supported| supported.to_xilem().collect::<Vec<_>>())
                                .flatten()
                                .collect::<Vec<_>>()
                                .chunks(2) // TODO: a slightly more sophisticated layout algorithm
                                .map(|chunk| {
                                    xilem_html_element(
                                        "tr",
                                        vec![syn::Expr::Tuple(syn::ExprTuple {
                                            attrs: Vec::new(),
                                            paren_token: token::Paren::default(),
                                            elems: Punctuated::from_iter(
                                                chunk.into_iter().map(|item| item.to_owned()),
                                            ),
                                        })],
                                    )
                                }),
                        ),
                    })),
                ))
                .into_iter(),
            ),
            SupportedContent::UnorderedList(list) => Box::new(
                Some(xilem_html_element(
                    "ul",
                    vec![syn::Expr::Tuple(syn::ExprTuple {
                        attrs: Vec::new(),
                        paren_token: token::Paren::default(),
                        elems: Punctuated::from_iter(
                            list.children()
                                .into_iter()
                                .map(|content| SupportedContent::downcast(content.body()))
                                .map(|supported| supported.to_xilem().collect::<Vec<_>>())
                                .flatten(),
                        ),
                    })],
                ))
                .into_iter(),
            ),
            SupportedContent::OrderedList(list) => Box::new(
                Some(xilem_html_element(
                    "ol",
                    vec![syn::Expr::Tuple(syn::ExprTuple {
                        attrs: Vec::new(),
                        paren_token: token::Paren::default(),
                        elems: Punctuated::from_iter(
                            list.children()
                                .into_iter()
                                .map(|content| SupportedContent::downcast(content.body()))
                                .map(|supported| supported.to_xilem().collect::<Vec<_>>())
                                .flatten(),
                        ),
                    })],
                ))
                .into_iter(),
            ),
            SupportedContent::UnorderedListItem(item) => Box::new(
                Some(xilem_html_element(
                    "ul",
                    vec![xilem_html_element(
                        "li",
                        vec![syn::Expr::Tuple(syn::ExprTuple {
                            attrs: Vec::new(),
                            paren_token: token::Paren::default(),
                            elems: Punctuated::from_iter(
                                SupportedContent::downcast(item.body()).to_xilem(),
                            ),
                        })],
                    )],
                ))
                .into_iter(),
            ),
            SupportedContent::OrderedListItem(item) => Box::new(
                Some(xilem_html_element(
                    "ol",
                    vec![xilem_html_element(
                        "li",
                        vec![syn::Expr::Tuple(syn::ExprTuple {
                            attrs: Vec::new(),
                            paren_token: token::Paren::default(),
                            elems: Punctuated::from_iter(
                                SupportedContent::downcast(item.body()).to_xilem(),
                            ),
                        })],
                    )],
                ))
                .into_iter(),
            ),
            SupportedContent::ParagraphBreak(_parahraph_break) => Box::new(
                Some(xilem_html_element(
                    "br",
                    vec![syn::Expr::Tuple(syn::ExprTuple {
                        attrs: Vec::new(),
                        paren_token: token::Paren::default(),
                        elems: Punctuated::new(),
                    })],
                ))
                .into_iter(),
            ),
        }
    }
}

fn path_segment(segment: &str) -> syn::PathSegment {
    syn::PathSegment {
        ident: syn::Ident::new(segment, proc_macro2::Span::call_site()),
        arguments: syn::PathArguments::None,
    }
}

fn expr_path(name: &str) -> syn::Expr {
    let mut segments: Punctuated<PathSegment, token::PathSep> = Punctuated::new();

    // The path should be elements::name
    // e.g. elements::h2
    segments.push(path_segment("elements"));
    segments.push(path_segment(name));

    let expr_path = ExprPath {
        attrs: Vec::new(),
        qself: None,
        path: syn::Path {
            leading_colon: None,
            segments,
        },
    };

    syn::Expr::Path(expr_path)
}

fn xilem_html_element(name: &str, expressions: impl IntoIterator<Item = syn::Expr>) -> syn::Expr {
    syn::Expr::Call(syn::ExprCall {
        attrs: Vec::new(),
        func: Box::new(expr_path(name)),
        paren_token: token::Paren::default(),
        args: Punctuated::from_iter(expressions.into_iter()),
    })
}