use crate::{
  bus::{publish_system_event, publish_watcher_event, subscribe_system_event, SystemEvent},
  log,
  node::NodeCommand,
  watcher::WatcherCommand,
};

use cli_renderer::pane::RenderTarget;
use thiserror::Error;
use tokio::{select, sync::mpsc};
use vlogger::*;

use super::ExecutorCommand;
use crate::RenderCommand;

#[derive(Debug, Error)]
pub enum InProcessError {
  #[error("TODO: {0}")]
  TODO(String),
}

pub struct Executor {
  node_tx: mpsc::Sender<NodeCommand>,
  rx: mpsc::Receiver<ExecutorCommand>,
  exit: bool,
}

impl Executor {
  pub fn new(node_tx: mpsc::Sender<NodeCommand>, rx: mpsc::Receiver<ExecutorCommand>) -> Self {
    Self {
      node_tx,
      rx,
      exit: false,
    }
  }

  pub async fn run(&mut self) {
    publish_system_event(SystemEvent::ExecutorStarted);
    let mut sys_rx = subscribe_system_event();
    while !self.exit {
      select! {
        _ = self.listen() => {}
        event_res = sys_rx.recv() => {
          if let Ok(event) = event_res {
            match event {
              SystemEvent::Shutdown => {
                self.exit().await;
              }
              _ => {}
            }
          }
        }
      }
    }
  }

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

  async fn listen(&mut self) {
    if let Some(cmd) = self.rx.recv().await {
      let _ = self.execute(cmd).await;
    }
  }

  async fn send_node_cmd(&self, cmd: NodeCommand) {
    self.node_tx.send(cmd).await.unwrap()
  }

  async fn handle_node_cmd(&self, cmd: NodeCommand) {
    self.send_node_cmd(cmd).await;
  }

  async fn echo(&self, s: Vec<String>) {
    let mut str = s.join(" ");
    str.push_str("\n");
    let rd_cmd = WatcherCommand::Render(RenderCommand::StringToPaneId {
      str,
      pane: RenderTarget::CliOutput,
    });
    publish_watcher_event(rd_cmd);
  }

  async fn invalid_command(&self, str: String) {
    let rd_cmd = WatcherCommand::Render(RenderCommand::StringToPaneId {
      str,
      pane: RenderTarget::CliOutput,
    });
    publish_watcher_event(rd_cmd);
  }

  async fn execute(&mut self, cmd: ExecutorCommand) {
    match cmd {
      ExecutorCommand::NodeResponse(resp) => log(resp),
      ExecutorCommand::Node(n) => self.handle_node_cmd(n).await,
      ExecutorCommand::Render(p) => publish_watcher_event(WatcherCommand::Render(p)),
      ExecutorCommand::Watcher(w) => publish_watcher_event(w),
      ExecutorCommand::Echo(s) => self.echo(s).await,
      ExecutorCommand::Print(s) => log(s),
      ExecutorCommand::InvalidCommand(str) => self.invalid_command(str).await,
      ExecutorCommand::Exit => self.exit().await,
    }
  }
}
