use boolinator::Boolinator;
use proc_macro2::TokenStream;
use quote::{quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{Expr, Token};

use super::{HtmlRootBraced, ToNodeIterator};
use crate::PeekValue;

pub struct HtmlIf {
    if_token: Token![if],
    cond: Box<Expr>,
    then_branch: HtmlRootBraced,
    else_branch: Option<(Token![else], Box<HtmlRootBracedOrIf>)>,
}

impl PeekValue<()> for HtmlIf {
    fn peek(cursor: Cursor) -> Option<()> {
        let (ident, _) = cursor.ident()?;
        (ident == "if").as_option()
    }
}

impl Parse for HtmlIf {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let if_token = input.parse()?;
        let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
        match &*cond {
            Expr::Block(syn::ExprBlock { block, .. }) if block.stmts.is_empty() => {
                return Err(syn::Error::new(
                    cond.span(),
                    "missing condition for `if` expression",
                ))
            }
            _ => {}
        }
        if input.is_empty() {
            return Err(syn::Error::new(
                cond.span(),
                "this `if` expression has a condition, but no block",
            ));
        }

        let then_branch = input.parse()?;
        let else_branch = input
            .parse::<Token![else]>()
            .ok()
            .map(|else_token| {
                if input.is_empty() {
                    return Err(syn::Error::new(
                        else_token.span(),
                        "expected block or `if` after `else`",
                    ));
                }

                input.parse().map(|branch| (else_token, branch))
            })
            .transpose()?;

        Ok(HtmlIf {
            if_token,
            cond,
            then_branch,
            else_branch,
        })
    }
}

impl ToTokens for HtmlIf {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let HtmlIf {
            if_token,
            cond,
            then_branch,
            else_branch,
        } = self;
        let default_else_branch = syn::parse_quote! { {} };
        let else_branch = else_branch
            .as_ref()
            .map(|(_, branch)| branch)
            .unwrap_or(&default_else_branch);
        let new_tokens = quote_spanned! {if_token.span()=>
            if #cond #then_branch else #else_branch
        };

        tokens.extend(new_tokens);
    }
}

impl ToNodeIterator for HtmlIf {
    fn to_node_iterator_stream(&self) -> Option<TokenStream> {
        let HtmlIf {
            if_token,
            cond,
            then_branch,
            else_branch,
        } = self;
        let default_else_branch = syn::parse_str("{}").unwrap();
        let else_branch = else_branch
            .as_ref()
            .map(|(_, branch)| branch)
            .unwrap_or(&default_else_branch);
        let new_tokens = quote_spanned! {if_token.span()=>
            if #cond #then_branch else #else_branch
        };

        Some(quote_spanned! {if_token.span=> #new_tokens})
    }
}

pub enum HtmlRootBracedOrIf {
    Branch(HtmlRootBraced),
    If(HtmlIf),
}

impl PeekValue<()> for HtmlRootBracedOrIf {
    fn peek(cursor: Cursor) -> Option<()> {
        HtmlRootBraced::peek(cursor).or_else(|| HtmlIf::peek(cursor))
    }
}

impl Parse for HtmlRootBracedOrIf {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        if HtmlRootBraced::peek(input.cursor()).is_some() {
            input.parse().map(Self::Branch)
        } else {
            input.parse().map(Self::If)
        }
    }
}

impl ToTokens for HtmlRootBracedOrIf {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Branch(x) => x.to_tokens(tokens),
            Self::If(x) => x.to_tokens(tokens),
        }
    }
}
