use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::langtype::{BuiltinElement, EnumerationValue, Type};
use crate::layout::Orientation;
use crate::lookup::LookupCtx;
use crate::object_tree::*;
use crate::parser::{NodeOrToken, SyntaxNode};
use core::cell::RefCell;
use std::cell::Cell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
pub use crate::namedreference::NamedReference;
pub use crate::passes::resolving;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BuiltinFunction {
    GetWindowScaleFactor,
    GetWindowDefaultFontSize,
    AnimationTick,
    Debug,
    Mod,
    Round,
    Ceil,
    Floor,
    Abs,
    Sqrt,
    Cos,
    Sin,
    Tan,
    ACos,
    ASin,
    ATan,
    Log,
    Pow,
    SetFocusItem,
    ClearFocusItem,
    ShowPopupWindow,
    ClosePopupWindow,
    SetSelectionOffsets,
    ItemMemberFunction(String),
    StringToFloat,
    StringIsFloat,
    ColorRgbaStruct,
    ColorHsvaStruct,
    ColorBrighter,
    ColorDarker,
    ColorTransparentize,
    ColorMix,
    ColorWithAlpha,
    ImageSize,
    ArrayLength,
    Rgb,
    Hsv,
    ColorScheme,
    Use24HourFormat,
    MonthDayCount,
    MonthOffset,
    FormatDate,
    DateNow,
    ValidDate,
    ParseDate,
    TextInputFocused,
    SetTextInputFocused,
    ImplicitLayoutInfo(Orientation),
    ItemAbsolutePosition,
    RegisterCustomFontByPath,
    RegisterCustomFontByMemory,
    RegisterBitmapFont,
    Translate,
}
#[derive(Debug, Clone)]
pub enum BuiltinMacroFunction {
    Min,
    Max,
    Clamp,
    Mod,
    Abs,
    CubicBezier,
    Rgb,
    Hsv,
    Debug,
}
impl BuiltinFunction {
    pub fn ty(&self) -> Type {
        match self {
            BuiltinFunction::GetWindowScaleFactor => Type::Function {
                return_type: Box::new(Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)])),
                args: vec![],
            },
            BuiltinFunction::GetWindowDefaultFontSize => {
                Type::Function { return_type: Box::new(Type::LogicalLength), args: vec![] }
            }
            BuiltinFunction::AnimationTick => {
                Type::Function { return_type: Type::Duration.into(), args: vec![] }
            }
            BuiltinFunction::Debug => {
                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
            }
            BuiltinFunction::Mod => Type::Function {
                return_type: Box::new(Type::Int32),
                args: vec![Type::Int32, Type::Int32],
            },
            BuiltinFunction::Round | BuiltinFunction::Ceil | BuiltinFunction::Floor => {
                Type::Function { return_type: Box::new(Type::Int32), args: vec![Type::Float32] }
            }
            BuiltinFunction::Sqrt | BuiltinFunction::Abs => {
                Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::Float32] }
            }
            BuiltinFunction::Cos | BuiltinFunction::Sin | BuiltinFunction::Tan => {
                Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::Angle] }
            }
            BuiltinFunction::ACos | BuiltinFunction::ASin | BuiltinFunction::ATan => {
                Type::Function { return_type: Box::new(Type::Angle), args: vec![Type::Float32] }
            }
            BuiltinFunction::Log | BuiltinFunction::Pow => Type::Function {
                return_type: Box::new(Type::Float32),
                args: vec![Type::Float32, Type::Float32],
            },
            BuiltinFunction::SetFocusItem => Type::Function {
                return_type: Box::new(Type::Void),
                args: vec![Type::ElementReference],
            },
            BuiltinFunction::ClearFocusItem => Type::Function {
                return_type: Box::new(Type::Void),
                args: vec![Type::ElementReference],
            },
            BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => {
                Type::Function {
                    return_type: Box::new(Type::Void),
                    args: vec![Type::ElementReference],
                }
            }
            BuiltinFunction::SetSelectionOffsets => Type::Function {
                return_type: Box::new(Type::Void),
                args: vec![Type::ElementReference, Type::Int32, Type::Int32],
            },
            BuiltinFunction::ItemMemberFunction(..) => Type::Function {
                return_type: Box::new(Type::Void),
                args: vec![Type::ElementReference],
            },
            BuiltinFunction::StringToFloat => {
                Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::String] }
            }
            BuiltinFunction::StringIsFloat => {
                Type::Function { return_type: Box::new(Type::Bool), args: vec![Type::String] }
            }
            BuiltinFunction::ImplicitLayoutInfo(_) => Type::Function {
                return_type: Box::new(crate::layout::layout_info_type()),
                args: vec![Type::ElementReference],
            },
            BuiltinFunction::ColorRgbaStruct => Type::Function {
                return_type: Box::new(Type::Struct {
                    fields: IntoIterator::into_iter([
                        ("red".to_string(), Type::Int32),
                        ("green".to_string(), Type::Int32),
                        ("blue".to_string(), Type::Int32),
                        ("alpha".to_string(), Type::Int32),
                    ])
                    .collect(),
                    name: Some("Color".into()),
                    node: None,
                    rust_attributes: None,
                }),
                args: vec![Type::Color],
            },
            BuiltinFunction::ColorHsvaStruct => Type::Function {
                return_type: Box::new(Type::Struct {
                    fields: IntoIterator::into_iter([
                        ("hue".to_string(), Type::Float32),
                        ("saturation".to_string(), Type::Float32),
                        ("value".to_string(), Type::Float32),
                        ("alpha".to_string(), Type::Float32),
                    ])
                    .collect(),
                    name: Some("Color".into()),
                    node: None,
                    rust_attributes: None,
                }),
                args: vec![Type::Color],
            },
            BuiltinFunction::ColorBrighter => Type::Function {
                return_type: Box::new(Type::Brush),
                args: vec![Type::Brush, Type::Float32],
            },
            BuiltinFunction::ColorDarker => Type::Function {
                return_type: Box::new(Type::Brush),
                args: vec![Type::Brush, Type::Float32],
            },
            BuiltinFunction::ColorTransparentize => Type::Function {
                return_type: Box::new(Type::Brush),
                args: vec![Type::Brush, Type::Float32],
            },
            BuiltinFunction::ColorMix => Type::Function {
                return_type: Box::new(Type::Color),
                args: vec![Type::Color, Type::Color, Type::Float32],
            },
            BuiltinFunction::ColorWithAlpha => Type::Function {
                return_type: Box::new(Type::Brush),
                args: vec![Type::Brush, Type::Float32],
            },
            BuiltinFunction::ImageSize => Type::Function {
                return_type: Box::new(Type::Struct {
                    fields: IntoIterator::into_iter([
                        ("width".to_string(), Type::Int32),
                        ("height".to_string(), Type::Int32),
                    ])
                    .collect(),
                    name: Some("Size".to_string()),
                    node: None,
                    rust_attributes: None,
                }),
                args: vec![Type::Image],
            },
            BuiltinFunction::ArrayLength => {
                Type::Function { return_type: Box::new(Type::Int32), args: vec![Type::Model] }
            }
            BuiltinFunction::Rgb => Type::Function {
                return_type: Box::new(Type::Color),
                args: vec![Type::Int32, Type::Int32, Type::Int32, Type::Float32],
            },
            BuiltinFunction::Hsv => Type::Function {
                return_type: Box::new(Type::Color),
                args: vec![Type::Float32, Type::Float32, Type::Float32, Type::Float32],
            },
            BuiltinFunction::ColorScheme => Type::Function {
                return_type: Box::new(Type::Enumeration(
                    crate::typeregister::BUILTIN_ENUMS.with(|e| e.ColorScheme.clone()),
                )),
                args: vec![],
            },
            BuiltinFunction::MonthDayCount => Type::Function {
                return_type: Box::new(Type::Int32),
                args: vec![Type::Int32, Type::Int32],
            },
            BuiltinFunction::MonthOffset => Type::Function {
                return_type: Box::new(Type::Int32),
                args: vec![Type::Int32, Type::Int32],
            },
            BuiltinFunction::FormatDate => Type::Function {
                return_type: Box::new(Type::String),
                args: vec![Type::String, Type::Int32, Type::Int32, Type::Int32],
            },
            BuiltinFunction::TextInputFocused => {
                Type::Function { return_type: Box::new(Type::Bool), args: vec![] }
            }
            BuiltinFunction::DateNow => Type::Function {
                return_type: Box::new(Type::Array(Box::new(Type::Int32))),
                args: vec![],
            },
            BuiltinFunction::ValidDate => Type::Function {
                return_type: Box::new(Type::Bool),
                args: vec![Type::String, Type::String],
            },
            BuiltinFunction::ParseDate => Type::Function {
                return_type: Box::new(Type::Array(Box::new(Type::Int32))),
                args: vec![Type::String, Type::String],
            },
            BuiltinFunction::SetTextInputFocused => {
                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::Bool] }
            }
            BuiltinFunction::ItemAbsolutePosition => Type::Function {
                return_type: Box::new(crate::typeregister::logical_point_type()),
                args: vec![Type::ElementReference],
            },
            BuiltinFunction::RegisterCustomFontByPath => {
                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
            }
            BuiltinFunction::RegisterCustomFontByMemory => {
                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::Int32] }
            }
            BuiltinFunction::RegisterBitmapFont => {
                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::Int32] }
            }
            BuiltinFunction::Translate => Type::Function {
                return_type: Box::new(Type::String),
                args: vec![
                    Type::String,
                    Type::String,
                    Type::String,
                    Type::Array(Type::String.into()),
                ],
            },
            BuiltinFunction::Use24HourFormat => {
                Type::Function { return_type: Box::new(Type::Bool), args: vec![] }
            }
        }
    }
    fn is_const(&self) -> bool {
        match self {
            BuiltinFunction::GetWindowScaleFactor => false,
            BuiltinFunction::GetWindowDefaultFontSize => false,
            BuiltinFunction::AnimationTick => false,
            BuiltinFunction::ColorScheme => false,
            BuiltinFunction::MonthDayCount => false,
            BuiltinFunction::MonthOffset => false,
            BuiltinFunction::FormatDate => false,
            BuiltinFunction::DateNow => false,
            BuiltinFunction::ValidDate => false,
            BuiltinFunction::ParseDate => false,
            BuiltinFunction::Debug => true,
            BuiltinFunction::Mod
            | BuiltinFunction::Round
            | BuiltinFunction::Ceil
            | BuiltinFunction::Floor
            | BuiltinFunction::Abs
            | BuiltinFunction::Sqrt
            | BuiltinFunction::Cos
            | BuiltinFunction::Sin
            | BuiltinFunction::Tan
            | BuiltinFunction::ACos
            | BuiltinFunction::ASin
            | BuiltinFunction::Log
            | BuiltinFunction::Pow
            | BuiltinFunction::ATan => true,
            BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
            BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
            BuiltinFunction::SetSelectionOffsets => false,
            BuiltinFunction::ItemMemberFunction(..) => false,
            BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
            BuiltinFunction::ColorRgbaStruct
            | BuiltinFunction::ColorHsvaStruct
            | BuiltinFunction::ColorBrighter
            | BuiltinFunction::ColorDarker
            | BuiltinFunction::ColorTransparentize
            | BuiltinFunction::ColorMix
            | BuiltinFunction::ColorWithAlpha => true,
            #[cfg(not(target_arch = "wasm32"))]
            BuiltinFunction::ImageSize => true,
            #[cfg(target_arch = "wasm32")]
            BuiltinFunction::ImageSize => false,
            BuiltinFunction::ArrayLength => true,
            BuiltinFunction::Rgb => true,
            BuiltinFunction::Hsv => true,
            BuiltinFunction::SetTextInputFocused => false,
            BuiltinFunction::TextInputFocused => false,
            BuiltinFunction::ImplicitLayoutInfo(_) => false,
            BuiltinFunction::ItemAbsolutePosition => true,
            BuiltinFunction::RegisterCustomFontByPath
            | BuiltinFunction::RegisterCustomFontByMemory
            | BuiltinFunction::RegisterBitmapFont => false,
            BuiltinFunction::Translate => false,
            BuiltinFunction::Use24HourFormat => false,
        }
    }
    pub fn is_pure(&self) -> bool {
        match self {
            BuiltinFunction::GetWindowScaleFactor => true,
            BuiltinFunction::GetWindowDefaultFontSize => true,
            BuiltinFunction::AnimationTick => true,
            BuiltinFunction::ColorScheme => true,
            BuiltinFunction::MonthDayCount => true,
            BuiltinFunction::MonthOffset => true,
            BuiltinFunction::FormatDate => true,
            BuiltinFunction::DateNow => true,
            BuiltinFunction::ValidDate => true,
            BuiltinFunction::ParseDate => true,
            BuiltinFunction::Debug => true,
            BuiltinFunction::Mod
            | BuiltinFunction::Round
            | BuiltinFunction::Ceil
            | BuiltinFunction::Floor
            | BuiltinFunction::Abs
            | BuiltinFunction::Sqrt
            | BuiltinFunction::Cos
            | BuiltinFunction::Sin
            | BuiltinFunction::Tan
            | BuiltinFunction::ACos
            | BuiltinFunction::ASin
            | BuiltinFunction::Log
            | BuiltinFunction::Pow
            | BuiltinFunction::ATan => true,
            BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
            BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
            BuiltinFunction::SetSelectionOffsets => false,
            BuiltinFunction::ItemMemberFunction(..) => false,
            BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
            BuiltinFunction::ColorRgbaStruct
            | BuiltinFunction::ColorHsvaStruct
            | BuiltinFunction::ColorBrighter
            | BuiltinFunction::ColorDarker
            | BuiltinFunction::ColorTransparentize
            | BuiltinFunction::ColorMix
            | BuiltinFunction::ColorWithAlpha => true,
            BuiltinFunction::ImageSize => true,
            BuiltinFunction::ArrayLength => true,
            BuiltinFunction::Rgb => true,
            BuiltinFunction::Hsv => true,
            BuiltinFunction::ImplicitLayoutInfo(_) => true,
            BuiltinFunction::ItemAbsolutePosition => true,
            BuiltinFunction::SetTextInputFocused => false,
            BuiltinFunction::TextInputFocused => true,
            BuiltinFunction::RegisterCustomFontByPath
            | BuiltinFunction::RegisterCustomFontByMemory
            | BuiltinFunction::RegisterBitmapFont => false,
            BuiltinFunction::Translate => true,
            BuiltinFunction::Use24HourFormat => true,
        }
    }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum OperatorClass {
    ComparisonOp,
    LogicalOp,
    ArithmeticOp,
}
pub fn operator_class(op: char) -> OperatorClass {
    match op {
        '=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp,
        '&' | '|' => OperatorClass::LogicalOp,
        '+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp,
        _ => panic!("Invalid operator {:?}", op),
    }
}
macro_rules! declare_units {
    ($( $(#[$m:meta])* $ident:ident = $string:literal -> $ty:ident $(* $factor:expr)? ,)*) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
        pub enum Unit {
            $($(#[$m])* $ident,)*
        }
        impl std::fmt::Display for Unit {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match self {
                    $(Self::$ident => write!(f, $string), )*
                }
            }
        }
        impl std::str::FromStr for Unit {
            type Err = ();
            fn from_str(s: &str) -> Result<Self, Self::Err> {
                match s {
                    $($string => Ok(Self::$ident), )*
                    _ => Err(())
                }
            }
        }
        impl Unit {
            pub fn ty(self) -> Type {
                match self {
                    $(Self::$ident => Type::$ty, )*
                }
            }
            pub fn normalize(self, x: f64) -> f64 {
                match self {
                    $(Self::$ident => x $(* $factor as f64)?, )*
                }
            }
        }
    };
}
declare_units! {
    None = "" -> Float32,
    Percent = "%" -> Percent,
    Phx = "phx" -> PhysicalLength,
    Px = "px" -> LogicalLength,
    Cm = "cm" -> LogicalLength * 37.8,
    Mm = "mm" -> LogicalLength * 3.78,
    In = "in" -> LogicalLength * 96,
    Pt = "pt" -> LogicalLength * 96./72.,
    Rem = "rem" -> Rem,
    S = "s" -> Duration * 1000,
    Ms = "ms" -> Duration,
    Deg = "deg" -> Angle,
    Grad = "grad" -> Angle * 360./180.,
    Turn = "turn" -> Angle * 360.,
    Rad = "rad" -> Angle * 360./std::f32::consts::TAU,
}
impl Default for Unit {
    fn default() -> Self {
        Self::None
    }
}
#[derive(Debug, Clone, Copy)]
pub enum MinMaxOp {
    Min,
    Max,
}
#[derive(Debug, Clone, Default)]
pub enum Expression {
    #[default]
    Invalid,
    Uncompiled(SyntaxNode),
    StringLiteral(String),
    NumberLiteral(f64, Unit),
    BoolLiteral(bool),
    CallbackReference(NamedReference, Option<NodeOrToken>),
    PropertyReference(NamedReference),
    FunctionReference(NamedReference, Option<NodeOrToken>),
    BuiltinFunctionReference(BuiltinFunction, Option<SourceLocation>),
    MemberFunction {
        base: Box<Expression>,
        base_node: Option<NodeOrToken>,
        member: Box<Expression>,
    },
    BuiltinMacroReference(BuiltinMacroFunction, Option<NodeOrToken>),
    ElementReference(Weak<RefCell<Element>>),
    RepeaterIndexReference {
        element: Weak<RefCell<Element>>,
    },
    RepeaterModelReference {
        element: Weak<RefCell<Element>>,
    },
    FunctionParameterReference {
        index: usize,
        ty: Type,
    },
    StoreLocalVariable {
        name: String,
        value: Box<Expression>,
    },
    ReadLocalVariable {
        name: String,
        ty: Type,
    },
    StructFieldAccess {
        base: Box<Expression>,
        name: String,
    },
    ArrayIndex {
        array: Box<Expression>,
        index: Box<Expression>,
    },
    Cast {
        from: Box<Expression>,
        to: Type,
    },
    CodeBlock(Vec<Expression>),
    FunctionCall {
        function: Box<Expression>,
        arguments: Vec<Expression>,
        source_location: Option<SourceLocation>,
    },
    SelfAssignment {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
        op: char,
        node: Option<NodeOrToken>,
    },
    BinaryExpression {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
        op: char,
    },
    UnaryOp {
        sub: Box<Expression>,
        op: char,
    },
    ImageReference {
        resource_ref: ImageReference,
        source_location: Option<SourceLocation>,
        nine_slice: Option<[u16; 4]>,
    },
    Condition {
        condition: Box<Expression>,
        true_expr: Box<Expression>,
        false_expr: Box<Expression>,
    },
    Array {
        element_ty: Type,
        values: Vec<Expression>,
    },
    Struct {
        ty: Type,
        values: HashMap<String, Expression>,
    },
    PathData(Path),
    EasingCurve(EasingCurve),
    LinearGradient {
        angle: Box<Expression>,
        stops: Vec<(Expression, Expression)>,
    },
    RadialGradient {
        stops: Vec<(Expression, Expression)>,
    },
    EnumerationValue(EnumerationValue),
    ReturnStatement(Option<Box<Expression>>),
    LayoutCacheAccess {
        layout_cache_prop: NamedReference,
        index: usize,
        repeater_index: Option<Box<Expression>>,
    },
    ComputeLayoutInfo(crate::layout::Layout, crate::layout::Orientation),
    SolveLayout(crate::layout::Layout, crate::layout::Orientation),
    MinMax {
        ty: Type,
        op: MinMaxOp,
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },
    EmptyComponentFactory,
}
impl Expression {
    pub fn ty(&self) -> Type {
        match self {
            Expression::Invalid => Type::Invalid,
            Expression::Uncompiled(_) => Type::Invalid,
            Expression::StringLiteral(_) => Type::String,
            Expression::NumberLiteral(_, unit) => unit.ty(),
            Expression::BoolLiteral(_) => Type::Bool,
            Expression::CallbackReference(nr, _) => nr.ty(),
            Expression::FunctionReference(nr, _) => nr.ty(),
            Expression::PropertyReference(nr) => nr.ty(),
            Expression::BuiltinFunctionReference(funcref, _) => funcref.ty(),
            Expression::MemberFunction { member, .. } => member.ty(),
            Expression::BuiltinMacroReference { .. } => Type::Invalid, Expression::ElementReference(_) => Type::ElementReference,
            Expression::RepeaterIndexReference { .. } => Type::Int32,
            Expression::RepeaterModelReference { element } => element
                .upgrade()
                .unwrap()
                .borrow()
                .repeated
                .as_ref()
                .map_or(Type::Invalid, |e| model_inner_type(&e.model)),
            Expression::FunctionParameterReference { ty, .. } => ty.clone(),
            Expression::StructFieldAccess { base, name } => match base.ty() {
                Type::Struct { fields, .. } => {
                    fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone()
                }
                _ => Type::Invalid,
            },
            Expression::ArrayIndex { array, .. } => match array.ty() {
                Type::Array(ty) => (*ty).clone(),
                _ => Type::Invalid,
            },
            Expression::Cast { to, .. } => to.clone(),
            Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
            Expression::FunctionCall { function, .. } => match function.ty() {
                Type::Function { return_type, .. } => *return_type,
                Type::Callback { return_type, .. } => return_type.map_or(Type::Void, |x| *x),
                _ => Type::Invalid,
            },
            Expression::SelfAssignment { .. } => Type::Void,
            Expression::ImageReference { .. } => Type::Image,
            Expression::Condition { condition: _, true_expr, false_expr } => {
                let true_type = true_expr.ty();
                let false_type = false_expr.ty();
                if true_type == false_type {
                    true_type
                } else if true_type == Type::Invalid {
                    false_type
                } else if false_type == Type::Invalid {
                    true_type
                } else {
                    Type::Void
                }
            }
            Expression::BinaryExpression { op, lhs, rhs } => {
                if operator_class(*op) != OperatorClass::ArithmeticOp {
                    Type::Bool
                } else if *op == '+' || *op == '-' {
                    let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty());
                    if rhs_ty == lhs_ty {
                        rhs_ty
                    } else {
                        Type::Invalid
                    }
                } else {
                    debug_assert!(*op == '*' || *op == '/');
                    let unit_vec = |ty| {
                        if let Type::UnitProduct(v) = ty {
                            v
                        } else if let Some(u) = ty.default_unit() {
                            vec![(u, 1)]
                        } else {
                            vec![]
                        }
                    };
                    let mut l_units = unit_vec(lhs.ty());
                    let mut r_units = unit_vec(rhs.ty());
                    if *op == '/' {
                        for (_, power) in &mut r_units {
                            *power = -*power;
                        }
                    }
                    for (unit, power) in r_units {
                        if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) {
                            *p += power;
                        } else {
                            l_units.push((unit, power));
                        }
                    }
                    l_units.retain(|(_, p)| *p != 0);
                    l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) {
                        std::cmp::Ordering::Equal => u1.cmp(u2),
                        x => x,
                    });
                    if l_units.is_empty() {
                        Type::Float32
                    } else if l_units.len() == 1 && l_units[0].1 == 1 {
                        l_units[0].0.ty()
                    } else {
                        Type::UnitProduct(l_units)
                    }
                }
            }
            Expression::UnaryOp { sub, .. } => sub.ty(),
            Expression::Array { element_ty, .. } => Type::Array(Box::new(element_ty.clone())),
            Expression::Struct { ty, .. } => ty.clone(),
            Expression::PathData { .. } => Type::PathData,
            Expression::StoreLocalVariable { .. } => Type::Void,
            Expression::ReadLocalVariable { ty, .. } => ty.clone(),
            Expression::EasingCurve(_) => Type::Easing,
            Expression::LinearGradient { .. } => Type::Brush,
            Expression::RadialGradient { .. } => Type::Brush,
            Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
            Expression::ReturnStatement(_) => Type::Invalid,
            Expression::LayoutCacheAccess { .. } => Type::LogicalLength,
            Expression::ComputeLayoutInfo(..) => crate::layout::layout_info_type(),
            Expression::SolveLayout(..) => Type::LayoutCache,
            Expression::MinMax { ty, .. } => ty.clone(),
            Expression::EmptyComponentFactory => Type::ComponentFactory,
        }
    }
    pub fn visit(&self, mut visitor: impl FnMut(&Self)) {
        match self {
            Expression::Invalid => {}
            Expression::Uncompiled(_) => {}
            Expression::StringLiteral(_) => {}
            Expression::NumberLiteral(_, _) => {}
            Expression::BoolLiteral(_) => {}
            Expression::CallbackReference { .. } => {}
            Expression::PropertyReference { .. } => {}
            Expression::FunctionReference { .. } => {}
            Expression::FunctionParameterReference { .. } => {}
            Expression::BuiltinFunctionReference { .. } => {}
            Expression::MemberFunction { base, member, .. } => {
                visitor(base);
                visitor(member);
            }
            Expression::BuiltinMacroReference { .. } => {}
            Expression::ElementReference(_) => {}
            Expression::StructFieldAccess { base, .. } => visitor(base),
            Expression::ArrayIndex { array, index } => {
                visitor(array);
                visitor(index);
            }
            Expression::RepeaterIndexReference { .. } => {}
            Expression::RepeaterModelReference { .. } => {}
            Expression::Cast { from, .. } => visitor(from),
            Expression::CodeBlock(sub) => {
                sub.iter().for_each(visitor);
            }
            Expression::FunctionCall { function, arguments, source_location: _ } => {
                visitor(function);
                arguments.iter().for_each(visitor);
            }
            Expression::SelfAssignment { lhs, rhs, .. } => {
                visitor(lhs);
                visitor(rhs);
            }
            Expression::ImageReference { .. } => {}
            Expression::Condition { condition, true_expr, false_expr } => {
                visitor(condition);
                visitor(true_expr);
                visitor(false_expr);
            }
            Expression::BinaryExpression { lhs, rhs, .. } => {
                visitor(lhs);
                visitor(rhs);
            }
            Expression::UnaryOp { sub, .. } => visitor(sub),
            Expression::Array { values, .. } => {
                for x in values {
                    visitor(x);
                }
            }
            Expression::Struct { values, .. } => {
                for x in values.values() {
                    visitor(x);
                }
            }
            Expression::PathData(data) => match data {
                Path::Elements(elements) => {
                    for element in elements {
                        element.bindings.values().for_each(|binding| visitor(&binding.borrow()))
                    }
                }
                Path::Events(events, coordinates) => {
                    events.iter().chain(coordinates.iter()).for_each(visitor);
                }
                Path::Commands(commands) => visitor(commands),
            },
            Expression::StoreLocalVariable { value, .. } => visitor(value),
            Expression::ReadLocalVariable { .. } => {}
            Expression::EasingCurve(_) => {}
            Expression::LinearGradient { angle, stops } => {
                visitor(angle);
                for (c, s) in stops {
                    visitor(c);
                    visitor(s);
                }
            }
            Expression::RadialGradient { stops } => {
                for (c, s) in stops {
                    visitor(c);
                    visitor(s);
                }
            }
            Expression::EnumerationValue(_) => {}
            Expression::ReturnStatement(expr) => {
                expr.as_deref().map(visitor);
            }
            Expression::LayoutCacheAccess { repeater_index, .. } => {
                repeater_index.as_deref().map(visitor);
            }
            Expression::ComputeLayoutInfo(..) => {}
            Expression::SolveLayout(..) => {}
            Expression::MinMax { lhs, rhs, .. } => {
                visitor(lhs);
                visitor(rhs);
            }
            Expression::EmptyComponentFactory => {}
        }
    }
    pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) {
        match self {
            Expression::Invalid => {}
            Expression::Uncompiled(_) => {}
            Expression::StringLiteral(_) => {}
            Expression::NumberLiteral(_, _) => {}
            Expression::BoolLiteral(_) => {}
            Expression::CallbackReference { .. } => {}
            Expression::PropertyReference { .. } => {}
            Expression::FunctionReference { .. } => {}
            Expression::FunctionParameterReference { .. } => {}
            Expression::BuiltinFunctionReference { .. } => {}
            Expression::MemberFunction { base, member, .. } => {
                visitor(base);
                visitor(member);
            }
            Expression::BuiltinMacroReference { .. } => {}
            Expression::ElementReference(_) => {}
            Expression::StructFieldAccess { base, .. } => visitor(base),
            Expression::ArrayIndex { array, index } => {
                visitor(array);
                visitor(index);
            }
            Expression::RepeaterIndexReference { .. } => {}
            Expression::RepeaterModelReference { .. } => {}
            Expression::Cast { from, .. } => visitor(from),
            Expression::CodeBlock(sub) => {
                sub.iter_mut().for_each(visitor);
            }
            Expression::FunctionCall { function, arguments, source_location: _ } => {
                visitor(function);
                arguments.iter_mut().for_each(visitor);
            }
            Expression::SelfAssignment { lhs, rhs, .. } => {
                visitor(lhs);
                visitor(rhs);
            }
            Expression::ImageReference { .. } => {}
            Expression::Condition { condition, true_expr, false_expr } => {
                visitor(condition);
                visitor(true_expr);
                visitor(false_expr);
            }
            Expression::BinaryExpression { lhs, rhs, .. } => {
                visitor(lhs);
                visitor(rhs);
            }
            Expression::UnaryOp { sub, .. } => visitor(sub),
            Expression::Array { values, .. } => {
                for x in values {
                    visitor(x);
                }
            }
            Expression::Struct { values, .. } => {
                for x in values.values_mut() {
                    visitor(x);
                }
            }
            Expression::PathData(data) => match data {
                Path::Elements(elements) => {
                    for element in elements {
                        element
                            .bindings
                            .values_mut()
                            .for_each(|binding| visitor(&mut binding.borrow_mut()))
                    }
                }
                Path::Events(events, coordinates) => {
                    events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
                }
                Path::Commands(commands) => visitor(commands),
            },
            Expression::StoreLocalVariable { value, .. } => visitor(value),
            Expression::ReadLocalVariable { .. } => {}
            Expression::EasingCurve(_) => {}
            Expression::LinearGradient { angle, stops } => {
                visitor(angle);
                for (c, s) in stops {
                    visitor(c);
                    visitor(s);
                }
            }
            Expression::RadialGradient { stops } => {
                for (c, s) in stops {
                    visitor(c);
                    visitor(s);
                }
            }
            Expression::EnumerationValue(_) => {}
            Expression::ReturnStatement(expr) => {
                expr.as_deref_mut().map(visitor);
            }
            Expression::LayoutCacheAccess { repeater_index, .. } => {
                repeater_index.as_deref_mut().map(visitor);
            }
            Expression::ComputeLayoutInfo(..) => {}
            Expression::SolveLayout(..) => {}
            Expression::MinMax { lhs, rhs, .. } => {
                visitor(lhs);
                visitor(rhs);
            }
            Expression::EmptyComponentFactory => {}
        }
    }
    pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) {
        visitor(self);
        self.visit(|e| e.visit_recursive(visitor));
    }
    pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
        visitor(self);
        self.visit_mut(|e| e.visit_recursive_mut(visitor));
    }
    pub fn is_constant(&self) -> bool {
        match self {
            Expression::Invalid => true,
            Expression::Uncompiled(_) => false,
            Expression::StringLiteral(_) => true,
            Expression::NumberLiteral(_, _) => true,
            Expression::BoolLiteral(_) => true,
            Expression::CallbackReference { .. } => false,
            Expression::FunctionReference(nr, _) => nr.is_constant(),
            Expression::PropertyReference(nr) => nr.is_constant(),
            Expression::BuiltinFunctionReference(func, _) => func.is_const(),
            Expression::MemberFunction { .. } => false,
            Expression::ElementReference(_) => false,
            Expression::RepeaterIndexReference { .. } => false,
            Expression::RepeaterModelReference { .. } => false,
            Expression::FunctionParameterReference { .. } => false,
            Expression::BuiltinMacroReference { .. } => true,
            Expression::StructFieldAccess { base, .. } => base.is_constant(),
            Expression::ArrayIndex { array, index } => array.is_constant() && index.is_constant(),
            Expression::Cast { from, .. } => from.is_constant(),
            Expression::CodeBlock(sub) => sub.len() == 1 && sub.first().unwrap().is_constant(),
            Expression::FunctionCall { function, arguments, .. } => {
                function.is_constant() && arguments.iter().all(|a| a.is_constant())
            }
            Expression::SelfAssignment { .. } => false,
            Expression::ImageReference { .. } => true,
            Expression::Condition { condition, false_expr, true_expr } => {
                condition.is_constant() && false_expr.is_constant() && true_expr.is_constant()
            }
            Expression::BinaryExpression { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(),
            Expression::UnaryOp { sub, .. } => sub.is_constant(),
            Expression::Array { .. } => false,
            Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant()),
            Expression::PathData(data) => match data {
                Path::Elements(elements) => elements
                    .iter()
                    .all(|element| element.bindings.values().all(|v| v.borrow().is_constant())),
                Path::Events(_, _) => true,
                Path::Commands(_) => false,
            },
            Expression::StoreLocalVariable { .. } => false,
            Expression::ReadLocalVariable { .. } => false,
            Expression::EasingCurve(_) => true,
            Expression::LinearGradient { angle, stops } => {
                angle.is_constant() && stops.iter().all(|(c, s)| c.is_constant() && s.is_constant())
            }
            Expression::RadialGradient { stops } => {
                stops.iter().all(|(c, s)| c.is_constant() && s.is_constant())
            }
            Expression::EnumerationValue(_) => true,
            Expression::ReturnStatement(expr) => {
                expr.as_ref().map_or(true, |expr| expr.is_constant())
            }
            Expression::LayoutCacheAccess { .. } => false,
            Expression::ComputeLayoutInfo(..) => false,
            Expression::SolveLayout(..) => false,
            Expression::MinMax { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(),
            Expression::EmptyComponentFactory => true,
        }
    }
    #[must_use]
    pub fn maybe_convert_to(
        self,
        target_type: Type,
        node: &impl Spanned,
        diag: &mut BuildDiagnostics,
    ) -> Expression {
        let ty = self.ty();
        if ty == target_type
            || target_type == Type::Void
            || target_type == Type::Invalid
            || ty == Type::Invalid
        {
            self
        } else if ty.can_convert(&target_type) {
            let from = match (ty, &target_type) {
                (Type::Percent, Type::Float32) => Expression::BinaryExpression {
                    lhs: Box::new(self),
                    rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
                    op: '*',
                },
                (
                    ref from_ty @ Type::Struct { fields: ref left, .. },
                    Type::Struct { fields: right, .. },
                ) if left != right => {
                    if let Expression::Struct { mut values, .. } = self {
                        let mut new_values = HashMap::new();
                        for (key, ty) in right {
                            let (key, expression) = values.remove_entry(key).map_or_else(
                                || (key.clone(), Expression::default_value_for_type(ty)),
                                |(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)),
                            );
                            new_values.insert(key, expression);
                        }
                        return Expression::Struct { values: new_values, ty: target_type };
                    }
                    let var_name = "tmpobj";
                    let mut new_values = HashMap::new();
                    for (key, ty) in right {
                        let expression = if left.contains_key(key) {
                            Expression::StructFieldAccess {
                                base: Box::new(Expression::ReadLocalVariable {
                                    name: var_name.into(),
                                    ty: from_ty.clone(),
                                }),
                                name: key.clone(),
                            }
                            .maybe_convert_to(ty.clone(), node, diag)
                        } else {
                            Expression::default_value_for_type(ty)
                        };
                        new_values.insert(key.clone(), expression);
                    }
                    return Expression::CodeBlock(vec![
                        Expression::StoreLocalVariable {
                            name: var_name.into(),
                            value: Box::new(self),
                        },
                        Expression::Struct { values: new_values, ty: target_type },
                    ]);
                }
                (left, right) => match (left.as_unit_product(), right.as_unit_product()) {
                    (Some(left), Some(right)) => {
                        if let Some(conversion_powers) =
                            crate::langtype::unit_product_length_conversion(&left, &right)
                        {
                            let apply_power =
                                |mut result, power: i8, builtin_fn: BuiltinFunction| {
                                    let op = if power < 0 { '*' } else { '/' };
                                    for _ in 0..power.abs() {
                                        result = Expression::BinaryExpression {
                                            lhs: Box::new(result),
                                            rhs: Box::new(Expression::FunctionCall {
                                                function: Box::new(
                                                    Expression::BuiltinFunctionReference(
                                                        builtin_fn.clone(),
                                                        Some(node.to_source_location()),
                                                    ),
                                                ),
                                                arguments: vec![],
                                                source_location: Some(node.to_source_location()),
                                            }),
                                            op,
                                        }
                                    }
                                    result
                                };
                            let mut result = self;
                            if conversion_powers.rem_to_px_power != 0 {
                                result = apply_power(
                                    result,
                                    conversion_powers.rem_to_px_power,
                                    BuiltinFunction::GetWindowDefaultFontSize,
                                )
                            }
                            if conversion_powers.px_to_phx_power != 0 {
                                result = apply_power(
                                    result,
                                    conversion_powers.px_to_phx_power,
                                    BuiltinFunction::GetWindowScaleFactor,
                                )
                            }
                            result
                        } else {
                            self
                        }
                    }
                    _ => self,
                },
            };
            Expression::Cast { from: Box::new(from), to: target_type }
        } else if matches!(
            (&ty, &target_type, &self),
            (Type::Array(_), Type::Array(_), Expression::Array { .. })
        ) {
            match (self, target_type) {
                (Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array {
                    values: values
                        .into_iter()
                        .map(|e| e.maybe_convert_to((*target_type).clone(), node, diag))
                        .take_while(|e| !matches!(e, Expression::Invalid))
                        .collect(),
                    element_ty: *target_type,
                },
                _ => unreachable!(),
            }
        } else if let (Type::Struct { fields, .. }, Expression::Struct { values, .. }) =
            (&target_type, &self)
        {
            let mut fields = fields.clone();
            let mut new_values = HashMap::new();
            for (f, v) in values {
                if let Some(t) = fields.remove(f) {
                    new_values.insert(f.clone(), v.clone().maybe_convert_to(t, node, diag));
                } else {
                    diag.push_error(format!("Cannot convert {} to {}", ty, target_type), node);
                    return self;
                }
            }
            for (f, t) in fields {
                new_values.insert(f, Expression::default_value_for_type(&t));
            }
            Expression::Struct { ty: target_type, values: new_values }
        } else {
            let mut message = format!("Cannot convert {} to {}", ty, target_type);
            if let Some(from_unit) = ty.default_unit() {
                if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) {
                    message = format!(
                        "{}. Divide by 1{} to convert to a plain number",
                        message, from_unit
                    );
                }
            } else if let Some(to_unit) = target_type.default_unit() {
                if matches!(ty, Type::Int32 | Type::Float32) {
                    if let Expression::NumberLiteral(value, Unit::None) = self {
                        if value == 0. {
                            return Expression::NumberLiteral(0., to_unit);
                        }
                    }
                    message = format!(
                        "{}. Use an unit, or multiply by 1{} to convert explicitly",
                        message, to_unit
                    );
                }
            }
            diag.push_error(message, node);
            self
        }
    }
    pub fn default_value_for_type(ty: &Type) -> Expression {
        match ty {
            Type::Invalid
            | Type::Callback { .. }
            | Type::Function { .. }
            | Type::InferredProperty
            | Type::InferredCallback
            | Type::ElementReference
            | Type::LayoutCache => Expression::Invalid,
            Type::Void => Expression::CodeBlock(vec![]),
            Type::Float32 => Expression::NumberLiteral(0., Unit::None),
            Type::String => Expression::StringLiteral(String::new()),
            Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast {
                from: Box::new(Expression::NumberLiteral(0., Unit::None)),
                to: ty.clone(),
            },
            Type::Duration => Expression::NumberLiteral(0., Unit::Ms),
            Type::Angle => Expression::NumberLiteral(0., Unit::Deg),
            Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx),
            Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px),
            Type::Rem => Expression::NumberLiteral(0., Unit::Rem),
            Type::Percent => Expression::NumberLiteral(100., Unit::Percent),
            Type::Image => Expression::ImageReference {
                resource_ref: ImageReference::None,
                source_location: None,
                nine_slice: None,
            },
            Type::Bool => Expression::BoolLiteral(false),
            Type::Model => Expression::Invalid,
            Type::PathData => Expression::PathData(Path::Elements(vec![])),
            Type::Array(element_ty) => {
                Expression::Array { element_ty: (**element_ty).clone(), values: vec![] }
            }
            Type::Struct { fields, .. } => Expression::Struct {
                ty: ty.clone(),
                values: fields
                    .iter()
                    .map(|(k, v)| (k.clone(), Expression::default_value_for_type(v)))
                    .collect(),
            },
            Type::Easing => Expression::EasingCurve(EasingCurve::default()),
            Type::Brush => Expression::Cast {
                from: Box::new(Expression::default_value_for_type(&Type::Color)),
                to: Type::Brush,
            },
            Type::Enumeration(enumeration) => {
                Expression::EnumerationValue(enumeration.clone().default_value())
            }
            Type::ComponentFactory => Expression::EmptyComponentFactory,
        }
    }
    pub fn try_set_rw(
        &mut self,
        ctx: &mut LookupCtx,
        what: &'static str,
        node: &dyn Spanned,
    ) -> bool {
        match self {
            Expression::PropertyReference(nr) => {
                nr.mark_as_set();
                let mut lookup = nr.element().borrow().lookup_property(nr.name());
                lookup.is_local_to_component &= ctx.is_local_element(&nr.element());
                if lookup.property_visibility == PropertyVisibility::Constexpr {
                    ctx.diag.push_error(
                        "The property must be known at compile time and cannot be changed at runtime"
                            .into(),
                        node,
                    );
                    false
                } else if lookup.is_valid_for_assignment() {
                    if !nr
                        .element()
                        .borrow()
                        .property_analysis
                        .borrow()
                        .get(nr.name())
                        .map_or(false, |d| d.is_linked_to_read_only)
                    {
                        true
                    } else if ctx.is_legacy_component() {
                        ctx.diag.push_warning("Modifying a property that is linked to a read-only property is deprecated".into(), node);
                        true
                    } else {
                        ctx.diag.push_error(
                            "Cannot modify a property that is linked to a read-only property"
                                .into(),
                            node,
                        );
                        false
                    }
                } else if ctx.is_legacy_component()
                    && lookup.property_visibility == PropertyVisibility::Output
                {
                    ctx.diag
                        .push_warning(format!("{what} on an output property is deprecated"), node);
                    true
                } else {
                    ctx.diag.push_error(
                        format!("{what} on a {} property", lookup.property_visibility),
                        node,
                    );
                    false
                }
            }
            Expression::StructFieldAccess { base, .. } => base.try_set_rw(ctx, what, node),
            Expression::RepeaterModelReference { .. } => true,
            Expression::ArrayIndex { array, .. } => array.try_set_rw(ctx, what, node),
            _ => {
                ctx.diag.push_error(format!("{what} needs to be done on a property"), node);
                false
            }
        }
    }
}
fn model_inner_type(model: &Expression) -> Type {
    match model {
        Expression::Cast { from, to: Type::Model } => model_inner_type(from),
        Expression::CodeBlock(cb) => cb.last().map_or(Type::Invalid, model_inner_type),
        _ => match model.ty() {
            Type::Float32 | Type::Int32 => Type::Int32,
            Type::Array(elem) => *elem,
            _ => Type::Invalid,
        },
    }
}
#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
pub struct BindingExpression {
    #[deref]
    #[deref_mut]
    pub expression: Expression,
    pub span: Option<SourceLocation>,
    pub priority: i32,
    pub animation: Option<PropertyAnimation>,
    pub analysis: Option<BindingAnalysis>,
    pub two_way_bindings: Vec<NamedReference>,
}
impl std::convert::From<Expression> for BindingExpression {
    fn from(expression: Expression) -> Self {
        Self {
            expression,
            span: None,
            priority: 0,
            animation: Default::default(),
            analysis: Default::default(),
            two_way_bindings: Default::default(),
        }
    }
}
impl BindingExpression {
    pub fn new_uncompiled(node: SyntaxNode) -> Self {
        Self {
            expression: Expression::Uncompiled(node.clone()),
            span: Some(node.to_source_location()),
            priority: 1,
            animation: Default::default(),
            analysis: Default::default(),
            two_way_bindings: Default::default(),
        }
    }
    pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self {
        Self {
            expression,
            span: Some(span),
            priority: 0,
            animation: Default::default(),
            analysis: Default::default(),
            two_way_bindings: Default::default(),
        }
    }
    pub fn new_two_way(other: NamedReference) -> Self {
        Self {
            expression: Expression::Invalid,
            span: None,
            priority: 0,
            animation: Default::default(),
            analysis: Default::default(),
            two_way_bindings: vec![other],
        }
    }
    pub fn merge_with(&mut self, other: &Self) -> bool {
        if self.animation.is_none() {
            self.animation.clone_from(&other.animation);
        }
        let has_binding = self.has_binding();
        self.two_way_bindings.extend_from_slice(&other.two_way_bindings);
        if !has_binding {
            self.priority = other.priority;
            self.expression = other.expression.clone();
            true
        } else {
            false
        }
    }
    pub fn has_binding(&self) -> bool {
        !matches!(self.expression, Expression::Invalid) || !self.two_way_bindings.is_empty()
    }
}
impl Spanned for BindingExpression {
    fn span(&self) -> crate::diagnostics::Span {
        self.span.as_ref().map(|x| x.span()).unwrap_or_default()
    }
    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
        self.span.as_ref().and_then(|x| x.source_file())
    }
}
#[derive(Default, Debug, Clone)]
pub struct BindingAnalysis {
    pub is_in_binding_loop: Cell<bool>,
    pub is_const: bool,
    pub no_external_dependencies: bool,
}
#[derive(Debug, Clone)]
pub enum Path {
    Elements(Vec<PathElement>),
    Events(Vec<Expression>, Vec<Expression>),
    Commands(Box<Expression>), }
#[derive(Debug, Clone)]
pub struct PathElement {
    pub element_type: Rc<BuiltinElement>,
    pub bindings: BindingsMap,
}
#[derive(Clone, Debug, Default)]
pub enum EasingCurve {
    #[default]
    Linear,
    CubicBezier(f32, f32, f32, f32),
    EaseInElastic,
    EaseOutElastic,
    EaseInOutElastic,
    EaseInBounce,
    EaseOutBounce,
    EaseInOutBounce,
    }
#[derive(Clone, Debug)]
pub enum ImageReference {
    None,
    AbsolutePath(String),
    EmbeddedData { resource_id: usize, extension: String },
    EmbeddedTexture { resource_id: usize },
}
pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result {
    match expression {
        Expression::Invalid => write!(f, "<invalid>"),
        Expression::Uncompiled(u) => write!(f, "{:?}", u),
        Expression::StringLiteral(s) => write!(f, "{:?}", s),
        Expression::NumberLiteral(vl, unit) => write!(f, "{}{}", vl, unit),
        Expression::BoolLiteral(b) => write!(f, "{:?}", b),
        Expression::CallbackReference(a, _) => write!(f, "{:?}", a),
        Expression::PropertyReference(a) => write!(f, "{:?}", a),
        Expression::FunctionReference(a, _) => write!(f, "{:?}", a),
        Expression::BuiltinFunctionReference(a, _) => write!(f, "{:?}", a),
        Expression::MemberFunction { base, base_node: _, member } => {
            pretty_print(f, base)?;
            write!(f, ".")?;
            pretty_print(f, member)
        }
        Expression::BuiltinMacroReference(a, _) => write!(f, "{:?}", a),
        Expression::ElementReference(a) => write!(f, "{:?}", a),
        Expression::RepeaterIndexReference { element } => {
            crate::namedreference::pretty_print_element_ref(f, element)
        }
        Expression::RepeaterModelReference { element } => {
            crate::namedreference::pretty_print_element_ref(f, element)?;
            write!(f, ".@model")
        }
        Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_{}", index),
        Expression::StoreLocalVariable { name, value } => {
            write!(f, "{} = ", name)?;
            pretty_print(f, value)
        }
        Expression::ReadLocalVariable { name, ty: _ } => write!(f, "{}", name),
        Expression::StructFieldAccess { base, name } => {
            pretty_print(f, base)?;
            write!(f, ".{}", name)
        }
        Expression::ArrayIndex { array, index } => {
            pretty_print(f, array)?;
            write!(f, "[")?;
            pretty_print(f, index)?;
            write!(f, "]")
        }
        Expression::Cast { from, to } => {
            write!(f, "(")?;
            pretty_print(f, from)?;
            write!(f, "/* as {} */)", to)
        }
        Expression::CodeBlock(c) => {
            write!(f, "{{ ")?;
            for e in c {
                pretty_print(f, e)?;
                write!(f, "; ")?;
            }
            write!(f, "}}")
        }
        Expression::FunctionCall { function, arguments, source_location: _ } => {
            pretty_print(f, function)?;
            write!(f, "(")?;
            for e in arguments {
                pretty_print(f, e)?;
                write!(f, ", ")?;
            }
            write!(f, ")")
        }
        Expression::SelfAssignment { lhs, rhs, op, .. } => {
            pretty_print(f, lhs)?;
            write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
            pretty_print(f, rhs)
        }
        Expression::BinaryExpression { lhs, rhs, op } => {
            write!(f, "(")?;
            pretty_print(f, lhs)?;
            match *op {
                '=' | '!' => write!(f, " {}= ", op)?,
                _ => write!(f, " {} ", op)?,
            };
            pretty_print(f, rhs)?;
            write!(f, ")")
        }
        Expression::UnaryOp { sub, op } => {
            write!(f, "{}", op)?;
            pretty_print(f, sub)
        }
        Expression::ImageReference { resource_ref, .. } => write!(f, "{:?}", resource_ref),
        Expression::Condition { condition, true_expr, false_expr } => {
            write!(f, "if (")?;
            pretty_print(f, condition)?;
            write!(f, ") {{ ")?;
            pretty_print(f, true_expr)?;
            write!(f, " }} else {{ ")?;
            pretty_print(f, false_expr)?;
            write!(f, " }}")
        }
        Expression::Array { element_ty: _, values } => {
            write!(f, "[")?;
            for e in values {
                pretty_print(f, e)?;
                write!(f, ", ")?;
            }
            write!(f, "]")
        }
        Expression::Struct { ty: _, values } => {
            write!(f, "{{ ")?;
            for (name, e) in values {
                write!(f, "{}: ", name)?;
                pretty_print(f, e)?;
                write!(f, ", ")?;
            }
            write!(f, " }}")
        }
        Expression::PathData(data) => write!(f, "{:?}", data),
        Expression::EasingCurve(e) => write!(f, "{:?}", e),
        Expression::LinearGradient { angle, stops } => {
            write!(f, "@linear-gradient(")?;
            pretty_print(f, angle)?;
            for (c, s) in stops {
                write!(f, ", ")?;
                pretty_print(f, c)?;
                write!(f, "  ")?;
                pretty_print(f, s)?;
            }
            write!(f, ")")
        }
        Expression::RadialGradient { stops } => {
            write!(f, "@radial-gradient(circle")?;
            for (c, s) in stops {
                write!(f, ", ")?;
                pretty_print(f, c)?;
                write!(f, "  ")?;
                pretty_print(f, s)?;
            }
            write!(f, ")")
        }
        Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value) {
            Some(val) => write!(f, "{}.{}", e.enumeration.name, val),
            None => write!(f, "{}.{}", e.enumeration.name, e.value),
        },
        Expression::ReturnStatement(e) => {
            write!(f, "return ")?;
            e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(()))
        }
        Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
            write!(
                f,
                "{:?}[{}{}]",
                layout_cache_prop,
                index,
                if repeater_index.is_some() { " + $index" } else { "" }
            )
        }
        Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)"),
        Expression::SolveLayout(..) => write!(f, "solve_layout(..)"),
        Expression::MinMax { ty: _, op, lhs, rhs } => {
            match op {
                MinMaxOp::Min => write!(f, "min(")?,
                MinMaxOp::Max => write!(f, "max(")?,
            }
            pretty_print(f, lhs)?;
            write!(f, ", ")?;
            pretty_print(f, rhs)?;
            write!(f, ")")
        }
        Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>"),
    }
}