use std::{
  ops::Deref,
  sync::{Arc, RwLock, RwLockReadGuard},
};

use crate::{
  core::Signal,
  layout::{scrollbar::ScrollBarStyle, text_style::TextStyle},
  node::{color::Color, dimension::Dimension},
};

mod border;
mod breakpoints;
mod caret;
#[cfg(feature = "form")]
mod form;
mod palette;
mod radius;
mod spacing;
mod typography;

pub use border::{BorderSize, ThemeBorderSizes};
pub use breakpoints::{Breakpoint, ThemeBreakpoints};
pub use caret::{CaretMode, ThemeCaret};
#[cfg(feature = "form")]
pub use form::{
  FormButtonRole, FormButtonTheme, FormCheckboxStyle, FormFieldTheme, FormInputTheme, FormSliderStyle, FormTextRole,
  FormTheme,
};
pub use palette::{PaletteColor, ThemePalette};
pub use radius::{RadiusSize, ThemeRadii};
pub use spacing::{SpacingSize, ThemeSpacing};
pub use typography::{ThemeFonts, ThemeTypography, TypographyStyle};

#[derive(Clone)]
pub struct Theme {
  inner: Arc<RwLock<ThemeInner>>,
  version_signal: Signal<u64>,
}

struct ThemeInner {
  palette: ThemePalette,
  border_sizes: ThemeBorderSizes,
  spacing: ThemeSpacing,
  radii: ThemeRadii,
  breakpoints: ThemeBreakpoints,
  caret: ThemeCaret,
  scrollbar: ScrollBarStyle,
  typography: ThemeTypography,
  #[cfg(feature = "form")]
  form: FormTheme,
  version: u64,
}

pub struct ThemeRef<'a, T> {
  inner: RwLockReadGuard<'a, ThemeInner>,
  value: fn(&ThemeInner) -> &T,
}

impl<T> Deref for ThemeRef<'_, T> {
  type Target = T;

  fn deref(&self) -> &Self::Target {
    (self.value)(&self.inner)
  }
}

impl Default for Theme {
  fn default() -> Self {
    Self {
      inner: Arc::new(RwLock::new(ThemeInner {
        palette: ThemePalette::default(),
        border_sizes: ThemeBorderSizes::default(),
        spacing: ThemeSpacing::default(),
        radii: ThemeRadii::default(),
        breakpoints: ThemeBreakpoints::default(),
        caret: ThemeCaret::default(),
        scrollbar: ScrollBarStyle::default(),
        typography: ThemeTypography::default(),
        #[cfg(feature = "form")]
        form: FormTheme::default(),
        version: 0,
      })),
      version_signal: Signal::new(0),
    }
  }
}

impl Theme {
  pub fn new() -> Self {
    Self::default()
  }

  pub fn lens<T: Clone + Send + Sync + 'static>(
    &self,
    getter: impl Fn(&Theme) -> T + Send + Sync + 'static,
    setter: impl Fn(&Theme, T) + Send + Sync + 'static,
  ) -> ThemeLens<T> {
    ThemeLens {
      theme: self.clone(),
      getter: Arc::new(getter),
      setter: Arc::new(setter),
    }
  }

  pub fn palette(&self) -> ThemeRef<'_, ThemePalette> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.palette,
    }
  }

  pub fn set_palette(&self, palette: ThemePalette) {
    self.mutate_inner(|inner| inner.palette = palette);
  }

  pub fn set_palette_color(&self, palette_color: impl Into<PaletteColor>, color: Color) {
    self.mutate_inner(|inner| inner.palette.set(palette_color, color));
  }

  pub fn palette_color(&self, palette_color: impl Into<PaletteColor>) -> Color {
    self.inner.read().unwrap().palette.get(palette_color)
  }

  pub fn border_sizes(&self) -> ThemeRef<'_, ThemeBorderSizes> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.border_sizes,
    }
  }

  pub fn set_border_sizes(&self, border_sizes: ThemeBorderSizes) {
    self.mutate_inner(|inner| inner.border_sizes = border_sizes);
  }

  pub fn set_border_size_value(&self, size: impl Into<BorderSize>, value: f32) {
    self.mutate_inner(|inner| inner.border_sizes.set(size, value));
  }

  pub fn border_size_value(&self, size: impl Into<BorderSize>) -> f32 {
    self.inner.read().unwrap().border_sizes.get(size)
  }

  pub fn spacing(&self) -> ThemeRef<'_, ThemeSpacing> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.spacing,
    }
  }

  pub fn set_spacing(&self, spacing: ThemeSpacing) {
    self.mutate_inner(|inner| inner.spacing = spacing);
  }

  pub fn set_spacing_value(&self, size: impl Into<SpacingSize>, value: impl Into<Dimension>) {
    self.mutate_inner(|inner| inner.spacing.set(size, value));
  }

  pub fn spacing_value(&self, size: impl Into<SpacingSize>) -> Dimension {
    self.inner.read().unwrap().spacing.get(size)
  }

  pub fn radii(&self) -> ThemeRef<'_, ThemeRadii> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.radii,
    }
  }

  pub fn set_radii(&self, radii: ThemeRadii) {
    self.mutate_inner(|inner| inner.radii = radii);
  }

  pub fn set_radius_value(&self, size: impl Into<RadiusSize>, value: f32) {
    self.mutate_inner(|inner| inner.radii.set(size, value));
  }

  pub fn radius_value(&self, size: impl Into<RadiusSize>) -> f32 {
    self.inner.read().unwrap().radii.get(size)
  }

  pub fn breakpoints(&self) -> ThemeRef<'_, ThemeBreakpoints> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.breakpoints,
    }
  }

  pub fn set_breakpoints(&self, breakpoints: ThemeBreakpoints) {
    self.mutate_inner(|inner| inner.breakpoints = breakpoints);
  }

  pub fn set_breakpoint_value(&self, breakpoint: Breakpoint, value: f32) {
    self.mutate_inner(|inner| inner.breakpoints.set(breakpoint, value));
  }

  pub fn breakpoint_value(&self, breakpoint: Breakpoint) -> f32 {
    self.inner.read().unwrap().breakpoints.get(breakpoint)
  }

  pub fn caret(&self) -> ThemeRef<'_, ThemeCaret> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.caret,
    }
  }

  pub fn set_caret(&self, caret: ThemeCaret) {
    self.mutate_inner(|inner| inner.caret = caret);
  }

  pub fn set_caret_mode(&self, mode: CaretMode) {
    self.mutate_inner(|inner| inner.caret.set_mode(mode));
  }

  pub fn caret_mode(&self) -> CaretMode {
    self.inner.read().unwrap().caret.mode()
  }

  pub fn scrollbar(&self) -> ThemeRef<'_, ScrollBarStyle> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.scrollbar,
    }
  }

  pub fn set_scrollbar(&self, scrollbar: ScrollBarStyle) {
    self.mutate_inner(|inner| inner.scrollbar = scrollbar);
  }

  pub fn typography(&self) -> ThemeRef<'_, ThemeTypography> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.typography,
    }
  }

  pub fn set_typography(&self, typography: ThemeTypography) {
    self.mutate_inner(|inner| inner.typography = typography);
  }

  pub fn set_typography_style(&self, typography_style: impl Into<TypographyStyle>, style: TextStyle) {
    self.mutate_inner(|inner| inner.typography.set(typography_style, style));
  }

  pub fn default_text_style(&self) -> TextStyle {
    self.inner.read().unwrap().typography.default_style().clone()
  }

  pub fn set_default_text_style(&self, style: TextStyle) {
    self.mutate_inner(|inner| inner.typography.set_default_style(style));
  }

  pub fn typography_style(&self, typography_style: impl Into<TypographyStyle>) -> TextStyle {
    self.inner.read().unwrap().typography.get(typography_style)
  }

  pub fn fonts(&self) -> ThemeFonts {
    self.inner.read().unwrap().typography.clone().into()
  }

  pub fn set_fonts(&self, fonts: ThemeFonts) {
    self.set_typography(fonts.into());
  }

  #[cfg(feature = "form")]
  pub fn form(&self) -> ThemeRef<'_, FormTheme> {
    ThemeRef {
      inner: self.inner.read().unwrap(),
      value: |inner| &inner.form,
    }
  }

  #[cfg(feature = "form")]
  pub fn set_form(&self, form: FormTheme) {
    self.mutate_inner(|inner| inner.form = form);
  }

  pub(crate) fn version(&self) -> u64 {
    self.inner.read().unwrap().version
  }

  pub(crate) fn track_access(&self) {
    let _ = self.version_signal.get();
  }

  fn mutate_inner(&self, f: impl FnOnce(&mut ThemeInner)) {
    let version = {
      let mut inner = self.inner.write().unwrap();
      f(&mut inner);
      inner.version = inner.version.wrapping_add(1);
      inner.version
    };
    self.version_signal.set(version);
  }
}

pub struct ThemeLens<T> {
  theme: Theme,
  getter: Arc<dyn Fn(&Theme) -> T + Send + Sync>,
  setter: Arc<dyn Fn(&Theme, T) + Send + Sync>,
}

impl<T: Clone + Send + Sync + 'static> ThemeLens<T> {
  pub fn get(&self) -> T {
    (self.getter)(&self.theme)
  }

  pub fn set(&self, value: T) {
    (self.setter)(&self.theme, value);
  }

  pub fn update(&self, f: impl FnOnce(&mut T)) {
    let mut value = self.get();
    f(&mut value);
    self.set(value);
  }
}

impl<T> Clone for ThemeLens<T> {
  fn clone(&self) -> Self {
    Self {
      theme: self.theme.clone(),
      getter: self.getter.clone(),
      setter: self.setter.clone(),
    }
  }
}
