//! Events fed into the application's update function.
//!
//! `AppEvent` is the single input type the runtime delivers to
//! `App::update`. Synthetic instances drive Tier 3 integration
//! tests (see ADR-0008), so the type is plain data with no
//! runtime dependency.

use crossterm::event::KeyEvent;

use crate::db::{
    AddColumnResult, ChangeColumnTypeResult, DataResult, DbError, DeleteResult, DropColumnResult,
    InsertResult, QueryPlan, RelationshipDiagramData, TableDescription, UpdateResult,
};
use crate::dsl::Command;

#[derive(Debug, Clone)]
pub enum AppEvent {
    Key(KeyEvent),
    Resize {
        cols: u16,
        rows: u16,
    },
    Tick,
    /// A DSL command finished successfully. `description` is
    /// `Some` for commands that produce a table view (create,
    /// add column) and `None` for commands that don't (drop).
    DslSucceeded {
        command: Command,
        description: Option<TableDescription>,
        /// The DSL → SQL teaching echo (ADR-0038): equivalent advanced-mode
        /// SQL, built by the runtime when a DSL-form command ran in an
        /// advanced effective mode (ADR-0037). `None` when no echo applies
        /// (simple mode, SQL-entered, or a form with no echo). The App
        /// renders it beneath `[ok]`.
        echo: Option<Vec<String>>,
    },
    /// A SQL `CREATE TABLE IF NOT EXISTS` matched an existing table —
    /// a no-op (ADR-0035 §4). Renders the existing structure plus an
    /// "already exists — skipped" note.
    DslCreateSkipped {
        command: Command,
        description: TableDescription,
    },
    /// A SQL `DROP TABLE IF EXISTS` matched no table — a no-op
    /// (ADR-0035 §4, 4c). Renders a "doesn't exist — skipped" note;
    /// there is no structure to show.
    DslDropSkipped {
        command: Command,
    },
    /// A SQL `DROP INDEX IF EXISTS` matched no index — a no-op
    /// (ADR-0035 §4d). Renders an index-specific "doesn't exist —
    /// skipped" note; no structure to show.
    DslDropIndexSkipped {
        command: Command,
    },
    /// A SQL `CREATE INDEX IF NOT EXISTS` matched an existing index name
    /// — a no-op (ADR-0035 §4d). `name` is the resolved index name (the
    /// auto-name is not on the command). Renders "already exists —
    /// skipped"; no structure to show.
    DslCreateIndexSkipped {
        command: Command,
        name: String,
    },
    /// A `show data` query succeeded. `echo` is the DSL → SQL teaching
    /// echo (ADR-0038) — built post-execution because the limited form
    /// orders by the table's primary key (handoff §5). `None` for a
    /// SQL-entered `SELECT` or any simple-mode submission.
    DslDataSucceeded {
        command: Command,
        data: DataResult,
        echo: Option<Vec<String>>,
    },
    /// An `explain …` command succeeded (ADR-0028). `plan`
    /// carries the captured query plan; nothing was executed.
    DslExplainSucceeded {
        command: Command,
        plan: QueryPlan,
    },
    /// A `show <kind>` list command (V5) — carries pre-formatted
    /// display lines (tables / relationships / indexes).
    DslShowListSucceeded {
        command: Command,
        lines: Vec<String>,
    },
    /// `show relationship <name>` (ADR-0044) — structured data for the
    /// diagram, rendered App-side; `None` when no such relationship.
    DslShowRelationshipSucceeded {
        command: Command,
        data: Option<RelationshipDiagramData>,
    },
    DslInsertSucceeded {
        command: Command,
        result: InsertResult,
    },
    DslSeedSucceeded {
        command: Command,
        result: crate::db::SeedResult,
    },
    DslUpdateSucceeded {
        command: Command,
        result: UpdateResult,
        /// The DSL → SQL teaching echo (ADR-0038): `UPDATE T SET …` for an
        /// `update … --all-rows` fall-through. `None` for a SQL-entered
        /// `UPDATE` or any simple-mode submission.
        echo: Option<Vec<String>>,
    },
    DslDeleteSucceeded {
        command: Command,
        result: DeleteResult,
        /// The DSL → SQL teaching echo (ADR-0038): `DELETE FROM T` for a
        /// `delete … --all-rows` fall-through. `None` for a SQL-entered
        /// `DELETE` or any simple-mode submission.
        echo: Option<Vec<String>>,
    },
    /// A `change column …` succeeded. `result` carries both the
    /// post-rebuild description (for the auto-show) and the
    /// optional `[client-side]` note (ADR-0017 §6).
    DslChangeColumnSucceeded {
        command: Command,
        result: ChangeColumnTypeResult,
        /// The DSL → SQL teaching echo (ADR-0038): `ALTER TABLE T ALTER
        /// COLUMN c SET DATA TYPE …`. `None` in simple mode.
        echo: Option<Vec<String>>,
        /// The `--dont-convert` caveat (ADR-0038 §6 category 3): set by the
        /// runtime to `true` when the command ran with
        /// `ChangeColumnMode::DontConvert` in an advanced effective mode
        /// (the only case where it is meaningful — the line references
        /// "the line above," i.e. the echo). The App renders the prose
        /// caveat between the existing client-side notes and the structure.
        dont_convert_caveat: bool,
    },
    /// An `add column …` succeeded. `result` carries the
    /// post-add description plus any `[client-side]` notes
    /// from the auto-fill paths (ADR-0018 §9).
    DslAddColumnSucceeded {
        command: Command,
        result: AddColumnResult,
        /// The DSL → SQL teaching echo (ADR-0038): `ALTER TABLE T ADD
        /// COLUMN c <ty> …`. `None` in simple mode.
        echo: Option<Vec<String>>,
    },
    /// A `drop column …` succeeded. `result` carries the
    /// post-drop description plus the names of any indexes
    /// removed by `--cascade` (ADR-0025).
    DslDropColumnSucceeded {
        command: Command,
        result: DropColumnResult,
        /// The DSL → SQL teaching echo (ADR-0038): `ALTER TABLE T DROP
        /// COLUMN c` for a plain (non-`--cascade`) drop. `None` in simple
        /// mode, and for `--cascade` (a multi-statement echo, Phase 2).
        echo: Option<Vec<String>>,
    },
    /// A DSL command failed. `error` is the structured
    /// payload, `facts` is the runtime-built schema-resolved
    /// enrichment (parent tables, attempted values,
    /// pinpointed offending rows). App applies its current
    /// verbosity setting (`messages_verbosity`) when rendering
    /// through `friendly::translate_error` (ADR-0019 §5, §6).
    DslFailed {
        command: Command,
        error: DbError,
        facts: crate::friendly::FailureContext,
        /// The original user-typed source line, retained so the
        /// App can journal the failed command as an `err` record
        /// (ADR-0034 §1/§2). The worker only journals successful
        /// commands, so an execution failure would otherwise be
        /// lost across sessions.
        source: String,
        /// Whether the rejected command was submitted in an advanced
        /// effective mode (ADR-0052): threaded so the App can tag the
        /// `err` record `err:adv` and the failed advanced command
        /// hydrates in its `:`-prefixed, simple-mode-recallable form.
        advanced: bool,
    },
    /// Refreshed list of tables in the database.
    TablesRefreshed(Vec<String>),
    /// Refreshed schema lookup cache feeding Tab completion
    /// for identifier slots (ADR-0022 §9 + stage 8d). Runtime
    /// posts this alongside `TablesRefreshed` after project
    /// load and after every successful DDL.
    SchemaCacheRefreshed(crate::completion::SchemaCache),
    /// Refreshed list of relationships as full schema records, for the
    /// sidebar relationships panel (ADR-0046 DB2). Posted by the runtime
    /// alongside `SchemaCacheRefreshed` after every schema refresh.
    RelationshipsRefreshed(Vec<crate::persistence::RelationshipSchema>),
    /// A persistence failure occurred (ADR-0015 §8). The
    /// application surfaces a fatal banner and exits cleanly so
    /// the message remains above the shell prompt.
    PersistenceFatal {
        operation: String,
        path: std::path::PathBuf,
        message: String,
    },
    /// Runtime has computed the rebuild summary from
    /// `project.yaml` + `data/` and is ready for the user to
    /// confirm. App opens the confirmation modal.
    RebuildPrepared {
        summary: String,
    },
    /// Rebuild completed successfully. App closes the modal,
    /// surfaces a friendly outcome message, and refreshes the
    /// table list.
    RebuildSucceeded {
        summary: String,
    },
    /// Rebuild failed in a non-fatal way (e.g., user-visible
    /// constraint problem) — surfaced like other DSL failures.
    RebuildFailed {
        error: String,
    },
    /// Runtime peeked the snapshot `undo`/`redo` would restore and
    /// is ready for the user to confirm (ADR-0006 Amendment 1). App
    /// opens the confirmation modal naming `command`. `is_redo`
    /// selects undo vs redo wording.
    UndoPrepared {
        command: String,
        timestamp: String,
        is_redo: bool,
    },
    /// Nothing to undo / redo (the ring or redo stack was empty).
    /// App surfaces a friendly note.
    UndoUnavailable {
        is_redo: bool,
    },
    /// Undo / redo completed. App closes the modal, notes the
    /// command that was undone/redone; the runtime also refreshes
    /// the table list + schema cache.
    UndoSucceeded {
        command: String,
        is_redo: bool,
    },
    /// Undo / redo failed (a rare restore error). App closes the
    /// modal and surfaces the error.
    UndoFailed {
        error: String,
        is_redo: bool,
    },
    /// Runtime has gathered the list of available projects
    /// for the load picker. App opens the picker modal.
    LoadPickerReady {
        entries: Vec<crate::app::LoadPickerEntry>,
    },
    /// A project switch (load / new / save-as / import)
    /// succeeded. Carries the new display name, the temp
    /// flag (drives the `[TEMP]` status-bar prefix), the
    /// seed entries for input-history hydration off the new
    /// project's `history.log` (I2-persist, ADR-0015 §12), and
    /// the mode to restore for the switched-to project (its
    /// stored mode, ADR-0015 mode-restore amendment, issue #14 —
    /// "loading triggers the mode switch each time").
    ProjectSwitched {
        display_name: String,
        is_temp: bool,
        history_entries: Vec<String>,
        mode: crate::mode::Mode,
    },
    /// A project switch failed in a non-fatal way (target
    /// already exists, path unreadable, …). Surfaced as an
    /// error in the output panel.
    ProjectSwitchFailed {
        error: String,
    },
    /// Export wrote a zip successfully. Carries the resolved
    /// final path so the user gets a "wrote to: …" note.
    ExportSucceeded {
        path: std::path::PathBuf,
    },
    /// Export failed in a non-fatal way (target exists, IO
    /// error, sequence range exhausted, …).
    ExportFailed {
        error: String,
    },
    /// A `replay <path>` finished without error, after running
    /// `count` non-blank, non-comment commands from the file.
    /// Surfaced as `[ok] replay — N command(s)` in the output.
    ReplayCompleted {
        path: String,
        count: usize,
        /// Pre-rendered `[skip]` warnings for app-lifecycle commands
        /// whose omission can leave the replayed state incomplete —
        /// `import` and a nested `replay` (ADR-0034). Other skipped
        /// app commands are silent and do not appear here.
        warnings: Vec<String>,
    },
    /// A `replay <path>` aborted at line `line_number`. `command`
    /// is the line text as it appeared in the file (for the
    /// user's eyeline so they can locate the failing entry);
    /// `error` is the rendered parse or runtime error.
    ReplayFailed {
        path: String,
        line_number: usize,
        command: String,
        error: String,
    },
}
