//! Cooperative session owned by the IDE event loop, on the VM's thread.
use crate::app::{App, DialogKind, Execution, Field, WindowKind};
use anyhow::{anyhow, Result};
use crossterm::event::Event;
use std::{collections::VecDeque, path::PathBuf};
use tb_runtime::{
    errors::RuntimeError,
    host::{Ereignis, Host},
    screen::TextScreen,
};
use tb_vm::{
    interp::{PollResult, RunEvent, Vm},
    project_io::{load_program, new_execution, run_target},
};

#[derive(Default)]
pub struct IdeHost {
    pub events: VecDeque<Ereignis>,
    pub now: u64,
    pub shell_request: Option<String>,
    pub shell_result: Option<Result<i32, RuntimeError>>,
}
impl Host for IdeHost {
    fn present(&mut self, _: &TextScreen) {}
    fn next_event(&mut self, _: bool) -> Option<Ereignis> {
        self.events.pop_front()
    }
    fn warten(&mut self, _: Option<u64>) -> Option<Ereignis> {
        panic!("IDE poll must never block")
    }
    fn jetzt_ms(&mut self) -> u64 {
        self.now
    }
    fn shell(&mut self, command: &str) -> Result<Option<i32>, RuntimeError> {
        if let Some(result) = self.shell_result.take() {
            result.map(Some)
        } else {
            self.shell_request.get_or_insert_with(|| command.into());
            Ok(None)
        }
    }
}
pub struct Session {
    pub vm: Option<Vm>,
    pub host: IdeHost,
    pub command: String,
    pub target: Option<PathBuf>,
    pub project_target: bool,
    pub old_revision: bool,
    pub fullscreen: bool,
    pub forms_phase: bool,
    pub file_shell: bool,
    revision: Vec<u8>,
    source_revision: Option<crate::editor::Revision>,
    output: TextScreen,
}
impl Default for Session {
    fn default() -> Self {
        Self {
            vm: None,
            host: IdeHost::default(),
            command: String::new(),
            target: None,
            project_target: true,
            old_revision: false,
            fullscreen: false,
            forms_phase: false,
            file_shell: false,
            revision: Vec::new(),
            source_revision: None,
            output: TextScreen::default(),
        }
    }
}
impl Session {
    pub fn screen(&self) -> &TextScreen {
        self.vm.as_ref().map_or(&self.output, |vm| &vm.rt.screen)
    }
    pub fn finish(&mut self) {
        if let Some(mut vm) = self.vm.take() {
            self.output = std::mem::take(&mut vm.rt.screen);
        }
        self.host.events.clear();
        self.host.shell_request = None;
        self.host.shell_result = None;
        self.file_shell = false;
    }
}
impl App {
    pub fn start_execution(&mut self) -> Result<()> {
        self.reset_execution(None, None, true)
    }
    fn reset_execution(
        &mut self,
        target: Option<PathBuf>,
        line: Option<u32>,
        project: bool,
    ) -> Result<()> {
        self.execution = Execution::Compiling;
        let result = (|| {
            let module = if project {
                self.compile_current()?.clone()
            } else {
                load_program(
                    target
                        .as_deref()
                        .ok_or_else(|| anyhow!("Kein Ausführungsziel"))?,
                )
                .map_err(|e| anyhow!(e))?
            };
            let revision = module.to_tbc();
            let vm = new_execution(
                module,
                &self.session.command,
                Some((self.size.0 as usize, self.size.1 as usize)),
                line,
            )?;
            let target = if project {
                self.project.path().map(PathBuf::from).or_else(|| {
                    self.project
                        .members()
                        .first()
                        .and_then(|id| self.project.document(*id).ok())
                        .map(|d| d.source_path().to_path_buf())
                })
            } else {
                target
            };
            self.session.finish();
            self.basic_events.clear();
            self.session.vm = Some(vm);
            self.session.target = target;
            self.session.project_target = project;
            self.session.revision = revision;
            self.session.source_revision = if project {
                self.editor.revision.clone()
            } else {
                None
            };
            self.session.old_revision = false;
            self.session.forms_phase = false;
            self.execution = Execution::Running;
            self.show_tool(WindowKind::Output);
            self.message = "Running".into();
            Ok(())
        })();
        if let Err(e) = &result {
            self.session.finish();
            self.execution = Execution::Error;
            self.message = format!("{e:#}");
        }
        result
    }
    pub fn pause_execution(&mut self) {
        if matches!(self.execution, Execution::Running | Execution::Waiting) {
            self.execution = Execution::Paused;
            self.editor.chord = None;
            self.message = self.session.vm.as_ref().map_or_else(
                || "Paused".into(),
                |vm| {
                    format!(
                        "Paused · {}:{} · F5: Continue",
                        vm.current_file(),
                        vm.current_line()
                    )
                },
            );
        }
    }
    pub fn continue_execution(&mut self) -> Result<()> {
        if matches!(self.execution, Execution::Running | Execution::Waiting) {
            return Ok(());
        }
        if self.execution != Execution::Paused {
            return self.start_execution();
        }
        let current = if self.session.project_target {
            self.compile_current().map(|m| m.to_tbc())
        } else {
            self.session
                .target
                .as_deref()
                .ok_or_else(|| anyhow!("Kein Ausführungsziel"))
                .and_then(|p| load_program(p).map_err(|e| anyhow!(e)))
                .map(|m| m.to_tbc())
        };
        if current
            .as_ref()
            .is_ok_and(|bytes| *bytes == self.session.revision)
            && (!self.session.project_target
                || self.editor.revision == self.session.source_revision)
        {
            self.resume_execution(false);
        } else {
            self.open_dialog(
                "Quellstand geändert",
                DialogKind::ResumeRevision,
                vec![Field::choice(
                    "Fortsetzen",
                    vec![
                        "Neu starten (aktueller Quellstand)".into(),
                        "Altes Kompilat ausdrücklich fortsetzen".into(),
                    ],
                    0,
                )],
            );
        }
        Ok(())
    }
    pub fn resume_execution(&mut self, old: bool) {
        if let Some(vm) = self.session.vm.as_mut() {
            vm.rt.abbruch = false;
            self.session.old_revision = old;
            self.execution = Execution::Running;
            self.message = if old {
                "Running · ALTES KOMPILAT"
            } else {
                "Running"
            }
            .into();
        }
    }
    pub fn restart_target(&mut self) -> Result<()> {
        self.reset_execution(
            self.session.target.clone(),
            None,
            self.session.project_target,
        )
    }
    /// One bounded slice. Tests supply virtual milliseconds, main uses Instant.
    pub fn tick_execution(&mut self, now: u64) {
        self.session.host.now = now;
        if !matches!(self.execution, Execution::Running | Execution::Waiting) {
            return;
        }
        for event in std::mem::take(&mut self.basic_events) {
            match event {
                Event::Key(k) => {
                    if let Some(e) = tb_ui::host::taste_zu_ereignis(k) {
                        self.session.host.events.push_back(e);
                    }
                }
                Event::Mouse(m) => {
                    if let Some(e) = tb_ui::host::maus_zu_ereignis(
                        m,
                        (self.size.0 as usize, self.size.1 as usize),
                    ) {
                        self.session.host.events.push_back(e);
                    }
                }
                Event::Paste(s) => {
                    self.session.host.events.extend(s.chars().map(|c| {
                        Ereignis::Taste(
                            if c == '\n' {
                                "\r".into()
                            } else {
                                c.to_string()
                            },
                            0,
                        )
                    }));
                }
                _ => {}
            }
        }
        let Some(vm) = self.session.vm.as_mut() else {
            return;
        };
        vm.rt.pump(&mut self.session.host, false);
        let result = if self.session.forms_phase {
            vm.poll_visible_forms(&mut self.session.host, 4096)
        } else {
            vm.poll(&mut self.session.host, 4096)
        };
        match result {
            PollResult::Yield => self.execution = Execution::Running,
            PollResult::Waiting { .. } => self.execution = Execution::Waiting,
            PollResult::Event(event) => match event {
                RunEvent::Ended
                    if !self.session.forms_phase
                        && vm.forms.has_visible_forms()
                        && !vm.is_terminated() =>
                {
                    self.session.forms_phase = true;
                    self.execution = Execution::Waiting;
                }
                RunEvent::Ended => {
                    self.session.finish();
                    self.execution = Execution::Ended;
                    self.message = "Ended".into();
                }
                RunEvent::Stopped { .. }
                | RunEvent::Interrupted { .. }
                | RunEvent::Breakpoint { .. }
                | RunEvent::Stepped { .. } => self.pause_execution(),
                RunEvent::Error {
                    code,
                    line,
                    message,
                } => {
                    self.session.finish();
                    self.execution = Execution::Error;
                    self.message = format!("Runtime error {code} in Zeile {line}: {message}");
                }
                RunEvent::Restart { program, line } => {
                    let target = program
                        .as_deref()
                        .map(|p| run_target(self.session.target.as_deref().unwrap(), p))
                        .transpose();
                    match target {
                        Ok(target) => {
                            let project = self.session.project_target
                                && (target.is_none() || target == self.session.target);
                            let target = target.or_else(|| self.session.target.clone());
                            let _ = self.reset_execution(target, line, project);
                        }
                        Err(error) => {
                            self.session.finish();
                            self.execution = Execution::Error;
                            self.message = error;
                        }
                    }
                }
            },
        }
    }
}
