use tui_term::vt100::Parser;
use tui_term::widget::PseudoTerminal;
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::component::Component;
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Props, QueryResult, Style};
use tuirealm::ratatui::layout::Rect;
use tuirealm::ratatui::widgets::Block;
use tuirealm::state::{State, StateValue};

use super::Line;
use super::history::History;

const DEFAULT_HISTORY_SIZE: usize = 128;

pub struct TerminalComponent {
    pub parser: Parser,
    history: History,
    line: Line,
    props: Props,
    scroll: usize,
    size: (u16, u16),
}

impl Default for TerminalComponent {
    fn default() -> Self {
        let props = Props::default();
        let parser = Parser::new(40, 220, 2048);

        TerminalComponent {
            parser,
            history: History::new(DEFAULT_HISTORY_SIZE),
            line: Line::default(),
            props,
            scroll: 0,
            size: (40, 220),
        }
    }
}

impl TerminalComponent {
    /// Set prompt line for the terminal
    pub fn prompt(mut self, prompt: impl ToString) -> Self {
        self.attr(Attribute::Content, AttrValue::String(prompt.to_string()));
        self.write_prompt();
        self
    }

    pub fn write_prompt(&mut self) {
        if let Some(AttrValue::String(prompt)) = self.props.get(Attribute::Content).cloned() {
            self.parser.process(prompt.as_bytes());
        }
    }

    /// Set current line to the previous command in the [`History`]
    fn history_prev(&mut self) {
        if let Some(cmd) = self.history.previous() {
            self.write_line(cmd.as_bytes());
            self.line.set(cmd);
        }
    }

    /// Set current line to the next command in the [`History`]
    fn history_next(&mut self) {
        if let Some(cmd) = self.history.next() {
            self.write_line(cmd.as_bytes());
            self.line.set(cmd);
        } else {
            // If there is no next command, clear the line
            self.line.set(String::new());
            self.write_line(&[]);
        }
    }

    /// Write a line to the terminal, processing it through the parser
    fn write_line(&mut self, data: &[u8]) {
        self.parser.process(b"\r");
        // blank the line
        self.write_prompt();
        self.parser.process(&[b' '; 15]);
        self.parser.process(b"\r");
        self.write_prompt();
        self.parser.process(data);
    }
}

impl Component for TerminalComponent {
    fn view(&mut self, frame: &mut tuirealm::ratatui::Frame, area: Rect) {
        let width = area.width.saturating_sub(2);
        let height = area.height.saturating_sub(2);

        // update the terminal size if it has changed
        if self.size != (width, height) {
            self.size = (width, height);
            self.parser.set_size(height, width);
        }

        let title = self
            .props
            .get(Attribute::Title)
            .and_then(|v| match v {
                AttrValue::String(s) => Some(s.clone()),
                _ => None,
            })
            .unwrap_or_else(|| "Terminal".to_string());

        let fg = self
            .props
            .get(Attribute::Foreground)
            .and_then(AttrValue::as_color)
            .unwrap_or(tuirealm::ratatui::style::Color::Reset);

        let bg = self
            .props
            .get(Attribute::Background)
            .and_then(AttrValue::as_color)
            .unwrap_or(tuirealm::ratatui::style::Color::Reset);

        let border_color = self
            .props
            .get(Attribute::Borders)
            .and_then(AttrValue::as_color)
            .unwrap_or(tuirealm::ratatui::style::Color::Reset);

        let terminal = PseudoTerminal::new(self.parser.screen())
            .block(
                Block::default()
                    .title(title)
                    .border_type(BorderType::Rounded)
                    .border_style(Style::default().fg(border_color))
                    .borders(BorderSides::ALL)
                    .style(Style::default().fg(fg).bg(bg)),
            )
            .style(Style::default().fg(fg).bg(bg));

        frame.render_widget(terminal, area);
    }

    fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
        self.props.get_for_query(attr)
    }

    fn attr(&mut self, attr: Attribute, value: AttrValue) {
        if attr == Attribute::Text {
            if let AttrValue::String(s) = value {
                self.parser.process(b"\r");
                self.parser.process(s.as_bytes());
                self.parser.process(b"\r");
                self.write_prompt();
            }
        } else {
            self.props.set(attr, value);
        }
    }

    fn state(&self) -> State {
        State::Single(StateValue::String(self.line.content().to_string()))
    }

    fn perform(&mut self, cmd: Cmd) -> CmdResult {
        match cmd {
            Cmd::Type(s) => {
                if !s.is_ascii() || self.scroll > 0 {
                    return CmdResult::NoChange; // Ignore non-ASCII characters or if scrolled
                }
                self.parser.process(&[s as u8]);
                self.line.push(s);
                CmdResult::Changed(self.state())
            }
            Cmd::Move(Direction::Down) => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move down if not scrolled
                }

                self.history_next();

                CmdResult::NoChange
            }
            Cmd::Move(Direction::Left) => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move up if not scrolled
                }

                if self.line.left() {
                    self.parser.process(&[27, 91, 68]);
                }

                CmdResult::NoChange
            }
            Cmd::Move(Direction::Right) => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move up if not scrolled
                }

                if self.line.right() {
                    self.parser.process(&[27, 91, 67]);
                }

                CmdResult::NoChange
            }
            Cmd::Move(Direction::Up) => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move up if not scrolled
                }

                self.history_prev();
                CmdResult::NoChange
            }
            Cmd::Cancel => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move to the beginning if scrolled
                }

                if !self.line.is_empty() {
                    self.line.backspace();
                    self.parser.process(&[8]); // Backspace character
                    // delete the last character from the line
                    // write one empty character to the terminal
                    self.parser.process(&[32]); // Space character
                    self.parser.process(&[8]); // Backspace character
                }
                CmdResult::Changed(self.state())
            }
            Cmd::Delete => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move to the beginning if scrolled
                }

                if !self.line.is_empty() {
                    self.line.delete();
                    self.parser.process(&[27, 91, 51, 126]); // Delete character
                    // write one empty character to the terminal
                    self.parser.process(&[32]); // Space character
                    self.parser.process(&[8]); // Backspace character
                }
                CmdResult::Changed(self.state())
            }
            Cmd::Scroll(Direction::Down) => {
                self.scroll = self.scroll.saturating_sub(8);
                self.parser.set_scrollback(self.scroll);

                CmdResult::NoChange
            }
            Cmd::Scroll(Direction::Up) => {
                self.parser.set_scrollback(self.scroll.saturating_add(8));
                let scrollback = self.parser.screen().scrollback();
                self.scroll = scrollback;

                CmdResult::NoChange
            }
            Cmd::Toggle => {
                // insert
                self.parser.process(&[27, 91, 50, 126]); // Toggle insert mode
                CmdResult::NoChange
            }
            Cmd::GoTo(Position::Begin) => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move to the beginning if scrolled
                }

                for _ in 0..self.line.begin() {
                    self.parser.process(&[27, 91, 68]); // Move cursor to the left
                }

                CmdResult::NoChange
            }
            Cmd::GoTo(Position::End) => {
                if self.scroll > 0 {
                    return CmdResult::NoChange; // Cannot move to the beginning if scrolled
                }

                for _ in 0..self.line.end() {
                    self.parser.process(&[27, 91, 67]); // Move cursor to the right
                }
                CmdResult::NoChange
            }
            Cmd::Submit => {
                self.scroll = 0; // Reset scroll on submit
                self.parser.set_scrollback(self.scroll);

                if cfg!(target_family = "unix") {
                    self.parser.process(b"\n");
                } else {
                    self.parser.process(b"\r\n\r");
                }

                let line = self.line.take();
                if !line.is_empty() {
                    self.history.push(&line);
                }

                CmdResult::Submit(State::Single(StateValue::String(line)))
            }
            _ => CmdResult::NoChange,
        }
    }
}
