use crate::{
    commands::{self, Command, Menu},
    documents::{Decision, Destination, DocumentId, Project, SavePlan, StartupRemoval, ViewId},
    export::{Artifact, ExportRequest, ExportStatus},
    options::{Options, ELEMENTS},
};
use anyhow::{anyhow, ensure, Result};
use crossterm::event::{
    Event, KeyCode as K, KeyEvent, KeyEventKind, KeyModifiers as M, ModifierKeyCode, MouseButton,
    MouseEventKind,
};
use ratatui::layout::Rect;
use std::path::{Path, PathBuf};
use tb_vm::project_io::Content;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
    Environment,
    Designer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Execution {
    Idle,
    Compiling,
    Paused,
    Running,
    Waiting,
    Ended,
    Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowKind {
    Toolbox,
    Palette,
    MenuDesign,
    Code(ViewId),
    Project,
    Output,
    Calls,
    Debug,
    Immediate,
    Help,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowState {
    Normal,
    Minimized,
    Maximized,
}
#[derive(Debug, Clone)]
pub struct Window {
    pub id: u64,
    pub kind: WindowKind,
    pub normal: Rect,
    pub state: WindowState,
}
#[derive(Debug, Clone)]
pub enum FieldValue {
    Text(String),
    Choice { items: Vec<String>, selected: usize },
    Toggle(bool),
}
#[derive(Debug, Clone)]
pub struct Field {
    pub label: String,
    pub value: FieldValue,
    pub cursor: usize,
    pub selected: bool,
}
impl Field {
    pub fn text(label: impl Into<String>, text: impl Into<String>) -> Self {
        let text = text.into();
        Self {
            label: label.into(),
            cursor: text.len(),
            value: FieldValue::Text(text),
            selected: true,
        }
    }
    pub fn choice(label: impl Into<String>, items: Vec<String>, selected: usize) -> Self {
        Self {
            label: label.into(),
            value: FieldValue::Choice { selected, items },
            cursor: 0,
            selected: false,
        }
    }
    pub fn toggle(label: impl Into<String>, value: bool) -> Self {
        Self {
            label: label.into(),
            value: FieldValue::Toggle(value),
            cursor: 0,
            selected: false,
        }
    }
    pub fn string(&self) -> String {
        match &self.value {
            FieldValue::Text(s) => s.clone(),
            FieldValue::Choice { items, selected } => {
                items.get(*selected).cloned().unwrap_or_default()
            }
            FieldValue::Toggle(v) => v.to_string(),
        }
    }
    pub fn index(&self) -> usize {
        match self.value {
            FieldValue::Choice { selected, .. } => selected,
            _ => 0,
        }
    }
    pub fn flag(&self) -> bool {
        matches!(self.value, FieldValue::Toggle(true))
    }
    pub(crate) fn key(&mut self, key: KeyEvent) {
        match &mut self.value {
            FieldValue::Toggle(value) => {
                if matches!(key.code, K::Char(' ') | K::Left | K::Right) {
                    *value = !*value
                }
            }
            FieldValue::Choice { items, selected } => {
                if !items.is_empty() {
                    match key.code {
                        K::Left => *selected = (*selected + items.len() - 1) % items.len(),
                        K::Right | K::Char(' ') => *selected = (*selected + 1) % items.len(),
                        _ => {}
                    }
                }
            }
            FieldValue::Text(s) => {
                if key.modifiers.contains(M::CONTROL) && key.code == K::Char('a') {
                    self.selected = true;
                    return;
                }
                match key.code {
                    K::Char(c) if !key.modifiers.intersects(M::CONTROL | M::ALT) => {
                        if self.selected {
                            s.clear();
                            self.cursor = 0;
                        }
                        s.insert(self.cursor, c);
                        self.cursor += c.len_utf8();
                    }
                    K::Backspace => {
                        if self.selected {
                            s.clear();
                            self.cursor = 0;
                        } else if self.cursor > 0 {
                            let p = s[..self.cursor].char_indices().last().unwrap().0;
                            s.drain(p..self.cursor);
                            self.cursor = p;
                        }
                    }
                    K::Delete => {
                        if self.selected {
                            s.clear();
                            self.cursor = 0;
                        } else if self.cursor < s.len() {
                            let n = s[self.cursor..].chars().next().unwrap().len_utf8();
                            s.drain(self.cursor..self.cursor + n);
                        }
                    }
                    K::Left => {
                        self.cursor = s[..self.cursor]
                            .char_indices()
                            .last()
                            .map(|(i, _)| i)
                            .unwrap_or(0);
                    }
                    K::Right => {
                        self.cursor += s[self.cursor..]
                            .chars()
                            .next()
                            .map(char::len_utf8)
                            .unwrap_or(0);
                    }
                    K::Home => self.cursor = 0,
                    K::End => self.cursor = s.len(),
                    _ => return,
                }
                self.selected = false;
            }
        }
    }
}
#[derive(Debug, Clone)]
pub enum AfterSave {
    Stay,
    Exit,
    NewProject,
    OpenProject(PathBuf),
}
#[derive(Debug, Clone)]
pub enum DialogKind {
    DesignProperty,
    DesignPalette,
    DesignMenu((String, Option<i32>)),
    DesignObject(Vec<u64>),
    DesignEvent(u64),
    Search {
        view: ViewId,
        selection: Option<std::ops::Range<usize>>,
    },
    Procedure(bool),
    Procedures(Vec<usize>),
    Diagnostics,
    Browse {
        return_to: Box<Dialog>,
        field: usize,
        entries: Vec<PathBuf>,
    },
    Message,
    OpenProject,
    NewModule,
    AddFile,
    ChooseCode,
    ChooseForm,
    Remove,
    Startup,
    Save {
        project: bool,
        ids: Vec<DocumentId>,
        after: AfterSave,
    },
    Dirty(AfterSave),
    LoadText,
    SaveText,
    Print,
    CommandLine,
    ResumeRevision,
    Display,
    Paths,
    RightMouse,
    SaveOptions,
    Export(Artifact),
}
#[derive(Debug, Clone)]
pub struct Dialog {
    pub title: String,
    pub kind: DialogKind,
    pub fields: Vec<Field>,
    pub focus: usize,
    pub error: String,
    pub request: Option<ExportRequest>,
    pub export_status: ExportStatus,
}
impl Dialog {
    fn new(title: impl Into<String>, kind: DialogKind, fields: Vec<Field>) -> Self {
        Self {
            title: title.into(),
            kind,
            fields,
            focus: 0,
            error: String::new(),
            request: None,
            export_status: ExportStatus::Ready,
        }
    }
}
#[derive(Debug, Clone, Copy)]
pub enum Hit {
    DesignTool(usize),
    DesignPaint(&'static str, u8),
    DesignObject(u64),
    DesignAction(&'static str),
    Command(Command),
    Menu(usize),
    MenuItem(usize),
    Window(u64),
    ProjectMember(usize),
    ProjectView(bool),
    DialogField(usize),
    DialogSubmit,
    DialogCancel,
}

pub struct App {
    pub designer: crate::designer::Designer,
    pub session: crate::execution::Session,
    pub editor: crate::editor::Editor,
    pub project: Project,
    pub options: Options,
    pub mode: Mode,
    pub execution: Execution,
    pub windows: Vec<Window>,
    pub active: u64,
    pub size: (u16, u16),
    pub menu: Option<(usize, usize)>,
    pub control_menu: bool,
    pub dialog: Option<Dialog>,
    pub properties: bool,
    pub value_focus: bool,
    pub selected_member: usize,
    pub message: String,
    pub quit: bool,
    pub last_command: Option<Command>,
    pub hits: Vec<(Rect, Hit)>,
    pub basic_events: Vec<Event>,
    pub last_export: Option<(ExportRequest, ExportStatus)>,
    pub config_path: PathBuf,
    pub saved_options: Options,
    config_disk: Option<Vec<u8>>,
    next_window: u64,
    geometry_action: Option<(bool, Rect)>,
    base: PathBuf,
}
impl App {
    pub fn load_initial_project(&mut self, path: PathBuf) -> Result<()> {
        self.finish_change(AfterSave::OpenProject(path))
    }
    pub fn new(base: &Path, config_path: PathBuf, size: (u16, u16)) -> Result<Self> {
        let mut project = Project::new(base)?;
        let doc = untitled(&mut project)?;
        let (options, errors, config_disk) = Options::load(&config_path);
        project.include_paths = options.include_paths.clone();
        let mut app = Self {
            designer: Default::default(),
            session: Default::default(),
            editor: Default::default(),
            project,
            options: options.clone(),
            saved_options: options,
            config_path,
            config_disk,
            base: tb_vm::project_io::identity(base)?,
            mode: Mode::Environment,
            execution: Execution::Idle,
            windows: vec![],
            active: 0,
            size,
            menu: None,
            control_menu: false,
            dialog: None,
            properties: false,
            value_focus: false,
            selected_member: 0,
            message: format!(
                "Terminal Basic {} · Copyright {} · {}",
                env!("CARGO_PKG_VERSION"),
                env!("CARGO_PKG_AUTHORS"),
                errors.join("; ")
            ),
            quit: false,
            last_command: None,
            hits: vec![],
            basic_events: vec![],
            last_export: None,
            next_window: 1,
            geometry_action: None,
        };
        let width = size.0.max(80);
        let height = size.1.max(25);
        let view = app.project.open_view(doc)?;
        let code = app.add_window(
            WindowKind::Code(view),
            Rect::new(0, 1, width.saturating_sub(32), height - 3),
        );
        app.add_window(
            WindowKind::Project,
            Rect::new(width - 32, 1, 32, height - 3),
        );
        app.active = code;
        Ok(app)
    }
    pub fn area(&self) -> Rect {
        Rect::new(
            0,
            1,
            self.size.0,
            self.size
                .1
                .saturating_sub(if self.mode == Mode::Designer { 1 } else { 2 }),
        )
    }
    pub fn rect(&self, w: &Window) -> Rect {
        let area = self.area();
        match w.state {
            WindowState::Maximized => area,
            WindowState::Minimized => Rect::new(
                0,
                area.bottom().saturating_sub(2),
                24.min(area.width),
                2.min(area.height),
            ),
            WindowState::Normal => clamp(w.normal, area),
        }
    }
    fn add_window(&mut self, kind: WindowKind, normal: Rect) -> u64 {
        let id = self.next_window;
        self.next_window += 1;
        self.windows.push(Window {
            id,
            kind,
            normal,
            state: WindowState::Normal,
        });
        self.active = id;
        id
    }
    pub fn active_window(&self) -> Option<&Window> {
        self.windows.iter().find(|w| w.id == self.active)
    }
    pub fn active_document(&self) -> Option<DocumentId> {
        match self.active_window()?.kind {
            WindowKind::Code(v) => self.project.view(v).ok().map(|v| v.document()),
            WindowKind::Toolbox | WindowKind::Palette | WindowKind::MenuDesign => {
                self.designer.document
            }
            _ => self.project.members().get(self.selected_member).copied(),
        }
    }
    pub fn title(&self, w: &Window) -> String {
        match w.kind {
            WindowKind::Code(v) => {
                let doc = self
                    .project
                    .document(self.project.view(v).unwrap().document())
                    .unwrap();
                let name = doc
                    .path()
                    .and_then(|p| p.file_name())
                    .map(|s| s.to_string_lossy().into_owned())
                    .unwrap_or_else(|| {
                        doc.source_path()
                            .file_stem()
                            .unwrap_or_default()
                            .to_string_lossy()
                            .into_owned()
                    });
                format!(
                    "[{}] {}{}{}",
                    w.id,
                    if self.editor.expansions.contains_key(&v) {
                        "Included Lines [read-only] · "
                    } else {
                        ""
                    },
                    name,
                    if doc.is_dirty() { " *" } else { "" }
                )
            }
            WindowKind::Project => format!(
                "Project: {}",
                self.project
                    .path()
                    .and_then(|p| p.file_name())
                    .map(|s| s.to_string_lossy().into_owned())
                    .unwrap_or_else(|| "Untitled".into())
            )
            .trim_end()
            .to_string(),
            WindowKind::Output if self.session.old_revision => "Output · ALTES KOMPILAT".into(),
            _ => format!("{:?}", w.kind),
        }
    }
    pub fn menus(&self) -> Vec<Menu> {
        commands::menus(self.mode == Mode::Designer)
    }
    pub fn window_commands(&self) -> Vec<(String, Command)> {
        self.windows
            .iter()
            .filter(|w| matches!(w.kind, WindowKind::Code(_)))
            .filter(|w| self.mode!=Mode::Designer || matches!(w.kind,WindowKind::Code(v) if self.project.view(v).ok().and_then(|v|self.project.document(v.document()).ok()).is_some_and(|d|matches!(d.content(),Content::Form(_)))))
            .map(|w| {
                (
                    format!(
                        "{}{}{}",
                        if w.id == self.active { "• " } else { "  " },
                        if self.mode==Mode::Designer {"Form: "}else{""},
                        self.title(w)
                    ),
                    Command::FocusWindow(w.id),
                )
            })
            .collect()
    }
    pub fn availability(&self, command: Command) -> Option<String> {
        if let Some(phase) = command.feature_phase() {
            return Some(format!("Fachfunktion folgt in Phase-5-Change {phase:02}"));
        }
        use Command::*;
        if command == Shell && self.session.host.shell_request.is_some() {
            return Some("Shell-Übergabe bereits angefordert".into());
        }
        if matches!(
            command,
            Undo | Cut | Paste | Clear | LoadText | NewSub | NewFunction | Replace
        ) && self
            .editor_view()
            .is_ok_and(|v| self.editor.expansions.contains_key(&v))
        {
            return Some(
                "Included Lines ist schreibgeschützt; Included File öffnet die Originaldatei"
                    .into(),
            );
        }
        if !(self.mode == Mode::Designer && matches!(command, Cut | Copy | Paste | Clear))
            && matches!(
                command,
                LoadText
                    | Print
                    | SaveText
                    | Cut
                    | Copy
                    | Paste
                    | Clear
                    | NewSub
                    | NewFunction
                    | IncludedFile
                    | IncludedLines
                    | Find
                    | SelectedText
                    | FindNext
                    | Replace
                    | Procedures
                    | PreviousCode
                    | Diagnostics
            )
            && !matches!(
                self.active_window().map(|w| w.kind),
                Some(WindowKind::Code(_))
            )
        {
            return Some("Kein Codefenster aktiv".into());
        }
        if matches!(
            command,
            SaveFile | SaveAs | LoadText | SaveText | Undo | NewWindow
        ) && self.active_document().is_none()
        {
            return Some("Kein Dokument ausgewählt".into());
        }
        if matches!(command, Form)
            && !self
                .project
                .documents()
                .any(|(_, d)| matches!(d.content(), Content::Form(_)))
        {
            return Some("Kein Formular vorhanden".into());
        }
        if matches!(command, RemoveFile | Startup) && self.project.members().is_empty() {
            return Some("Kein Projektmitglied vorhanden".into());
        }
        if matches!(
            command,
            CloseWindow | MoveWindow | SizeWindow | Minimize | Maximize | Restore | ControlMenu
        ) && self.active_window().is_none()
        {
            return Some("Kein Fenster aktiv".into());
        }
        None
    }
    pub fn execute(&mut self, command: Command) {
        self.last_command = Some(command);
        if let Some(reason) = self.availability(command) {
            self.message = reason;
            return;
        }
        self.menu = None;
        self.control_menu = false;
        if let Err(e) = self.action(command) {
            self.message = format!("{e:#}");
            if self.mode == Mode::Designer {
                self.open_dialog(
                    "Designer",
                    DialogKind::Message,
                    vec![Field::text("Fehler", self.message.clone())],
                );
            }
        }
    }
    fn action(&mut self, command: Command) -> Result<()> {
        if self.designer_command(command)? {
            return Ok(());
        }
        use Command::*;
        let id = self.active_document();
        match command {
            Start => self.start_execution()?,
            Restart => self.restart_target()?,
            Continue => self.continue_execution()?,
            Pause => self.pause_execution(),
            OutputScreen => {
                self.session.fullscreen = !self.session.fullscreen;
            }
            CommandLine => self.open_dialog(
                "COMMAND$",
                DialogKind::CommandLine,
                vec![Field::text(
                    "Argumente für den nächsten Start",
                    &self.session.command,
                )],
            ),
            Shell => {
                self.session.file_shell = true;
                self.session.host.shell_request = Some(String::new());
            }
            Print => self.open_dialog(
                "Print",
                DialogKind::Print,
                vec![
                    Field::text("UTF-8-Ausgabedatei", self.output_default("LPT1.TXT")),
                    Field::toggle("Bestehendes Ziel überschreiben", false),
                    Field::toggle(
                        "Nur Auswahl",
                        self.project
                            .view(self.editor_view()?)?
                            .selection()
                            .is_some(),
                    ),
                ],
            ),
            Cut | Copy | Paste | Clear | NewSub | NewFunction | IncludedFile | IncludedLines
            | Find | SelectedText | FindNext | Replace | Procedures | PreviousCode
            | Diagnostics => self.editor_command(command)?,
            NewProject => self.change_project(AfterSave::NewProject)?,
            OpenProject => self.open_dialog(
                "Open Project",
                DialogKind::OpenProject,
                vec![Field::text("Datei", self.source_default())],
            ),
            Exit => self.change_project(AfterSave::Exit)?,
            NewModule => self.open_dialog(
                "New Module",
                DialogKind::NewModule,
                vec![Field::text("Name", "Module1")],
            ),
            NewForm => {
                let mut n = 1;
                while self
                    .project
                    .loader()?
                    .resolve(self.project.directory(), &format!("Form{n}.frm"))
                    .is_ok()
                    || self.project.documents().any(|(_, d)| {
                        d.source_path()
                            .file_stem()
                            .is_some_and(|s| s.eq_ignore_ascii_case(format!("Form{n}").as_str()))
                    })
                {
                    n += 1;
                }
                let id = self.project.new_form(&format!("Form{n}"))?;
                self.show_document(id, true)?;
            }
            AddFile => self.open_dialog(
                "Add File",
                DialogKind::AddFile,
                vec![Field::text("Datei", self.source_default())],
            ),
            SaveFile => {
                let id = id.unwrap();
                if self.project.document(id)?.path().is_none()
                    || self.project.save_file(id, None).is_err()
                {
                    self.save_dialog(false, vec![id], AfterSave::Stay)?;
                } else {
                    self.message = format!(
                        "Datei gespeichert · {}",
                        self.project.save_notices.join("; ")
                    );
                }
            }
            SaveAs => self.save_dialog(false, vec![id.unwrap()], AfterSave::Stay)?,
            SaveProject => self.save_dialog(
                true,
                self.project.documents().map(|(id, _)| id).collect(),
                AfterSave::Stay,
            )?,
            RemoveFile => {
                let ids = self.project.members();
                let names = self.names(&ids);
                let mut replacements = vec!["Bisheriger Standard".into()];
                replacements.extend(names.clone());
                self.open_dialog(
                    "Remove File",
                    DialogKind::Remove,
                    vec![
                        Field::choice("Mitglied", names, self.selected_member.min(ids.len() - 1)),
                        Field::choice("Ersatz für Startdatei", replacements, 0),
                    ],
                );
            }
            Startup => {
                let ids = self.project.members();
                let mut names = vec!["Bisheriger Standard".into()];
                names.extend(self.names(&ids));
                let selected = self
                    .project
                    .startup()
                    .and_then(|id| ids.iter().position(|i| *i == id))
                    .map(|n| n + 1)
                    .unwrap_or(0);
                self.open_dialog(
                    "Set Start-up File",
                    DialogKind::Startup,
                    vec![Field::choice("Startdatei", names, selected)],
                );
            }
            Code | Form => {
                let ids: Vec<_> = self
                    .project
                    .documents()
                    .filter(|(_, d)| command == Code || matches!(d.content(), Content::Form(_)))
                    .map(|(id, _)| id)
                    .collect();
                let selected = id
                    .and_then(|id| ids.iter().position(|i| *i == id))
                    .unwrap_or(0);
                let names = self.names(&ids);
                self.open_dialog(
                    if command == Code { "Code" } else { "Form" },
                    if command == Code {
                        DialogKind::ChooseCode
                    } else {
                        DialogKind::ChooseForm
                    },
                    vec![Field::choice("Dokument", names, selected)],
                );
            }
            LoadText => self.open_dialog(
                "Load Text",
                DialogKind::LoadText,
                vec![Field::text("Datei", self.source_default())],
            ),
            SaveText => self.open_dialog(
                "Save Text",
                DialogKind::SaveText,
                vec![
                    Field::text("Datei", self.output_default("text.txt")),
                    Field::toggle("Bestehendes Ziel überschreiben", false),
                ],
            ),
            Undo => {
                self.project.undo(id.unwrap())?;
            }
            NewWindow => {
                let view = self.project.open_view(id.unwrap())?;
                self.add_window(WindowKind::Code(view), self.cascade());
            }
            Arrange => self.arrange(),
            NextWindow => self.cycle(1),
            PreviousWindow => self.cycle(-1),
            CloseWindow => {
                if let Some(i) = self.windows.iter().position(|w| w.id == self.active) {
                    let w = self.windows.remove(i);
                    if let WindowKind::Code(v) = w.kind {
                        self.project.close_view(v)?;
                    }
                    self.active = self.windows.first().map(|w| w.id).unwrap_or(0);
                }
            }
            MoveWindow | SizeWindow => {
                if let Some(w) = self.windows.iter().find(|w| w.id == self.active) {
                    self.geometry_action = Some((command == SizeWindow, w.normal));
                    self.message = "Pfeile ändern Fenster; Enter bestätigt, Esc verwirft".into();
                }
            }
            Minimize | Maximize | Restore => {
                if let Some(w) = self.windows.iter_mut().find(|w| w.id == self.active) {
                    w.state = match command {
                        Minimize => WindowState::Minimized,
                        Maximize => WindowState::Maximized,
                        _ => WindowState::Normal,
                    };
                }
            }
            ControlMenu => {
                self.control_menu = true;
                self.menu = Some((0, 0));
            }
            FocusWindow(id) => {
                if self.windows.iter().any(|w| w.id == id) {
                    self.active = id;
                    if self.mode == Mode::Designer {
                        if let Some(WindowKind::Code(v)) = self.active_window().map(|w| w.kind) {
                            let doc = self.project.view(v)?.document();
                            if matches!(self.project.document(doc)?.content(), Content::Form(_)) {
                                self.design_enter(doc)?;
                            } else {
                                self.mode = Mode::Environment;
                                self.properties = false;
                                self.value_focus = false;
                            }
                        }
                    }
                }
            }
            Calls | Debug | HelpWindow | Immediate | Output | Project => {
                let kind = match command {
                    Calls => WindowKind::Calls,
                    Debug => WindowKind::Debug,
                    HelpWindow => WindowKind::Help,
                    Immediate => WindowKind::Immediate,
                    Output => WindowKind::Output,
                    _ => WindowKind::Project,
                };
                self.show_tool(kind);
                if command == Output {
                    self.session.fullscreen = false;
                }
            }
            MenuBar => {
                self.properties = !self.properties;
                self.value_focus = false;
                self.menu = if self.properties { None } else { Some((0, 0)) };
            }
            Display => {
                let mut fields = vec![];
                for (i, name) in ELEMENTS.iter().enumerate() {
                    fields.push(Field::choice(
                        format!("{name} Vordergrund"),
                        (0..16).map(|n| n.to_string()).collect(),
                        self.options.colors[i].0 as usize,
                    ));
                    fields.push(Field::choice(
                        format!("{name} Hintergrund"),
                        (0..16).map(|n| n.to_string()).collect(),
                        self.options.colors[i].1 as usize,
                    ));
                }
                fields.extend([
                    Field::text("Desktopzeichen", self.options.desktop.to_string()),
                    Field::text("Tabweite", self.options.tab_width.to_string()),
                ]);
                self.open_dialog("Display", DialogKind::Display, fields);
            }
            Paths => self.open_dialog(
                "Set Paths",
                DialogKind::Paths,
                vec![
                    Field::text("Quellen", self.options.source_dir.display().to_string()),
                    Field::text("Ausgaben", self.options.output_dir.display().to_string()),
                    Field::text(
                        "Include-Pfade (| getrennt)",
                        self.options
                            .include_paths
                            .iter()
                            .map(|p| p.display().to_string())
                            .collect::<Vec<_>>()
                            .join("|"),
                    ),
                ],
            ),
            RightMouse => self.open_dialog(
                "Right Mouse",
                DialogKind::RightMouse,
                vec![Field::toggle(
                    "Rechtsklick öffnet Kontext-Hilfe",
                    self.options.right_help,
                )],
            ),
            SaveOptions => self.open_dialog("Save Options", DialogKind::SaveOptions, vec![]),
            SyntaxChecking => {
                self.options.syntax_checking = !self.options.syntax_checking;
                self.message = format!("Syntax Checking: {}", self.options.syntax_checking);
            }
            About => self.open_dialog(
                format!(
                    "Terminal Basic {} · Copyright {}",
                    env!("CARGO_PKG_VERSION"),
                    env!("CARGO_PKG_AUTHORS")
                ),
                DialogKind::Message,
                vec![],
            ),
            MakeExe | MakeLibrary => {
                let artifact = if command == MakeExe {
                    Artifact::Executable
                } else {
                    Artifact::Library
                };
                let systems = vec!["linux".into(), "macos".into(), "windows".into()];
                let selected = systems
                    .iter()
                    .position(|s| s == std::env::consts::OS)
                    .unwrap_or(0);
                let architectures = vec!["x86_64".into(), "aarch64".into()];
                let arch = architectures
                    .iter()
                    .position(|s| s == std::env::consts::ARCH)
                    .unwrap_or(0);
                self.open_dialog(
                    if command == MakeExe {
                        "Make EXE File"
                    } else {
                        "Make Library"
                    },
                    DialogKind::Export(artifact),
                    vec![
                        Field::text("Artefakt", artifact.label()),
                        Field::choice("Zielsystem", systems, selected),
                        Field::choice("Architektur", architectures, arch),
                        Field::text(
                            "Ausgabepfad",
                            self.output_default(if artifact == Artifact::Executable {
                                "program"
                            } else {
                                "library"
                            }),
                        ),
                        Field::toggle("Bestehendes Ziel überschreiben", false),
                    ],
                );
            }
            _ => {}
        }
        Ok(())
    }
    fn names(&self, ids: &[DocumentId]) -> Vec<String> {
        ids.iter()
            .map(|id| {
                self.project
                    .document(*id)
                    .unwrap()
                    .source_path()
                    .file_name()
                    .unwrap_or_default()
                    .to_string_lossy()
                    .into_owned()
            })
            .collect()
    }
    fn source_default(&self) -> String {
        if self.options.source_dir.as_os_str().is_empty() {
            self.base.display().to_string()
        } else {
            self.options.source_dir.display().to_string()
        }
    }
    fn output_default(&self, name: &str) -> String {
        if self.options.output_dir.as_os_str().is_empty() {
            self.base.join(name)
        } else {
            self.options.output_dir.join(name)
        }
        .display()
        .to_string()
    }
    pub(crate) fn open_dialog(
        &mut self,
        title: impl Into<String>,
        kind: DialogKind,
        fields: Vec<Field>,
    ) {
        self.dialog = Some(Dialog::new(title, kind, fields));
    }
    fn cascade(&self) -> Rect {
        let offset = (self.windows.len() % 5) as u16 * 2;
        clamp(Rect::new(offset, 1 + offset, 60, 16), self.area())
    }
    pub(crate) fn show_tool(&mut self, kind: WindowKind) {
        if let Some(w) = self.windows.iter_mut().find(|w| w.kind == kind) {
            self.active = w.id;
            w.state = WindowState::Normal;
        } else {
            self.add_window(kind, self.cascade());
        }
    }
    pub(crate) fn show_document(&mut self, id: DocumentId, form: bool) -> Result<()> {
        if form {
            ensure!(
                matches!(self.project.document(id)?.content(), Content::Form(_)),
                "Kein Formular"
            );
        }
        if let Some(w)=self.windows.iter_mut().find(|w|matches!(w.kind,WindowKind::Code(v) if self.project.view(v).is_ok_and(|v|v.document()==id))){self.active=w.id;w.state=WindowState::Normal;}else{let view=self.project.open_view(id)?;self.add_window(WindowKind::Code(view),self.cascade());}
        self.mode = if form {
            Mode::Designer
        } else {
            Mode::Environment
        };
        self.properties = form;
        self.value_focus = false;
        if form {
            self.design_enter(id)?;
            if !self.windows.iter().any(|w| w.kind == WindowKind::Toolbox) {
                let active = self.active;
                self.add_window(WindowKind::Toolbox, Rect::new(0, 1, 20, 21));
                if let Some(w) = self.windows.iter_mut().find(|w| w.id == active) {
                    w.normal = Rect::new(
                        20,
                        1,
                        self.size.0.saturating_sub(20),
                        self.size.1.saturating_sub(1),
                    );
                }
                self.active = active;
            }
        }
        Ok(())
    }
    fn cycle(&mut self, direction: isize) {
        if self.windows.is_empty() {
            return;
        }
        let i = self
            .windows
            .iter()
            .position(|w| w.id == self.active)
            .unwrap_or(0);
        let i = (i as isize + direction).rem_euclid(self.windows.len() as isize) as usize;
        if let Err(e) = self.action(Command::FocusWindow(self.windows[i].id)) {
            self.message = format!("{e:#}");
        }
    }
    fn arrange(&mut self) {
        let area = self.area();
        let n = self.windows.len();
        if n == 0 {
            return;
        }
        let cols = (n as f64).sqrt().ceil() as u16;
        let rows = (n as u16).div_ceil(cols);
        for (i, w) in self.windows.iter_mut().enumerate() {
            let col = i as u16 % cols;
            let row = i as u16 / cols;
            let x = area.width * col / cols;
            let right = area.width * (col + 1) / cols;
            let y = area.height * row / rows;
            let bottom = area.height * (row + 1) / rows;
            w.normal = Rect::new(x, area.y + y, right - x, bottom - y);
            w.state = WindowState::Normal;
        }
    }
    fn change_project(&mut self, after: AfterSave) -> Result<()> {
        if self.project.is_dirty() {
            self.open_dialog(
                "Ungespeicherte Änderungen",
                DialogKind::Dirty(after),
                vec![Field::choice(
                    "Entscheidung",
                    vec!["Abbrechen".into(), "Speichern".into(), "Verwerfen".into()],
                    0,
                )],
            );
        } else {
            self.finish_change(after)?;
        }
        Ok(())
    }
    fn finish_change(&mut self, after: AfterSave) -> Result<()> {
        match after {
            AfterSave::Stay => return Ok(()),
            AfterSave::Exit => {
                self.session.finish();
                self.basic_events.clear();
                self.quit = true;
                return Ok(());
            }
            AfterSave::NewProject => {
                self.project.new_project(Decision::Discard)?;
                untitled(&mut self.project)?;
            }
            AfterSave::OpenProject(path) => {
                self.project.open_project(&path, Decision::Discard)?;
            }
        }
        self.session = Default::default();
        self.designer = Default::default();
        self.basic_events.clear();
        self.execution = Execution::Idle;
        self.base = self.project.directory().to_path_buf();
        self.editor = Default::default();
        self.windows.clear();
        self.active = 0;
        self.selected_member = 0;
        self.mode = Mode::Environment;
        self.properties = false;
        if let Some(id) = self.project.members().first().copied() {
            self.show_document(id, false)?;
        }
        let active = self.active;
        self.show_tool(WindowKind::Project);
        if active != 0 {
            self.active = active;
        }
        self.arrange();
        Ok(())
    }
    fn save_dialog(&mut self, project: bool, ids: Vec<DocumentId>, after: AfterSave) -> Result<()> {
        let mut fields = vec![];
        if project {
            fields.push(Field::text(
                "Projekt (.mak)",
                self.project
                    .path()
                    .map(|p| p.display().to_string())
                    .unwrap_or_else(|| self.output_default("project.mak")),
            ));
            fields.push(Field::toggle("Projektziel überschreiben", false));
        }
        for id in &ids {
            let doc = self.project.document(*id)?;
            fields.push(Field::text(
                self.names(&[*id])[0].clone(),
                doc.path()
                    .map(|p| p.display().to_string())
                    .unwrap_or_else(|| doc.source_path().display().to_string()),
            ));
            fields.push(Field::toggle("Dateiziel überschreiben", false));
        }
        self.open_dialog(
            if project {
                "Save Project"
            } else {
                "Save File As"
            },
            DialogKind::Save {
                project,
                ids,
                after,
            },
            fields,
        );
        Ok(())
    }
    fn submit_dialog(&mut self) {
        let Some(mut d) = self.dialog.take() else {
            return;
        };
        match self.submit(&mut d) {
            Ok(keep) => {
                if keep {
                    self.dialog = Some(d);
                }
            }
            Err(e) => {
                d.error = format!("{e:#}");
                self.dialog = Some(d);
            }
        }
    }
    fn submit(&mut self, d: &mut Dialog) -> Result<bool> {
        match d.kind.clone() {
            DialogKind::DesignProperty
            | DialogKind::DesignPalette
            | DialogKind::DesignMenu(_)
            | DialogKind::DesignObject(_)
            | DialogKind::DesignEvent(_) => {
                self.design_submit(d)?;
                return Ok(false);
            }
            DialogKind::Search { .. }
            | DialogKind::Procedure(_)
            | DialogKind::Procedures(_)
            | DialogKind::Diagnostics => return self.editor_submit(d),
            DialogKind::Browse {
                mut return_to,
                field,
                entries,
            } => {
                let path = entries
                    .get(d.fields[0].index())
                    .ok_or_else(|| anyhow!("Keine Datei ausgewählt"))?;
                if path.is_dir() {
                    self.dialog = Some(Self::browser(*return_to, field, path)?);
                } else {
                    return_to.fields[field] = Field::text(
                        return_to.fields[field].label.clone(),
                        path.display().to_string(),
                    );
                    return_to.request = None;
                    self.dialog = Some(*return_to);
                }
            }
            DialogKind::Message => {}
            DialogKind::OpenProject => {
                let path = self.base.join(d.fields[0].string());
                Project::open(&path, self.options.include_paths.clone())?;
                self.change_project(AfterSave::OpenProject(path))?;
            }
            DialogKind::NewModule => {
                let id = self.project.new_module(&d.fields[0].string())?;
                self.show_document(id, false)?;
            }
            DialogKind::AddFile => {
                let id = self.project.add_file(Path::new(&d.fields[0].string()))?;
                self.show_document(id, false)?;
            }
            DialogKind::ChooseCode | DialogKind::ChooseForm => {
                let form = matches!(d.kind, DialogKind::ChooseForm);
                let ids: Vec<_> = self
                    .project
                    .documents()
                    .filter(|(_, d)| !form || matches!(d.content(), Content::Form(_)))
                    .map(|(id, _)| id)
                    .collect();
                let id = *ids
                    .get(d.fields[0].index())
                    .ok_or_else(|| anyhow!("Kein Dokument"))?;
                self.show_document(id, form)?;
            }
            DialogKind::Remove => {
                let ids = self.project.members();
                let id = ids[d.fields[0].index()];
                let replacement = d.fields[1].index();
                self.project.remove_file(
                    id,
                    Some(if replacement == 0 {
                        StartupRemoval::Default
                    } else {
                        StartupRemoval::Replace(ids[replacement - 1])
                    }),
                )?;
                self.selected_member = self
                    .selected_member
                    .min(self.project.members().len().saturating_sub(1));
            }
            DialogKind::Startup => {
                let ids = self.project.members();
                let choice = d.fields[0].index();
                self.project.set_startup(if choice == 0 {
                    None
                } else {
                    Some(ids[choice - 1])
                })?;
            }
            DialogKind::Dirty(after) => match d.fields[0].index() {
                1 => self.save_dialog(
                    true,
                    self.project.documents().map(|(id, _)| id).collect(),
                    after,
                )?,
                2 => self.finish_change(after)?,
                _ => {}
            },
            DialogKind::Save {
                project,
                ids,
                after,
            } => {
                let mut plan = SavePlan::default();
                let mut offset = 0;
                if project {
                    plan.project = Some(Destination {
                        path: d.fields[0].string().into(),
                        overwrite: d.fields[1].flag(),
                    });
                    offset = 2;
                }
                for (i, id) in ids.iter().enumerate() {
                    let path = PathBuf::from(d.fields[offset + 2 * i].string());
                    let doc = self.project.document(*id)?;
                    // Unveränderte Mitglieder brauchen keinen erneuten Schreibzugriff (insbesondere Binärimporte).
                    if !project || doc.is_dirty() || doc.path() != Some(path.as_path()) {
                        plan.files.insert(
                            *id,
                            Destination {
                                path,
                                overwrite: d.fields[offset + 2 * i + 1].flag(),
                            },
                        );
                    }
                }
                if project {
                    self.project.save_project(&plan)?;
                    self.base = self.project.path().unwrap().parent().unwrap().to_path_buf();
                } else {
                    self.project.save_file(ids[0], plan.files.get(&ids[0]))?;
                }
                self.message = format!("Gespeichert · {}", self.project.save_notices.join("; "));
                self.finish_change(after)?;
            }
            DialogKind::LoadText => {
                let text = std::fs::read_to_string(self.base.join(d.fields[0].string()))?;
                self.editor_insert(&text)?;
            }
            DialogKind::CommandLine => {
                self.session.command = d.fields[0].string();
            }
            DialogKind::ResumeRevision => {
                if d.fields[0].index() == 0 {
                    self.restart_target()?;
                } else {
                    self.resume_execution(true);
                }
            }
            DialogKind::SaveText | DialogKind::Print => {
                let (id, _) = self.code_cursor()?;
                self.project.save_text(
                    id,
                    if matches!(d.kind, DialogKind::Print) && !d.fields[2].flag() {
                        None
                    } else {
                        self.project.view(self.editor_view()?)?.selection()
                    },
                    &Destination {
                        path: d.fields[0].string().into(),
                        overwrite: d.fields[1].flag(),
                    },
                )?;
            }
            DialogKind::Display => {
                let mut options = self.options.clone();
                for i in 0..7 {
                    options.colors[i] = (
                        d.fields[2 * i].index() as u8,
                        d.fields[2 * i + 1].index() as u8,
                    );
                }
                let s = d.fields[14].string();
                ensure!(s.chars().count() == 1, "Ein Desktopzeichen erforderlich");
                options.desktop = s.chars().next().unwrap();
                options.tab_width = d.fields[15].string().parse()?;
                options.validate()?;
                self.options = options;
            }
            DialogKind::Paths => {
                let mut options = self.options.clone();
                let path = |s: String| {
                    if s.is_empty() {
                        PathBuf::new()
                    } else {
                        self.base.join(s)
                    }
                };
                options.source_dir = path(d.fields[0].string());
                options.output_dir = path(d.fields[1].string());
                options.include_paths = d.fields[2]
                    .string()
                    .split('|')
                    .filter(|s| !s.is_empty())
                    .map(|s| path(s.into()))
                    .collect();
                options.validate()?;
                self.project.include_paths = options.include_paths.clone();
                self.options = options;
            }
            DialogKind::RightMouse => self.options.right_help = d.fields[0].flag(),
            DialogKind::SaveOptions => {
                self.config_disk = Some(
                    self.options
                        .save(&self.config_path, self.config_disk.as_deref())?,
                );
                self.saved_options = self.options.clone();
                self.message = format!("Optionen gespeichert: {}", self.config_path.display());
            }
            DialogKind::Export(artifact) => {
                let request = ExportRequest::validate(
                    &self.project,
                    artifact,
                    &d.fields[1].string(),
                    &d.fields[2].string(),
                    &Destination {
                        path: d.fields[3].string().into(),
                        overwrite: d.fields[4].flag(),
                    },
                )?;
                d.request = Some(request);
                d.export_status = ExportStatus::Ready;
                d.error = crate::export::UNAVAILABLE.into();
                return Ok(true);
            }
        }
        Ok(false)
    }
    /// Die spätere Anbindung und Oberflächentests liefern denselben konkreten Auftrag zurück.
    pub fn export_result(&mut self, request: &ExportRequest, status: ExportStatus) -> Result<()> {
        let d = self
            .dialog
            .as_mut()
            .ok_or_else(|| anyhow!("Kein Exportdialog offen"))?;
        ensure!(
            d.request.as_ref() == Some(request),
            "Exportauftrag stimmt nicht mit dem Dialog überein"
        );
        ensure!(
            request.project == crate::export::ProjectStamp::capture(&self.project),
            "Projekt wurde seit dem Exportauftrag geändert"
        );
        d.export_status = status.clone();
        d.error.clear();
        self.last_export = Some((request.clone(), status));
        Ok(())
    }
    fn code_cursor(&self) -> Result<(DocumentId, usize)> {
        let Some(Window {
            kind: WindowKind::Code(view),
            ..
        }) = self.active_window()
        else {
            return Err(anyhow!("Kein Codefenster aktiv"));
        };
        let view = self.project.view(*view)?;
        Ok((view.document(), view.cursor))
    }
    pub fn handle(&mut self, event: Event) {
        let old = self.editor_position();
        if !matches!(event, Event::Key(_) | Event::Resize(_, _)) {
            self.editor.chord = None;
        }
        self.handle_event(event);
        self.line_leave(old);
    }
    fn handle_event(&mut self, event: Event) {
        if matches!(event, Event::Key(k) if k.kind != KeyEventKind::Release && k.code == K::Pause && k.modifiers.contains(M::CONTROL))
        {
            self.pause_execution();
            return;
        }
        if let Event::Resize(w, h) = event {
            self.size = (w, h);
            if let Some(vm) = self.session.vm.as_mut() {
                vm.rt.ereignis(tb_runtime::host::Ereignis::Groesse {
                    cols: w as usize,
                    rows: h as usize,
                });
                vm.forms.resize(w as usize, h as usize);
            }
            return;
        }
        if self.size.0 < 80 || self.size.1 < 25 {
            return;
        }
        if let Event::Key(key) = event {
            if key.kind == KeyEventKind::Release {
                return;
            }
            self.key(key);
        } else if let Event::Mouse(mouse) = event {
            match self.design_mouse(mouse) {
                Ok(true) => return,
                Err(e) => {
                    self.message = format!("{e:#}");
                    self.open_dialog(
                        "Designer",
                        DialogKind::Message,
                        vec![Field::text("Fehler", self.message.clone())],
                    );
                    return;
                }
                _ => {}
            }
            if self.dialog.is_none() && self.menu.is_none() && self.program_focus() {
                if self.session.fullscreen {
                    self.basic_events.push(Event::Mouse(mouse));
                    return;
                }
                if let Some(w) = self.active_window() {
                    let r = self.rect(w);
                    let inner = Rect::new(
                        r.x + 1,
                        r.y + 1,
                        r.width.saturating_sub(2),
                        r.height.saturating_sub(2),
                    );
                    if inner.contains((mouse.column, mouse.row).into()) {
                        let mut mouse = mouse;
                        mouse.column -= inner.x;
                        mouse.row -= inner.y;
                        self.basic_events.push(Event::Mouse(mouse));
                        return;
                    }
                }
            }
            if mouse.kind == MouseEventKind::Down(MouseButton::Right) {
                if self.dialog.is_none() && self.menu.is_none() && self.options.right_help {
                    self.execute(Command::Topic);
                }
                return;
            }
            if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
                let hit = self
                    .hits
                    .iter()
                    .rev()
                    .find(|(r, _)| r.contains((mouse.column, mouse.row).into()))
                    .map(|(_, hit)| *hit);
                if let Some(hit) = hit {
                    self.click(hit);
                }
            } else if self.dialog.is_none() && self.menu.is_none() {
                if let Some(Window {
                    kind: WindowKind::Code(v),
                    ..
                }) = self.active_window()
                {
                    let v = *v;
                    if let Ok(view) = self.project.view_mut(v) {
                        match mouse.kind {
                            MouseEventKind::ScrollDown => view.scroll_line += 1,
                            MouseEventKind::ScrollUp => {
                                view.scroll_line = view.scroll_line.saturating_sub(1)
                            }
                            _ => {}
                        }
                    }
                }
            }
        } else if self.program_focus() {
            self.basic_events.push(event);
        } else if let Event::Paste(text) = event {
            if self.dialog.is_none() && self.menu.is_none() && self.mode == Mode::Environment {
                if let Err(e) = self.editor_insert(&text) {
                    self.message = e.to_string();
                }
            }
        }
    }
    fn click(&mut self, hit: Hit) {
        if self.dialog.is_some() {
            match hit {
                Hit::DialogField(i) => {
                    let d = self.dialog.as_mut().unwrap();
                    d.focus = i.min(d.fields.len());
                    if let Some(field) = d.fields.get_mut(i) {
                        if !matches!(field.value, FieldValue::Text(_)) {
                            field.key(KeyEvent::new(K::Right, M::NONE));
                            d.request = None;
                            d.export_status = ExportStatus::Ready;
                        }
                    }
                }
                Hit::DialogSubmit => self.submit_dialog(),
                Hit::DialogCancel => self.cancel_dialog(),
                _ => {}
            }
            return;
        }
        if self.menu.is_some() {
            match hit {
                Hit::Menu(i) => self.menu = Some((i, 0)),
                Hit::MenuItem(i) => {
                    if let Some((menu, _)) = self.menu {
                        self.menu = Some((menu, i));
                        self.menu_enter();
                    }
                }
                _ => {
                    self.menu = None;
                    self.control_menu = false;
                }
            }
            return;
        }
        match hit {
            Hit::DesignTool(_) => {}
            Hit::DesignObject(id) => {
                if let Err(e) = self.design_select(id, false) {
                    self.message = e.to_string();
                }
            }
            Hit::DesignAction(action) => {
                if let Err(e) = self.design_action(action) {
                    self.message = format!("{e:#}");
                    self.open_dialog(
                        "Designer",
                        DialogKind::Message,
                        vec![Field::text("Fehler", self.message.clone())],
                    );
                }
            }

            Hit::Command(c) => self.execute(c),
            Hit::Menu(i) => {
                self.properties = false;
                self.menu = Some((i, 0));
            }
            Hit::Window(id) => self.execute(Command::FocusWindow(id)),
            Hit::ProjectMember(i) => {
                self.selected_member = i;
                if let Some(w) = self.windows.iter().find(|w| w.kind == WindowKind::Project) {
                    self.active = w.id;
                }
            }
            Hit::ProjectView(form) => {
                self.last_command = Some(if form { Command::Form } else { Command::Code });
                if let Some(id) = self.project.members().get(self.selected_member).copied() {
                    if let Err(e) = self.show_document(id, form) {
                        self.message = e.to_string();
                    }
                }
            }
            _ => {}
        }
    }
    fn cancel_dialog(&mut self) {
        if let Some(d) = self.dialog.take() {
            if let DialogKind::Browse { return_to, .. } = d.kind {
                self.dialog = Some(*return_to);
                return;
            }
            if let Some(request) = d.request {
                self.last_export = Some((request, ExportStatus::Cancelled));
            }
        }
    }
    fn program_focus(&self) -> bool {
        matches!(self.execution, Execution::Running | Execution::Waiting)
            && (self.session.fullscreen
                || matches!(
                    self.active_window().map(|w| w.kind),
                    Some(WindowKind::Output)
                ))
    }
    fn key(&mut self, key: KeyEvent) {
        if self.session.fullscreen && self.dialog.is_none() && key.code == K::F(4) {
            self.execute(Command::OutputScreen);
            return;
        }
        if self.editor.chord.is_some()
            && self.dialog.is_none()
            && self.menu.is_none()
            && !self.program_focus()
        {
            if let Err(e) = self.editor_key(key) {
                self.message = e.to_string();
            }
            return;
        }
        if key.code == K::F(2) && self.dialog.is_some() {
            self.browse();
            return;
        }
        if let Some(d) = self.dialog.as_mut() {
            match key.code {
                K::Esc => self.cancel_dialog(),
                K::Enter => self.submit_dialog(),
                K::Tab if key.modifiers.contains(M::SHIFT) => {
                    d.focus = (d.focus + d.fields.len()) % (d.fields.len() + 1)
                }
                K::BackTab | K::Up => d.focus = (d.focus + d.fields.len()) % (d.fields.len() + 1),
                K::Tab | K::Down => d.focus = (d.focus + 1) % (d.fields.len() + 1),
                _ => {
                    if let Some(field) = d.fields.get_mut(d.focus) {
                        if !(matches!(d.kind, DialogKind::Export(_)) && d.focus == 0) {
                            field.key(key);
                            d.request = None;
                            d.export_status = ExportStatus::Ready;
                        }
                    }
                }
            }
            return;
        }
        if self.mode == Mode::Designer
            && key.code == K::F(10)
            && !key.modifiers.contains(M::CONTROL)
        {
            self.menu = None;
            self.execute(Command::MenuBar);
            return;
        }
        if self.menu.is_some() {
            if key.modifiers.contains(M::ALT) {
                if let K::Char(c) = key.code {
                    if let Some(i) = self
                        .menus()
                        .iter()
                        .position(|m| m.mnemonic == c.to_ascii_lowercase())
                    {
                        self.control_menu = false;
                        self.menu = Some((i, 0));
                        return;
                    }
                }
            }
            match key.code {
                K::Esc => {
                    self.menu = None;
                    self.control_menu = false;
                }
                K::Left | K::Right if !self.control_menu => {
                    let n = self.menus().len();
                    let (m, _) = self.menu.unwrap();
                    self.menu = Some((
                        (m + n + if key.code == K::Right { 1 } else { n - 1 }) % n,
                        0,
                    ));
                }
                K::Up | K::Down => {
                    let (m, i) = self.menu.unwrap();
                    let n = self.menu_entries(m).len();
                    if n > 0 {
                        self.menu =
                            Some((m, (i + n + if key.code == K::Down { 1 } else { n - 1 }) % n));
                    }
                }
                K::Enter => self.menu_enter(),
                K::Char(c) => {
                    let (m, _) = self.menu.unwrap();
                    let entries = self.menu_entries(m);
                    if let Some(i) = entries.iter().position(|(label, _)| {
                        label
                            .split_once('&')
                            .and_then(|(_, s)| s.chars().next())
                            .is_some_and(|v| v.eq_ignore_ascii_case(&c))
                    }) {
                        self.menu = Some((m, i));
                        self.menu_enter();
                    }
                }
                _ => {}
            }
            return;
        }
        if let Some((sizing, original)) = self.geometry_action {
            match key.code {
                K::Esc => {
                    if let Some(w) = self.windows.iter_mut().find(|w| w.id == self.active) {
                        w.normal = original;
                    }
                    self.geometry_action = None;
                }
                K::Enter => self.geometry_action = None,
                K::Left | K::Right | K::Up | K::Down => {
                    let area = self.area();
                    if let Some(w) = self.windows.iter_mut().find(|w| w.id == self.active) {
                        let delta = if key.modifiers.contains(M::CONTROL) {
                            5
                        } else {
                            1
                        };
                        let grow = matches!(key.code, K::Right | K::Down);
                        let value = match (sizing, key.code) {
                            (true, K::Left | K::Right) => &mut w.normal.width,
                            (true, _) => &mut w.normal.height,
                            (false, K::Left | K::Right) => &mut w.normal.x,
                            (false, _) => &mut w.normal.y,
                        };
                        *value = if grow {
                            value.saturating_add(delta)
                        } else {
                            value.saturating_sub(delta)
                        };
                        w.normal = clamp(w.normal, area);
                    }
                }
                _ => {}
            }
            return;
        }
        if key.code == K::F(11)
            || matches!(
                key.code,
                K::Modifier(ModifierKeyCode::LeftAlt | ModifierKeyCode::RightAlt)
            )
        {
            self.properties = false;
            self.menu = Some((0, 0));
            return;
        }
        if key.modifiers.contains(M::ALT) {
            match key.code {
                K::Char('-') => {
                    self.execute(Command::ControlMenu);
                    return;
                }
                K::F(4) => {
                    self.execute(Command::Exit);
                    return;
                }
                K::Char(c) => {
                    if let Some(i) = self
                        .menus()
                        .iter()
                        .position(|m| m.mnemonic == c.to_ascii_lowercase())
                    {
                        self.properties = false;
                        self.menu = Some((i, 0));
                        return;
                    }
                }
                _ => {}
            }
        }
        if self.mode == Mode::Designer
            && key.code == K::F(10)
            && !key.modifiers.contains(M::CONTROL)
        {
            self.execute(Command::MenuBar);
            return;
        }
        if self.mode == Mode::Designer && key.code == K::F(2) {
            if let Err(e) = self.design_value_focus() {
                self.message = format!("{e:#}");
            }
            return;
        }
        if self.program_focus() && key.modifiers.contains(M::CONTROL) && key.code == K::Char('c') {
            self.pause_execution();
            return;
        }
        if self.mode == Mode::Designer && self.value_focus {
            if let Err(e) = self.design_key(key) {
                self.message = format!("{e:#}");
                self.open_dialog(
                    "Property",
                    DialogKind::Message,
                    vec![Field::text("Fehler", self.message.clone())],
                );
            }
            return;
        }
        if matches!(
            self.active_window().map(|w| w.kind),
            Some(WindowKind::MenuDesign | WindowKind::Toolbox | WindowKind::Palette)
        ) {
            match self.design_key(key) {
                Ok(true) => return,
                Err(e) => {
                    self.message = format!("{e:#}");
                    self.open_dialog(
                        "Designer",
                        DialogKind::Message,
                        vec![Field::text("Fehler", self.message.clone())],
                    );
                    return;
                }
                _ => {}
            }
        }
        if let Some(command) = shortcut(key) {
            self.execute(command);
            return;
        }
        match self.design_key(key) {
            Ok(true) => return,
            Err(e) => {
                self.message = format!("{e:#}");
                self.open_dialog(
                    "Designer",
                    DialogKind::Message,
                    vec![Field::text("Fehler", self.message.clone())],
                );
                return;
            }
            _ => {}
        }
        if self.program_focus() {
            self.basic_events.push(Event::Key(key));
            return;
        }
        if matches!(
            self.active_window().map(|w| w.kind),
            Some(WindowKind::Project)
        ) {
            let n = self.project.members().len();
            match key.code {
                K::Down if n > 0 => self.selected_member = (self.selected_member + 1) % n,
                K::Up if n > 0 => self.selected_member = (self.selected_member + n - 1) % n,
                K::Enter if n > 0 => {
                    let id = self.project.members()[self.selected_member];
                    let form = matches!(
                        self.project.document(id).unwrap().content(),
                        Content::Form(_)
                    );
                    if let Err(e) = self.show_document(id, form) {
                        self.message = e.to_string();
                    }
                }
                _ => {}
            }
            return;
        }
        if self.mode == Mode::Environment {
            if let Err(e) = self.code_key(key) {
                self.message = e.to_string();
            }
        }
    }
    fn browse(&mut self) {
        let Some(dialog) = self.dialog.take() else {
            return;
        };
        let index = dialog.focus;
        let allowed = match dialog.kind {
            DialogKind::OpenProject
            | DialogKind::AddFile
            | DialogKind::LoadText
            | DialogKind::SaveText
            | DialogKind::Print => index == 0,
            DialogKind::Save { .. } => index % 2 == 0,
            DialogKind::Export(_) => index == 3,
            _ => false,
        };
        if !allowed || index >= dialog.fields.len() {
            self.dialog = Some(dialog);
            return;
        }
        let path = self.base.join(dialog.fields[index].string());
        let dir = if path.is_dir() {
            path
        } else {
            path.parent().unwrap_or(&self.base).to_path_buf()
        };
        match Self::browser(dialog.clone(), index, &dir) {
            Ok(d) => self.dialog = Some(d),
            Err(e) => {
                let mut dialog = dialog;
                dialog.error = e.to_string();
                self.dialog = Some(dialog);
            }
        }
    }
    fn browser(return_to: Dialog, field: usize, dir: &Path) -> Result<Dialog> {
        let dir = dir.canonicalize()?;
        let mut entries = std::fs::read_dir(&dir)?
            .map(|e| e.map(|e| e.path()))
            .collect::<std::io::Result<Vec<_>>>()?;
        entries.sort_by_key(|p| (!p.is_dir(), p.clone()));
        if let Some(parent) = dir.parent() {
            entries.insert(0, parent.to_path_buf());
        }
        let names = entries
            .iter()
            .map(|p| {
                format!(
                    "{}{}",
                    p.file_name().unwrap_or_default().to_string_lossy(),
                    if p.is_dir() { "/" } else { "" }
                )
            })
            .collect();
        Ok(Dialog::new(
            format!("Dateiwahl: {}", dir.display()),
            DialogKind::Browse {
                return_to: Box::new(return_to),
                field,
                entries,
            },
            vec![Field::choice("Datei/Verzeichnis", names, 0)],
        ))
    }
    fn code_key(&mut self, key: KeyEvent) -> Result<()> {
        if self.mode == Mode::Designer || self.editor_view().is_err() {
            return Ok(());
        }
        self.editor_key(key)
    }
    pub fn menu_entries(&self, index: usize) -> Vec<(String, Option<Command>)> {
        if self.control_menu {
            return vec![
                ("&Restore".into(), Some(Command::Restore)),
                ("&Move".into(), Some(Command::MoveWindow)),
                ("&Size".into(), Some(Command::SizeWindow)),
                ("Mi&nimize".into(), Some(Command::Minimize)),
                ("Ma&ximize".into(), Some(Command::Maximize)),
                ("&Close".into(), Some(Command::CloseWindow)),
            ];
        }
        let menus = self.menus();
        let mut entries = menus[index]
            .items
            .iter()
            .map(|i| (i.label.clone(), i.command))
            .collect::<Vec<_>>();
        if menus[index].title == "Window" {
            entries.extend(
                self.window_commands()
                    .into_iter()
                    .enumerate()
                    .map(|(i, (label, c))| (format!("&{} {label}", i + 1), Some(c))),
            );
        }
        entries
    }
    fn menu_enter(&mut self) {
        if let Some((m, i)) = self.menu {
            if let Some((_, Some(c))) = self.menu_entries(m).get(i) {
                self.execute(*c);
            }
        }
    }
}
pub fn clamp(r: Rect, area: Rect) -> Rect {
    let width = r.width.max(12).min(area.width);
    let height = r.height.max(4).min(area.height);
    Rect::new(
        r.x.clamp(area.x, area.right().saturating_sub(width)),
        r.y.clamp(area.y, area.bottom().saturating_sub(height)),
        width,
        height,
    )
}
fn untitled(project: &mut Project) -> Result<DocumentId> {
    let mut n = 0;
    loop {
        let name = if n == 0 {
            "Untitled".into()
        } else {
            format!("Untitled{n}")
        };
        if project
            .loader()?
            .resolve(project.directory(), &format!("{name}.bas"))
            .is_err()
        {
            return project.new_module(&name);
        }
        n += 1;
    }
}
pub fn shortcut(key: KeyEvent) -> Option<Command> {
    use Command::*;
    let ctrl = key.modifiers.contains(M::CONTROL);
    let shift = key.modifiers.contains(M::SHIFT);
    Some(match key.code {
        K::F(6) => {
            if shift {
                PreviousWindow
            } else {
                NextWindow
            }
        }
        K::F(4) if ctrl => CloseWindow,
        K::F(7) if ctrl => MoveWindow,
        K::F(8) if ctrl => SizeWindow,
        K::F(9) if ctrl => Minimize,
        K::F(10) if ctrl => Maximize,
        K::F(5) if ctrl => Restore,
        K::F(1) if shift => UsingHelp,
        K::F(1) => Topic,
        K::F(2) if shift => Procedures,
        K::F(2) if ctrl => PreviousCode,
        K::F(2) => Code,
        K::F(3) => FindNext,
        K::F(4) => OutputScreen,
        K::F(5) if shift => Start,
        K::F(5) => Continue,
        K::F(7) => RunToCursor,
        K::F(8) if shift => HistoryBack,
        K::F(8) => Step,
        K::F(9) if shift => InstantWatch,
        K::F(9) => Breakpoint,
        K::F(10) if shift => HistoryForward,
        K::F(10) => ProcedureStep,
        K::F(12) if shift => Form,
        K::F(12) => Events,
        K::Insert if ctrl => Copy,
        K::Insert if shift => Paste,
        K::Delete if shift => Cut,
        K::Backspace if key.modifiers.contains(M::ALT) => Undo,
        K::Char('c') if ctrl => Copy,
        K::Char('x') if ctrl => Cut,
        K::Char('v') if ctrl => Paste,
        K::Char('z') if ctrl => Undo,
        K::Char('\\') if ctrl => SelectedText,
        K::Delete => Clear,
        _ => return None,
    })
}
