///|
pub struct FilterFunction {
  expression : Expression
} derive(Eq, Debug)

///|
pub struct FilterList {
  expression : Expression
} derive(Eq, Debug)

///|
pub struct Shadow {
  x : Expression
  y : Expression
  blur_value : Expression?
  spread_value : Expression?
  color_value : Expression?
  inset_value : Bool
} derive(Eq, Debug)

///|
pub struct BoxShadowList {
  expression : Expression
} derive(Eq, Debug)

///|
pub struct TextShadowList {
  expression : Expression
} derive(Eq, Debug)

///|
fn effect_length(value : Expression, nonnegative : Bool) -> Expression {
  let value = match value.node {
    Group(_, _, _, _) => value.constrain("no-percentage")
    _ => value.constrain("math:length")
  }
  if nonnegative {
    atom_nonnegative(value)
  } else {
    value
  }
}

///|
pub fn[T : LengthMathInput] blur_filter(value : T) -> FilterFunction {
  {
    expression: group_expression(
      "filter",
      "blur(",
      [effect_length(value.length_math().expression(), true)],
      "",
      ")",
    ),
  }
}

///|
pub trait FilterAmountInput {
  fn filter_amount(Self) -> Expression
}

///|
pub trait FilterAmountDimension {}

///|
fn amount_filter(name : String, value : Expression) -> FilterFunction {
  {
    expression: group_expression(
      "filter",
      name + "(",
      [atom_nonnegative(value)],
      "",
      ")",
    ),
  }
}

///|
pub fn[T : FilterAmountInput] brightness_filter(value : T) -> FilterFunction {
  amount_filter("brightness", value.filter_amount())
}

///|
pub fn[T : FilterAmountInput] contrast_filter(value : T) -> FilterFunction {
  amount_filter("contrast", value.filter_amount())
}

///|
pub fn[T : FilterAmountInput] grayscale_filter(value : T) -> FilterFunction {
  amount_filter("grayscale", value.filter_amount())
}

///|
pub fn[T : AngleMathInput] hue_rotate_filter(value : T) -> FilterFunction {
  {
    expression: group_expression(
      "filter",
      "hue-rotate(",
      [value.angle_math().expression()],
      "",
      ")",
    ),
  }
}

///|
pub fn authored_filter_list(values : Array[FilterFunction]) -> FilterList {
  {
    expression: if values.is_empty() {
      literal_expression(modern_invalid("filter"), "filter-list")
    } else {
      group_expression(
        "filter-list",
        "",
        values.map(value => value.expression),
        " ",
        "",
      )
    },
  }
}

///|
pub fn[X : LengthMathInput, Y : LengthMathInput] shadow(
  x : X,
  y : Y,
  blur~ : Length? = None,
  spread~ : Length? = None,
  color~ : Color? = None,
  inset~ : Bool = false,
) -> Shadow {
  let result : Shadow = {
    x: effect_length(x.length_math().expression(), false),
    y: effect_length(y.length_math().expression(), false),
    blur_value: None,
    spread_value: None,
    color_value: None,
    inset_value: inset,
  }
  let result = match blur {
    Some(value) => result.blur(value)
    None => result
  }
  let result = match spread {
    Some(value) => result.spread(value)
    None => result
  }
  match color {
    Some(value) => result.color(value)
    None => result
  }
}

///|
pub fn[T : LengthMathInput] Shadow::blur(self : Shadow, value : T) -> Shadow {
  {
    ..self,
    blur_value: Some(effect_length(value.length_math().expression(), true)),
  }
}

///|
pub fn[T : LengthMathInput] Shadow::spread(self : Shadow, value : T) -> Shadow {
  {
    ..self,
    spread_value: Some(effect_length(value.length_math().expression(), false)),
  }
}

///|
pub fn[T : GradientColorInput] Shadow::color(
  self : Shadow,
  value : T,
) -> Shadow {
  { ..self, color_value: Some(value.gradient_color()), }
}

///|
pub fn Shadow::inset(self : Shadow) -> Shadow {
  { ..self, inset_value: true, }
}

///|
pub fn Shadow::expression(self : Shadow) -> Expression {
  let parts = []
  if self.inset_value {
    parts.push(literal_expression("inset", "keyword"))
  }
  parts.push(self.x)
  parts.push(self.y)
  match self.blur_value {
    Some(value) => parts.push(value)
    None =>
      if self.spread_value is Some(_) {
        parts.push(literal_expression("0", "length"))
      }
  }
  match self.spread_value {
    Some(value) => parts.push(value)
    None => ()
  }
  match self.color_value {
    Some(value) => parts.push(value)
    None => ()
  }
  group_expression("shadow", "", parts, " ", "")
}

///|
pub fn authored_box_shadows(values : Array[Shadow]) -> BoxShadowList {
  {
    expression: if values.is_empty() {
      literal_expression(modern_invalid("box-shadow"), "box-shadow-list")
    } else {
      group_expression(
        "box-shadow-list",
        "",
        values.map(value => value.expression()),
        ", ",
        "",
      )
    },
  }
}

///|
pub fn authored_text_shadows(values : Array[Shadow]) -> TextShadowList {
  {
    expression: if values.is_empty() ||
      values.any(value => value.inset_value || value.spread_value is Some(_)) {
      literal_expression(
        issue_css(
          "STYLE_TEXT_SHADOW", "Text shadows cannot contain inset or spread and require a shadow",
        ),
        "text-shadow-list",
      )
    } else {
      group_expression(
        "text-shadow-list",
        "",
        values.map(value => value.expression()),
        ", ",
        "",
      )
    },
  }
}

///|
pub fn FilterList::expression(self : FilterList) -> Expression {
  self.expression
}

///|
pub fn BoxShadowList::expression(self : BoxShadowList) -> Expression {
  self.expression
}

///|
pub fn TextShadowList::expression(self : TextShadowList) -> Expression {
  self.expression
}