use super::input_box_popup::InputBoxPopup;
use super::message::Message;
use super::popup::Size;
use crate::models::stateful_table::StatefulTable;
use crate::ui::HIGHLIGHT_SYMBOL;
use crate::ui::styles::{ManagarrStyle, highlight_style};
use crate::ui::utils::{borderless_block, centered_rect, title_block_centered};
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::popup::Popup;
use crate::ui::widgets::selectable_list::SelectableList;
use derive_setters::Setters;
use ratatui::Frame;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Position, Rect};
use ratatui::prelude::{Stylize, Text};
use ratatui::widgets::{Block, ListItem, Row, StatefulWidget, Table, Widget, WidgetRef};
use std::fmt::Debug;
use std::sync::atomic::Ordering;

#[cfg(test)]
#[path = "managarr_table_tests.rs"]
mod managarr_table_tests;

#[derive(Setters)]
pub struct ManagarrTable<'a, T, F>
where
  F: Fn(&T) -> Row<'a>,
  T: Clone + PartialEq + Eq + Debug,
{
  #[setters(strip_option)]
  content: Option<&'a mut StatefulTable<T>>,
  #[setters(skip)]
  table_headers: Vec<String>,
  #[setters(skip)]
  constraints: Vec<Constraint>,
  row_mapper: F,
  block: Block<'a>,
  margin: u16,
  #[setters(rename = "loading")]
  is_loading: bool,
  highlight_rows: bool,
  #[setters(rename = "sorting")]
  is_sorting: bool,
  #[setters(rename = "searching")]
  is_searching: bool,
  search_produced_empty_results: bool,
  #[setters(rename = "filtering")]
  is_filtering: bool,
  filter_produced_empty_results: bool,
  search_box_content_length: usize,
  search_box_offset: usize,
  filter_box_content_length: usize,
  filter_box_offset: usize,
}

impl<'a, T, F> ManagarrTable<'a, T, F>
where
  F: Fn(&T) -> Row<'a>,
  T: Clone + PartialEq + Eq + Debug,
{
  pub fn new(content: Option<&'a mut StatefulTable<T>>, row_mapper: F) -> Self {
    let mut managarr_table = Self {
      content: None,
      table_headers: Vec::new(),
      constraints: Vec::new(),
      row_mapper,
      block: borderless_block(),
      margin: 0,
      is_loading: false,
      highlight_rows: true,
      is_sorting: false,
      is_searching: false,
      search_produced_empty_results: false,
      is_filtering: false,
      filter_produced_empty_results: false,
      search_box_content_length: 0,
      search_box_offset: 0,
      filter_box_content_length: 0,
      filter_box_offset: 0,
    };

    if let Some(content) = content.as_ref() {
      if let Some(search) = content.search.as_ref() {
        managarr_table.search_box_content_length = search.text.len();
        managarr_table.search_box_offset = search.offset.load(Ordering::SeqCst);
      } else if let Some(filter) = content.filter.as_ref() {
        managarr_table.filter_box_content_length = filter.text.len();
        managarr_table.filter_box_offset = filter.offset.load(Ordering::SeqCst);
      }
    }

    managarr_table.content = content;
    managarr_table
  }

  pub fn headers<I>(mut self, headers: I) -> Self
  where
    I: IntoIterator,
    I::Item: Into<String>,
  {
    self.table_headers = headers.into_iter().map(Into::into).collect();
    self
  }

  pub fn constraints<I>(mut self, constraints: I) -> Self
  where
    I: IntoIterator,
    I::Item: Into<Constraint>,
  {
    self.constraints = constraints.into_iter().map(Into::into).collect();
    self
  }

  fn render_table(self, area: Rect, buf: &mut Buffer) {
    let table_headers = self.parse_headers();
    let table_area = {
      let [content_area, _] = Layout::vertical([Constraint::Fill(1), Constraint::Fill(0)])
        .margin(self.margin)
        .areas(area);

      content_area
    };
    let loading_block = LoadingBlock::new(self.is_loading, self.block.clone());

    if let Some(content) = self.content
      && !self.is_loading
    {
      let (table_contents, table_state) = if let Some(items) = &content.filtered_items {
        (items, content.filtered_state.as_mut().unwrap())
      } else {
        (&content.items, &mut content.state)
      };
      if !table_contents.is_empty() {
        let rows = table_contents.iter().map(&self.row_mapper);

        let headers = Row::new(table_headers)
          .default_color()
          .bold()
          .bottom_margin(0);

        let mut table = Table::new(rows, &self.constraints)
          .header(headers)
          .block(self.block);

        if self.highlight_rows {
          table = table
            .row_highlight_style(highlight_style())
            .highlight_symbol(HIGHLIGHT_SYMBOL);
        }

        StatefulWidget::render(table, table_area, buf, table_state);

        if let Some(sort) = &mut content.sort
          && self.is_sorting
        {
          let selectable_list =
            SelectableList::new(sort, |item| ListItem::new(Text::from(item.name)));
          Popup::new(selectable_list)
            .dimensions(20, 50)
            .render(table_area, buf);
        }

        if self.is_searching {
          let box_content = &content.search.as_ref().unwrap();
          InputBoxPopup::new(&box_content.text)
            .offset(box_content.offset.load(Ordering::SeqCst))
            .block(title_block_centered("Search"))
            .render_ref(table_area, buf);
        }

        if self.is_filtering {
          let box_content = &content.filter.as_ref().unwrap();
          InputBoxPopup::new(&box_content.text)
            .offset(box_content.offset.load(Ordering::SeqCst))
            .block(title_block_centered("Filter"))
            .render_ref(table_area, buf);
        }

        if self.search_produced_empty_results {
          Popup::new(Message::new("No items found matching search"))
            .size(Size::Message)
            .render(table_area, buf);
        }

        if self.filter_produced_empty_results {
          Popup::new(Message::new("The given filter produced empty results"))
            .size(Size::Message)
            .render(table_area, buf);
        }
      } else {
        loading_block.render(table_area, buf);
      }
    } else {
      loading_block.render(table_area, buf);
    }
  }

  fn parse_headers(&self) -> Vec<Text<'a>> {
    if let Some(ref content) = self.content
      && let Some(ref sort_list) = content.sort
      && !self.is_sorting
    {
      let mut new_headers = self.table_headers.clone();
      let idx = sort_list.state.selected().unwrap_or(0);
      let direction = if content.sort_asc { " ▲" } else { " ▼" };
      new_headers[idx].push_str(direction);

      return new_headers.into_iter().map(Text::from).collect();
    }

    self
      .table_headers
      .clone()
      .into_iter()
      .map(Text::from)
      .collect()
  }

  pub fn show_cursor(&self, f: &mut Frame<'_>, area: Rect) {
    let mut draw_cursor = |length: usize, offset: usize| {
      let popup_area = Rect {
        height: 7,
        ..centered_rect(30, 20, area)
      };
      let [text_box_area, _] = Layout::vertical([Constraint::Fill(1), Constraint::Fill(0)])
        .margin(1)
        .areas(popup_area);
      f.set_cursor_position(Position {
        x: text_box_area.x + (length - offset) as u16 + 1,
        y: text_box_area.y + 1,
      });
    };

    if self.is_searching {
      draw_cursor(self.search_box_content_length, self.search_box_offset);
    } else if self.is_filtering {
      draw_cursor(self.filter_box_content_length, self.filter_box_offset);
    }
  }
}

impl<'a, T, F> Widget for ManagarrTable<'a, T, F>
where
  F: Fn(&T) -> Row<'a>,
  T: Clone + PartialEq + Eq + Debug,
{
  fn render(self, area: Rect, buf: &mut Buffer) {
    self.render_table(area, buf);
  }
}
