use std::{
    borrow::Cow,
    collections::HashMap,
    fmt::{Debug, Display},
};

use ariadne::{Config, IndexType, Label, Report, ReportBuilder, ReportKind};
use itertools::Itertools;

use crate::{ast::MemberAccess, AriadneCache, ContextualizationError, Span};

use super::{Indentation, Type};

#[derive(Clone, PartialEq, Eq)]
/// An error which can occur in the type checking phase of the build process for a
/// [`crate::Templates`]. Encountering this indicates the syntax of the source file(s) is correct
/// since parsing errors would have ended the process early.
pub enum Error<'s> {
    /// An error occurred when contextualizing static text (see [`crate::Contextualizer`] for more
    /// information).
    Contextualization(ContextualizationError, Span<'s>),
    /// A line of a template / template literal was not indented with the minimum indentation set
    /// by the indentation marker.
    NotEnoughIndentation {
        /// The indentation and location of the indentation marker that set the minimum indentation
        /// for the template / template literal.
        indentation_marker_indentation_and_span: (Indentation, Span<'s>),
        /// The indentation and location of the indentation of the line that was found to be
        /// insufficient.
        insufficent_indentation_and_span: (Indentation, Span<'s>),
    },
    /// Occurs when two lines of a template / template literal have the same (minimal) amount of
    /// whitespace characters indenting them but those are not the same.
    InconsistentMinimumIndentation((Indentation, Span<'s>), (Indentation, Span<'s>)),
    /// Occurs when you attempt to assign a value to a variable with an incompatible type.
    VariableAssignmentIncompatibleType {
        /// The name of the variable
        var_name: &'s str,
        /// Where the variable was originally declared
        original_location: Span<'s>,
        /// The original type of the variable
        original_type: Type,

        /// Where the invalid assignment happened
        second_assignment: Span<'s>,
        /// The incompatible type of the value that was attempted to be assigned
        second_type: Type,
    },
    /// Occurs when attempting to use a variable or function which is unknown to the engine. Maybe
    /// you typoed?
    UnknownVariableOrFunction(Span<'s>),
    /// Occurs when attempting to use an unknown function. Maybe you typoed?
    UnknownFunction(Span<'s>),
    /// Attempted to include a value into a template with an invalid type.
    BodyExpressionInvalidType(Type, Span<'s>),
    /// The function rejected the presented argument types. Maybe the order or arity is wrong?
    CallWrongArguments {
        /// The location of the call
        call_span: Span<'s>,
        /// The name of the function that the template attempted to call
        name: Cow<'s, str>,
    },
    /// Attempted to use a non boolean value as the condition for an if expression.
    IfExpressionConditionNotBoolType {
        /// The location of the erroneous condition
        condition_span: Span<'s>,
        /// The (non bool) type of the condition
        condition_ty: Type,
    },
    /// The if expression needs a value to return in case the condition does not hold (unless the
    /// type is [`Type::Text`]) and this is missing.
    IfExpressionMissingElseExpression {
        /// The location of the if expression
        if_span: Span<'s>,
        /// The type of the output of the if expression
        ty: Type,
    },
    /// Happens if the then and else expressions of an if expressions are of different types.
    IfExpressionThenAndElseIncompatibleType {
        /// The location of the then expression
        then_span: Span<'s>,
        /// The type of the then expression
        then_ty: Type,
        /// The location of the else expression
        else_span: Span<'s>,
        /// The type of the else expression
        else_ty: Type,
    },
    /// The array expression of the each expression was found to not actually be of an array type.
    /// This is however required for iteration.
    EachExpressionNotArray {
        /// The location of the array expression
        expression_span: Span<'s>,
        /// The actual type of the array expression
        expression_ty: Type,
    },
    /// The source expression of the select expression was found to not be of a sum type and thus is not selectable.
    NonSumTypeInSelect {
        /// The location of the expression with the non sum type
        source_expr_span: Span<'s>,
        /// The type of the expression
        source_expr_ty: Type,
    },
    /// Two arms of a select expression have been found to return values with incompatible types.
    SelectArmsIncompatibleTypes {
        /// The location of the first expression
        first_expr_span: Span<'s>,
        /// The type of the first expression
        first_expr_ty: Type,

        /// The location of the second expression
        second_expr_span: Span<'s>,
        /// The incompatible type of the second expression
        second_expr_ty: Type,
    },
    /// One arm selected a duplicate or invalid type.
    SelectArmDuplicateOrInvalidType {
        /// The location of the source expression
        source_expr_span: Span<'s>,
        /// The type of the source expression
        source_expr_ty: Type,

        /// The location of the type that was attempted to select
        arm_ty_span: Span<'s>,
        /// The type that was attempted to select
        arm_ty: Type,
    },
    /// This select expression has no arms.
    SelectNoArms {
        /// The location of the select expression
        select_span: Span<'s>,
    },
    /// This select expression does not cover all possible types with an arm for a non text result
    /// type.
    SelectNotAllOptionsCovered {
        /// The location of the source expression
        source_expr_span: Span<'s>,
        /// The type of the source expression where at least one of it's options was not covered
        source_expr_ty: Type,

        /// The remaining types that were not covered by arms
        remaining: Vec<Type>,
    },
    /// This occurs when you attempt to call something that is not just a name but rather the
    /// result of some other computation. Maybe the engine misinterpreted your source code?
    CallInvalidFunction(Span<'s>),
    /// Occurs when you attempt to access a value from a tuple that does not exist. For example the
    /// third value of a tuple with arity two.
    InvalidTupleAccess {
        /// The location of the tuple access
        access_span: Span<'s>,
        /// The type of the tuple
        tuple_ty: Type,
        /// The index that was attempted to access
        index: usize,
    },
    /// Occurs when you attempt to access the value of a struct with no such value. Maybe you
    /// typoed?
    InvalidStructAccess {
        /// The location of the access
        access_span: Span<'s>,
        /// The type of the struct
        struct_ty: Type,
        /// The name of the value that was attempted to be accessed
        name: &'s str,
    },
    /// Attempted to access the member of value that is neither a struct nor a tuple
    InvalidMemberAccess {
        /// The location of the access expression
        expr_span: Span<'s>,
        /// The type of the expression that access was attempted with
        expr_ty: Type,
        /// The kind of access that was attempted
        access: MemberAccess<'s>,
    },
    /// Attempted to construct an array using a literal with elements that were of different types.
    ArrayLiteralIncompatibleTypes {
        /// The location of the first element of the literal
        first_span: Span<'s>,
        /// The type of the first element of the literal
        first_ty: Type,
        /// The location of the element with the incompatible type
        second_span: Span<'s>,
        /// The incompatible type
        second_ty: Type,
    },
    /// Attempted to define a type with the same name twice.
    DuplicateTypeDefinition {
        /// The location of the first type definition
        first_span: Span<'s>,
        /// The location of the second conflicting type definition
        second_span: Span<'s>,
    },
}

impl<'s> Error<'s> {
    // hardly avoidable
    #[allow(clippy::too_many_lines)]
    fn write_to_buf(&self, cache: &mut AriadneCache<'s>, buf: &mut Vec<u8>, with_color: bool) {
        fn builder(at: Span<'_>, with_color: bool) -> ReportBuilder<'_, Span<'_>> {
            Report::build(ReportKind::Error, at).with_config(
                Config::new()
                    .with_color(with_color)
                    .with_index_type(IndexType::Byte),
            )
        }

        fn write<'s>(
            builder: ReportBuilder<'s, Span<'s>>,
            cache: &mut AriadneCache<'s>,
            buf: &mut Vec<u8>,
        ) {
            builder.finish().write(cache, buf).unwrap();
        }

        match self {
            Self::Contextualization(err, at) => write(builder(*at, with_color).with_message("Error contextualizing").with_label(Label::new(*at).with_message(err.to_string())), cache, buf),
            Self::ArrayLiteralIncompatibleTypes {
                first_span,
                first_ty,
                second_span,
                second_ty,
            } => write(
                builder(*second_span, with_color)
                    .with_message("Incompatible types in array literal")
                    .with_label(Label::new(*first_span).with_message(format!("This expression is of type {first_ty}")))
                    .with_label(Label::new(*second_span).with_message(format!("But this expression is of type {second_ty} which is incompatible with {first_ty}")))
                    .with_note("Arrays need to be homogenous"),
                cache,
                buf,
            ),

            Self::CallWrongArguments { call_span, name } => write(
                builder(*call_span, with_color)
                    .with_message("Call wrong arguments")
                    .with_label(Label::new(*call_span).with_message(format!("The function {name} got called with wrong arguments"))),
                cache,
                buf
            ),

            Self::EachExpressionNotArray { expression_span, expression_ty } => write(
                builder(*expression_span, with_color)
                    .with_message("Each expression non array source")
                    .with_label(
                        Label::new(*expression_span)
                            .with_message(format!("The source expression of an each expression needs to be an array type but this was found to be of type {expression_ty}"))
                    ),
                cache,
                buf
            ),

            Self::NonSumTypeInSelect { source_expr_span: expression_span, source_expr_ty: expression_ty } => write(
                builder(*expression_span, with_color)
                    .with_message("Non sum type in select expression")
                    .with_label(
                        Label::new(*expression_span)
                            .with_message(format!("This expression was found to be of type {expression_ty} which is not a Sum type as required by a select expression"))
                    ),
                cache,
                buf
            ),

            Self::SelectArmDuplicateOrInvalidType { source_expr_span: select_expr_span, source_expr_ty: select_expr_ty, arm_ty_span, arm_ty: _ } => write(
                builder(*arm_ty_span, with_color)
                    .with_message("Select arm duplicate or invalid type")
                    .with_label(
                        Label::new(*arm_ty_span)
                            .with_message("This type was either already handled or was not a possible type to begin with")
                    ).with_label(
                        Label::new(*select_expr_span)
                            .with_message(format!("This expression was found to be of type {select_expr_ty}"))
                    ),
                cache,
                buf
            ),

            Self::SelectNoArms { select_span } => write(
                builder(*select_span, with_color)
                    .with_message("Select expression has no arms")
                    .with_label(
                        Label::new(*select_span)
                            .with_message("This select expression has no arms but requires them")
                    ),
                cache,
                buf
            ),

            Self::SelectArmsIncompatibleTypes { first_expr_span, first_expr_ty, second_expr_span, second_expr_ty } => write(
                builder(*second_expr_span, with_color)
                    .with_message("Select arms have incompatible types")
                    .with_label(
                        Label::new(*second_expr_span)
                            .with_message(format!("This expression was found to be of type {second_expr_ty} which is incompatible"))
                    )
                    .with_label(
                        Label::new(*first_expr_span)
                            .with_message(format!("This expression was found to be of type {first_expr_ty}"))
                    ),
                cache,
                buf
            ),

            Self::SelectNotAllOptionsCovered { source_expr_span, source_expr_ty, remaining } => write(
                builder(*source_expr_span, with_color)
                    .with_message("Select arms don't cover all options")
                    .with_label(
                        Label::new(*source_expr_span)
                            .with_message(format!(
                                    "This expression was found to be of type {source_expr_ty}. The following types were not covered by arms: {}",
                                    remaining.iter().map(Type::to_string).join(", ")
                            ))
                    ),
                cache,
                buf
            ),

            Self::NotEnoughIndentation { indentation_marker_indentation_and_span, insufficent_indentation_and_span } => write(
                builder(insufficent_indentation_and_span.1, with_color)
                    .with_message("Insufficent indentation for indentation marker")
                    .with_label(Label::new(insufficent_indentation_and_span.1).with_message(format!("This indentation is {}", insufficent_indentation_and_span.0)))
                    .with_label(Label::new(indentation_marker_indentation_and_span.1).with_message(format!("But the indentation marker for this template body requires at least {}", indentation_marker_indentation_and_span.0))),
                cache,
                buf
            ),

            Self::InconsistentMinimumIndentation(first, second) => write(
                builder(second.1, with_color)
                    .with_message("Inconsistent minimum indentation")
                    .with_label(Label::new(first.1).with_message(format!("This line has an indentation of {}", first.0)))
                    .with_label(Label::new(second.1).with_message(format!("But this line, which is tied for minimum indentation, has {} which is inconsistent", second.0))),
                cache,
                buf
            ),
            Self::VariableAssignmentIncompatibleType { original_location, original_type, second_assignment, second_type, var_name: _ } => write(
                builder(*second_assignment, with_color)
                    .with_message("Incompatible type for variable assignment")
                    .with_label(Label::new(*original_location).with_message(format!("The variable was first declared here with type {original_type}")))
                    .with_label(Label::new(*second_assignment).with_message(format!("It is reassigned here, but the new type {second_type} is incompatible with the first"))),
                cache,
                buf
            ),
            Self::UnknownVariableOrFunction(span) => write(
                builder(*span, with_color)
                .with_message("Unknown variable or function")
                .with_label(Label::new(*span).with_message("Unknown variable or function")),
                cache,
                buf
            ),
            Self::UnknownFunction(span) => write(
                builder(*span, with_color)
                    .with_message("Unknown function")
                    .with_label(Label::new(*span).with_message("Unknown function")),
                cache,
                buf
            ),
            Self::BodyExpressionInvalidType(ty, span) => write(
                {
                    let builder = builder(*span, with_color)
                        .with_message("Invalid type for template body expression")
                        .with_label(Label::new(*span).with_message(format!("This expression is of type {ty} which cannot be directly included in a template body")));

                    if matches!(ty, Type::Array(_)) {
                        builder.with_note("An array type can be used in a freestanding expression if the inner type is printable and it is the sole content on the line")
                    } else {
                        builder
                    }
                },
                cache,
                buf
            ),
            Self::IfExpressionConditionNotBoolType { condition_span, condition_ty } => write(
                builder(*condition_span, with_color)
                    .with_message("If expression non bool condition")
                    .with_label(
                        Label::new(*condition_span)
                            .with_message(format!("The condition expression of an if expression needs to be bool but this was found to be of type {condition_ty}"))
                    ),
                cache,
                buf
            ),
            Self::IfExpressionMissingElseExpression { if_span, ty } => write(
                builder(*if_span, with_color)
                    .with_message("If expression missing else expression")
                    .with_label(Label::new(*if_span).with_message(format!("The then expression of this if expression is of type {ty} which requires an else expression but none was provided"))),
                cache,
                buf
            ),
            Self::IfExpressionThenAndElseIncompatibleType { then_span, then_ty, else_span, else_ty } => write(
                builder(*else_span, with_color)
                    .with_message("If expression incompatible then and else expression types")
                    .with_label(Label::new(*then_span).with_message(format!("The then expression of this if expression is of type {then_ty}")))
                    .with_label(Label::new(*else_span).with_message(format!("But the type of the else expression is {else_ty} which is incompatible."))),
                cache,
                buf
            ),
            Self::CallInvalidFunction(span) => write(
                builder(*span, with_color)
                    .with_message("Call invalid function expression")
                    .with_label(Label::new(*span).with_message("The function of a function call cannot be computed and needs to be an indentifier")),
                cache,
                buf
            ),
            Self::InvalidTupleAccess { access_span, tuple_ty, index: _index } => write(
                builder(*access_span, with_color)
                    .with_message("Invalid tuple access")
                    .with_label(Label::new(*access_span).with_message(format!("This tuple is of type {tuple_ty}"))),
                cache,
                buf
            ),
            Self::InvalidStructAccess { access_span, struct_ty, name: _name } => write(
                builder(*access_span, with_color)
                    .with_message("Invalid struct access")
                    .with_label(Label::new(*access_span).with_message(format!("This struct is of type {struct_ty} and doesn't have the requested member"))),
                cache,
                buf
            ),
            Self::InvalidMemberAccess { expr_span, expr_ty, access } => write(
                builder(*expr_span, with_color)
                    .with_message("Invalid access")
                    .with_label(Label::new(*expr_span).with_message(format!("This expression is of type {expr_ty} which cannot be accessed with a {} access", match access {
                        MemberAccess::Named(_ , _) => "named",
                        MemberAccess::Numeric(_, _) => "numeric",
                    }))),
                cache,
                buf
            ),
            Self::DuplicateTypeDefinition { first_span, second_span } => write(builder(*second_span, with_color).with_message("Defined type twice").with_message("This type name was defined twice").with_labels([Label::new(*first_span).with_message("The type was first defined here"), Label::new(*second_span)]), cache, buf),
        }
    }
}

impl Debug for Error<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("\n")?;
        <Self as Display>::fmt(self, f)
    }
}

impl Display for Error<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut buf = Vec::new();
        let mut cache = AriadneCache(HashMap::new());

        self.write_to_buf(&mut cache, &mut buf, !f.alternate());

        f.write_str(std::str::from_utf8(&buf).unwrap())
    }
}

impl std::error::Error for Error<'_> {}
