use std::sync::Arc;

use crossterm::event::KeyCode;
use ratatui::{Frame, buffer::Buffer, layout::Rect, widgets::Widget};
use vlogger::*;

use super::*;

#[derive(Debug, Clone)]
pub enum InputMode {
  Input,
  PopUp(Arc<Vec<String>>, String, usize),
}

#[derive(Debug)]
pub struct Renderer {
  buffer: String,
  exit: bool,
  layout: RenderLayout,
  mode: InputMode,
  area: Rect,
}

#[allow(dead_code)]
impl Renderer {
  pub fn new(layout: RenderLayoutKind) -> Self {
    Self {
      buffer: String::new(),
      exit: false,
      layout: RenderLayout::generate(layout),
      mode: InputMode::Input,
      area: Default::default(),
    }
  }

  fn log(&mut self, msg: String) {
    self.apply(RenderCommand::StringToPaneFocused{str: msg})
  }

  pub fn draw(&mut self, frame: &mut Frame) {
    frame.render_widget(self, frame.area());
  }

  fn exit(&mut self) {
    log!(DEBUG, "Renderer Exit");
    self.exit = true;
  }

  fn buffer_extend<S: AsRef<str>>(&mut self, input: S) {
    self.buffer.push_str(input.as_ref());
  }

  fn input_pane(&mut self) -> Option<&mut Pane> {
    self
      .layout
      .panes
      .iter_mut()
      .find(|p| p.target == RenderTarget::CliInput)
  }

  fn get_pane(&mut self, pane: RenderTarget) -> Option<&mut Pane> {
    self.layout.panes.iter_mut().find(|p| p.target == pane)
  }

  fn focused(&mut self) -> Option<&mut Pane> {
    self.layout.panes.iter_mut().find(|p| p.focused == true)
  }

  pub fn rects(&self) -> std::rc::Rc<[Rect]> {
    self.layout.rects(self.area)
  }

  pub fn set_area(&mut self, area: Rect) {
    self.area = area;
  }

  pub fn handle_mouse_click_left(&mut self, x: u16, y: u16, rects: std::rc::Rc<[Rect]>) {
    for (i, r) in rects.iter().enumerate() {
      if r.contains(ratatui::layout::Position { x, y }) {
        self.layout.panes[i].focused = true;
      } else {
        self.layout.panes[i].focused = false;
      }
    }
  }

  pub fn handle_scroll_up(&mut self) {
    if let Some(p) = self.focused() {
      if p.scroll < p.max_scroll {
        p.scroll += 1;
      }
    }
  }

  pub fn handle_scroll_down(&mut self) {
    if let Some(p) = self.focused() {
      if p.scroll > i16::MIN {
        p.scroll -= 1;
      }
    }
  }

  pub fn handle_char_input(&mut self, c: char) {
    if let Some(p) = self.input_pane() {
      if let RenderBuffer::List { list, index, .. } = &mut p.buffer {
        list[*index].push(c);
      }
    }
  }

  pub fn handle_backspace(&mut self) {
    if let Some(p) = self.input_pane() {
      if let RenderBuffer::List { list, index, .. } = &mut p.buffer {
        list[*index].pop();
      }
    }
  }

  pub fn handle_enter(&mut self) {
    if let Some(p) = self.input_pane() {
      if let RenderBuffer::List { list, index, .. } = &mut p.buffer {
        list.push(String::new());
        *index += 1;
      }
    }
  }

  pub fn handle_arrow_key(&mut self, key: KeyCode) {
    if let Some(pane) = self.focused() {
      match &mut pane.target {
        RenderTarget::CliInput => {}
        RenderTarget::CliOutput => {}
        RenderTarget::PopUp => {
          match &mut pane.buffer {
            RenderBuffer::Select(content, idx) => {
              match key {
                KeyCode::Up => { *idx = idx.saturating_sub(1) }
                KeyCode::Down => {
                  if *idx < content.len().saturating_sub(1) {
                    *idx += 1;
                  }
                }
                _ => {}
              }
            }
            _ => {}
          }
        }
        _ => {}
      }
    }
  }

  pub fn list_move(&mut self, pane: RenderTarget, index: usize) {
    if let Some(p) = self.get_pane(pane) {
      if let RenderBuffer::List {
        list, index: idx, ..
      } = &mut p.buffer
      {
        if index > 0 && index < list.len() {
          list[*idx] = list[index].clone();
        }
      }
    }
  }

  pub fn render_string_to_focused(&mut self, str: String) {
    if let Some(p) = self.focused() {
      match &mut p.buffer {
        RenderBuffer::List { list, index, .. } => {
          list.push(str);
          *index += 1;
        }
        RenderBuffer::String(s) => s.push_str(&str),
        _ => {}
      }
    }
  }

  pub fn render_string_to_id(&mut self, str: String, pane: RenderTarget) {
    if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
      match &mut p.buffer {
        RenderBuffer::List { list, index, .. } => {
          list.push(str);
          *index += 1;
        }
        RenderBuffer::String(s) => s.push_str(&str),
        _ => {}
      }
    }
  }

  fn clear_focus(&mut self) {
    while let Some(focused) = self.focused() {
      focused.focused = false;
    }
  }

  pub fn clear_pane(&mut self, pane: RenderTarget) {
    if matches!(pane, RenderTarget::All) {
      for p in self.layout.panes.iter_mut() {
        match &mut p.buffer {
          RenderBuffer::List { list, index, .. } => {
            list.clear();
            *index = 0;
            list.push(String::new());
          }
          RenderBuffer::String(s) => s.clear(),
        _ => {}
        }
      }
    } else if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) {
      match &mut p.buffer {
        RenderBuffer::List { list, index, .. } => {
          list.clear();
          *index = 0;
          list.push(String::new());
        }
        RenderBuffer::String(s) => s.clear(),
        _ => {}
      }
    }
  }

  pub fn apply(&mut self, mes: RenderCommand) {
    let rects = self.rects();
    match mes {
      RenderCommand::MouseClickLeft(x, y) => self.handle_mouse_click_left(x, y, rects),
      RenderCommand::MouseScrollUp => self.handle_scroll_up(),
      RenderCommand::MouseScrollDown => self.handle_scroll_down(),
      RenderCommand::KeyInput(k) => match k {
        KeyCode::Char(c) => self.handle_char_input(c),
        KeyCode::Backspace => self.handle_backspace(),
        KeyCode::Enter => self.handle_enter(),
        KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right => self.handle_arrow_key(k),
        _ => {}
      },
      RenderCommand::ListMove { pane, index } => self.list_move(pane, index),
      RenderCommand::StringToPaneFocused { str } => self.render_string_to_focused(str),
      RenderCommand::StringToPaneId { str, pane } => self.render_string_to_id(str, pane),
      RenderCommand::Exit => self.exit(),
      RenderCommand::ChangeLayout(l) => self.layout = RenderLayout::generate(l),
      RenderCommand::ClearPane => self.clear_pane(RenderTarget::All),
      RenderCommand::SetMode(mode) => {
        match &mode {
          InputMode::Input => {
            if let InputMode::PopUp(..) = self.mode {
              self.layout.panes.pop();
            }
          }
          InputMode::PopUp(content, title, ..) => {
            self.clear_focus();
            let pane = Pane::new(
              Some(title.to_string()),
              RenderTarget::PopUp,
              RenderBuffer::Select(content.clone(), 0),
              true,
            );
            self.layout.panes.push(pane);
          }
        }
        self.mode = mode
      },
    }
  }

}

impl Widget for &mut Renderer {
  fn render(self, area: Rect, buf: &mut Buffer) {
    let rects = self.layout.rects(area);
    for (i, p) in self.layout.panes.iter_mut().enumerate() {
      if p.target == RenderTarget::PopUp {
        p.render(area, buf);
      } else {
        p.render(rects[i], buf)
      }
    }
  }
}
