use std::fmt::Display;

use crate::{Span, __internal__parse_with_path};

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SourceFile<'s> {
    pub(crate) templates: Vec<(Template<'s>, Span<'s>)>,

    pub(crate) type_defs: Vec<(TypeDefinition<'s>, Span<'s>)>,
}

impl<'s> SourceFile<'s> {
    #[must_use]
    pub fn into_templates_and_type_defs(
        self,
    ) -> (
        impl ExactSizeIterator<Item = Template<'s>>,
        impl ExactSizeIterator<Item = (TypeDefinition<'s>, Span<'s>)>,
    ) {
        (
            self.templates.into_iter().map(|(tmpl, _)| tmpl),
            self.type_defs.into_iter(),
        )
    }
}

impl<'s> SourceFile<'s> {
    pub(crate) fn parse(
        path: &'s str,
        source: &'s str,
    ) -> Result<SourceFile<'s>, super::parse::Error<'s>> {
        __internal__parse_with_path!(super::parse::source_file::source_file(), path, source)
            .into_result()
            .map_err(|errors| {
                if errors.len() == 1 {
                    errors.into_iter().next().unwrap()
                } else {
                    super::parse::Error::Multiple(errors)
                }
            })
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Template<'s> {
    pub(crate) output_type: (Option<&'s str>, Span<'s>),
    pub(crate) name: (&'s str, Span<'s>),
    pub(crate) parameters: Vec<(TemplateParameterName<'s>, TemplateParameterType<'s>)>,
    pub(crate) body: TemplateBody<'s>,
}

pub(crate) type TemplateParameterName<'s> = (&'s str, Span<'s>);
pub(crate) type TemplateParameterType<'s> = (Option<Type<'s>>, Span<'s>);

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct TemplateBody<'s> {
    pub(crate) indentation_marker: IndentationMarker<'s>,
    pub(crate) lines: Vec<(TemplateLine<'s>, Span<'s>)>,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum IndentationMarker<'s> {
    None,
    Present {
        indentation_and_span: (&'s str, Span<'s>),
    },
    Escaped {
        indentation_and_span: (&'s str, Span<'s>),
        marker_span: Span<'s>,
        newline: (&'s str, Span<'s>),
    },
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Type<'s> {
    Ident(&'s str),
    Options(Vec<(Type<'s>, Span<'s>)>),
    Array((Option<Box<Type<'s>>>, Span<'s>)),
    Tuple(Vec<(Type<'s>, Span<'s>)>),
    Struct(Vec<(StructEntryName<'s>, StructEntryType<'s>)>),
}

pub(crate) type StructEntryName<'s> = (&'s str, Span<'s>);
pub(crate) type StructEntryType<'s> = (Option<Type<'s>>, Span<'s>);

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct TemplateLine<'s> {
    /// whether the line contained comments; used to determine comment only lines
    pub had_comments: bool,
    pub indentation: (&'s str, Span<'s>),
    pub content: Vec<(TemplateContent<'s>, Span<'s>)>,
    pub newline: (TemplateLineNewline<'s>, Span<'s>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TemplateLineNewline<'s> {
    Normal(&'s str),
    Escaped,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum TemplateContent<'s> {
    Static(&'s str),
    EscapeSequence(TemplateContentEscapeSequence<'s>),
    /// boxed to reduce enum size
    Command(Box<Command<'s>>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EscapeSequence {
    Tab,
    Newline,
    Backslash,
    CarriageReturn,
}

impl EscapeSequence {
    pub(crate) fn str(self) -> &'static str {
        match self {
            EscapeSequence::Tab => "\t",
            EscapeSequence::Newline => "\n",
            EscapeSequence::Backslash => "\\",
            EscapeSequence::CarriageReturn => "\r",
        }
    }
}

// fully public because we expose it in the Error type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CurlyKind {
    Opening,
    Closing,
}

impl Display for CurlyKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Opening => write!(f, "opening"),
            Self::Closing => write!(f, "closing"),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum TemplateContentEscapeSequence<'s> {
    Normal(EscapeSequence),
    Curly(CurlyKind, usize, &'s str, Span<'s>),
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Command<'s> {
    Expression(Expression<'s>, Span<'s>),
    Assignment {
        variable: (&'s str, Span<'s>),
        expression: (Expression<'s>, Span<'s>),
    },
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Expression<'s> {
    Literal(Literal<'s>),
    VariableOrCall(&'s str),
    Call {
        func: (Box<Expression<'s>>, Span<'s>),
        arguments: Vec<(Expression<'s>, Span<'s>)>,
    },
    If {
        condition: (Box<Expression<'s>>, Span<'s>),
        then_expression: (Box<Expression<'s>>, Span<'s>),
        else_expression: Option<(Box<Expression<'s>>, Span<'s>)>,
    },
    Each {
        variable: (&'s str, Span<'s>),
        array_expression: (Box<Expression<'s>>, Span<'s>),
        body_expression: (Box<Expression<'s>>, Span<'s>),
    },
    Select {
        expr: (Box<Expression<'s>>, Span<'s>),
        #[allow(clippy::type_complexity)]
        arms: Vec<(
            (&'s str, Span<'s>),
            (Type<'s>, Span<'s>),
            (Expression<'s>, Span<'s>),
        )>,
    },
    MemberAccess {
        expression: (Box<Expression<'s>>, Span<'s>),
        access: (MemberAccess<'s>, Span<'s>),
    },
}

// fully public because it's exposed in the error
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MemberAccess<'s> {
    Numeric(u32, Span<'s>),
    Named(&'s str, Span<'s>),
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Literal<'s> {
    Int(i64),
    Float(f64),
    Bool(bool),
    String(Vec<(StringLiteralComponent<'s>, Span<'s>)>),
    // template body is very large compared to the rest of the literals, therefore boxing it here
    // to keep the other far more common literals smaller is justified
    Template((Option<&'s str>, Span<'s>), Box<TemplateBody<'s>>),
    Tuple(Vec<(Expression<'s>, Span<'s>)>),
    Array(Vec<(Expression<'s>, Span<'s>)>),
    Struct(Vec<(StructLiteralEntryName<'s>, StructLiteralEntryExpression<'s>)>),
}

pub(crate) type StructLiteralEntryName<'s> = (&'s str, Span<'s>);
pub(crate) type StructLiteralEntryExpression<'s> = (Expression<'s>, Span<'s>);

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum StringLiteralComponent<'s> {
    Literal(&'s str),
    EscapedQuote,
    EscapeSequence(EscapeSequence),
}

#[derive(Debug, Clone, PartialEq)]
pub struct TypeDefinition<'s> {
    pub(crate) name: (&'s str, Span<'s>),
    pub(crate) ty: (Type<'s>, Span<'s>),
}
