use ratatui::prelude::{Buffer, Constraint, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::widgets::{Cell, Row, StatefulWidget, Table, Widget};
use zeroize::Zeroize;

use crate::{Error, EventHandler, InputField, InputList, Result, Terminal, field_access};

mod field;
mod state;

pub use field::ExtraField;
pub use state::ExtraFieldListState;

/// Represents a items of [ExtraField]s.
#[derive(Clone, Debug, Eq, PartialEq, Zeroize)]
pub struct ExtraFieldList {
    items: Vec<ExtraField>,
}

impl ExtraFieldList {
    /// Creates a new [ExtraFieldList].
    pub const fn new() -> Self {
        Self { items: Vec::new() }
    }

    /// Pushes an [ExtraField] into the list.
    #[inline]
    pub fn push(&mut self, field: ExtraField) {
        self.items.push(field);
    }

    /// Gets the length of the list.
    #[inline]
    pub fn len(&self) -> usize {
        self.items.len()
    }

    /// Gets whether the list is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    /// Gets a mutable reference to the item list.
    pub fn items_mut(&mut self) -> &mut [ExtraField] {
        self.items.as_mut()
    }

    /// Attempts to find a extra field content by title.
    ///
    /// **NOTE**: finds the first field with a matching title.
    pub fn find_content(&self, title: &str) -> Result<&str> {
        self.items
            .iter()
            .find(|f| f.title() == title)
            .ok_or(Error::io_other(format!(
                "missing field with title: {title}"
            )))
            .map(|f| f.content())
    }

    /// Converts the [ExtraFieldList] into a TUI widget.
    pub fn as_widget(&self, area: Rect) -> Table<'_> {
        let fields = self.items.iter().map(|f| f.as_widget()).collect::<Vec<_>>();

        let columns = ExtraField::columns() as u16;
        let col_width_max = area
            .width
            .saturating_div(columns)
            .saturating_sub(columns.saturating_mul(2));

        let widths = (0..columns)
            .map(|_| Constraint::Max(col_width_max))
            .collect::<Vec<_>>();

        Table::new(fields, widths).header(
            Row::new(vec![Cell::new("Title"), Cell::new("Content")])
                .style(Style::default().bold().blue()),
        )
    }

    /// Selects the current field indicated by the state.
    pub fn select_input(&mut self, state: &mut ExtraFieldListState) {
        let pos = state.current();
        if let Some(_i) = self.items.get_mut(pos) {
            state.select(pos);
        }
    }

    /// Gets a reference the current items item.
    pub fn current_item(&self, state: &ExtraFieldListState) -> Result<&ExtraField> {
        let s = state.current();
        self.items
            .get(s)
            .ok_or(Error::io_other(format!("invalid extra field: {s}")))
    }

    /// Gets a mutable reference the current items item.
    pub fn current_item_mut(&mut self, state: &ExtraFieldListState) -> Result<&mut ExtraField> {
        let s = state.current();
        self.items
            .get_mut(s)
            .ok_or(Error::io_other(format!("invalid extra field: {s}")))
    }

    /// Adds an [ExtraFieldList] field entry.
    pub async fn add_field(
        &mut self,
        terminal: &mut Terminal,
        events: &mut EventHandler,
    ) -> Result<()> {
        let res = InputList::new()
            .with_items(vec![
                InputField::create("title"),
                InputField::create("content"),
            ])
            .run(terminal, events)
            .await?;

        if let (Some(t), Some(c)) = (res.first(), res.get(1)) {
            let field = ExtraField::new().with_title(t).with_content(c);
            self.items.push(field);
        }

        Ok(())
    }

    /// Edits an [ExtraFieldList] field entry.
    pub async fn edit_field(
        &mut self,
        terminal: &mut Terminal,
        events: &mut EventHandler,
        state: &ExtraFieldListState,
    ) -> Result<()> {
        let cur = self.current_item_mut(state)?;
        let encrypted = cur.state().is_encrypted();

        if encrypted {
            cur.session_decrypt()?;
        }

        let res = InputList::new()
            .with_items(vec![
                InputField::create("title").with_input(cur.title()),
                InputField::create("content").with_input(cur.content()),
            ])
            .run(terminal, events)
            .await?;

        if let (Some(t), Some(c)) = (res.first(), res.get(1)) {
            cur.set_title(t);
            cur.set_content(c);
        }

        if encrypted {
            cur.session_encrypt()?;
        }

        Ok(())
    }
}

field_access! {
    ExtraFieldList {
        items: cow { [ExtraField] },
    }
}

impl Default for ExtraFieldList {
    fn default() -> Self {
        Self::new()
    }
}

impl From<Vec<ExtraField>> for ExtraFieldList {
    fn from(val: Vec<ExtraField>) -> Self {
        Self { items: val }
    }
}

impl Widget for &mut ExtraFieldList {
    fn render(self, area: Rect, buf: &mut Buffer) {
        Widget::render(self.as_widget(area), area, buf);
    }
}

impl StatefulWidget for &mut ExtraFieldList {
    type State = ExtraFieldListState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        self.select_input(state);

        <Self as Widget>::render(self, area, buf);
    }
}
