use maybe_sync::{dyn_maybe_send_sync, Rc};
use std::{collections::BTreeMap, fmt::Debug};

use crate::Function;

use super::Type;

#[derive(Debug, Clone)]
pub(crate) struct Template<Esc> {
    pub(crate) params: Type,
    pub(crate) variable_count: usize,
    pub(crate) body: Vec<TemplateContent<Esc>>,
}

pub(crate) type VariableIndex = usize;

#[derive(Debug, Clone)]
pub(crate) enum TemplateContent<Esc> {
    /// required to figure out where string breaks in the text array of a template literal go
    OriginalTemplateNewline(String),
    Static(String),
    Assignment(VariableIndex, Expression<Esc>),
    BodyExpression {
        indentation: String,
        expression: Expression<Esc>,
        newline: String,
    },
}

#[cfg(feature = "sync")]
#[derive(Clone)]
pub(crate) enum Call<Esc> {
    Function(Rc<dyn Function + Send + Sync>, Vec<Expression<Esc>>),
    Template(usize, Vec<(String, Expression<Esc>)>),
}

#[cfg(not(feature = "sync"))]
#[derive(Clone)]
pub(crate) enum Call<Esc> {
    Function(Rc<dyn Function>, Vec<Expression<Esc>>),
    Template(usize, Vec<(String, Expression<Esc>)>),
}

#[derive(Clone)]
pub(crate) enum CallKind {
    Function(Rc<dyn_maybe_send_sync!(Function)>),
    Template {
        idx: usize,
        out_ty: String,
        parameters: Vec<(String, Type)>,
    },
}

impl Debug for CallKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CallKind::Function(_) => f.debug_tuple("Function").finish_non_exhaustive(),
            CallKind::Template { .. } => f.debug_tuple("Template").finish_non_exhaustive(),
        }
    }
}

impl<Esc> Debug for Call<Esc>
where
    Esc: Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Call::Function(_, expr) => f
                .debug_tuple("Function")
                .field(expr)
                .finish_non_exhaustive(),
            Call::Template(_, expr) => f
                .debug_tuple("Template")
                .field(expr)
                .finish_non_exhaustive(),
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) enum Expression<Esc> {
    Escaped(Box<Expression<Esc>>, Esc),
    Literal(Literal<Esc>),
    Variable(VariableIndex),
    Call(Call<Esc>),
    If {
        condition: Box<Expression<Esc>>,
        then: Box<Expression<Esc>>,
        otherwise: Box<Expression<Esc>>,
    },
    Each {
        assignment_variable: VariableIndex,
        array_expression: Box<Expression<Esc>>,
        body: Box<Expression<Esc>>,
    },
    MemberAccess {
        expression: Box<Expression<Esc>>,
        access: usize,
    },
}

#[derive(Debug, Clone)]
pub(crate) enum Literal<Esc> {
    Int(i64),
    Float(f64),
    Bool(bool),
    String(String),
    Template(Vec<TemplateContent<Esc>>),
    ArrayOrTuple(Vec<Expression<Esc>>),
    Struct(BTreeMap<String, Expression<Esc>>),
}
