use crate::style::{LinearGradientDirection, *};

#[derive(Debug, Default)]
pub(super) struct TailwindDeclarationBuilder {
  pub(super) declarations: StyleDeclarationBlock,
  pub(super) gradient_state: TwGradientState,
  pub(super) transform_state: TwTransformState,
  pub(super) shadow: Option<Vec<BoxShadow>>,
  pub(super) shadow_important: bool,
  pub(super) shadow_color: Option<ColorInput>,
  pub(super) text_shadow: Option<Vec<TextShadow>>,
  pub(super) text_shadow_important: bool,
  pub(super) text_shadow_color: Option<ColorInput>,
  pub(super) filter: Option<Filters>,
  pub(super) filter_important: bool,
  pub(super) backdrop_filter: Option<Filters>,
  pub(super) backdrop_filter_important: bool,
  pub(super) grid_column: TwGridLineState,
  pub(super) grid_row: TwGridLineState,
}

#[derive(Debug, Default)]
pub(super) struct TwGridLineState {
  pub(super) start: Option<GridPlacement>,
  pub(super) end: Option<GridPlacement>,
  pub(super) start_important: bool,
  pub(super) end_important: bool,
}

impl TwGridLineState {
  fn set_line(&mut self, grid_line: GridLine, start_important: bool, end_important: bool) {
    self.set_start(grid_line.start, start_important);
    self.set_end(grid_line.end, end_important);
  }

  fn set_start(&mut self, grid_placement: GridPlacement, important: bool) {
    self.start = Some(grid_placement);
    self.start_important = important;
  }

  fn set_end(&mut self, grid_placement: GridPlacement, important: bool) {
    self.end = Some(grid_placement);
    self.end_important = important;
  }

  fn push_declarations(
    self,
    declarations: &mut StyleDeclarationBlock,
    start_decl: fn(GridPlacement) -> StyleDeclaration,
    end_decl: fn(GridPlacement) -> StyleDeclaration,
  ) {
    if let Some(start) = self.start {
      declarations.push(start_decl(start), self.start_important);
    }

    if let Some(end) = self.end {
      declarations.push(end_decl(end), self.end_important);
    }
  }
}

impl TailwindDeclarationBuilder {
  pub(super) fn push(&mut self, declaration: StyleDeclaration, important: bool) {
    self.declarations.push(declaration, important);
  }

  pub(super) fn set_shadow_layers<I: IntoIterator<Item = BoxShadow>>(
    &mut self,
    layers: I,
    important: bool,
  ) {
    let mut shadows: Vec<BoxShadow> = layers.into_iter().collect();
    if let Some(color) = self.shadow_color {
      for shadow in &mut shadows {
        shadow.color = color;
      }
    }

    self.shadow = Some(shadows);
    self.shadow_important = important;
  }

  pub(super) fn reset_shadow(&mut self, important: bool) {
    self.shadow = None;
    self.shadow_color = None;
    self.shadow_important = important;
  }

  pub(super) fn reset_text_shadow(&mut self, important: bool) {
    self.text_shadow = None;
    self.text_shadow_color = None;
    self.text_shadow_important = important;
  }

  pub(super) fn set_shadow_color(&mut self, color: ColorInput, important: bool) {
    self.shadow_color = Some(color);
    self.shadow_important = important;

    if let Some(shadows) = self.shadow.as_mut() {
      for shadow in shadows {
        shadow.color = color;
      }
    }
  }

  pub(super) fn set_text_shadow_layers<I: IntoIterator<Item = TextShadow>>(
    &mut self,
    layers: I,
    important: bool,
  ) {
    let mut shadows: Vec<TextShadow> = layers.into_iter().collect();
    if let Some(color) = self.text_shadow_color {
      for shadow in &mut shadows {
        shadow.color = color;
      }
    }

    self.text_shadow = Some(shadows);
    self.text_shadow_important = important;
  }

  pub(super) fn set_text_shadow_color(&mut self, color: ColorInput, important: bool) {
    self.text_shadow_color = Some(color);
    self.text_shadow_important = important;

    if let Some(shadows) = self.text_shadow.as_mut() {
      for shadow in shadows {
        shadow.color = color;
      }
    }
  }

  pub(super) fn push_filter(&mut self, filter: Filter, important: bool) {
    self.filter.get_or_insert_with(Vec::new).push(filter);
    self.filter_important = important;
  }

  pub(super) fn push_backdrop_filter(&mut self, filter: Filter, important: bool) {
    self
      .backdrop_filter
      .get_or_insert_with(Vec::new)
      .push(filter);
    self.backdrop_filter_important = important;
  }

  pub(super) fn set_filter_reset(&mut self, important: bool, use_backdrop: bool) {
    let (filter, filter_important) = if use_backdrop {
      (
        &mut self.backdrop_filter,
        &mut self.backdrop_filter_important,
      )
    } else {
      (&mut self.filter, &mut self.filter_important)
    };
    *filter = Some(Filters::default());
    *filter_important = important;
  }

  pub(super) fn set_grid_column(&mut self, grid_line: GridLine, important: bool) {
    self.grid_column.set_line(grid_line, important, important);
  }

  pub(super) fn set_grid_row(&mut self, grid_line: GridLine, important: bool) {
    self.grid_row.set_line(grid_line, important, important);
  }

  pub(super) fn finish(mut self) -> StyleDeclarationBlock {
    if let Some(shadows) = self.shadow.take() {
      self.push(
        StyleDeclaration::box_shadow(Some(shadows.into_boxed_slice())),
        self.shadow_important,
      );
    }

    if let Some(shadows) = self.text_shadow.take() {
      self.push(
        StyleDeclaration::text_shadow(Some(shadows.into_boxed_slice())),
        self.text_shadow_important,
      );
    }

    if let Some(filter) = self.filter.take() {
      self.push(StyleDeclaration::filter(filter), self.filter_important);
    }

    if let Some(backdrop_filter) = self.backdrop_filter.take() {
      self.push(
        StyleDeclaration::backdrop_filter(backdrop_filter),
        self.backdrop_filter_important,
      );
    }

    self.transform_state.apply(&mut self.declarations);
    self.gradient_state.apply(&mut self.declarations);

    self.grid_column.push_declarations(
      &mut self.declarations,
      StyleDeclaration::grid_column_start,
      StyleDeclaration::grid_column_end,
    );
    self.grid_row.push_declarations(
      &mut self.declarations,
      StyleDeclaration::grid_row_start,
      StyleDeclaration::grid_row_end,
    );

    type BorderSide = (LonghandId, LonghandId, fn(BorderStyle) -> StyleDeclaration);
    let sides: [BorderSide; 4] = [
      (
        LonghandId::BorderTopWidth,
        LonghandId::BorderTopStyle,
        StyleDeclaration::border_top_style,
      ),
      (
        LonghandId::BorderRightWidth,
        LonghandId::BorderRightStyle,
        StyleDeclaration::border_right_style,
      ),
      (
        LonghandId::BorderBottomWidth,
        LonghandId::BorderBottomStyle,
        StyleDeclaration::border_bottom_style,
      ),
      (
        LonghandId::BorderLeftWidth,
        LonghandId::BorderLeftStyle,
        StyleDeclaration::border_left_style,
      ),
    ];
    for (width_id, style_id, style_decl) in sides {
      let has_width = self
        .declarations
        .iter()
        .any(|d| d.longhand_id() == width_id);
      let has_style = self
        .declarations
        .iter()
        .any(|d| d.longhand_id() == style_id);
      if has_width && !has_style {
        self
          .declarations
          .push(style_decl(BorderStyle::Solid), false);
      }
    }

    self.declarations
  }
}

#[derive(Debug, Default)]
pub(super) struct TwTransformState {
  translate: Option<SpacePair<Length>>,
  translate_important: bool,
  scale: Option<SpacePair<PercentageNumber>>,
  scale_important: bool,
}

impl TwTransformState {
  pub(super) fn set_translate(&mut self, value: SpacePair<Length>, important: bool) {
    self.translate = Some(value);
    self.translate_important = important;
  }

  pub(super) fn translate_mut(&mut self, important: bool) -> &mut SpacePair<Length> {
    self.translate_important = important;
    self
      .translate
      .get_or_insert_with(SpacePair::<Length>::default)
  }

  pub(super) fn set_scale(&mut self, value: SpacePair<PercentageNumber>, important: bool) {
    self.scale = Some(value);
    self.scale_important = important;
  }

  pub(super) fn scale_mut(&mut self, important: bool) -> &mut SpacePair<PercentageNumber> {
    self.scale_important = important;
    self
      .scale
      .get_or_insert_with(SpacePair::<PercentageNumber>::default)
  }

  fn apply(self, declarations: &mut StyleDeclarationBlock) {
    if let Some(translate) = self.translate {
      declarations.push(
        StyleDeclaration::translate(translate),
        self.translate_important,
      );
    }

    if let Some(scale) = self.scale {
      declarations.push(StyleDeclaration::scale(scale), self.scale_important);
    }
  }
}

#[derive(Debug, Default)]
pub struct TwGradientState {
  pub gradient_type: TwGradientType,
  pub angle: Option<Angle>,
  pub from: Option<ColorInput>,
  pub to: Option<ColorInput>,
  pub via: Option<ColorInput>,
  pub from_position: Option<Length>,
  pub via_position: Option<Length>,
  pub to_position: Option<Length>,
  pub important: bool,
}

#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub enum TwGradientType {
  #[default]
  Linear,
  Radial,
  Conic,
}

impl TwGradientState {
  pub fn apply(self, declarations: &mut StyleDeclarationBlock) {
    // Stop positions alone (`from-50%` etc.) must not synthesise a gradient —
    // a color or angle has to be present.
    if self.from.is_none() && self.to.is_none() && self.via.is_none() && self.angle.is_none() {
      return;
    }

    let angle = self.angle.unwrap_or_else(|| Angle::new(180.0));

    let from_color = self.from.unwrap_or(ColorInput::Value(Color([0, 0, 0, 0])));
    let to_color = self.to.unwrap_or_else(|| {
      if let ColorInput::Value(from_c) = from_color {
        ColorInput::Value(Color([from_c.0[0], from_c.0[1], from_c.0[2], 0]))
      } else {
        ColorInput::Value(Color([0, 0, 0, 0]))
      }
    });

    let mut stops = Vec::new();
    stops.push(GradientStop::ColorHint {
      color: from_color,
      hint: Some(StopPosition(
        self.from_position.unwrap_or(Length::Percentage(0.0)),
      )),
    });

    if let Some(via_color) = self.via {
      stops.push(GradientStop::ColorHint {
        color: via_color,
        hint: Some(StopPosition(
          self.via_position.unwrap_or(Length::Percentage(50.0)),
        )),
      });
    }

    stops.push(GradientStop::ColorHint {
      color: to_color,
      hint: Some(StopPosition(
        self.to_position.unwrap_or(Length::Percentage(100.0)),
      )),
    });

    match self.gradient_type {
      TwGradientType::Linear => {
        let gradient = LinearGradient {
          repeating: false,
          direction: LinearGradientDirection::Angle(angle),
          interpolation: ColorInterpolationMethod::default(),
          stops: stops.into_boxed_slice(),
        };

        declarations.push(
          StyleDeclaration::background_image(Some([BackgroundImage::Linear(gradient)].into())),
          self.important,
        );
      }
      TwGradientType::Radial => {
        let gradient = RadialGradient {
          repeating: false,
          shape: RadialShape::Ellipse,
          size: RadialSize::FarthestCorner,
          center: BackgroundPosition::default(),
          interpolation: ColorInterpolationMethod::default(),
          stops: stops.into_boxed_slice(),
        };

        declarations.push(
          StyleDeclaration::background_image(Some([BackgroundImage::Radial(gradient)].into())),
          self.important,
        );
      }
      TwGradientType::Conic => {
        let gradient = ConicGradient {
          repeating: false,
          from_angle: angle,
          center: BackgroundPosition::default(),
          interpolation: ColorInterpolationMethod::default(),
          stops: stops.into_boxed_slice(),
        };

        declarations.push(
          StyleDeclaration::background_image(Some([BackgroundImage::Conic(gradient)].into())),
          self.important,
        );
      }
    }
  }
}
