//! TBVM-Interpreter: zustandsbehaftete, vom Einbetter getriebene
//! Ausführung (Design: docs/tbvm-design.md).
//!
//! - Enge `match`-Schleife über dekodierte Instruktionen; Tick-Prüfung
//!   (Breakpoints, Einzelschritt, Abbruch) nur an Anweisungsgrenzen
//!   (`Stmt`-Instruktion) über ein einzelnes Flag-Wort.
//! - Fehlersemantik: Handler modulweit + Frame-lokal, Unwinding bis zum
//!   Handler-Frame, `RESUME` über gemerkten Anweisungs-Offset; Fehler
//!   während aktiver Behandlung ist fatal (keine Kaskaden).
//! - `GOSUB`-Stack pro Frame; `RETURN` ohne GOSUB → Fehler 3.

use crate::bytecode::{CmpOp, CompiledModule, Instr};
use std::collections::{HashSet, VecDeque};
use std::rc::Rc;
use tb_runtime::builtins::{builtin_table, ids, RtState};
use tb_runtime::errors::RuntimeError;
use tb_runtime::host::{Ereignis, Host};
use tb_runtime::traps::Quelle;
use tb_runtime::value::{self, default_value, ArrayObj, RecordObj, TypeInit, Value, VarRef};
use tb_ui::forms::{FormEvent, FormsModel, PropertyValue, ShowResult};

/// Warum die VM die Kontrolle abgibt.
#[derive(Debug, Clone, PartialEq)]
pub enum RunEvent {
    /// `END`, `SYSTEM` oder Programmende.
    Ended,
    /// `STOP` — VM-Zustand bleibt fortsetzbar (IDE: CONT).
    Stopped {
        line: u32,
    },
    Breakpoint {
        line: u32,
    },
    Stepped {
        line: u32,
    },
    Interrupted {
        line: u32,
    },
    Restart {
        program: Option<String>,
        line: Option<u32>,
    },
    /// Unbehandelter Laufzeitfehler.
    Error {
        code: u16,
        line: u32,
        message: String,
    },
}

/// Scheduling result, deliberately separate from debugger events.
#[derive(Debug, Clone, PartialEq)]
pub enum PollResult {
    Yield,
    Waiting { deadline: Option<u64> },
    Event(RunEvent),
}

const F_STEP: u32 = 1;
const F_BREAK: u32 = 2;
const F_POLL: u32 = 4;

#[derive(Clone, Copy, PartialEq)]
enum Handler {
    None,
    Goto(u32),
    ResumeNext,
}

struct Frame {
    proc: usize,
    pc: usize,
    locals_base: usize,
    stack_base: usize,
    gosub: Vec<usize>,
    local_handler: Handler,
    /// Instruktionsindex der zuletzt begonnenen Anweisung (`Stmt`).
    last_stmt_pc: usize,
    line: u32,
    source: u32,
    column: u32,
    /// Gesetzt, wenn dieser Frame der Handler eines Ereignis-Traps ist.
    /// Er läuft im Modulrumpf und **teilt dessen Locals** — ein eigener
    /// Satz würde dem Handler leere Modulvariablen zeigen. Sein `RETURN`
    /// beendet den Handler (design.md, D2).
    trap: Option<Quelle>,
    waiting_form: Option<u16>,
    pending_show: Option<u16>,
    form_event: Option<FormEventReturn>,
    handler_start: bool,
    eingabe: Option<KonsolenEingabe>,
    sleep: Option<Option<u64>>,
}

struct KonsolenEingabe {
    text: String,
    /// None = Zeile bis Enter; Some = noch erwartete Tasten für INPUT$.
    rest: Option<usize>,
}

#[derive(Clone, Copy, PartialEq)]
enum Zustellpunkt {
    Anweisung,
    Kooperativ,
    Konsole,
}

#[derive(Clone, Copy)]
enum FormEventReturn {
    Normal,
    Load,
    Unload(u16),
}

pub struct Vm {
    module: CompiledModule,
    globals: Vec<Value>,
    locals: Vec<Value>,
    stack: Vec<Value>,
    frames: Vec<Frame>,
    pub rt: RtState,
    // Fehlerzustand
    err: u16,
    /// Wert von `ERL`: die Zeilennummer, in der der Fehler auftrat. Wird
    /// **nur** beim Auslösen gesetzt — liefe sie mit `zeile_nr` mit, meldete
    /// ein Handler auf einer nummerierten Zeile seine eigene Nummer.
    erl: u32,
    /// Zuletzt durchlaufene numerische Zeilennummer (0 = keine).
    zeile_nr: u32,
    module_handlers: Vec<Handler>,
    in_handler: bool,
    resume_pc: usize,
    // Steuerung
    flags: u32,
    /// Zählt Anweisungsgrenzen für die regelmäßige Ereignisabholung.
    tick_zaehler: u32,
    breakpoints: HashSet<(u16, u32)>,
    data_ptr: usize,
    start_pc: Option<usize>,
    pub forms: FormsModel,
    waiting: Option<Option<u64>>,
    dialog: Option<tb_ui::forms::Dialog>,
    terminated: bool,
}

/// Quellenarten, wie der Codegenerator sie kodiert.
const ART_KEY: u8 = 0;
const ART_TIMER: u8 = 1;
const ART_UEVENT: u8 = 2;
const ART_SIGNAL: u8 = 3;

/// Art + Kennung → Quelle. Die Kennung kann ein Ausdruck sein, deshalb
/// wird der Wertebereich hier noch einmal geprüft (Fehler 5).
///
/// Das Zeitintervall gehört **nicht** hierher: `TIMER ON` trägt keine
/// Kennung, geprüft wird es nur an `ON TIMER(n&)`.
fn trap_quelle(art: u8, n: i32) -> Result<Quelle, RuntimeError> {
    let ok = match art {
        ART_KEY => matches!(n, 0..=25 | 30 | 31),
        ART_SIGNAL => (1..=2).contains(&n),
        _ => true,
    };
    if !ok {
        return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
    }
    Ok(match art {
        ART_KEY => Quelle::Key(n as u8),
        ART_TIMER => Quelle::Timer,
        ART_UEVENT => Quelle::UEvent,
        _ => Quelle::Signal(n as u8),
    })
}

enum Flow {
    Normal,
    Event(RunEvent),
}

impl Vm {
    pub fn new(module: CompiledModule) -> Vm {
        let globals = module
            .globals_init
            .iter()
            .map(|t| default_value(t, &module.udts))
            .collect();
        let mut forms = FormsModel::new(module.objects.clone(), 80, 25);
        for initial in &module.form_initial {
            initial
                .apply(&mut forms)
                .expect("validierte Forms-Anfangsdaten");
        }
        if let Some(form) = module.startup_form {
            forms.show(form, false).expect("validiertes Startformular");
        }
        let mut vm = Vm {
            globals,
            locals: Vec::new(),
            stack: Vec::new(),
            frames: Vec::new(),
            rt: RtState::default(),
            err: 0,
            erl: 0,
            zeile_nr: 0,
            module_handlers: vec![Handler::None; module.modules.len()],
            in_handler: false,
            resume_pc: 0,
            flags: 0,
            tick_zaehler: 0,
            breakpoints: HashSet::new(),
            data_ptr: 0,
            start_pc: None,
            waiting: None,
            dialog: None,
            terminated: false,
            forms,
            module,
        };
        // ISAM leitet das Satzlayout aus dem Typ der `OPEN`-Anweisung ab und
        // braucht dafür die UDT-Tabelle des Moduls.
        vm.rt.isam.udts = vm.module.udts.clone();
        vm.push_frame(0, 0);
        vm
    }

    pub fn start_at_line(&mut self, line: u32) -> Result<(), RuntimeError> {
        let target = self.module.procs[0]
            .code
            .iter()
            .position(|instruction| matches!(instruction, Instr::SetErl(found) if *found == line))
            .or_else(|| {
                self.module.procs[0].code.iter().position(
                    |instruction| matches!(instruction, Instr::Stmt(found) if *found == line),
                )
            });
        let target = target.ok_or(RuntimeError(8))?;
        if self.module.procs[0]
            .code
            .iter()
            .any(|instruction| matches!(instruction, Instr::Stmt(0)))
        {
            self.start_pc = Some(target);
        } else {
            self.frames[0].pc = target;
        }
        Ok(())
    }

    fn push_frame(&mut self, proc: usize, argc: usize) {
        // Argumente liegen zuoberst auf dem Stack (links → rechts).
        let locals_base = self.locals.len();
        let p = &self.module.procs[proc];
        for init in &p.locals_init {
            self.locals.push(default_value(init, &self.module.udts));
        }
        for i in (0..argc).rev() {
            let v = self.stack.pop().unwrap_or(Value::Empty);
            self.locals[locals_base + i] = v;
        }
        let stack_base = self.stack.len();
        self.frames.push(Frame {
            source: 0,
            column: 0,
            proc,
            pc: 0,
            locals_base,
            stack_base,
            gosub: Vec::new(),
            local_handler: Handler::None,
            last_stmt_pc: 0,
            line: 0,
            trap: None,
            waiting_form: None,
            pending_show: None,
            form_event: None,
            handler_start: false,
            eingabe: None,
            sleep: None,
        });
    }

    fn external_arg(&self, value: &Value) -> Result<Value, RuntimeError> {
        match value {
            Value::Ref(reference) => self.read_ref(reference),
            value => Ok(value.clone()),
        }
    }

    fn external_string(&self, value: &Value) -> Result<String, RuntimeError> {
        match self.external_arg(value)? {
            Value::Str(text) => Ok(text.to_string()),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn external_set(
        &mut self,
        args: &[Value],
        index: usize,
        value: Value,
    ) -> Result<(), RuntimeError> {
        match args.get(index) {
            Some(Value::Ref(reference)) => self.write_ref(reference, value),
            _ => Ok(()),
        }
    }

    fn dialog_integer(&self, value: &Value) -> Result<i32, RuntimeError> {
        match self.external_arg(value)? {
            Value::Int(value) => Ok(value as i32),
            Value::Lng(value) => Ok(value),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn take_dialog_input(&mut self) -> VecDeque<tb_runtime::builtins::Eingabe> {
        std::mem::take(&mut self.rt.eingaben)
    }

    fn restore_dialog_input(&mut self, events: VecDeque<tb_runtime::builtins::Eingabe>) {
        for input in events {
            match input.ereignis {
                Ereignis::Taste(..) | Ereignis::Maus(_) => self.rt.eingaben.push_back(input),
                event => self.rt.ereignis(event),
            }
        }
    }

    fn forms_dialog(
        &mut self,
        id: u16,
        args: &[Value],
        host: &mut dyn Host,
    ) -> Result<Option<Value>, RuntimeError> {
        if self.dialog.is_none() {
            let text = self.external_string(args.first().ok_or(RuntimeError::TYPE_MISMATCH)?)?;
            self.dialog = Some(match id {
                ids::MSGBOX => {
                    let kind = args.get(1).map_or(Ok(0), |v| self.dialog_integer(v))?;
                    let title = args
                        .get(2)
                        .map_or_else(|| Ok(String::new()), |v| self.external_string(v))?;
                    tb_ui::forms::message_dialog(&self.rt.screen, &text, kind, &title)?
                }
                ids::INPUTBOX_S => {
                    let title = args
                        .get(1)
                        .map_or_else(|| Ok(String::new()), |v| self.external_string(v))?;
                    let initial = args
                        .get(2)
                        .map_or_else(|| Ok(String::new()), |v| self.external_string(v))?;
                    let position = match (args.get(3), args.get(4)) {
                        (None, None) => None,
                        (Some(x), Some(y)) => {
                            Some((self.dialog_integer(x)?, self.dialog_integer(y)?))
                        }
                        _ => return Err(RuntimeError::TYPE_MISMATCH),
                    };
                    tb_ui::forms::input_dialog(&self.rt.screen, &text, &title, &initial, position)?
                }
                _ => return Err(RuntimeError::FEATURE_UNAVAILABLE),
            });
        }
        let mut queued = self.take_dialog_input();
        let value = self
            .dialog
            .as_mut()
            .unwrap()
            .poll(&mut self.rt.screen, host, &mut queued);
        self.restore_dialog_input(queued);
        if let Some(value) = value {
            self.dialog = None;
            Ok(Some(match value {
                tb_ui::forms::DialogValue::Number(n) => Value::Int(n),
                tb_ui::forms::DialogValue::Text(s) => Value::Str(Rc::from(s)),
            }))
        } else {
            self.waiting = Some(None);
            Ok(None)
        }
    }

    fn external_dialog(
        &mut self,
        proc: usize,
        argc: usize,
        host: &mut dyn Host,
    ) -> Result<bool, RuntimeError> {
        if !matches!(self.module.procs[proc].code.as_slice(), [Instr::RetProc]) {
            return Ok(false);
        }
        let name = self.module.procs[proc]
            .name
            .rsplit('!')
            .next()
            .unwrap()
            .to_ascii_uppercase();
        if !matches!(
            name.as_str(),
            "CMNDLGREGISTER"
                | "CMNDLGCLOSE"
                | "ABOUT"
                | "FILEOPEN"
                | "FILESAVE"
                | "FILEPRINT"
                | "FINDTEXT"
                | "CHANGETEXT"
                | "COLORPALETTE"
        ) {
            return Ok(false);
        }
        let args = self.stack[self.stack.len().saturating_sub(argc)..].to_vec();
        match name.as_str() {
            "CMNDLGREGISTER" => self.external_set(&args, 0, Value::Int(-1))?,
            "CMNDLGCLOSE" => {}
            "ABOUT" => {
                let dialog = vec![
                    self.external_arg(&args[0])?,
                    Value::Lng(0),
                    Value::Str(Rc::from("About")),
                ];
                if self.forms_dialog(ids::MSGBOX, &dialog, host)?.is_none() {
                    return Ok(true);
                }
            }
            "FILEOPEN" | "FILESAVE" => {
                let file = self.external_string(&args[0])?;
                let path = self.external_string(&args[1])?;
                let title = if name == "FILEOPEN" {
                    "Open file"
                } else {
                    "Save file"
                };
                let default = if path.is_empty() {
                    file
                } else {
                    format!("{path}{sep}{file}", sep = std::path::MAIN_SEPARATOR)
                };
                let dialog = vec![
                    Value::Str(Rc::from(title)),
                    Value::Str(Rc::from(title)),
                    Value::Str(Rc::from(default)),
                ];
                let Some(result) = self.forms_dialog(ids::INPUTBOX_S, &dialog, host)? else {
                    return Ok(true);
                };
                let Value::Str(result) = result else {
                    return Err(RuntimeError::TYPE_MISMATCH);
                };
                let result = result.to_string();
                if result.is_empty() {
                    self.external_set(&args, 7, Value::Int(-1))?;
                } else {
                    let (path, file) = result
                        .rsplit_once(['/', '\\'])
                        .map_or(("", result.as_str()), |(path, file)| (path, file));
                    self.external_set(&args, 0, Value::Str(Rc::from(file)))?;
                    self.external_set(&args, 1, Value::Str(Rc::from(path)))?;
                    self.external_set(&args, 7, Value::Int(0))?;
                }
            }
            "FILEPRINT" => {
                self.external_set(&args, 0, Value::Int(1))?;
                self.external_set(&args, 3, Value::Int(0))?;
            }
            "FINDTEXT" | "CHANGETEXT" => {
                let dialog = vec![
                    Value::Str(Rc::from(if name == "FINDTEXT" {
                        "Find text"
                    } else {
                        "Replacement text"
                    })),
                    Value::Str(Rc::from("Find")),
                    self.external_arg(&args[usize::from(name == "CHANGETEXT")])?,
                ];
                let Some(result) = self.forms_dialog(ids::INPUTBOX_S, &dialog, host)? else {
                    return Ok(true);
                };
                let target = usize::from(name == "CHANGETEXT");
                self.external_set(&args, target, result.clone())?;
                let empty = matches!(&result, Value::Str(text) if text.is_empty());
                self.external_set(&args, args.len() - 1, Value::Int(-i16::from(empty)))?;
            }
            "COLORPALETTE" => {
                let color = self.external_arg(&args[0])?;
                self.external_set(&args, 0, color)?;
                self.external_set(&args, 3, Value::Int(0))?;
            }
            _ => unreachable!(),
        }
        self.stack.truncate(self.stack.len() - argc);
        Ok(true)
    }

    pub fn queue_form_event(&mut self, event: FormEvent) {
        self.forms.queue(event);
    }

    fn form_value(v: PropertyValue) -> Value {
        match v {
            PropertyValue::Integer(v) => Value::Int(v as i16),
            PropertyValue::Single(v) => Value::Sng(v),
            PropertyValue::String(v) => Value::Str(Rc::from(v)),
            PropertyValue::Boolean(v) => Value::Int(if v { -1 } else { 0 }),
            PropertyValue::Object(v) => v
                .map(|(object, index)| Value::Obj(object, index))
                .unwrap_or(Value::Empty),
            PropertyValue::IntegerArray(_) => Value::Empty,
        }
    }

    fn form_arg(v: Value) -> Result<PropertyValue, RuntimeError> {
        Ok(match v {
            Value::Int(v) => PropertyValue::Integer(v as i32),
            Value::Lng(v) => PropertyValue::Integer(v),
            Value::Sng(v) => PropertyValue::Single(v),
            Value::Str(v) => PropertyValue::String(v.to_string()),
            Value::Obj(object, index) => PropertyValue::Object(Some((object, index))),
            _ => return Err(RuntimeError::TYPE_MISMATCH),
        })
    }

    fn property_value(
        &self,
        object: u16,
        property: u16,
        v: Value,
    ) -> Result<PropertyValue, RuntimeError> {
        let class = self
            .module
            .objects
            .get(object as usize)
            .ok_or(RuntimeError(420))?
            .class;
        let spec = tb_frontend::forms::properties(class)
            .get(property as usize)
            .copied()
            .ok_or(RuntimeError(422))?;
        Ok(match (spec.ty, v) {
            (tb_frontend::forms::PropertyType::Integer, Value::Int(v)) => {
                PropertyValue::Integer(v as i32)
            }
            (tb_frontend::forms::PropertyType::Integer, Value::Lng(v)) => PropertyValue::Integer(v),
            (tb_frontend::forms::PropertyType::Boolean, Value::Int(v)) => {
                PropertyValue::Boolean(v != 0)
            }
            (tb_frontend::forms::PropertyType::Boolean, Value::Lng(v)) => {
                PropertyValue::Boolean(v != 0)
            }
            (tb_frontend::forms::PropertyType::Single, Value::Sng(v)) => PropertyValue::Single(v),
            (tb_frontend::forms::PropertyType::String, Value::Str(v)) => {
                PropertyValue::String(v.to_string())
            }
            (tb_frontend::forms::PropertyType::Object, Value::Obj(object, index)) => {
                PropertyValue::Object(Some((object, index)))
            }
            _ => return Err(RuntimeError::TYPE_MISMATCH),
        })
    }

    fn dispatch_form_event(&mut self, event: FormEvent, on_return: FormEventReturn) -> bool {
        let Some(binding) = self
            .module
            .event_procs
            .iter()
            .find(|e| e.object == event.object && e.event.eq_ignore_ascii_case(&event.name))
            .cloned()
        else {
            return false;
        };
        if matches!(
            event.name.as_str(),
            "CLICK" | "DBLCLICK" | "GOTFOCUS" | "KEYDOWN" | "KEYPRESS" | "KEYUP" | "MOUSEDOWN"
        ) && self
            .module
            .objects
            .get(event.object as usize)
            .is_some_and(|o| {
                !matches!(
                    o.class,
                    tb_frontend::forms::ObjectClass::Form | tb_frontend::forms::ObjectClass::Screen
                )
            })
        {
            let _ = self
                .forms
                .set_active_control(event.object, event.array_index);
        }
        if let Some(index) = event.array_index {
            self.push(Value::Int(index as i16));
        }
        for arg in event.args {
            self.push(Self::form_value(arg));
        }
        self.push_frame(
            binding.proc as usize,
            self.module.procs[binding.proc as usize].n_params as usize,
        );
        if let Some(frame) = self.frames.last_mut() {
            frame.form_event = Some(on_return);
            frame.handler_start = true;
        }
        true
    }

    fn dispatch_next_form_event(&mut self, load_only: bool) -> bool {
        let mut i = 0;
        while let Some(event) = self.forms.events.get(i) {
            if load_only && !event.name.eq_ignore_ascii_case("LOAD") {
                i += 1;
                continue;
            }
            let laeuft = self
                .module
                .event_procs
                .iter()
                .find(|binding| {
                    binding.object == event.object
                        && binding.event.eq_ignore_ascii_case(&event.name)
                })
                .is_some_and(|binding| {
                    self.frames.iter().any(|frame| {
                        frame.form_event.is_some() && frame.proc == binding.proc as usize
                    })
                });
            // Gesperrte Timer und laufende Prozeduren bleiben anstehend.
            // Menübedienung und explizite modale Kooperation müssen trotzdem
            // andere Handler erreichen können.
            if laeuft || (self.forms.menu_is_open() && event.name.eq_ignore_ascii_case("TIMER")) {
                i += 1;
                continue;
            }
            let event = self.forms.events.remove(i).unwrap();
            if self.dispatch_form_event(
                event,
                if load_only {
                    FormEventReturn::Load
                } else {
                    FormEventReturn::Normal
                },
            ) {
                return true;
            }
        }
        false
    }

    fn request_unload(&mut self, object: u16) -> Result<(), RuntimeError> {
        if self.frames.iter().any(|frame| {
            matches!(frame.form_event,
            Some(FormEventReturn::Unload(running)) if running == object)
        }) {
            return Ok(());
        }
        let event = FormEvent {
            object,
            array_index: None,
            name: "UNLOAD".into(),
            args: vec![PropertyValue::Integer(0)],
        };
        if !self.dispatch_form_event(event, FormEventReturn::Unload(object)) {
            self.forms.unload_with(object, |_| {})?;
        }
        Ok(())
    }

    fn tick(&mut self, host: &mut dyn Host) {
        self.rt.traps.suspendieren(self.forms.menu_is_open());
        self.forms.render(&mut self.rt.screen);
        host.present(&self.rt.screen);
        self.rt.pump(host, false);
        if !self.forms.menu_is_open() && self.rt.traps.naechste_frist().is_some() {
            self.rt.traps.zeit_pruefen(host.jetzt_ms());
        }
    }

    /// Ein gemeinsamer Zustellpfad für Statements, DOEVENTS, SLEEP und
    /// blockierende Eingabe. Nur explizite Kooperation verschachtelt Forms.
    fn zustellen(&mut self, host: &mut dyn Host, punkt: Zustellpunkt) -> bool {
        let kooperativ = punkt != Zustellpunkt::Anweisung;
        'zustellung: loop {
            self.rt.traps.suspendieren(self.forms.menu_is_open());
            self.forms
                .resize(self.rt.screen.cols(), self.rt.screen.rows());
            if self.forms.next_deadline().is_some() {
                self.forms.timers(host.jetzt_ms());
            }
            let forms_frei = kooperativ || !self.frames.iter().any(|f| f.form_event.is_some());
            if forms_frei && self.dispatch_next_form_event(false) {
                return true;
            }
            if self.rt.traps.aktiv() {
                self.rt.tasten_traps_pruefen();
                if (kooperativ || self.tick_zaehler.is_multiple_of(64))
                    && self.rt.traps.naechste_frist().is_some()
                {
                    self.rt.traps.zeit_pruefen(host.jetzt_ms());
                }
                if let Some((q, ziel)) = self.rt.traps.naechstes() {
                    self.trap_frame_aufsetzen(q, ziel);
                    return true;
                }
            }
            // Unverbrauchte Konsolentasten bleiben liegen. Maus ohne Formular
            // wird verworfen; mit Formular wird jede Eingabe samt ihrer Handler
            // abgearbeitet, bevor die nächste Eingabe angenommen wird.
            let mut i = 0;
            while i < self.rt.eingaben.len() {
                if !self.forms.menu_is_open() && self.rt.taste_trappen(i) {
                    if let Some((q, ziel)) = self.rt.traps.naechstes() {
                        self.trap_frame_aufsetzen(q, ziel);
                        return true;
                    }
                    continue;
                }
                if punkt == Zustellpunkt::Konsole
                    && matches!(self.rt.eingaben[i].ereignis, Ereignis::Taste(..))
                {
                    i += 1;
                    continue;
                }
                if self.forms.active_form().is_some() {
                    if !forms_frei {
                        break;
                    }
                    match self.rt.eingaben.remove(i).unwrap().ereignis {
                        Ereignis::Taste(key, shift) => {
                            let menu_war_offen = self.forms.menu_is_open();
                            self.forms.handle_key(&key, shift);
                            self.rt.traps.suspendieren(self.forms.menu_is_open());
                            if menu_war_offen && !self.forms.menu_is_open() {
                                continue 'zustellung;
                            }
                        }
                        Ereignis::Maus(m) => {
                            if m.zeile > 0
                                && m.zeile <= self.rt.screen.rows()
                                && m.spalte > 0
                                && m.spalte <= self.rt.screen.cols()
                            {
                                let now = if self.forms.mouse_needs_time(m) {
                                    host.jetzt_ms()
                                } else {
                                    0
                                };
                                self.forms.handle_mouse_at(m, now);
                            }
                        }
                        _ => unreachable!(),
                    }
                    if self.dispatch_next_form_event(false) {
                        return true;
                    }
                } else if matches!(self.rt.eingaben[i].ereignis, Ereignis::Maus(_)) {
                    self.rt.eingaben.remove(i);
                } else {
                    i += 1;
                }
            }
            self.forms.render(&mut self.rt.screen);
            return false;
        }
    }

    fn warten(&mut self, host: &mut dyn Host, deadline: Option<u64>) {
        let deadline = deadline
            .into_iter()
            .chain(self.forms.next_deadline())
            .chain(if self.forms.menu_is_open() {
                None
            } else {
                self.rt.traps.naechste_frist()
            })
            .min();
        if let Some(event) = host.warten(deadline) {
            self.rt.ereignis(event);
        }
    }

    /// Ein zugestelltes Ereignis beendet SLEEP; Fristen und Warten gehören
    /// ausschließlich dem Host, einschließlich einer virtuellen Uhr.
    /// Trap-Handler aufsetzen: ein Frame auf den Modulrumpf, der dessen
    /// Locals mitbenutzt, mit eigenem `GOSUB`-Stapel und einer Rückmarke
    /// auf die unterbrochene Stelle (design.md, D2).
    fn trap_frame_aufsetzen(&mut self, q: Quelle, ziel: u32) {
        let locals_base = self.frames[0].locals_base;
        let stack_base = self.stack.len();
        self.frames.push(Frame {
            source: 0,
            column: 0,
            proc: 0,
            pc: ziel as usize,
            locals_base,
            stack_base,
            gosub: Vec::new(),
            local_handler: Handler::None,
            last_stmt_pc: 0,
            line: 0,
            trap: Some(q),
            waiting_form: None,
            pending_show: None,
            form_event: None,
            handler_start: true,
            eingabe: None,
            sleep: None,
        });
    }

    /// Trap-Frame abräumen. Anders als `pop_frame` bleiben die Locals
    /// stehen — sie gehören dem Modulrumpf, nicht dem Handler.
    fn trap_frame_abraeumen(&mut self) -> Option<Quelle> {
        let f = self.frames.pop()?;
        self.stack.truncate(f.stack_base);
        f.trap
    }

    fn pop_frame(&mut self) {
        let f = self.frames.pop().expect("Frame-Unterlauf");
        if let Some(q) = f.trap {
            self.rt.traps.handler_beendet(q);
        } else {
            self.locals.truncate(f.locals_base);
        }
        self.stack.truncate(f.stack_base);
    }

    // ---- Steuerung (Debugger/Runner) ----------------------------------------

    pub fn set_step(&mut self, on: bool) {
        if on {
            self.flags |= F_STEP;
        } else {
            self.flags &= !F_STEP;
        }
    }

    pub fn set_poll_interrupt(&mut self, on: bool) {
        if on {
            self.flags |= F_POLL;
        } else {
            self.flags &= !F_POLL;
        }
    }

    pub fn add_breakpoint(&mut self, line: u32) {
        self.breakpoints.insert((0, line));
        self.flags |= F_BREAK;
    }

    pub fn remove_breakpoint(&mut self, line: u32) {
        self.breakpoints.remove(&(0, line));
        if self.breakpoints.is_empty() {
            self.flags &= !F_BREAK;
        }
    }

    pub fn add_module_breakpoint(&mut self, module: u16, line: u32) {
        self.breakpoints.insert((module, line));
        self.flags |= F_BREAK;
    }

    pub fn remove_module_breakpoint(&mut self, module: u16, line: u32) {
        self.breakpoints.remove(&(module, line));
        if self.breakpoints.is_empty() {
            self.flags &= !F_BREAK;
        }
    }

    pub fn current_source_pos(&self) -> tb_frontend::SourcePos {
        self.frames
            .last()
            .map(|f| tb_frontend::SourcePos {
                source: f.source,
                line: f.line,
                column: f.column,
            })
            .unwrap_or_default()
    }

    pub fn current_module(&self) -> u16 {
        self.module
            .sources
            .get(self.current_source_pos().source as usize)
            .map_or(0, |s| s.module)
    }

    pub fn current_file(&self) -> &str {
        self.module
            .sources
            .get(self.current_source_pos().source as usize)
            .map_or(&self.module.name, |s| &s.path)
    }

    pub fn current_line(&self) -> u32 {
        self.frames.last().map(|f| f.line).unwrap_or(0)
    }

    pub fn current_proc_name(&self) -> &str {
        self.frames
            .last()
            .map(|f| self.module.procs[f.proc].name.as_str())
            .unwrap_or("")
    }

    /// Variableninspektion: lokale Namen vor Modulvariablen, explizite
    /// Typ-Suffixe vor einer eindeutigen Suche nach dem Basisnamen.
    pub fn inspect(&self, name: &str) -> Option<Value> {
        fn matches(stored: &str, requested: &str) -> bool {
            let suffixes = ['%', '&', '!', '#', '$', '@'];
            stored.eq_ignore_ascii_case(requested)
                || (!(stored.ends_with(suffixes) && requested.ends_with(suffixes))
                    && stored
                        .trim_end_matches(suffixes)
                        .eq_ignore_ascii_case(requested.trim_end_matches(suffixes)))
        }
        fn unique(mut ids: impl Iterator<Item = usize>) -> Option<usize> {
            let first = ids.next()?;
            ids.next().is_none().then_some(first)
        }
        if let Some(f) = self.frames.last() {
            let p = &self.module.procs[f.proc];
            if let Some(i) = unique(
                p.local_names
                    .iter()
                    .enumerate()
                    .filter(|(_, n)| matches(n, name))
                    .map(|(i, _)| i),
            ) {
                return Some(self.deref_for_inspect(self.locals[f.locals_base + i].clone()));
            }
        }
        let qualified = format!(
            "{}!{name}",
            self.module.modules[self.current_module() as usize].0
        );
        if let Some(id) = unique(
            self.module
                .global_names
                .iter()
                .enumerate()
                .filter(|(_, n)| matches(n, name) || matches(n, &qualified))
                .map(|(i, _)| i),
        ) {
            return Some(self.globals[id].clone());
        }
        let id = unique(
            self.module
                .global_names
                .iter()
                .enumerate()
                .filter(|(_, n)| {
                    matches(n.split_once('!').map_or(n.as_str(), |(_, name)| name), name)
                })
                .map(|(i, _)| i),
        )?;
        Some(self.globals[id].clone())
    }

    /// Arrayelement inspizieren.
    pub fn inspect_element(&self, name: &str, idx: &[i32]) -> Option<Value> {
        let v = self.inspect(name)?;
        match v {
            Value::Arr(a) => {
                let a = a.borrow();
                let flat = a.flat_index(idx).ok()?;
                Some(a.data[flat as usize].clone())
            }
            _ => None,
        }
    }

    /// UDT-Feld inspizieren (Pfad über Feldindizes).
    pub fn inspect_field(&self, name: &str, path: &[u16]) -> Option<Value> {
        let mut v = self.inspect(name)?;
        for f in path {
            let Value::Rec(r) = v else { return None };
            let r = r.borrow();
            v = r.fields.get(*f as usize)?.clone();
        }
        Some(v)
    }

    fn deref_for_inspect(&self, v: Value) -> Value {
        match v {
            Value::Ref(r) => self.read_ref(&r).unwrap_or(Value::Empty),
            other => other,
        }
    }

    // ---- Hauptschleife --------------------------------------------------------

    pub fn is_terminated(&self) -> bool {
        self.terminated
    }

    /// Hält modellose Formulare nach dem Ende des Modulrumpfs bedienbar.
    /// Ereignisprozeduren laufen weiter auf derselben VM und können das
    /// Formular schließen, ein anderes Programm starten oder einen Fehler
    /// auslösen.
    pub fn run_visible_forms(&mut self, host: &mut dyn Host) -> RunEvent {
        // Legacy explicit entry point: the caller deliberately starts a new
        // forms pump, even after END. IDE/CLI only call it after natural end.
        self.terminated = false;
        loop {
            match self.poll_visible_forms(host, usize::MAX) {
                PollResult::Event(event) => return event,
                PollResult::Waiting { deadline } => {
                    if (self.dialog.is_none() || self.rt.eingaben.is_empty())
                        && !self.rt.abbruch
                        && !self.rt.ende
                    {
                        self.warten(host, deadline);
                    }
                }
                PollResult::Yield => {}
            }
        }
    }

    pub fn run(&mut self, host: &mut dyn Host) -> RunEvent {
        loop {
            match self.poll(host, usize::MAX) {
                PollResult::Event(e) => return e,
                PollResult::Waiting { deadline } => {
                    if (self.dialog.is_none() || self.rt.eingaben.is_empty())
                        && !self.rt.abbruch
                        && !self.rt.ende
                    {
                        self.warten(host, deadline);
                    }
                }
                PollResult::Yield => {}
            }
        }
    }

    /// Polls modeless forms after the module body has ended.
    pub fn poll_visible_forms(&mut self, host: &mut dyn Host, budget: usize) -> PollResult {
        if self.terminated {
            return PollResult::Event(RunEvent::Ended);
        }
        if self
            .frames
            .last()
            .is_some_and(|f| f.pc < self.module.procs[f.proc].code.len())
        {
            return match self.poll(host, budget) {
                PollResult::Event(RunEvent::Ended) if !self.terminated => PollResult::Yield,
                result => result,
            };
        }
        if !self.forms.has_visible_forms() {
            return PollResult::Event(RunEvent::Ended);
        }
        self.tick(host);
        if self.rt.abbruch {
            return PollResult::Event(RunEvent::Interrupted {
                line: self.current_line(),
            });
        }
        if self.zustellen(host, Zustellpunkt::Kooperativ) {
            match self.poll(host, budget) {
                PollResult::Event(RunEvent::Ended) if !self.terminated => PollResult::Yield,
                result => result,
            }
        } else if self.rt.ende || !self.forms.has_visible_forms() {
            PollResult::Event(RunEvent::Ended)
        } else {
            PollResult::Waiting {
                deadline: self.wait_deadline(None),
            }
        }
    }

    fn wait_deadline(&self, deadline: Option<u64>) -> Option<u64> {
        deadline
            .into_iter()
            .chain(self.forms.next_deadline())
            .chain(if self.forms.menu_is_open() {
                None
            } else {
                self.rt.traps.naechste_frist()
            })
            .min()
    }

    /// Executes at most `budget` instructions without entering a host wait.
    pub fn poll(&mut self, host: &mut dyn Host, budget: usize) -> PollResult {
        self.waiting = None;
        self.forms.sync_timers(|| host.jetzt_ms());
        for _ in 0..budget {
            if self.dialog.is_some() && self.rt.ende {
                return PollResult::Event(RunEvent::Ended);
            }
            if self.rt.abbruch {
                return PollResult::Event(RunEvent::Interrupted {
                    line: self.current_line(),
                });
            }
            if let Some(form) = self.frames.last().and_then(|f| f.waiting_form) {
                if !self.forms.is_visible(form) {
                    self.frames.last_mut().unwrap().waiting_form = None;
                } else {
                    self.tick(host);
                    let dispatched = self.zustellen(host, Zustellpunkt::Kooperativ);
                    if !dispatched && self.rt.ende {
                        return PollResult::Event(RunEvent::Ended);
                    }
                    if !dispatched {
                        return PollResult::Waiting {
                            deadline: self.wait_deadline(None),
                        };
                    }
                    continue;
                }
            }
            let Some(frame) = self.frames.last_mut() else {
                return PollResult::Event(RunEvent::Ended);
            };
            let pc = frame.pc;
            let Some(instr) = self.module.procs[frame.proc].code.get(pc).cloned() else {
                return PollResult::Event(RunEvent::Ended);
            };
            frame.pc = pc + 1;
            match self.exec(instr, pc, host) {
                Ok(Flow::Normal) => {}
                Ok(Flow::Event(ev)) => return PollResult::Event(ev),
                Err(e) => {
                    self.dialog = None;
                    self.waiting = None;
                    if let Some(f) = self.frames.last_mut() {
                        f.sleep = None;
                    }
                    if let Some(ev) = self.handle_error(e.0, host) {
                        return PollResult::Event(ev);
                    }
                }
            }
            if let Some(deadline) = self.waiting {
                return PollResult::Waiting {
                    deadline: self.wait_deadline(deadline),
                };
            }
        }
        PollResult::Yield
    }

    // ---- Fehlerbehandlung -------------------------------------------------------

    /// Fehler auslösen: Handler suchen, Stack abwickeln, springen.
    /// `Some(event)` = unbehandelt (Programmabbruch).
    fn handle_error(&mut self, code: u16, _host: &mut dyn Host) -> Option<RunEvent> {
        let line = self.current_line();
        if self.in_handler {
            // Fehler im Handler: fatal, keine Kaskade.
            return Some(self.error_event(code, line));
        }
        // `ERL` friert auf der Zeile ein, in der es passiert ist.
        self.erl = self.zeile_nr;
        // Frame-Kette von innen nach außen nach LOCAL-Handlern absuchen.
        let mut target: Option<(usize, Handler)> = None;
        for (depth, f) in self.frames.iter().enumerate().rev() {
            if f.local_handler != Handler::None {
                target = Some((depth, f.local_handler));
                break;
            }
        }
        if target.is_none() {
            for frame in self.frames.iter().rev() {
                let module = self.module.sources[frame.source as usize].module as usize;
                let handler = self.module_handlers[module];
                if handler != Handler::None {
                    target = Some((0, handler));
                    break;
                }
            }
        }
        let Some((depth, handler)) = target else {
            return Some(self.error_event(code, line));
        };
        // Unwinding bis zum Handler-Frame.
        while self.frames.len() > depth + 1 {
            self.pop_frame();
        }
        let (stack_base, last_stmt_pc, fproc) = {
            let f = self.frames.last().unwrap();
            (f.stack_base, f.last_stmt_pc, f.proc)
        };
        self.stack.truncate(stack_base);
        self.frames.last_mut().unwrap().eingabe = None;
        self.frames.last_mut().unwrap().sleep = None;
        self.err = code;
        match handler {
            Handler::Goto(pc) => {
                self.resume_pc = last_stmt_pc;
                self.in_handler = true;
                self.frames.last_mut().unwrap().pc = pc as usize;
            }
            Handler::ResumeNext => {
                // Weiter mit der Anweisung nach der zuletzt begonnenen.
                let next = self.next_stmt_pc(fproc, last_stmt_pc);
                self.frames.last_mut().unwrap().pc = next;
            }
            Handler::None => unreachable!(),
        }
        None
    }

    fn error_event(&self, code: u16, line: u32) -> RunEvent {
        RunEvent::Error {
            code,
            line,
            message: RuntimeError(code).message().to_string(),
        }
    }

    /// Nächste Anweisungsgrenze nach `from` (Instruktionsindex).
    fn next_stmt_pc(&self, proc: usize, from: usize) -> usize {
        let code = &self.module.procs[proc].code;
        let mut i = from + 1;
        while i < code.len() {
            if matches!(code[i], Instr::Stmt(_) | Instr::InitStmt(_)) {
                return i;
            }
            i += 1;
        }
        code.len().saturating_sub(1) // läuft auf das Rumpfende (End/Ret)
    }

    // ---- Werte-Hilfen -------------------------------------------------------------

    fn pop(&mut self) -> Result<Value, RuntimeError> {
        self.stack.pop().ok_or(RuntimeError(51)) // Internal error
    }

    fn pop_i16(&mut self) -> Result<i16, RuntimeError> {
        match self.pop()? {
            Value::Int(v) => Ok(v),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_i32(&mut self) -> Result<i32, RuntimeError> {
        match self.pop()? {
            Value::Lng(v) => Ok(v),
            Value::Int(v) => Ok(v as i32),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_f32(&mut self) -> Result<f32, RuntimeError> {
        match self.pop()? {
            Value::Sng(v) => Ok(v),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_f64(&mut self) -> Result<f64, RuntimeError> {
        match self.pop()? {
            Value::Dbl(v) => Ok(v),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_cur(&mut self) -> Result<i64, RuntimeError> {
        match self.pop()? {
            Value::Cur(v) => Ok(v),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_str(&mut self) -> Result<Rc<str>, RuntimeError> {
        match self.pop()? {
            Value::Str(s) => Ok(s),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_arr(&mut self) -> Result<Rc<std::cell::RefCell<ArrayObj>>, RuntimeError> {
        match self.pop()? {
            Value::Arr(a) => Ok(a),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_rec(&mut self) -> Result<Rc<std::cell::RefCell<RecordObj>>, RuntimeError> {
        match self.pop()? {
            Value::Rec(r) => Ok(r),
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn pop_indices(&mut self, dims: u8) -> Result<Vec<i32>, RuntimeError> {
        let mut idx = vec![0i32; dims as usize];
        for i in (0..dims as usize).rev() {
            idx[i] = self.pop_i32()?;
        }
        Ok(idx)
    }

    fn push(&mut self, v: Value) {
        self.stack.push(v);
    }

    fn truthy(v: &Value) -> bool {
        match v {
            Value::Int(v) => *v != 0,
            Value::Lng(v) => *v != 0,
            Value::Sng(v) => *v != 0.0,
            Value::Dbl(v) => *v != 0.0,
            Value::Cur(v) => *v != 0,
            _ => false,
        }
    }

    fn read_ref(&self, r: &VarRef) -> Result<Value, RuntimeError> {
        Ok(match r {
            VarRef::Global(i) => self.globals[*i as usize].clone(),
            VarRef::Stack(i) => self.locals[*i as usize].clone(),
            VarRef::Elem(a, flat) => a.borrow().data[*flat as usize].clone(),
            VarRef::Field(rec, path) => {
                let mut v = Value::Rec(rec.clone());
                for f in path {
                    let Value::Rec(r) = v else {
                        return Err(RuntimeError::TYPE_MISMATCH);
                    };
                    let val = r.borrow().fields[*f as usize].clone();
                    v = val;
                }
                v
            }
        })
    }

    fn write_ref(&mut self, r: &VarRef, val: Value) -> Result<(), RuntimeError> {
        match r {
            VarRef::Global(i) => self.globals[*i as usize] = val,
            VarRef::Stack(i) => self.locals[*i as usize] = val,
            VarRef::Elem(a, flat) => a.borrow_mut().data[*flat as usize] = val,
            VarRef::Field(rec, path) => {
                let mut cur = rec.clone();
                for f in &path[..path.len() - 1] {
                    let next = match &cur.borrow().fields[*f as usize] {
                        Value::Rec(r) => r.clone(),
                        _ => return Err(RuntimeError::TYPE_MISMATCH),
                    };
                    cur = next;
                }
                cur.borrow_mut().fields[*path.last().unwrap() as usize] = val;
            }
        }
        Ok(())
    }

    /// Array-Slot sicherstellen (Auto-DIM impliziter Arrays) und Handle liefern.
    fn ensure_array(
        &mut self,
        global: bool,
        slot: u16,
        dims: u8,
        elem: &TypeInit,
    ) -> Result<Rc<std::cell::RefCell<ArrayObj>>, RuntimeError> {
        let frame_base = self.frames.last().map(|f| f.locals_base).unwrap_or(0);
        let cell = if global {
            &mut self.globals[slot as usize]
        } else {
            &mut self.locals[frame_base + slot as usize]
        };
        match cell {
            Value::Arr(a) => Ok(a.clone()),
            Value::Empty => {
                let lo = self.module.modules[self.current_module() as usize].1 as i32;
                let bounds = vec![(lo, 10); dims as usize];
                let arr = ArrayObj::new(elem.clone(), bounds, &self.module.udts)?;
                let handle = Rc::new(std::cell::RefCell::new(arr));
                let cell = if global {
                    &mut self.globals[slot as usize]
                } else {
                    &mut self.locals[frame_base + slot as usize]
                };
                *cell = Value::Arr(handle.clone());
                Ok(handle)
            }
            _ => Err(RuntimeError::TYPE_MISMATCH),
        }
    }

    fn slot_value(&mut self, global: bool, slot: u16) -> &mut Value {
        if global {
            &mut self.globals[slot as usize]
        } else {
            let base = self.frames.last().map(|f| f.locals_base).unwrap_or(0);
            &mut self.locals[base + slot as usize]
        }
    }

    fn jump(&mut self, pc: u32) {
        self.frames.last_mut().unwrap().pc = pc as usize;
    }

    // ---- Instruktionsausführung ------------------------------------------------

    fn exec(&mut self, instr: Instr, pc: usize, host: &mut dyn Host) -> Result<Flow, RuntimeError> {
        use Instr as I;
        match instr {
            I::Source(_, _) => Ok(Flow::Normal),
            I::Stmt(line) | I::InitStmt(line) => {
                let initializing = matches!(instr, I::InitStmt(_));
                if line == 0 {
                    if let Some(target) = self.start_pc.take() {
                        self.frames[0].pc = target;
                    }
                }
                let f = self.frames.last_mut().unwrap();
                f.line = line;
                if let Some(I::Source(source, column)) = pc
                    .checked_sub(1)
                    .and_then(|pc| self.module.procs[f.proc].code.get(pc))
                {
                    f.source = *source;
                    f.column = *column;
                }
                f.last_stmt_pc = pc;
                // Zustellpunkt: anzeigen, wenn sich der Bildschirm geändert
                // hat, und regelmäßig Ereignisse abholen. Das ist keine
                // Debugger-Funktion — ohne sie sähe niemand die Ausgabe und
                // Größenänderungen kämen nie an.
                self.tick_zaehler = self.tick_zaehler.wrapping_add(1);
                self.forms.render(&mut self.rt.screen);
                if !initializing
                    && (self.rt.screen.ist_veraendert() || self.tick_zaehler.is_multiple_of(1024))
                {
                    self.tick(host);
                    self.rt.screen.veraenderung_quittieren();
                }
                let erster_eintritt =
                    std::mem::take(&mut self.frames.last_mut().unwrap().handler_start);
                if !initializing
                    && !erster_eintritt
                    && self.zustellen(host, Zustellpunkt::Anweisung)
                {
                    return Ok(Flow::Normal);
                }
                if self.flags != 0 {
                    if self.flags & F_STEP != 0 {
                        return Ok(Flow::Event(RunEvent::Stepped { line }));
                    }
                    if self.flags & F_BREAK != 0
                        && self.breakpoints.contains(&(self.current_module(), line))
                    {
                        return Ok(Flow::Event(RunEvent::Breakpoint { line }));
                    }
                    if self.flags & F_POLL != 0 && self.rt.abbruch {
                        return Ok(Flow::Event(RunEvent::Interrupted { line }));
                    }
                }
                Ok(Flow::Normal)
            }
            // ---- Ereignis-Traps (Sprachreferenz §8) ----
            I::TrapDefine(art, ziel) => {
                let n = self.pop_i32()?;
                let q = trap_quelle(art, n)?;
                if art == ART_TIMER {
                    if !(1..=86_400).contains(&n) {
                        return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                    }
                    let jetzt = host.jetzt_ms();
                    self.rt.traps.timer_intervall(n as u64, jetzt);
                }
                self.rt.traps.definieren(q, Some(ziel));
                Ok(Flow::Normal)
            }
            I::TrapDisable(art) => {
                let n = self.pop_i32()?;
                let q = trap_quelle(art, n)?;
                self.rt.traps.definieren(q, None);
                Ok(Flow::Normal)
            }
            I::TrapSet(art, zustand) => {
                let n = self.pop_i32()?;
                let q = trap_quelle(art, n)?;
                let z = match zustand {
                    0 => tb_runtime::traps::Zustand::An,
                    1 => tb_runtime::traps::Zustand::Aus,
                    _ => tb_runtime::traps::Zustand::Gestoppt,
                };
                let jetzt = if art == ART_TIMER { host.jetzt_ms() } else { 0 };
                self.rt.traps.setzen(q, z, jetzt);
                Ok(Flow::Normal)
            }
            I::Doevents => {
                // Wert zuerst ablegen: ein hier zugestellter Handler
                // bekommt seinen eigenen Stapelabschnitt darüber und
                // lässt den Wert unberührt.
                self.stack.push(Value::Int(0));
                self.tick(host);
                self.zustellen(host, Zustellpunkt::Kooperativ);
                Ok(Flow::Normal)
            }
            I::Sleep(mit_arg) => {
                let caller = self.frames.len() - 1;
                if self.frames[caller].sleep.is_none() {
                    let seconds = if mit_arg { self.pop_f64()? } else { 0.0 };
                    self.frames[caller].sleep = Some(
                        (seconds > 0.0)
                            .then(|| host.jetzt_ms().saturating_add((seconds * 1000.0) as u64)),
                    );
                }
                let deadline = self.frames[caller].sleep.unwrap();
                self.tick(host);
                let dispatched = self.zustellen(host, Zustellpunkt::Kooperativ);
                if dispatched
                    || self.rt.ende
                    || deadline.is_some_and(|d| host.jetzt_ms() >= d)
                    || (deadline.is_none() && self.rt.gepufferte_taste().is_some())
                {
                    self.frames[caller].sleep = None;
                } else {
                    self.frames[caller].pc = pc;
                    self.waiting = Some(deadline);
                }
                Ok(Flow::Normal)
            }
            I::EventSwitch(an) => {
                self.rt.traps.event(an);
                Ok(Flow::Normal)
            }
            I::SetErl(n) => {
                self.zeile_nr = n;
                Ok(Flow::Normal)
            }
            I::End => Ok(Flow::Event(RunEvent::Ended)),
            I::SystemInstr => {
                self.terminated = true;
                Ok(Flow::Event(RunEvent::Ended))
            }
            I::StopInstr => Ok(Flow::Event(RunEvent::Stopped {
                line: self.current_line(),
            })),
            I::Unsupported(_) => Err(RuntimeError(73)), // Advanced feature

            I::PushInt(v) => {
                self.push(Value::Int(v));
                Ok(Flow::Normal)
            }
            I::PushLng(v) => {
                self.push(Value::Lng(v));
                Ok(Flow::Normal)
            }
            I::PushUdtId(id) => {
                self.push(Value::Lng(id as i32));
                Ok(Flow::Normal)
            }
            I::PushSng(v) => {
                self.push(Value::Sng(v));
                Ok(Flow::Normal)
            }
            I::PushDbl(v) => {
                self.push(Value::Dbl(v));
                Ok(Flow::Normal)
            }
            I::PushCur(v) => {
                self.push(Value::Cur(v));
                Ok(Flow::Normal)
            }
            I::PushStr(i) => {
                self.push(Value::Str(self.module.strings[i as usize].clone()));
                Ok(Flow::Normal)
            }
            I::LoadObjectProperty(object, property, has_index) => {
                let index = if has_index {
                    Some(self.pop_i32()?)
                } else {
                    None
                };
                if !self.forms.is_loaded_at(object, index) {
                    self.forms.ensure_loaded_at(object, index)?;
                    if index.is_none() && self.dispatch_next_form_event(true) {
                        let caller = self.frames.len() - 2;
                        self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
                        return Ok(Flow::Normal);
                    }
                }
                let value = self.forms.get_at(object, index, property)?;
                self.push(Self::form_value(value));
                Ok(Flow::Normal)
            }
            I::StoreObjectProperty(object, property, has_index) => {
                let value = self.pop()?;
                let index = if has_index {
                    Some(self.pop_i32()?)
                } else {
                    None
                };
                if !self.forms.is_loaded_at(object, index) {
                    self.forms.ensure_loaded_at(object, index)?;
                    if index.is_none() {
                        self.push(value.clone());
                    }
                    if index.is_none() && self.dispatch_next_form_event(true) {
                        let caller = self.frames.len() - 2;
                        self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
                        return Ok(Flow::Normal);
                    }
                    if index.is_none() {
                        let value = self.pop()?;
                        let value = self.property_value(object, property, value)?;
                        self.forms.set_at(object, index, property, value)?;
                        self.forms.sync_timers(|| host.jetzt_ms());
                        return Ok(Flow::Normal);
                    }
                }
                let value = self.property_value(object, property, value)?;
                self.forms.set_at(object, index, property, value)?;
                self.forms.sync_timers(|| host.jetzt_ms());
                Ok(Flow::Normal)
            }
            I::PushObject(object, has_index) => {
                let index = if has_index {
                    Some(self.pop_i32()?)
                } else {
                    None
                };
                self.forms.ensure_loaded_at(object, index)?;
                self.push(Value::Obj(object, index));
                self.dispatch_next_form_event(true);
                Ok(Flow::Normal)
            }
            I::LoadDynamicObjectProperty(property) => {
                let Value::Obj(object, index) = self.pop()? else {
                    return Err(RuntimeError::TYPE_MISMATCH);
                };
                let class = self
                    .module
                    .objects
                    .get(object as usize)
                    .ok_or(RuntimeError(420))?
                    .class;
                let name = self
                    .module
                    .strings
                    .get(property as usize)
                    .ok_or(RuntimeError(422))?;
                let property = tb_frontend::forms::property(class, name)
                    .map(|(property, _)| property)
                    .ok_or(RuntimeError(422))?;
                let value = self.forms.get_at(object, index, property)?;
                self.push(Self::form_value(value));
                Ok(Flow::Normal)
            }
            I::StoreDynamicObjectProperty(property) => {
                let value = self.pop()?;
                let Value::Obj(object, index) = self.pop()? else {
                    return Err(RuntimeError::TYPE_MISMATCH);
                };
                let class = self
                    .module
                    .objects
                    .get(object as usize)
                    .ok_or(RuntimeError(420))?
                    .class;
                let name = self
                    .module
                    .strings
                    .get(property as usize)
                    .ok_or(RuntimeError(422))?;
                let property = tb_frontend::forms::property(class, name)
                    .map(|(property, _)| property)
                    .ok_or(RuntimeError(422))?;
                let value = self.property_value(object, property, value)?;
                self.forms.set_at(object, index, property, value)?;
                self.forms.sync_timers(|| host.jetzt_ms());
                Ok(Flow::Normal)
            }
            I::LoadObjectIndexedProperty(object, property) => {
                let index = self.pop_i32()?;
                let object_index = if property & 0x8000 != 0 {
                    Some(self.pop_i32()?)
                } else {
                    None
                };
                let value =
                    self.forms
                        .get_indexed_at(object, object_index, property & 0x7fff, index)?;
                self.push(Self::form_value(value));
                Ok(Flow::Normal)
            }
            I::StoreObjectIndexedProperty(object, property) => {
                let value = self.pop_i32()?;
                let index = self.pop_i32()?;
                let object_index = if property & 0x8000 != 0 {
                    Some(self.pop_i32()?)
                } else {
                    None
                };
                self.forms
                    .set_indexed_at(object, object_index, property & 0x7fff, index, value)?;
                Ok(Flow::Normal)
            }
            I::ObjectMethodFn(object, method, argc) => {
                let indexed = argc & 0x80 != 0;
                let argc = argc & 0x7f;
                let class = self
                    .module
                    .objects
                    .get(object as usize)
                    .ok_or(RuntimeError(420))?
                    .class;
                let name = *tb_frontend::forms::methods(class)
                    .get(method as usize)
                    .ok_or(RuntimeError(421))?;
                let mut args = Vec::with_capacity(argc as usize);
                for _ in 0..argc {
                    args.push(Self::form_arg(self.pop()?)?);
                }
                args.reverse();
                let index = if indexed { Some(self.pop_i32()?) } else { None };
                let value = self
                    .forms
                    .object_method_at(object, index, name, args)?
                    .ok_or(RuntimeError(421))?;
                self.push(Self::form_value(value));
                Ok(Flow::Normal)
            }
            I::TypeOf(class) => {
                let matches = match self.pop()? {
                    Value::Obj(object, _) => self
                        .module
                        .objects
                        .get(object as usize)
                        .is_some_and(|o| o.class.id() == class),
                    _ => false,
                };
                self.push(Value::Int(if matches { -1 } else { 0 }));
                Ok(Flow::Normal)
            }
            I::ObjectMethod(object, method, argc) => {
                let indexed = argc & 0x80 != 0;
                let argc = argc & 0x7f;
                let class = self
                    .module
                    .objects
                    .get(object as usize)
                    .ok_or(RuntimeError(420))?
                    .class;
                let name = *tb_frontend::forms::methods(class)
                    .get(method as usize)
                    .ok_or(RuntimeError(421))?;
                let resumed_show = self.frames.last().unwrap().pending_show == Some(object);
                if resumed_show {
                    self.frames.last_mut().unwrap().pending_show = None;
                } else if class == tb_frontend::forms::ObjectClass::Form
                    && name == "SHOW"
                    && !self.forms.is_loaded(object)
                {
                    self.forms.ensure_loaded(object)?;
                    if self.dispatch_next_form_event(true) {
                        let caller = self.frames.len() - 2;
                        self.frames[caller].pending_show = Some(object);
                        self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
                        return Ok(Flow::Normal);
                    }
                }
                let mut args = Vec::with_capacity(argc as usize);
                for _ in 0..argc {
                    args.push(self.pop()?);
                }
                args.reverse();
                let index = if indexed { Some(self.pop_i32()?) } else { None };
                if resumed_show && !self.forms.is_loaded(object) {
                    return Ok(Flow::Normal);
                }
                match (class, name) {
                    (tb_frontend::forms::ObjectClass::Form, "SHOW") => {
                        let style = match args.first() {
                            None => 0,
                            Some(Value::Int(v)) => *v as i32,
                            Some(Value::Lng(v)) => *v,
                            _ => return Err(RuntimeError::TYPE_MISMATCH),
                        };
                        if !matches!(style, 0 | 1) {
                            return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                        }
                        if self.forms.show(object, style == 1)? == ShowResult::ModalWait {
                            self.frames.last_mut().unwrap().waiting_form = Some(object);
                        }
                    }
                    (tb_frontend::forms::ObjectClass::Form, "HIDE") => self.forms.hide(object)?,
                    (tb_frontend::forms::ObjectClass::Form, "LOAD") => {
                        self.forms.ensure_loaded(object)?
                    }
                    (tb_frontend::forms::ObjectClass::Form, "UNLOAD") => {
                        self.request_unload(object)?
                    }
                    (tb_frontend::forms::ObjectClass::Form, "PRINTFORM") => {
                        self.forms.render(&mut self.rt.screen);
                        self.rt
                            .print
                            .drucker
                            .push_str(&tb_runtime::snapshot::text(&self.rt.screen));
                    }
                    (tb_frontend::forms::ObjectClass::Screen, "SHOW") => {
                        self.forms.screen_show(true)
                    }
                    (tb_frontend::forms::ObjectClass::Screen, "HIDE") => {
                        self.forms.screen_show(false)
                    }
                    _ => {
                        let args = args
                            .into_iter()
                            .map(Self::form_arg)
                            .collect::<Result<Vec<_>, _>>()?;
                        self.forms.object_method_at(object, index, name, args)?;
                    }
                }
                self.forms.sync_timers(|| host.jetzt_ms());
                self.dispatch_next_form_event(true);
                Ok(Flow::Normal)
            }
            I::ObjectLoad(object, unload, has_index) => {
                if has_index {
                    let index = self.pop_i32()?;
                    if unload {
                        self.forms.unload_array(object, index)?;
                    } else {
                        self.forms.load_array(object, index)?;
                    }
                } else if unload {
                    self.request_unload(object)?;
                } else {
                    self.forms.ensure_loaded(object)?;
                    self.dispatch_next_form_event(true);
                }
                self.forms.sync_timers(|| host.jetzt_ms());
                Ok(Flow::Normal)
            }
            I::Dup => {
                let v = self.stack.last().cloned().ok_or(RuntimeError(51))?;
                self.push(v);
                Ok(Flow::Normal)
            }
            I::Pop => {
                self.pop()?;
                Ok(Flow::Normal)
            }

            I::LoadGlobal(i) => {
                self.push(self.globals[i as usize].clone());
                Ok(Flow::Normal)
            }
            I::StoreGlobal(i) => {
                let v = self.pop()?;
                self.globals[i as usize] = v;
                Ok(Flow::Normal)
            }
            I::LoadLocal(i) => {
                let base = self.frames.last().unwrap().locals_base;
                self.push(self.locals[base + i as usize].clone());
                Ok(Flow::Normal)
            }
            I::StoreLocal(i) => {
                let v = self.pop()?;
                let base = self.frames.last().unwrap().locals_base;
                self.locals[base + i as usize] = v;
                Ok(Flow::Normal)
            }
            I::LoadRef(i) => {
                let base = self.frames.last().unwrap().locals_base;
                match &self.locals[base + i as usize] {
                    Value::Ref(r) => {
                        let r = r.clone();
                        let v = self.read_ref(&r)?;
                        self.push(v);
                    }
                    // BYVAL-übergebener Wert im selben Slot
                    other => {
                        let v = other.clone();
                        self.push(v);
                    }
                }
                Ok(Flow::Normal)
            }
            I::StoreRef(i) => {
                let v = self.pop()?;
                let base = self.frames.last().unwrap().locals_base;
                match self.locals[base + i as usize].clone() {
                    Value::Ref(r) => self.write_ref(&r, v)?,
                    _ => self.locals[base + i as usize] = v,
                }
                Ok(Flow::Normal)
            }
            I::MakeRefGlobal(i) => {
                self.push(Value::Ref(VarRef::Global(i)));
                Ok(Flow::Normal)
            }
            I::MakeRefLocal(i) => {
                let base = self.frames.last().unwrap().locals_base;
                self.push(Value::Ref(VarRef::Stack((base + i as usize) as u32)));
                Ok(Flow::Normal)
            }
            I::MakeRefElem(dims) => {
                let idx = self.pop_indices(dims)?;
                let a = self.pop_arr()?;
                let flat = a.borrow().flat_index(&idx)?;
                self.push(Value::Ref(VarRef::Elem(a, flat)));
                Ok(Flow::Normal)
            }
            I::MakeRefField(f) => {
                match self.pop()? {
                    Value::Rec(r) => self.push(Value::Ref(VarRef::Field(r, vec![f]))),
                    Value::Ref(VarRef::Field(r, mut path)) => {
                        path.push(f);
                        self.push(Value::Ref(VarRef::Field(r, path)));
                    }
                    _ => return Err(RuntimeError::TYPE_MISMATCH),
                }
                Ok(Flow::Normal)
            }

            I::LoadArr(global, slot, dims, elem) => {
                let a = self.ensure_array(global, slot, dims, &elem)?;
                self.push(Value::Arr(a));
                Ok(Flow::Normal)
            }
            I::LoadElem(dims) => {
                let idx = self.pop_indices(dims)?;
                let a = self.pop_arr()?;
                let a = a.borrow();
                let flat = a.flat_index(&idx)?;
                self.push(a.data[flat as usize].clone());
                Ok(Flow::Normal)
            }
            I::StoreElem(dims) => {
                let v = self.pop()?;
                let idx = self.pop_indices(dims)?;
                let a = self.pop_arr()?;
                let mut a = a.borrow_mut();
                let flat = a.flat_index(&idx)?;
                a.data[flat as usize] = v;
                Ok(Flow::Normal)
            }
            I::DimArr(global, slot, dims, ref elem)
            | I::CommonArr(global, slot, dims, ref elem) => {
                let common = matches!(instr, I::CommonArr(..));
                let bounds = self.pop_bounds(dims)?;
                let cell = self.slot_value(global, slot);
                if common {
                    if let Value::Arr(array) = cell {
                        let array = array.borrow();
                        return if &array.elem == elem && array.dims == bounds {
                            Ok(Flow::Normal)
                        } else {
                            Err(RuntimeError::TYPE_MISMATCH)
                        };
                    }
                }
                if !matches!(cell, Value::Empty) {
                    return Err(RuntimeError::DUPLICATE_DEFINITION);
                }
                let arr = ArrayObj::new(elem.clone(), bounds, &self.module.udts)?;
                *self.slot_value(global, slot) = Value::Arr(Rc::new(std::cell::RefCell::new(arr)));
                Ok(Flow::Normal)
            }
            I::RedimArr(global, slot, dims, elem) => {
                let bounds = self.pop_bounds(dims)?;
                let arr = ArrayObj::new(elem, bounds, &self.module.udts)?;
                let cell = self.slot_value(global, slot);
                match cell {
                    Value::Arr(handle) => {
                        // In-place ersetzen: geteilte Handles (Parameter)
                        // sehen die neue Dimensionierung.
                        *handle.borrow_mut() = arr;
                    }
                    _ => *cell = Value::Arr(Rc::new(std::cell::RefCell::new(arr))),
                }
                Ok(Flow::Normal)
            }
            I::EraseSlot(global, slot) => {
                *self.slot_value(global, slot) = Value::Empty;
                Ok(Flow::Normal)
            }
            I::LoadField(f) => {
                let r = self.pop_rec()?;
                let v = r.borrow().fields[f as usize].clone();
                self.push(v);
                Ok(Flow::Normal)
            }
            I::StoreField(f) => {
                let v = self.pop()?;
                let r = self.pop_rec()?;
                r.borrow_mut().fields[f as usize] = v;
                Ok(Flow::Normal)
            }
            I::CopyRec => {
                let dst = self.pop_rec()?;
                let src = self.pop_rec()?;
                if !Rc::ptr_eq(&dst, &src) {
                    let copied: Vec<Value> = src.borrow().fields.iter().map(deep_copy).collect();
                    dst.borrow_mut().fields = copied;
                }
                Ok(Flow::Normal)
            }
            I::ArrBound(lower) => {
                let dim = self.pop_i32()?;
                let a = self.pop_arr()?;
                let a = a.borrow();
                if dim < 1 || dim as usize > a.dims.len() {
                    return Err(RuntimeError::SUBSCRIPT_OUT_OF_RANGE);
                }
                let (lo, hi) = a.dims[(dim - 1) as usize];
                self.push(Value::Lng(if lower { lo } else { hi }));
                Ok(Flow::Normal)
            }
            I::FixStr(n) => {
                let s = self.pop_str()?;
                let n = n as usize;
                let len = s.chars().count();
                let fixed: String = if len >= n {
                    s.chars().take(n).collect()
                } else {
                    let mut t = s.to_string();
                    t.extend(std::iter::repeat_n(' ', n - len));
                    t
                };
                self.push(Value::Str(Rc::from(fixed.as_str())));
                Ok(Flow::Normal)
            }

            // ---- Arithmetik ----
            I::AddI2 => self.bin_i16(|a, b| a.checked_add(b)),
            I::AddI4 => self.bin_i32(|a, b| a.checked_add(b)),
            I::AddR4 => self.bin_f32(|a, b| a + b),
            I::AddR8 => self.bin_f64(|a, b| a + b),
            I::AddCy => self.bin_cur(|a, b| a.checked_add(b)),
            I::SubI2 => self.bin_i16(|a, b| a.checked_sub(b)),
            I::SubI4 => self.bin_i32(|a, b| a.checked_sub(b)),
            I::SubR4 => self.bin_f32(|a, b| a - b),
            I::SubR8 => self.bin_f64(|a, b| a - b),
            I::SubCy => self.bin_cur(|a, b| a.checked_sub(b)),
            I::MulI2 => self.bin_i16(|a, b| a.checked_mul(b)),
            I::MulI4 => self.bin_i32(|a, b| a.checked_mul(b)),
            I::MulR4 => self.bin_f32(|a, b| a * b),
            I::MulR8 => self.bin_f64(|a, b| a * b),
            I::MulCy => {
                let b = self.pop_cur()?;
                let a = self.pop_cur()?;
                let r = cur_mul(a, b)?;
                self.push(Value::Cur(r));
                Ok(Flow::Normal)
            }
            I::NegI2 => {
                let a = self.pop_i16()?;
                self.push(Value::Int(a.checked_neg().ok_or(RuntimeError::OVERFLOW)?));
                Ok(Flow::Normal)
            }
            I::NegI4 => {
                let a = self.pop_i32()?;
                self.push(Value::Lng(a.checked_neg().ok_or(RuntimeError::OVERFLOW)?));
                Ok(Flow::Normal)
            }
            I::NegR4 => {
                let a = self.pop_f32()?;
                self.push(Value::Sng(-a));
                Ok(Flow::Normal)
            }
            I::NegR8 => {
                let a = self.pop_f64()?;
                self.push(Value::Dbl(-a));
                Ok(Flow::Normal)
            }
            I::NegCy => {
                let a = self.pop_cur()?;
                self.push(Value::Cur(a.checked_neg().ok_or(RuntimeError::OVERFLOW)?));
                Ok(Flow::Normal)
            }
            I::DivR4 => {
                let b = self.pop_f32()?;
                let a = self.pop_f32()?;
                if b == 0.0 {
                    return Err(RuntimeError::DIVISION_BY_ZERO);
                }
                let r = a / b;
                if r.is_infinite() {
                    return Err(RuntimeError::OVERFLOW);
                }
                self.push(Value::Sng(r));
                Ok(Flow::Normal)
            }
            I::DivR8 => {
                let b = self.pop_f64()?;
                let a = self.pop_f64()?;
                if b == 0.0 {
                    return Err(RuntimeError::DIVISION_BY_ZERO);
                }
                let r = a / b;
                if r.is_infinite() {
                    return Err(RuntimeError::OVERFLOW);
                }
                self.push(Value::Dbl(r));
                Ok(Flow::Normal)
            }
            I::IDivI2 => {
                let b = self.pop_i16()?;
                let a = self.pop_i16()?;
                if b == 0 {
                    return Err(RuntimeError::DIVISION_BY_ZERO);
                }
                let r = a.checked_div(b).ok_or(RuntimeError::OVERFLOW)?;
                self.push(Value::Int(r));
                Ok(Flow::Normal)
            }
            I::IDivI4 => {
                let b = self.pop_i32()?;
                let a = self.pop_i32()?;
                if b == 0 {
                    return Err(RuntimeError::DIVISION_BY_ZERO);
                }
                let r = a.checked_div(b).ok_or(RuntimeError::OVERFLOW)?;
                self.push(Value::Lng(r));
                Ok(Flow::Normal)
            }
            I::ModI2 => {
                let b = self.pop_i16()?;
                let a = self.pop_i16()?;
                if b == 0 {
                    return Err(RuntimeError::DIVISION_BY_ZERO);
                }
                // MIN MOD -1 ist mathematisch 0 (kein Überlauf).
                let r = a.checked_rem(b).unwrap_or(0);
                self.push(Value::Int(r));
                Ok(Flow::Normal)
            }
            I::ModI4 => {
                let b = self.pop_i32()?;
                let a = self.pop_i32()?;
                if b == 0 {
                    return Err(RuntimeError::DIVISION_BY_ZERO);
                }
                let r = a.checked_rem(b).unwrap_or(0);
                self.push(Value::Lng(r));
                Ok(Flow::Normal)
            }
            I::PowR8 => {
                let b = self.pop_f64()?;
                let a = self.pop_f64()?;
                if a == 0.0 && b < 0.0 {
                    return Err(RuntimeError::DIVISION_BY_ZERO);
                }
                if a < 0.0 && b.fract() != 0.0 {
                    return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                }
                let r = a.powf(b);
                if r.is_infinite() || r.is_nan() {
                    return Err(RuntimeError::OVERFLOW);
                }
                self.push(Value::Dbl(r));
                Ok(Flow::Normal)
            }
            I::Concat => {
                let b = self.pop_str()?;
                let a = self.pop_str()?;
                if a.chars().count() + b.chars().count() > 32_767 {
                    return Err(RuntimeError::OUT_OF_STRING_SPACE);
                }
                let mut s = a.to_string();
                s.push_str(&b);
                self.push(Value::Str(Rc::from(s.as_str())));
                Ok(Flow::Normal)
            }

            // ---- Konvertierungen (Matrix) ----
            I::ConvI2I4 => {
                let v = self.pop_i16()?;
                self.push(Value::Lng(v as i32));
                Ok(Flow::Normal)
            }
            I::ConvI2R4 => {
                let v = self.pop_i16()?;
                self.push(Value::Sng(v as f32));
                Ok(Flow::Normal)
            }
            I::ConvI2R8 => {
                let v = self.pop_i16()?;
                self.push(Value::Dbl(v as f64));
                Ok(Flow::Normal)
            }
            I::ConvI2Cy => {
                let v = self.pop_i16()?;
                self.push(Value::Cur(v as i64 * 10_000));
                Ok(Flow::Normal)
            }
            I::ConvI4I2 => {
                let v = self.pop_i32()?;
                self.push(Value::Int(value::i32_to_i16(v)?));
                Ok(Flow::Normal)
            }
            I::ConvI4R4 => {
                let v = self.pop_i32()?;
                self.push(Value::Sng(v as f32));
                Ok(Flow::Normal)
            }
            I::ConvI4R8 => {
                let v = self.pop_i32()?;
                self.push(Value::Dbl(v as f64));
                Ok(Flow::Normal)
            }
            I::ConvI4Cy => {
                let v = self.pop_i32()?;
                self.push(Value::Cur(v as i64 * 10_000));
                Ok(Flow::Normal)
            }
            I::ConvR4I2 => {
                let v = self.pop_f32()?;
                self.push(Value::Int(value::f64_to_i16(v as f64)?));
                Ok(Flow::Normal)
            }
            I::ConvR4I4 => {
                let v = self.pop_f32()?;
                self.push(Value::Lng(value::f64_to_i32(v as f64)?));
                Ok(Flow::Normal)
            }
            I::ConvR4R8 => {
                let v = self.pop_f32()?;
                self.push(Value::Dbl(v as f64));
                Ok(Flow::Normal)
            }
            I::ConvR4Cy => {
                let v = self.pop_f32()?;
                self.push(Value::Cur(value::f64_to_cur(v as f64)?));
                Ok(Flow::Normal)
            }
            I::ConvR8I2 => {
                let v = self.pop_f64()?;
                self.push(Value::Int(value::f64_to_i16(v)?));
                Ok(Flow::Normal)
            }
            I::ConvR8I4 => {
                let v = self.pop_f64()?;
                self.push(Value::Lng(value::f64_to_i32(v)?));
                Ok(Flow::Normal)
            }
            I::ConvR8R4 => {
                let v = self.pop_f64()?;
                self.push(Value::Sng(value::f64_to_f32(v)?));
                Ok(Flow::Normal)
            }
            I::ConvR8Cy => {
                let v = self.pop_f64()?;
                self.push(Value::Cur(value::f64_to_cur(v)?));
                Ok(Flow::Normal)
            }
            I::ConvCyI2 => {
                let v = self.pop_cur()?;
                self.push(Value::Int(value::cur_to_i16(v)?));
                Ok(Flow::Normal)
            }
            I::ConvCyI4 => {
                let v = self.pop_cur()?;
                self.push(Value::Lng(value::cur_to_i32(v)?));
                Ok(Flow::Normal)
            }
            I::ConvCyR4 => {
                let v = self.pop_cur()?;
                self.push(Value::Sng(value::cur_to_f64(v) as f32));
                Ok(Flow::Normal)
            }
            I::ConvCyR8 => {
                let v = self.pop_cur()?;
                self.push(Value::Dbl(value::cur_to_f64(v)));
                Ok(Flow::Normal)
            }

            // ---- Logik ----
            I::NotI2 => {
                let a = self.pop_i16()?;
                self.push(Value::Int(!a));
                Ok(Flow::Normal)
            }
            I::NotI4 => {
                let a = self.pop_i32()?;
                self.push(Value::Lng(!a));
                Ok(Flow::Normal)
            }
            I::AndI2 => self.logic_i16(|a, b| a & b),
            I::AndI4 => self.logic_i32(|a, b| a & b),
            I::OrI2 => self.logic_i16(|a, b| a | b),
            I::OrI4 => self.logic_i32(|a, b| a | b),
            I::XorI2 => self.logic_i16(|a, b| a ^ b),
            I::XorI4 => self.logic_i32(|a, b| a ^ b),
            I::EqvI2 => self.logic_i16(|a, b| !(a ^ b)),
            I::EqvI4 => self.logic_i32(|a, b| !(a ^ b)),
            I::ImpI2 => self.logic_i16(|a, b| !a | b),
            I::ImpI4 => self.logic_i32(|a, b| !a | b),

            // ---- Vergleiche ----
            I::CmpI2(op) => {
                let b = self.pop_i16()?;
                let a = self.pop_i16()?;
                self.push(cmp_result(cmp_ord(a.cmp(&b), op)));
                Ok(Flow::Normal)
            }
            I::CmpI4(op) => {
                let b = self.pop_i32()?;
                let a = self.pop_i32()?;
                self.push(cmp_result(cmp_ord(a.cmp(&b), op)));
                Ok(Flow::Normal)
            }
            I::CmpR4(op) => {
                let b = self.pop_f32()?;
                let a = self.pop_f32()?;
                self.push(cmp_result(cmp_float(a as f64, b as f64, op)));
                Ok(Flow::Normal)
            }
            I::CmpR8(op) => {
                let b = self.pop_f64()?;
                let a = self.pop_f64()?;
                self.push(cmp_result(cmp_float(a, b, op)));
                Ok(Flow::Normal)
            }
            I::CmpCy(op) => {
                let b = self.pop_cur()?;
                let a = self.pop_cur()?;
                self.push(cmp_result(cmp_ord(a.cmp(&b), op)));
                Ok(Flow::Normal)
            }
            I::CmpStr(op) => {
                let b = self.pop_str()?;
                let a = self.pop_str()?;
                self.push(cmp_result(cmp_ord(a.as_bytes().cmp(b.as_bytes()), op)));
                Ok(Flow::Normal)
            }

            // ---- Kontrollfluss ----
            I::Jump(t) => {
                self.jump(t);
                Ok(Flow::Normal)
            }
            I::JumpIfFalse(t) => {
                let v = self.pop()?;
                if !Self::truthy(&v) {
                    self.jump(t);
                }
                Ok(Flow::Normal)
            }
            I::JumpIfTrue(t) => {
                let v = self.pop()?;
                if Self::truthy(&v) {
                    self.jump(t);
                }
                Ok(Flow::Normal)
            }
            I::Gosub(t) => {
                let f = self.frames.last_mut().unwrap();
                let ret = f.pc;
                f.gosub.push(ret);
                f.pc = t as usize;
                Ok(Flow::Normal)
            }
            I::RetGosub => {
                let f = self.frames.last_mut().unwrap();
                match f.gosub.pop() {
                    Some(ret) => {
                        f.pc = ret;
                        Ok(Flow::Normal)
                    }
                    // Leerer GOSUB-Stapel in einem Trap-Frame: das `RETURN`
                    // beendet den Handler und führt an der unterbrochenen
                    // Stelle weiter.
                    None if f.trap.is_some() => {
                        let q = self.trap_frame_abraeumen().unwrap();
                        self.rt.traps.handler_beendet(q);
                        Ok(Flow::Normal)
                    }
                    None => Err(RuntimeError::RETURN_WITHOUT_GOSUB),
                }
            }
            I::RetGosubTo(t) => {
                let f = self.frames.last_mut().unwrap();
                match f.gosub.pop() {
                    Some(_) => {
                        f.pc = t as usize;
                        Ok(Flow::Normal)
                    }
                    // `RETURN label` aus einem Trap-Handler: der Handler
                    // endet, die unterbrochene Aufrufkette wird verworfen
                    // und der Modulrumpf läuft am Label weiter. Das ist die
                    // klassische — und bewusst rabiate — Bedeutung.
                    None if f.trap.is_some() => {
                        let q = self.trap_frame_abraeumen().unwrap();
                        self.rt.traps.handler_beendet(q);
                        while self.frames.len() > 1 {
                            self.pop_frame();
                        }
                        let f = self.frames.last_mut().unwrap();
                        f.eingabe = None;
                        self.stack.truncate(f.stack_base);
                        f.pc = t as usize;
                        Ok(Flow::Normal)
                    }
                    None => Err(RuntimeError::RETURN_WITHOUT_GOSUB),
                }
            }
            I::OnJump(table, gosub) => {
                let n = self.pop_i16()?;
                if !(0..=255).contains(&n) {
                    return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                }
                let t = &self.module.jump_tables[table as usize];
                if n >= 1 && (n as usize) <= t.len() {
                    let target = t[(n - 1) as usize];
                    let f = self.frames.last_mut().unwrap();
                    if gosub {
                        let ret = f.pc;
                        f.gosub.push(ret);
                    }
                    f.pc = target as usize;
                }
                Ok(Flow::Normal)
            }
            I::Run(kind) => {
                let (program, line) = match kind {
                    0 => (None, None),
                    1 => {
                        let line = self.pop_i32()?;
                        if line < 0 {
                            return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                        }
                        (None, Some(line as u32))
                    }
                    2 => (Some(self.pop_str()?.to_string()), None),
                    _ => return Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
                };
                Ok(Flow::Event(RunEvent::Restart { program, line }))
            }

            // ---- Prozeduren ----
            I::Call(proc, argc) => {
                if self.external_dialog(proc as usize, argc as usize, host)? {
                    if self.waiting.is_some() {
                        self.frames.last_mut().unwrap().pc = pc;
                    }
                    return Ok(Flow::Normal);
                }
                self.push_frame(proc as usize, argc as usize);
                Ok(Flow::Normal)
            }
            I::RetProc => {
                let event = self.frames.last().and_then(|f| f.form_event);
                let cancel = self
                    .frames
                    .last()
                    .and_then(|f| {
                        let base = f.locals_base;
                        match self.locals.get(base) {
                            Some(Value::Int(v)) => Some(*v),
                            _ => None,
                        }
                    })
                    .unwrap_or(0);
                self.pop_frame();
                if let Some(FormEventReturn::Unload(object)) = event {
                    self.forms.unload_with(object, |c| *c = cancel)?;
                }
                if matches!(event, Some(FormEventReturn::Normal))
                    && !self.frames.iter().any(|f| f.form_event.is_some())
                {
                    self.dispatch_next_form_event(false);
                }
                Ok(Flow::Normal)
            }
            I::RetFn => {
                let v = self.pop()?;
                self.pop_frame();
                self.push(v);
                Ok(Flow::Normal)
            }
            I::CallBuiltin(id, argc) => {
                if id == ids::RANDOMIZE && argc == 0 {
                    if self.frames.last().unwrap().eingabe.is_none() {
                        self.rt
                            .screen
                            .print("Random Number Seed (-32768 to 32767)? ");
                    }
                    let caller = self.frames.len() - 1;
                    if let Some(text) = self.konsoleneingabe(host, None)? {
                        self.rt.saat_setzen(tb_runtime::format::val(&text));
                    } else {
                        self.frames[caller].pc = pc;
                    }
                    return Ok(Flow::Normal);
                }
                if id == ids::INPUT_S && argc == 1 {
                    let n = match self.stack.last() {
                        Some(Value::Int(n)) => i32::from(*n),
                        Some(Value::Lng(n)) => *n,
                        _ => return Err(RuntimeError::TYPE_MISMATCH),
                    };
                    if n < 0 {
                        return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                    }
                    let caller = self.frames.len() - 1;
                    if let Some(text) = self.konsoleneingabe(host, Some(n as usize))? {
                        self.pop()?;
                        self.push(Value::Str(Rc::from(text)));
                    } else {
                        self.frames[caller].pc = pc;
                    }
                    return Ok(Flow::Normal);
                }
                let mut args = Vec::with_capacity(argc as usize);
                for _ in 0..argc {
                    args.push(self.pop()?);
                }
                args.reverse();
                if matches!(id, ids::MSGBOX | ids::INPUTBOX_S) {
                    if let Some(value) = self.forms_dialog(id, &args, host)? {
                        self.push(value);
                    } else {
                        self.stack.extend(args);
                        self.frames.last_mut().unwrap().pc = pc;
                    }
                    return Ok(Flow::Normal);
                }
                if matches!(id, ids::SHELL_STMT | ids::SHELL_FN) {
                    let command = args
                        .first()
                        .map(|v| self.external_string(v))
                        .transpose()?
                        .unwrap_or_default();
                    if let Some(code) = host.shell(&command)? {
                        if id == ids::SHELL_FN {
                            self.push(Value::Lng(code));
                        }
                    } else {
                        self.stack.extend(args);
                        self.frames.last_mut().unwrap().pc = pc;
                        self.waiting = Some(None);
                    }
                    return Ok(Flow::Normal);
                }
                let f = builtin_table()[id as usize];
                match f(&mut self.rt, host, &mut args) {
                    Ok(Some(v)) => {
                        self.push(v);
                        Ok(Flow::Normal)
                    }
                    Ok(None) => Ok(Flow::Normal),
                    Err(e) => Err(e),
                }
            }

            // ---- Fehlerbehandlung ----
            I::OnErrorGoto(t) => {
                let module = self.current_module() as usize;
                self.module_handlers[module] = Handler::Goto(t);
                Ok(Flow::Normal)
            }
            I::OnErrorLocal(t) => {
                self.frames.last_mut().unwrap().local_handler = Handler::Goto(t);
                Ok(Flow::Normal)
            }
            I::OnErrorDisable => {
                let module = self.current_module() as usize;
                self.module_handlers[module] = Handler::None;
                Ok(Flow::Normal)
            }
            I::OnErrorLocalDisable => {
                self.frames.last_mut().unwrap().local_handler = Handler::None;
                Ok(Flow::Normal)
            }
            I::OnErrorResumeNext(local) => {
                if local {
                    self.frames.last_mut().unwrap().local_handler = Handler::ResumeNext;
                } else {
                    let module = self.current_module() as usize;
                    self.module_handlers[module] = Handler::ResumeNext;
                }
                Ok(Flow::Normal)
            }
            I::Resume0 => self.do_resume(|vm| vm.resume_pc),
            I::ResumeNext => self.do_resume(|vm| {
                let f = vm.frames.last().unwrap();
                vm.next_stmt_pc(f.proc, vm.resume_pc)
            }),
            I::ResumeLabel(t) => self.do_resume(move |_| t as usize),
            I::RaiseError => {
                let code = self.pop_i16()?;
                if code < 1 {
                    return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                }
                Err(RuntimeError(code as u16))
            }
            I::LoadErr => {
                self.push(Value::Lng(self.err as i32));
                Ok(Flow::Normal)
            }
            I::LoadErl => {
                self.push(Value::Lng(self.erl as i32));
                Ok(Flow::Normal)
            }

            // ---- DATA und Eingabe ----
            I::ReadData(kind) => {
                if self.data_ptr >= self.module.data.len() {
                    return Err(RuntimeError::OUT_OF_DATA);
                }
                let item = self.module.data[self.data_ptr].text.clone();
                self.data_ptr += 1;
                if kind == 0 {
                    self.push(Value::Str(Rc::from(item.as_str())));
                } else {
                    let v = parse_data_number(&item)?;
                    self.push(Value::Dbl(v));
                }
                Ok(Flow::Normal)
            }
            I::Restore(idx) => {
                self.data_ptr = idx as usize;
                Ok(Flow::Normal)
            }
            I::Input(argc, line_mode, prompt, question) => {
                let caller = self.frames.len() - 1;
                if !self.do_input(argc, line_mode, prompt, question, host)? {
                    self.frames[caller].pc = pc;
                }
                Ok(Flow::Normal)
            }
            I::InputFile(argc, line_mode) => {
                self.do_input_file(argc, line_mode)?;
                Ok(Flow::Normal)
            }
            I::GetPut(put, mit_nr, art, zusatz) => {
                self.do_get_put(put, mit_nr, art, zusatz)?;
                Ok(Flow::Normal)
            }
            I::SetErr => {
                let n = self.pop_i32()?;
                if !(0..=32767).contains(&n) {
                    return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
                }
                self.err = n as u16;
                Ok(Flow::Normal)
            }
            I::Field(anzahl) => {
                self.do_field(anzahl)?;
                Ok(Flow::Normal)
            }
            I::LsetRset(rset) => {
                self.do_lset_rset(rset)?;
                Ok(Flow::Normal)
            }
        }
    }

    fn pop_bounds(&mut self, dims: u8) -> Result<Vec<(i32, i32)>, RuntimeError> {
        let mut flat = vec![0i32; dims as usize * 2];
        for i in (0..flat.len()).rev() {
            flat[i] = self.pop_i32()?;
        }
        Ok(flat.chunks(2).map(|c| (c[0], c[1])).collect())
    }

    fn do_resume(&mut self, target: impl FnOnce(&Vm) -> usize) -> Result<Flow, RuntimeError> {
        if !self.in_handler {
            return Err(RuntimeError(20)); // RESUME without error
        }
        self.in_handler = false;
        self.err = 0;
        let t = target(self);
        self.frames.last_mut().unwrap().pc = t;
        Ok(Flow::Normal)
    }

    /// Fortsetzbarer Leser: Text und Restlänge gehören zum unterbrochenen
    /// Frame; Referenzen/Argumente bleiben bis zum Abschluss auf dessen Stack.
    fn konsoleneingabe(
        &mut self,
        host: &mut dyn Host,
        rest: Option<usize>,
    ) -> Result<Option<String>, RuntimeError> {
        let caller = self.frames.len() - 1;
        self.frames[caller]
            .eingabe
            .get_or_insert_with(|| KonsolenEingabe {
                text: String::new(),
                rest,
            });
        for _ in 0..64 {
            if self.frames[caller].eingabe.as_ref().unwrap().rest == Some(0) {
                return Ok(Some(self.frames[caller].eingabe.take().unwrap().text));
            }
            self.tick(host);
            if self.rt.abbruch {
                return Ok(None);
            }
            if self.zustellen(host, Zustellpunkt::Konsole) {
                return Ok(None);
            }
            if let Some(taste) = self.rt.gepufferte_taste() {
                let eingabe = self.frames[caller].eingabe.as_mut().unwrap();
                if let Some(rest) = &mut eingabe.rest {
                    let taste = self.rt.tastenanteil(taste, *rest);
                    *rest -= taste.chars().count();
                    eingabe.text.push_str(&taste);
                } else {
                    match taste.as_str() {
                        tb_runtime::host::taste::ENTER => {
                            self.rt.screen.print("\n");
                            return Ok(Some(self.frames[caller].eingabe.take().unwrap().text));
                        }
                        tb_runtime::host::taste::BACKSPACE => {
                            if eingabe.text.pop().is_some() {
                                self.rt.screen.rueckschritt();
                            }
                        }
                        _ => {
                            if taste.chars().count() == 1
                                && taste.chars().next().is_some_and(|c| c as u32 >= 0x20)
                            {
                                eingabe.text.push_str(&taste);
                                self.rt.screen.print(&taste);
                            }
                        }
                    }
                }
                continue;
            }
            if self.rt.ende {
                self.frames[caller].eingabe = None;
                return Err(RuntimeError(62));
            }
            self.waiting = Some(None);
            return Ok(None);
        }
        Ok(None)
    }

    fn do_input(
        &mut self,
        argc: u8,
        line_mode: bool,
        prompt: u16,
        question: bool,
        host: &mut dyn Host,
    ) -> Result<bool, RuntimeError> {
        let start = self
            .stack
            .len()
            .checked_sub(argc as usize)
            .ok_or(RuntimeError(51))?;
        let refs = self.stack[start..]
            .iter()
            .map(|v| match v {
                Value::Ref(r) => Ok(r.clone()),
                _ => Err(RuntimeError::TYPE_MISMATCH),
            })
            .collect::<Result<Vec<_>, _>>()?;
        loop {
            if self.frames.last().unwrap().eingabe.is_none() {
                if prompt != 0xFFFF {
                    self.rt.screen.print(&self.module.strings[prompt as usize]);
                }
                if question {
                    self.rt.screen.print("? ");
                }
            }
            let Some(line) = self.konsoleneingabe(host, None)? else {
                return Ok(false);
            };
            if line_mode {
                let r = refs.first().ok_or(RuntimeError(51))?;
                self.write_ref(r, Value::Str(Rc::from(line)))?;
                self.stack.truncate(start);
                return Ok(true);
            }
            // Felder trennen (Quotes respektieren).
            let fields = split_input_fields(&line);
            if fields.len() != refs.len() {
                self.rt.screen.print("Redo from start\n");
                continue;
            }
            // Konvertieren nach aktuellem Zieltyp; Fehler → Redo.
            let mut vals = Vec::with_capacity(refs.len());
            let mut ok = true;
            for (r, text) in refs.iter().zip(&fields) {
                let target = self.read_ref(r)?;
                match input_value(&target, text) {
                    Some(v) => vals.push(v),
                    None => {
                        ok = false;
                        break;
                    }
                }
            }
            if !ok {
                self.rt.screen.print("Redo from start\n");
                continue;
            }
            for (r, v) in refs.iter().zip(vals) {
                self.write_ref(r, v)?;
            }
            self.stack.truncate(self.stack.len() - argc as usize);
            return Ok(true);
        }
    }

    /// `FIELD #n, laenge AS var$, …` — Recordpuffer aufteilen. Die
    /// Feldvariablen werden gemerkt: `GET` frischt sie aus dem Puffer auf,
    /// `LSET`/`RSET` schreiben zugleich in den Puffer.
    fn do_field(&mut self, anzahl: u8) -> Result<(), RuntimeError> {
        let mut paare = Vec::with_capacity(anzahl as usize);
        for _ in 0..anzahl {
            let r = match self.pop()? {
                Value::Ref(r) => r,
                _ => return Err(RuntimeError::TYPE_MISMATCH),
            };
            let laenge = self.pop_i32()?;
            paare.push((laenge, r));
        }
        paare.reverse();
        let nummer = self.pop_i32()?;
        let datei = self.rt.dateien.get(nummer)?;
        datei.felder.clear();
        let mut start = 0usize;
        for (laenge, r) in paare {
            if laenge < 0 {
                return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
            }
            let n = laenge as usize;
            if start + n * tb_runtime::fileio::UTF32_BREITE > datei.puffer.len() {
                return Err(RuntimeError(50)); // FIELD overflow
            }
            datei.felder.push(tb_runtime::fileio::Feld {
                referenz: r,
                start,
                laenge: n,
            });
            start += n * tb_runtime::fileio::UTF32_BREITE;
        }
        // Feldvariablen sofort aus dem Puffer belegen.
        self.felder_auffrischen(nummer)
    }

    /// Feldvariablen einer Datei aus dem Recordpuffer neu belegen.
    fn felder_auffrischen(&mut self, nummer: i32) -> Result<(), RuntimeError> {
        let datei = self.rt.dateien.get(nummer)?;
        let werte: Vec<(VarRef, String)> = datei
            .felder
            .iter()
            .map(|f| {
                (
                    f.referenz.clone(),
                    tb_runtime::fileio::feld_lesen(&datei.puffer, f.start, f.laenge),
                )
            })
            .collect();
        for (r, t) in werte {
            self.write_ref(&r, Value::Str(Rc::from(t.as_str())))?;
        }
        Ok(())
    }

    /// `LSET`/`RSET` — bündig zuweisen; bei einem `FIELD`-Feld zusätzlich in
    /// den Recordpuffer schreiben.
    fn do_lset_rset(&mut self, rset: bool) -> Result<(), RuntimeError> {
        let wert = self.pop()?;
        let ziel = match self.pop()? {
            Value::Ref(r) => r,
            _ => return Err(RuntimeError::TYPE_MISMATCH),
        };
        let Value::Str(text) = &wert else {
            // UDT-Zuweisung: unverändert durchreichen (Vorbild).
            self.write_ref(&ziel, wert)?;
            return Ok(());
        };

        // Gehört das Ziel zu einem FIELD-Puffer?
        let treffer = self.rt.dateien.feld_finden(&ziel);
        match treffer {
            Some((nummer, start, laenge)) => {
                let datei = self.rt.dateien.get(nummer)?;
                tb_runtime::fileio::feld_setzen(&mut datei.puffer, start, laenge, text, rset);
                let neu = tb_runtime::fileio::feld_lesen(&datei.puffer, start, laenge);
                self.write_ref(&ziel, Value::Str(Rc::from(neu.as_str())))?;
            }
            None => {
                // Ohne Feldbindung wirkt LSET/RSET auf die Stringlänge des
                // bisherigen Werts (Vorbild).
                let alt = match self.read_ref(&ziel)? {
                    Value::Str(s) => s.chars().count(),
                    _ => text.chars().count(),
                };
                let mut puffer = vec![0u8; alt * tb_runtime::fileio::UTF32_BREITE];
                tb_runtime::fileio::feld_setzen(&mut puffer, 0, alt, text, rset);
                let neu = tb_runtime::fileio::feld_lesen(&puffer, 0, alt);
                self.write_ref(&ziel, Value::Str(Rc::from(neu.as_str())))?;
            }
        }
        Ok(())
    }

    /// `GET`/`PUT` — Datensatz (RANDOM) bzw. Bytes (BINARY) übertragen.
    fn do_get_put(
        &mut self,
        put: bool,
        mit_nr: bool,
        art: u8,
        zusatz: u16,
    ) -> Result<(), RuntimeError> {
        use tb_runtime::fileio as fio;
        let ziel = if art != 0 {
            match self.pop()? {
                Value::Ref(r) => Some(r),
                _ => return Err(RuntimeError::TYPE_MISMATCH),
            }
        } else {
            None
        };
        let nr = if mit_nr { Some(self.pop_i32()?) } else { None };
        let nummer = self.pop_i32()?;

        let udts = self.module.udts.clone();
        let datei = self.rt.dateien.get(nummer)?;
        let binaer = datei.modus == fio::Modus::Binary;
        // Ohne Nummer gilt die Position nach der letzten Operation.
        let pos = match nr {
            Some(n) if n >= 1 => n as u64,
            Some(_) => return Err(RuntimeError::ILLEGAL_FUNCTION_CALL),
            None => datei.position,
        };

        // Typ der Zielvariablen bestimmt die übertragene Länge.
        let typ = match art {
            0 => None,
            1 => Some(TypeInit::Int),
            2 => Some(TypeInit::Lng),
            3 => Some(TypeInit::Sng),
            4 => Some(TypeInit::Dbl),
            5 => Some(TypeInit::Cur),
            6 => Some(TypeInit::FixedStr(zusatz as u32)),
            7 => Some(TypeInit::Udt(zusatz)),
            // Variabler String: aktuelle Zeichenzahl bestimmt die Länge.
            _ => {
                let laenge = match self.read_ref(ziel.as_ref().unwrap())? {
                    Value::Str(s) => s.chars().count() as u32,
                    _ => return Err(RuntimeError::TYPE_MISMATCH),
                };
                Some(TypeInit::FixedStr(laenge))
            }
        };

        if binaer {
            let Some(t) = typ else {
                // Ohne Variable überträgt BINARY nichts.
                return Ok(());
            };
            let n = fio::breite(&t, &udts).ok_or(RuntimeError::TYPE_MISMATCH)?;
            if put {
                let wert = self.read_ref(ziel.as_ref().unwrap())?;
                let mut puffer = vec![0u8; n];
                fio::wert_schreiben(&mut puffer, 0, &wert, &t, &udts)?;
                self.rt.dateien.get(nummer)?.bytes_schreiben(pos, &puffer)?;
            } else {
                let puffer = self.rt.dateien.get(nummer)?.bytes_lesen(pos, n)?;
                let (v, _) = fio::wert_lesen(&puffer, 0, &t, &udts)?;
                self.write_ref(ziel.as_ref().unwrap(), v)?;
            }
            return Ok(());
        }

        // RANDOM: über den Recordpuffer.
        if put {
            if let (Some(r), Some(t)) = (&ziel, &typ) {
                let wert = self.read_ref(r)?;
                let datei = self.rt.dateien.get(nummer)?;
                let mut puffer = std::mem::take(&mut datei.puffer);
                puffer.iter_mut().for_each(|b| *b = b' ');
                let ergebnis = fio::wert_schreiben(&mut puffer, 0, &wert, t, &udts);
                self.rt.dateien.get(nummer)?.puffer = puffer;
                ergebnis?;
            }
            self.rt.dateien.get(nummer)?.record_schreiben(pos)?;
        } else {
            self.rt.dateien.get(nummer)?.record_lesen(pos)?;
            self.felder_auffrischen(nummer)?;
            if let (Some(r), Some(t)) = (&ziel, &typ) {
                let puffer = self.rt.dateien.get(nummer)?.puffer.clone();
                let (v, _) = fio::wert_lesen(&puffer, 0, t, &udts)?;
                self.write_ref(r, v)?;
            }
        }
        Ok(())
    }

    /// `INPUT #n` / `LINE INPUT #n` — Werte aus einer sequenziellen Datei.
    fn do_input_file(&mut self, argc: u8, line_mode: bool) -> Result<(), RuntimeError> {
        let mut refs = Vec::with_capacity(argc as usize);
        for _ in 0..argc {
            match self.pop()? {
                Value::Ref(r) => refs.push(r),
                _ => return Err(RuntimeError::TYPE_MISMATCH),
            }
        }
        refs.reverse();
        let nummer = self.pop_i32()?;

        if line_mode {
            let zeile = self
                .rt
                .dateien
                .get(nummer)?
                .zeile_lesen()?
                .ok_or(RuntimeError(62))?;
            let r = refs.first().ok_or(RuntimeError(51))?;
            self.write_ref(r, Value::Str(Rc::from(zeile.as_str())))?;
            return Ok(());
        }
        for r in &refs {
            let text = self.rt.dateien.get(nummer)?.feld_lesen()?;
            let ziel = self.read_ref(r)?;
            // Der Feldleser hat Quotes und äußeren Leerraum bereits ausgewertet.
            let wert = if matches!(ziel, Value::Str(_)) {
                Value::Str(Rc::from(text))
            } else {
                input_value(&ziel, &text).ok_or(RuntimeError(64))?
            };
            self.write_ref(r, wert)?;
        }
        Ok(())
    }

    // Typisierte Binäroperationen mit Überlaufprüfung.
    fn bin_i16(&mut self, f: impl Fn(i16, i16) -> Option<i16>) -> Result<Flow, RuntimeError> {
        let b = self.pop_i16()?;
        let a = self.pop_i16()?;
        let r = f(a, b).ok_or(RuntimeError::OVERFLOW)?;
        self.push(Value::Int(r));
        Ok(Flow::Normal)
    }
    fn bin_i32(&mut self, f: impl Fn(i32, i32) -> Option<i32>) -> Result<Flow, RuntimeError> {
        let b = self.pop_i32()?;
        let a = self.pop_i32()?;
        let r = f(a, b).ok_or(RuntimeError::OVERFLOW)?;
        self.push(Value::Lng(r));
        Ok(Flow::Normal)
    }
    fn bin_f32(&mut self, f: impl Fn(f32, f32) -> f32) -> Result<Flow, RuntimeError> {
        let b = self.pop_f32()?;
        let a = self.pop_f32()?;
        let r = f(a, b);
        if r.is_infinite() {
            return Err(RuntimeError::OVERFLOW);
        }
        self.push(Value::Sng(r));
        Ok(Flow::Normal)
    }
    fn bin_f64(&mut self, f: impl Fn(f64, f64) -> f64) -> Result<Flow, RuntimeError> {
        let b = self.pop_f64()?;
        let a = self.pop_f64()?;
        let r = f(a, b);
        if r.is_infinite() {
            return Err(RuntimeError::OVERFLOW);
        }
        self.push(Value::Dbl(r));
        Ok(Flow::Normal)
    }
    fn bin_cur(&mut self, f: impl Fn(i64, i64) -> Option<i64>) -> Result<Flow, RuntimeError> {
        let b = self.pop_cur()?;
        let a = self.pop_cur()?;
        let r = f(a, b).ok_or(RuntimeError::OVERFLOW)?;
        self.push(Value::Cur(r));
        Ok(Flow::Normal)
    }
    fn logic_i16(&mut self, f: impl Fn(i16, i16) -> i16) -> Result<Flow, RuntimeError> {
        let b = self.pop_i16()?;
        let a = self.pop_i16()?;
        self.push(Value::Int(f(a, b)));
        Ok(Flow::Normal)
    }
    fn logic_i32(&mut self, f: impl Fn(i32, i32) -> i32) -> Result<Flow, RuntimeError> {
        let b = self.pop_i32()?;
        let a = self.pop_i32()?;
        self.push(Value::Lng(f(a, b)));
        Ok(Flow::Normal)
    }
}

/// CURRENCY-Multiplikation: exakt in i128, Skalierung ÷10 000 mit
/// Banker's Rounding.
fn cur_mul(a: i64, b: i64) -> Result<i64, RuntimeError> {
    let p = a as i128 * b as i128;
    let q = p.div_euclid(10_000);
    let r = p.rem_euclid(10_000);
    let rounded = if r > 5_000 {
        q + 1
    } else if r < 5_000 || q % 2 == 0 {
        q
    } else {
        q + 1
    };
    i64::try_from(rounded).map_err(|_| RuntimeError::OVERFLOW)
}

fn deep_copy(v: &Value) -> Value {
    match v {
        Value::Rec(r) => {
            let fields = r.borrow().fields.iter().map(deep_copy).collect();
            Value::Rec(Rc::new(std::cell::RefCell::new(RecordObj { fields })))
        }
        other => other.clone(),
    }
}

fn cmp_ord(o: std::cmp::Ordering, op: CmpOp) -> bool {
    use std::cmp::Ordering::*;
    match op {
        CmpOp::Eq => o == Equal,
        CmpOp::Ne => o != Equal,
        CmpOp::Lt => o == Less,
        CmpOp::Le => o != Greater,
        CmpOp::Gt => o == Greater,
        CmpOp::Ge => o != Less,
    }
}

fn cmp_float(a: f64, b: f64, op: CmpOp) -> bool {
    match op {
        CmpOp::Eq => a == b,
        CmpOp::Ne => a != b,
        CmpOp::Lt => a < b,
        CmpOp::Le => a <= b,
        CmpOp::Gt => a > b,
        CmpOp::Ge => a >= b,
    }
}

fn cmp_result(b: bool) -> Value {
    Value::Int(if b { -1 } else { 0 })
}

/// DATA-Element als Zahl: der komplette Eintrag muss numerisch sein
/// (leer = 0), sonst Fehler 13.
fn parse_data_number(s: &str) -> Result<f64, RuntimeError> {
    let t = s.trim();
    if t.is_empty() {
        return Ok(0.0);
    }
    let cleaned = t.replace(['d', 'D'], "E").replace('e', "E");
    cleaned
        .parse::<f64>()
        .map_err(|_| RuntimeError::TYPE_MISMATCH)
}

/// INPUT-Zeile in Felder trennen: Kommas trennen, Anführungszeichen
/// schützen; Leerraum um unquotierte Felder wird entfernt.
fn split_input_fields(line: &str) -> Vec<String> {
    let mut fields = Vec::new();
    let mut cur = String::new();
    let mut in_quotes = false;
    for c in line.chars() {
        match c {
            '"' => {
                in_quotes = !in_quotes;
                cur.push(c);
            }
            ',' if !in_quotes => {
                fields.push(cur.clone());
                cur.clear();
            }
            _ => cur.push(c),
        }
    }
    fields.push(cur);
    fields
}

/// Eingabefeld in den Typ des Ziels konvertieren (Tag des aktuellen
/// Werts bestimmt den Typ); `None` = „Redo from start".
fn input_value(target: &Value, text: &str) -> Option<Value> {
    let t = text.trim();
    match target {
        Value::Str(_) => {
            let s = t
                .strip_prefix('"')
                .and_then(|s| s.strip_suffix('"'))
                .unwrap_or(t);
            Some(Value::Str(Rc::from(s)))
        }
        Value::Int(_) => {
            let v = strict_number(t)?;
            value::f64_to_i16(v).ok().map(Value::Int)
        }
        Value::Lng(_) => {
            let v = strict_number(t)?;
            value::f64_to_i32(v).ok().map(Value::Lng)
        }
        Value::Sng(_) => strict_number(t).map(|v| Value::Sng(v as f32)),
        Value::Dbl(_) => strict_number(t).map(Value::Dbl),
        Value::Cur(_) => {
            let v = strict_number(t)?;
            value::f64_to_cur(v).ok().map(Value::Cur)
        }
        _ => None,
    }
}

fn strict_number(t: &str) -> Option<f64> {
    if t.is_empty() {
        return Some(0.0);
    }
    let cleaned = t.replace(['d', 'D'], "E").replace('e', "E");
    cleaned.parse::<f64>().ok()
}
