//! Typisiertes, abgesenktes HIR — die Ausgabe der semantischen Analyse
//! und Eingabe des Codegenerators (`tb-vm`).
//!
//! Eigenschaften (siehe Design der Phase-2-Änderung):
//! - Namen sind aufgelöst: Variablen sind Slot-Indizes (global/lokal),
//!   Prozeduren und UDTs Tabellenindizes, Sprungziele `LabelId`s je Rumpf.
//! - Jeder Ausdrucksknoten trägt seinen Ergebnistyp; implizite
//!   Konvertierungen sind als explizite `Conv`-Knoten materialisiert
//!   (Semantik: Konvertierungsmatrix in docs/tbvm-design.md).
//! - Kontrollzucker ist abgesenkt: `SELECT CASE` zu Vergleichsketten,
//!   `ELSEIF` zu verschachteltem `If`, `EXIT FOR/DO` zu `Goto` auf
//!   synthetisierte Labels, `SWAP` zu Zuweisungen über einen Temp-Slot.
//! - `STATIC`-Locals und versteckte Temps liegen im globalen Slot-Bereich.
//!
//! Ein vollständiges, korrektes HIR ist nur bei diagnose-freier Analyse
//! garantiert.

/// Numerischer Skalartyp (Kürzel wie im Opcode-Satz: I2/I4/CY/R4/R8).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumTy {
    Int,
    Lng,
    Cur,
    Sng,
    Dbl,
}

/// Ganzzahlbreite der Logik-Operatoren.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntKind {
    I2,
    I4,
}

/// Aufgelöster HIR-Typ.
#[derive(Debug, Clone, PartialEq)]
pub enum HTy {
    Num(NumTy),
    Str,
    /// Fester String mit Zeichenlänge (Zuweisung padded/kürzt).
    FixedStr(u32),
    /// Benutzerdefinierter Typ (Index in `HirModule::udts`).
    Udt(u16),
    Form,
    Control,
}

impl HTy {
    pub fn num(&self) -> Option<NumTy> {
        match self {
            HTy::Num(n) => Some(*n),
            _ => None,
        }
    }
    pub fn is_str(&self) -> bool {
        matches!(self, HTy::Str | HTy::FixedStr(_))
    }
}

/// Slot-Referenz: globaler Bereich (Modulvariablen, STATICs, versteckte
/// Temps des Hauptprogramms) oder Frame-lokal (Parameter zuerst).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VarSlot {
    Global(u16),
    Local(u16),
}

/// Sprungziel innerhalb eines Prozedurrumpfs.
pub type LabelId = u16;

/// Variablen-/Slotbeschreibung (auch für die Debugger-Inspektion).
#[derive(Debug, Clone)]
pub struct HVar {
    pub name: String,
    pub ty: HTy,
    pub array: bool,
}

#[derive(Debug, Clone)]
pub struct HUdt {
    pub name: String,
    pub fields: Vec<(String, HTy)>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HProcKind {
    /// Hauptprogramm (Modulrumpf) — immer Prozedur 0.
    Main,
    Sub,
    Function,
    /// `DEF FN` — Parameter BYVAL, freie Namen binden an Modulvariablen.
    DefFn,
}

#[derive(Debug, Clone)]
pub struct HParam {
    pub name: String,
    pub ty: HTy,
    pub array: bool,
    /// Skalar-Parameter, der als Referenz übergeben wird (Arrays und
    /// UDTs sind implizit immer Referenzen auf ihr Handle).
    pub by_ref: bool,
}

#[derive(Debug)]
pub struct HProc {
    pub name: String,
    pub kind: HProcKind,
    pub params: Vec<HParam>,
    /// Alle Frame-Slots; `params.len()` erste Einträge sind die Parameter.
    pub locals: Vec<HVar>,
    /// Slot der Rückgabevariablen (Function/DefFn).
    pub ret_slot: Option<VarSlot>,
    pub ret_ty: Option<HTy>,
    pub body: Vec<HStmt>,
    /// Anzahl vergebener LabelIds in diesem Rumpf.
    pub label_count: u16,
}

/// Eine DATA-Konstante (unkonvertiert; `READ` konvertiert zur Laufzeit).
#[derive(Debug, Clone)]
pub struct DataItem {
    pub text: String,
    pub line: u32,
}

/// Aufgelöste COMMON-Deklaration für die projektweite Slotverknüpfung.
#[derive(Debug, Clone)]
pub struct HCommon {
    pub slot: u16,
    pub block: Option<String>,
    pub key: String,
    pub ty: HTy,
    pub dims: Option<Vec<(Option<i32>, Option<i32>)>>,
    pub pos: crate::SourcePos,
}

#[derive(Debug)]
pub struct HirModule {
    pub name: String,
    pub globals: Vec<HVar>,
    pub commons: Vec<HCommon>,
    pub udts: Vec<HUdt>,
    /// Prozeduren; Index 0 ist das Hauptprogramm.
    pub procs: Vec<HProc>,
    pub data: Vec<DataItem>,
    pub option_base: u8,
    /// Zur Übersetzungszeit bekannte Forms-Objekte in Indexreihenfolge.
    pub objects: Vec<crate::forms::FormObject>,
    /// Ereignisprozeduren: Objektindex, Ereignisname, Prozedurindex.
    pub event_procs: Vec<HEventProc>,
}

#[derive(Debug, Clone)]
pub struct HEventProc {
    pub object: u16,
    pub event: String,
    pub proc: u16,
}

// ---- Ausdrücke -------------------------------------------------------------

/// L-Wert: Basis-Slot, optional Array-Indizes, optional UDT-Feldpfad.
#[derive(Debug, Clone)]
pub struct HPlace {
    pub base: VarSlot,
    /// Skalar-BYREF-Parameter: der Slot enthält eine Referenz.
    pub base_is_ref: bool,
    /// Array-Elementzugriff (leer = Skalar bzw. ganzes Array).
    pub indices: Vec<HExpr>,
    /// UDT-Feldpfad (Feldindizes je Ebene).
    pub fields: Vec<u16>,
    /// Typ des adressierten Werts.
    pub ty: HTy,
    /// Element-/Basistyp und Dimension für Auto-DIM impliziter Arrays.
    pub array_elem: Option<(HTy, u8)>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HArith {
    Add,
    Sub,
    Mul,
    Div,
    IDiv,
    Mod,
    Pow,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HCmp {
    Eq,
    Ne,
    Lt,
    Le,
    Gt,
    Ge,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HLogic {
    And,
    Or,
    Xor,
    Eqv,
    Imp,
}

/// Vergleichs-Operandentyp.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmpKind {
    Num(NumTy),
    Str,
}

/// Bibliotheksfunktionen/-anweisungen der Phase-2-Scheibe. Der
/// Diskriminant ist zugleich der stabile Index der Dispatch-Tabelle
/// (`CALL_BUILTIN`); Phase 3 erweitert ausschließlich am Ende.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum Builtin {
    // Strings
    Len,
    LeftS,
    RightS,
    MidS,
    InstrF,
    UcaseS,
    LcaseS,
    LtrimS,
    RtrimS,
    SpaceS,
    StringS,
    ChrS,
    Asc,
    StrS,
    Val,
    HexS,
    OctS,
    /// MID$-Anweisung als reine Funktion: (ziel, start, länge, ersatz) → neuer String.
    MidAssign,
    // Mathematik
    Abs,
    Sgn,
    IntF,
    Fix,
    Sqr,
    Exp,
    Log,
    Sin,
    Cos,
    Tan,
    Atn,
    Rnd,
    Randomize,
    // Konsole (PRINT-Familie; Wirkung über Host + Druckspalten-Zustand)
    PrintVal,
    PrintStrLit,
    PrintComma,
    PrintTab,
    PrintSpc,
    PrintNewline,
    PrintUsing,
    FormatS,
    SetFormatCc,
    // Bildschirm (Anweisungen)
    Cls,
    Color,
    Locate,
    Width,
    ViewPrint,
    ScreenStmt,
    KeyAssign,
    KeyList,
    KeyDisplay,
    // Bildschirm (Funktionen)
    Csrlin,
    PosFn,
    ScreenFn,
    // Tastatur
    InkeyS,
    InputS,
    // System
    EnvironS,
    EnvironSet,
    Fre,
    Clear,
    Tron,
    Troff,
    StackFn,
    StackStmt,
    Erdev,
    ErdevS,
    // Datei-E/A
    Open,
    Close,
    CloseAll,
    PrintZiel,
    WriteFile,
    EofF,
    LofF,
    LocF,
    SeekF,
    SeekStmt,
    Freefile,
    Fileattr,
    LockStmt,
    Kill,
    NameStmt,
    Files,
    Chdir,
    Chdrive,
    Mkdir,
    Rmdir,
    CurdirS,
    DirS,
    Lpos,
    ShellStmt,
    ShellFn,
    MkS,
    CvF,
    // Finanzmathematik
    Fv,
    Pv,
    Pmt,
    NPer,
    IPmt,
    PPmt,
    Rate,
    Npv,
    Irr,
    Mirr,
    Sln,
    Syd,
    Ddb,
    // Sonstiges
    Timer,
    DateS,
    TimeS,
    DateSet,
    TimeSet,
    Now,
    TimezoneKnown,
    DateSerial,
    TimeSerial,
    DateValue,
    TimeValue,
    DayF,
    MonthF,
    YearF,
    WeekdayF,
    HourF,
    MinuteF,
    SecondF,
    CommandS,
    Doevents,
    Sleep,
    /// `SetUEvent` — löst das benutzerdefinierte Ereignis aus (§8).
    SetUEvent,
    Beep,
    // ISAM (Change `phase-3-isam`)
    IsamOpen,
    IsamCreateIndex,
    IsamDeleteIndex,
    IsamSetIndex,
    IsamGetIndexS,
    IsamInsert,
    IsamRetrieve,
    IsamUpdate,
    IsamDelete,
    IsamDeleteTable,
    IsamMoveFirst,
    IsamMoveLast,
    IsamMoveNext,
    IsamMovePrevious,
    IsamSeekEq,
    IsamSeekGt,
    IsamSeekGe,
    IsamBeginTrans,
    IsamCommitTrans,
    IsamRollback,
    IsamSavepoint,
    IsamSetmem,
    IsamBof,
    MsgBox,
    InputBoxS,
    ClipboardAdd,
    ClipboardGet,
    GraphicsLine,
    GraphicsPaint,
    GraphicsView,
}

#[derive(Debug, Clone)]
pub enum HExpr {
    Int(i16),
    Lng(i32),
    /// TYPE-Tabellenreferenz für ISAM; beim Projektlinken zu versetzen.
    UdtId(u16),
    Sng(f32),
    Dbl(f64),
    Cur(i64),
    Str(String),
    Load(Box<HPlace>),
    ObjectProperty {
        object: u16,
        index: Option<Box<HExpr>>,
        property: u16,
        ty: HTy,
    },
    ObjectIndexedProperty {
        object: u16,
        object_index: Option<Box<HExpr>>,
        property: u16,
        index: Box<HExpr>,
        ty: HTy,
    },
    ObjectMethodCall {
        object: u16,
        index: Option<Box<HExpr>>,
        method: u16,
        args: Vec<HExpr>,
        ty: HTy,
    },
    DynamicObjectProperty {
        object: Box<HExpr>,
        property: String,
    },
    ObjectRef {
        object: u16,
        index: Option<Box<HExpr>>,
        class: crate::forms::ObjectClass,
    },
    TypeOf {
        value: Box<HExpr>,
        class: crate::forms::ObjectClass,
    },
    /// Numerische Konvertierung nach Matrix (Rundung/Überlauf).
    Conv {
        from: NumTy,
        to: NumTy,
        arg: Box<HExpr>,
    },
    /// Kürzen/Padden auf feste Stringlänge.
    FixStr {
        len: u32,
        arg: Box<HExpr>,
    },
    Neg {
        ty: NumTy,
        arg: Box<HExpr>,
    },
    /// Monomorphe Arithmetik: beide Operanden und das Ergebnis haben `ty`
    /// (bei `Div`/`Pow` nur R4/R8, bei `IDiv`/`Mod` nur I2/I4).
    Bin {
        op: HArith,
        ty: NumTy,
        l: Box<HExpr>,
        r: Box<HExpr>,
    },
    Not {
        ty: IntKind,
        arg: Box<HExpr>,
    },
    Logic {
        op: HLogic,
        ty: IntKind,
        l: Box<HExpr>,
        r: Box<HExpr>,
    },
    /// Vergleich; Ergebnis ist INTEGER (−1/0).
    Cmp {
        op: HCmp,
        ty: CmpKind,
        l: Box<HExpr>,
        r: Box<HExpr>,
    },
    Concat(Box<HExpr>, Box<HExpr>),
    /// FUNCTION-/DEF FN-Aufruf.
    FnCall {
        proc: u16,
        args: Vec<HArg>,
        ret: HTy,
    },
    Builtin {
        b: Builtin,
        args: Vec<HExpr>,
        ret: HTy,
    },
    /// LBOUND/UBOUND eines Arrays.
    ArrayBound {
        lower: bool,
        place: Box<HPlace>,
        dim: Box<HExpr>,
    },
    Err,
    Erl,
    /// Dokumentiertes, aber erst in einer späteren Phase implementiertes
    /// Feature: löst zur Laufzeit Fehler 73 „Advanced feature" aus.
    Unsupported(&'static str),
}

/// Prozedurargument.
#[derive(Debug, Clone)]
pub enum HArg {
    ByRef(HPlace),
    /// Ganzes Array als Referenz (`prozedur a()`).
    ArrayRef(HPlace),
    ByVal(HExpr),
}

// ---- Anweisungen -----------------------------------------------------------

#[derive(Debug, Clone)]
pub enum HPrintItem {
    Val(HExpr),
    Tab(HExpr),
    Spc(HExpr),
    Comma,
}

#[derive(Debug, Clone)]
pub enum HResume {
    Retry,
    Next,
    Label(LabelId),
}

#[derive(Debug)]
pub struct HStmt {
    pub pos: crate::SourcePos,
    pub line: u32,
    pub kind: HStmtKind,
}

#[derive(Debug)]
pub enum HStmtKind {
    /// Numerische Zeilennummer durchlaufen (setzt `ERL`).
    SetErl(u32),
    Label(LabelId),
    Assign {
        place: HPlace,
        value: HExpr,
    },
    SetObjectProperty {
        object: u16,
        index: Option<HExpr>,
        property: u16,
        value: HExpr,
    },
    SetObjectIndexedProperty {
        object: u16,
        object_index: Option<HExpr>,
        property: u16,
        index: HExpr,
        value: HExpr,
    },
    SetDynamicObjectProperty {
        object: HExpr,
        property: String,
        value: HExpr,
    },
    ObjectMethod {
        object: u16,
        index: Option<HExpr>,
        method: u16,
        args: Vec<HExpr>,
    },
    ObjectLoad {
        object: u16,
        index: Option<HExpr>,
        unload: bool,
    },
    Print {
        items: Vec<HPrintItem>,
        /// Endet die Anweisung mit `;`/`,` (kein Zeilenumbruch)?
        trailing: bool,
    },
    /// `FIELD #n, laenge AS var$, …` — Recordpuffer in Felder aufteilen.
    Field {
        file: HExpr,
        fields: Vec<(HExpr, HPlace)>,
    },
    /// `LSET`/`RSET` — links- bzw. rechtsbündig zuweisen; ist das Ziel ein
    /// `FIELD`-Feld, wirkt die Zuweisung zugleich auf den Recordpuffer.
    LsetRset {
        rset: bool,
        target: HPlace,
        value: HExpr,
    },
    /// `GET`/`PUT` auf einer Datei: Datensatz- bzw. Bytenummer und Ziel-
    /// bzw. Quellvariable. Ohne Variable wirkt der Recordpuffer.
    GetPut {
        put: bool,
        file: HExpr,
        recnum: Option<HExpr>,
        var: Option<HPlace>,
    },
    Input {
        /// `INPUT #n` — Quelle ist eine Datei statt der Tastatur.
        file: Option<HExpr>,
        line_mode: bool,
        prompt: Option<String>,
        /// Fragezeichen nach dem Prompt (`;`-Form).
        question: bool,
        targets: Vec<HPlace>,
    },
    If {
        cond: HExpr,
        then: Vec<HStmt>,
        els: Vec<HStmt>,
    },
    /// DO/LOOP, WHILE/WEND (nur `pre`) — Bedingungen: (ist_until, Ausdruck).
    Loop {
        end_pos: crate::SourcePos,
        pre: Option<(bool, HExpr)>,
        post: Option<(bool, HExpr)>,
        body: Vec<HStmt>,
        exit_label: LabelId,
    },
    For {
        end_pos: crate::SourcePos,
        var: HPlace,
        ty: NumTy,
        from: HExpr,
        to: HExpr,
        step: Option<HExpr>,
        /// Versteckte Slots für Grenze/Schritt (Schritt nur wenn dynamisch).
        limit_slot: VarSlot,
        step_slot: Option<VarSlot>,
        body: Vec<HStmt>,
        exit_label: LabelId,
    },
    Goto(LabelId),
    Gosub(LabelId),
    OnGoto {
        sel: HExpr,
        gosub: bool,
        targets: Vec<LabelId>,
    },
    ReturnGosub(Option<LabelId>),
    Run {
        target: Option<HExpr>,
        string: bool,
    },
    /// Ereignis-Trap erklären (Sprachreferenz §8). `art` ist die Quelle
    /// (0 KEY, 1 TIMER, 2 UEVENT, 3 SIGNAL), `index` ihre Kennung bzw. bei
    /// `TIMER` das Intervall in Sekunden. `ziel = None` = `GOSUB 0`.
    TrapDef {
        art: u8,
        index: HExpr,
        ziel: Option<LabelId>,
    },
    /// `<quelle>(n) ON|OFF|STOP` — `zustand` 0 An, 1 Aus, 2 Gestoppt.
    TrapSet {
        art: u8,
        index: HExpr,
        zustand: u8,
    },
    /// `EVENT ON` / `EVENT OFF`.
    EventSwitch(bool),
    /// EXIT SUB/FUNCTION/DEF bzw. Rumpfende.
    ExitProc,
    CallSub {
        proc: u16,
        args: Vec<HArg>,
    },
    BuiltinStmt {
        b: Builtin,
        args: Vec<HExpr>,
    },
    OnError {
        local: bool,
        /// `None` = `GOTO 0` (deaktivieren).
        target: Option<LabelId>,
    },
    OnErrorResumeNext {
        local: bool,
    },
    Resume(HResume),
    /// `ERROR n`.
    RaiseError(HExpr),
    /// `ERR = n` — Fehlercode setzen, ohne einen Fehler auszulösen.
    SetErr(HExpr),
    Read(Vec<HPlace>),
    /// Ziel als Index in `HirModule::data` (0 = Anfang).
    Restore(u32),
    /// DIM/REDIM eines Arrays: Grenzen (lo, hi) je Dimension.
    Dim {
        slot: VarSlot,
        elem: HTy,
        dims: Vec<(HExpr, HExpr)>,
        redim: bool,
    },
    Erase(Vec<VarSlot>),
    End,
    Stop,
    System,
    /// Dokumentiertes Feature einer späteren Phase → Laufzeitfehler 73.
    Unsupported(&'static str),
}

/// Konstanter Literalwert eines Ausdrucks (z. B. FOR-STEP-Erkennung im
/// Codegen: konstanter Schritt braucht keinen versteckten Slot).
pub fn literal_value(e: &HExpr) -> Option<f64> {
    match e {
        HExpr::Int(v) => Some(*v as f64),
        HExpr::Lng(v) => Some(*v as f64),
        HExpr::Sng(v) => Some(*v as f64),
        HExpr::Dbl(v) => Some(*v),
        HExpr::Cur(v) => Some(*v as f64 / 10_000.0),
        HExpr::Conv { arg, .. } => literal_value(arg),
        HExpr::Neg { arg, .. } => literal_value(arg).map(|v| -v),
        _ => None,
    }
}
