use std::collections::VecDeque;

use rhai::Dynamic;
use rhai::LexError;
use rhai::ParseError;
use rhai::Position;

use super::attribute::Attribute;
use super::attribute_value::AttributeValue;
use super::expression_reference::ExpressionReference;
use super::output_combined_symbol::OutputCombinedSymbol;
use super::output_semantic_symbol::OutputSemanticSymbol;
use super::output_symbol::OutputSymbol;
use super::tag::Tag;
use crate::rhai_components::tag_name::TagName;

pub fn combine_output_symbols(
    state: &Dynamic,
) -> Result<VecDeque<OutputSemanticSymbol>, ParseError> {
    let mut expression_index = 0;
    let mut combined_symbols: Vec<OutputCombinedSymbol> = vec![];

    let state_array = match state.as_array_ref() {
        Ok(array) => array,
        Err(err) => {
            return Err(
                LexError::Runtime(format!("Invalid state array {err}")).into_err(Position::NONE)
            );
        }
    };

    for node in state_array.iter() {
        match node.clone().try_cast::<OutputSymbol>().ok_or_else(|| {
            LexError::Runtime("Unable to cast state to output symbols".to_string())
                .into_err(Position::NONE)
        })? {
            OutputSymbol::BodyExpression => {
                combined_symbols.push(OutputCombinedSymbol::BodyExpression(ExpressionReference {
                    expression_index,
                }));
                expression_index += 1;
            }
            OutputSymbol::TagAttributeValueExpression => match combined_symbols.last_mut() {
                Some(OutputCombinedSymbol::TagAttributeName(_)) => {
                    combined_symbols.push(OutputCombinedSymbol::TagAttributeValue(
                        AttributeValue::Expression(ExpressionReference { expression_index }),
                    ));
                    expression_index += 1;
                }
                _ => {
                    return Err(LexError::Runtime(
                        "Attribute value expression without name".to_string(),
                    )
                    .into_err(Position::NONE));
                }
            },
            OutputSymbol::TagLeftAnglePlusWhitespace => match combined_symbols.last_mut() {
                Some(OutputCombinedSymbol::TagLeftAngle) => {}
                _ => {
                    combined_symbols.push(OutputCombinedSymbol::TagLeftAngle);
                }
            },
            OutputSymbol::TagCloseBeforeNamePlusWhitespace(_text) => {
                match combined_symbols.last_mut() {
                    Some(OutputCombinedSymbol::TagCloseBeforeName) => {}
                    _ => {
                        combined_symbols.push(OutputCombinedSymbol::TagCloseBeforeName);
                    }
                }
            }
            OutputSymbol::TagPadding => match combined_symbols.last_mut() {
                Some(OutputCombinedSymbol::TagPadding) => {}
                _ => {
                    combined_symbols.push(OutputCombinedSymbol::TagPadding);
                }
            },
            OutputSymbol::TagAttributeValueString(text) => match combined_symbols.last_mut() {
                Some(OutputCombinedSymbol::TagAttributeName(_)) => {
                    combined_symbols.push(OutputCombinedSymbol::TagAttributeValue(
                        AttributeValue::Text(text),
                    ));
                }
                Some(OutputCombinedSymbol::TagAttributeValue(AttributeValue::Text(value))) => {
                    value.push_str(&text);
                }
                _ => {
                    return Err(LexError::Runtime(
                        "Attribute value expression without name".to_string(),
                    )
                    .into_err(Position::NONE));
                }
            },
            OutputSymbol::TagAttributeName(text) => match combined_symbols.last_mut() {
                Some(OutputCombinedSymbol::TagAttributeName(existing_text)) => {
                    existing_text.push_str(&text);
                }
                _ => {
                    combined_symbols.push(OutputCombinedSymbol::TagAttributeName(text));
                }
            },
            OutputSymbol::TagName(text) => match combined_symbols.last_mut() {
                Some(OutputCombinedSymbol::TagName(existing_text)) => {
                    existing_text.push_str(&text);
                }
                _ => {
                    combined_symbols.push(OutputCombinedSymbol::TagName(text));
                }
            },
            OutputSymbol::TagSelfClose => {
                combined_symbols.push(OutputCombinedSymbol::TagSelfClose);
            }
            OutputSymbol::TagRightAngle => {
                combined_symbols.push(OutputCombinedSymbol::TagRightAngle);
            }
            OutputSymbol::Text(text) => match combined_symbols.last_mut() {
                Some(OutputCombinedSymbol::Text(existing_text)) => {
                    existing_text.push_str(&text);
                }
                _ => {
                    combined_symbols.push(OutputCombinedSymbol::Text(text));
                }
            },
        }
    }

    let mut semantic_symbols: VecDeque<OutputSemanticSymbol> = VecDeque::new();

    for output_combined_symbol in combined_symbols {
        match output_combined_symbol {
            OutputCombinedSymbol::BodyExpression(expression_reference) => {
                semantic_symbols
                    .push_back(OutputSemanticSymbol::BodyExpression(expression_reference));
            }
            OutputCombinedSymbol::Text(text) => match semantic_symbols.back_mut() {
                Some(OutputSemanticSymbol::Text(existing_text)) => {
                    existing_text.push_str(&text);
                }
                _ => {
                    semantic_symbols.push_back(OutputSemanticSymbol::Text(text.to_string()));
                }
            },
            OutputCombinedSymbol::TagLeftAngle => match semantic_symbols.back_mut() {
                Some(OutputSemanticSymbol::BodyExpression(_))
                | Some(OutputSemanticSymbol::Tag(_))
                | Some(OutputSemanticSymbol::Text(_)) => {
                    semantic_symbols.push_back(OutputSemanticSymbol::Tag(Tag {
                        attributes: vec![],
                        is_closing: false,
                        is_self_closing: false,
                        tag_name: TagName {
                            name: String::new(),
                        },
                    }));
                }
                last_symbol => {
                    return Err(LexError::UnexpectedInput(format!(
                        "Unexpected tag opening after {last_symbol:?}"
                    ))
                    .into_err(Position::NONE));
                }
            },
            OutputCombinedSymbol::TagCloseBeforeName => match semantic_symbols.back_mut() {
                Some(OutputSemanticSymbol::Tag(Tag { is_closing, .. })) => {
                    *is_closing = true;
                }
                _ => {
                    return Err(
                        LexError::UnexpectedInput("Unexpected tag closing".to_string())
                            .into_err(Position::NONE),
                    );
                }
            },
            OutputCombinedSymbol::TagName(name) => match semantic_symbols.back_mut() {
                Some(OutputSemanticSymbol::Tag(Tag {
                    tag_name: existing_name,
                    ..
                })) => {
                    existing_name.name = name;
                }
                _ => {
                    return Err(LexError::UnexpectedInput("Unexpected tag name".to_string())
                        .into_err(Position::NONE));
                }
            },
            OutputCombinedSymbol::TagAttributeName(name) => match semantic_symbols.back_mut() {
                Some(OutputSemanticSymbol::Tag(Tag { attributes, .. })) => {
                    attributes.push(Attribute { name, value: None });
                }
                _ => {
                    return Err(LexError::UnexpectedInput(
                        "Unexpected tag attribute name".to_string(),
                    )
                    .into_err(Position::NONE));
                }
            },
            OutputCombinedSymbol::TagAttributeValue(attribute_value) => {
                match semantic_symbols.back_mut() {
                    Some(OutputSemanticSymbol::Tag(Tag { attributes, .. })) => {
                        if let Some(last_attribute) = attributes.last_mut() {
                            last_attribute.value = Some(attribute_value);
                        } else {
                            return Err(LexError::UnexpectedInput(
                                "Attribute value without name".to_string(),
                            )
                            .into_err(Position::NONE));
                        }
                    }
                    _ => {
                        return Err(LexError::UnexpectedInput(
                            "Unexpected tag attribute value".to_string(),
                        )
                        .into_err(Position::NONE));
                    }
                }
            }
            OutputCombinedSymbol::TagPadding => {}
            OutputCombinedSymbol::TagSelfClose => match semantic_symbols.back_mut() {
                Some(OutputSemanticSymbol::Tag(Tag {
                    is_self_closing, ..
                })) => {
                    *is_self_closing = true;
                }
                _ => {
                    return Err(LexError::UnexpectedInput(
                        "Unexpected self-closing tag".to_string(),
                    )
                    .into_err(Position::NONE));
                }
            },
            OutputCombinedSymbol::TagRightAngle => {}
        }
    }

    Ok(semantic_symbols)
}
