use ratatui::crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};

use ratatui::layout::{Constraint, Layout};
use ratatui::prelude::{Buffer, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::{Line, Text};
use ratatui::widgets::{Paragraph, StatefulWidget, Widget};
use ratatui::{DefaultTerminal, Frame};

use crate::{Error, InputField, InputMode, Result, field_access};

mod state;

pub use state::InputListState;

/// Represents a list of [InputField]s.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InputList {
    input_mode: InputMode,
    items: Vec<InputField>,
}

impl InputList {
    /// Creates a new [InputList].
    pub const fn new() -> Self {
        Self {
            input_mode: InputMode::Editing,
            items: Vec::new(),
        }
    }

    /// Creates a new [InputListState] from the list.
    pub fn state(&self) -> InputListState {
        InputListState::create(self.items.len())
    }

    /// Selects the current input indicated by the list state.
    pub(crate) fn select_input(&mut self, state: &mut InputListState) {
        let pos = state.current();
        if let Some(i) = self.items.get_mut(pos) {
            i.state_mut().set_mode(InputMode::Editing);
            state.select(pos);
        }
    }

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

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

    pub(crate) fn submit_messages(&mut self) -> Vec<String> {
        self.items.iter_mut().map(|i| i.submit_message()).collect()
    }

    pub(crate) fn draw(&mut self, frame: &mut Frame, state: &mut InputListState) {
        if let Some((i, input)) = self
            .items
            .iter()
            .enumerate()
            .find(|(_, f)| f.state().mode().is_editing())
        {
            frame.set_cursor_position(input.cursor_position(i));
        }

        frame.render_stateful_widget(self, frame.area(), state);
    }

    pub(crate) fn help_message(&self) -> Paragraph<'_> {
        let (msg, style) = match self.input_mode {
            InputMode::Normal => (
                vec![
                    "Press ".into(),
                    "Esc".bold(),
                    " or ".into(),
                    "q".bold(),
                    " to exit | ".into(),
                    "Enter".bold(),
                    " to submit the input | ".into(),
                    "Tab".bold(),
                    " to select the next input field | ".into(),
                    "i".bold(),
                    " or ".into(),
                    "e".bold(),
                    " to start editing | ".into(),
                    "r".bold(),
                    " to show/hide the input".into(),
                ],
                Style::default(),
            ),
            InputMode::Editing => (
                vec![
                    "Press ".into(),
                    "Esc".bold(),
                    " to stop editing | ".into(),
                    "Enter".bold(),
                    " to submit the input | ".into(),
                    "Tab".bold(),
                    " to select the next input field".into(),
                ],
                Style::default(),
            ),
        };

        Paragraph::new(Text::from(Line::from(msg)).patch_style(style))
    }

    /// Runs an event loop for the [InputList].
    pub(crate) fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<Vec<String>> {
        let mut state = self.state();

        state.select_first();
        self.select_input(&mut state);

        loop {
            terminal.draw(|frame| self.draw(frame, &mut state))?;

            match event::read()? {
                Event::Key(key) => match self.input_mode {
                    InputMode::Normal => {
                        if let Some(ret) = self.handle_normal_key(&mut state, key)? {
                            return Ok(ret);
                        }
                    }
                    InputMode::Editing if key.kind == KeyEventKind::Press => {
                        if let Some(ret) = self.handle_editing_key(&mut state, key)? {
                            return Ok(ret);
                        }
                    }
                    InputMode::Editing => {}
                },
                Event::Paste(paste) => {
                    if matches!(self.input_mode, InputMode::Editing) {
                        let cur_item = self.current_item_mut(&state)?;
                        paste.chars().for_each(|c| cur_item.enter_char(c));
                    }
                }
                _ => {}
            }
        }
    }

    /// Handles a key event in normal mode.
    pub(crate) fn handle_normal_key(
        &mut self,
        state: &mut InputListState,
        key: KeyEvent,
    ) -> Result<Option<Vec<String>>> {
        let cur_item = self.current_item_mut(state)?;

        match (key.modifiers, key.code) {
            (_, KeyCode::Enter) => return Ok(Some(self.submit_messages())),
            (_, KeyCode::Char('e') | KeyCode::Char('i')) => self.set_input_mode(InputMode::Editing),
            (KeyModifiers::SHIFT, KeyCode::Tab)
            | (_, KeyCode::Left | KeyCode::Up | KeyCode::Char('k')) => {
                cur_item.state_mut().set_mode(InputMode::Normal);
                state.select_previous();
                self.select_input(state);
                self.set_input_mode(InputMode::Editing);
            }
            (_, KeyCode::Tab | KeyCode::Right | KeyCode::Down | KeyCode::Char('j')) => {
                cur_item.state_mut().set_mode(InputMode::Normal);
                state.select_next();
                self.select_input(state);
                self.set_input_mode(InputMode::Editing);
            }
            (_, KeyCode::Esc | KeyCode::Char('q')) => {
                return Ok(Some(Vec::new()));
            }
            (_, KeyCode::Char('r')) => {
                cur_item.state_mut().toggle_visibility();
            }
            _ => {}
        }

        Ok(None)
    }

    /// Handles a key event in editing mode.
    pub(crate) fn handle_editing_key(
        &mut self,
        state: &mut InputListState,
        key: KeyEvent,
    ) -> Result<Option<Vec<String>>> {
        let cur_item = self.current_item_mut(state)?;

        match (key.modifiers, key.code) {
            (_, KeyCode::Char(to_insert)) => cur_item.enter_char(to_insert),
            (_, KeyCode::Backspace) => cur_item.delete_char(),
            (_, KeyCode::Left) => cur_item.move_cursor_left(),
            (_, KeyCode::Right) => cur_item.move_cursor_right(),
            (_, KeyCode::Enter) => return Ok(Some(self.submit_messages())),
            (_, KeyCode::Esc) => self.set_input_mode(InputMode::Normal),
            (KeyModifiers::SHIFT, KeyCode::Tab) | (_, KeyCode::Up) => {
                cur_item.state_mut().set_mode(InputMode::Normal);
                state.select_previous();
                self.select_input(state);
            }
            (_, KeyCode::Tab | KeyCode::Down) => {
                cur_item.state_mut().set_mode(InputMode::Normal);
                state.select_next();
                self.select_input(state);
            }
            _ => {}
        }

        Ok(None)
    }
}

field_access! {
    InputList {
        input_mode: InputMode,
    }
}

field_access! {
    InputList {
        items: cow { [InputField] },
    }
}

impl Widget for &mut InputList {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let constraints = [Constraint::Length(1)]
            .into_iter()
            .chain(self.items.iter().map(|i| i.constraint()));

        Layout::vertical(constraints)
            .split(area)
            .iter()
            .enumerate()
            .for_each(|(i, &area)| {
                if i == 0 {
                    self.help_message().render(area, buf);
                } else {
                    self.items[i - 1].render(area, buf);
                }
            });
    }
}

impl StatefulWidget for &mut InputList {
    type State = InputListState;

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

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

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