use crossterm::event::KeyEvent;
use crossterm::execute;
use crossterm::terminal::*;
use futures::{FutureExt, StreamExt};
use ratatui::prelude::*;
use std::io::{self, stdout, Stdout};
use tokio::sync::mpsc;

pub type Tui = Terminal<CrosstermBackend<Stdout>>;

pub fn init() -> io::Result<Tui> {
    execute!(stdout(), EnterAlternateScreen)?;
    enable_raw_mode()?;
    Terminal::new(CrosstermBackend::new(stdout()))
}

pub fn restore() -> io::Result<()> {
    execute!(stdout(), LeaveAlternateScreen)?;
    disable_raw_mode()?;
    Ok(())
}

#[derive(Clone, Copy, Debug)]
pub enum Event {
    Key(KeyEvent),
    Error,
    Tick(u16),
}

#[derive(Debug)]
pub struct EventHandler {
    _tx: mpsc::UnboundedSender<Event>,
    rx: mpsc::UnboundedReceiver<Event>,
}

impl EventHandler {
    pub fn new(width: u16) -> Self {
        let tick_rate = std::time::Duration::from_millis(250);

        let (tx, rx) = mpsc::unbounded_channel();
        let _tx = tx.clone();
        let mut width = width;

        let _task = tokio::spawn(async move {
            let mut reader = crossterm::event::EventStream::new();
            let mut interval = tokio::time::interval(tick_rate);
            loop {
                let delay = interval.tick();
                let crossterm_event = reader.next().fuse();
                tokio::select! {
                  maybe_event = crossterm_event => {
                    match maybe_event {
                      Some(Ok(evt)) => {
                        match evt {
                          crossterm::event::Event::Key(key) => {
                            if key.kind == crossterm::event::KeyEventKind::Press
                              && tx.send(Event::Key(key)).is_err() {
                                break; // Channel closed, exit gracefully
                              }
                          },
                          crossterm::event::Event::Resize(col,_) => {width = col},
                          crossterm::event::Event::Mouse(event) => {
                            if event.kind == crossterm::event::MouseEventKind::ScrollUp
                              && tx.send(Event::Key(KeyEvent::new(crossterm::event::KeyCode::Char('k'), event.modifiers))).is_err() {
                                break;
                              }
                            if event.kind == crossterm::event::MouseEventKind::ScrollDown
                              && tx.send(Event::Key(KeyEvent::new(crossterm::event::KeyCode::Char('j'), event.modifiers))).is_err() {
                                break;
                              }
                          },
                          _ => {},
                        }
                      }
                      Some(Err(_)) => {
                        let _ = tx.send(Event::Error);
                        break;
                      }
                      None => {},
                    }
                  },
                  _ = delay => {
                       if tx.send(Event::Tick(width)).is_err() {
                         break; // Channel closed, exit gracefully
                       }
                  },
                }
            }
        });

        Self { _tx, rx }
    }

    pub async fn next(&mut self) -> Result<Event, io::Error> {
        self.rx
            .recv()
            .await
            .ok_or_else(|| io::Error::other("Unable to get event"))
    }
}
