//! The Command AST.
//!
//! `Command` is the parser's output and the database worker's
//! input. Each variant carries fully validated data — the parser
//! is responsible for shape, the database worker for semantics
//! (e.g. "table does not exist").
//!
//! The shape supports compound primary keys natively even though
//! only the dedicated `with pk a(int),b(int)` grammar exposes them
//! today. Future grammar extensions (inline column specs, `set
//! primary key`, junction-table convenience commands) emit into
//! the same shape.

use crate::dsl::action::ReferentialAction;
use crate::dsl::types::Type;
use crate::dsl::value::Value;

/// A foreign key declared in an advanced-mode SQL `CREATE TABLE`.
///
/// The SQL spelling of an ADR-0013 named relationship (ADR-0035 §5,
/// sub-phase 4b). Produced by both the inline
/// `<col> … REFERENCES <parent>[(<pcol>)] …` form (always auto-named)
/// and the table-level `[CONSTRAINT <name>] FOREIGN KEY (<ccol>)
/// REFERENCES <parent>[(<pcol>)] …` form. The relationship is created
/// together with the table, in one transaction = one undo step.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SqlForeignKey {
    /// `CONSTRAINT <name>` on a table-level FK; `None` for an inline
    /// FK or an unnamed table FK (auto-named at execution per
    /// ADR-0013).
    pub name: Option<String>,
    /// The column(s) in the table being created that hold the FK.
    /// One element for a single-column FK; ordered list for a
    /// compound FK (ADR-0043). Positionally paired with
    /// `parent_columns`.
    pub child_columns: Vec<String>,
    /// The referenced (parent) table — may be the table being created
    /// (a self-referencing FK).
    pub parent_table: String,
    /// The referenced parent column(s), positionally paired with
    /// `child_columns`. `None` for the bare `REFERENCES <parent>`
    /// form, resolved at execution to the parent's primary key —
    /// the single-column PK, or (ADR-0043 F-D) the full compound PK
    /// when the child arity matches.
    pub parent_columns: Option<Vec<String>>,
    pub on_delete: ReferentialAction,
    pub on_update: ReferentialAction,
    /// `true` for an inline column-level FK (`<col> REFERENCES …`),
    /// `false` for the table-level `FOREIGN KEY (…)` and `ALTER …`
    /// forms. An inline FK is single-column by construction, so when
    /// it references a compound key the resolver points the user at
    /// the table-level form rather than emitting the generic arity
    /// error (ADR-0043 D4).
    pub inline: bool,
}

/// A column at table-creation time: a name, a user-facing
/// type, and its column-level constraints (ADR-0029).
///
/// `PRIMARY KEY` is not represented here — it is a table-level
/// property carried separately in `Command::CreateTable`'s
/// `primary_key` list, since it may span columns.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnSpec {
    pub name: String,
    pub ty: Type,
    /// `NOT NULL` — the column rejects `NULL` (ADR-0029).
    pub not_null: bool,
    /// `UNIQUE` — non-`NULL` values must be distinct (ADR-0029).
    pub unique: bool,
    /// `DEFAULT <literal>` — the value used when an `insert`
    /// omits this column (ADR-0029). Simple-mode form.
    pub default: Option<Value>,
    /// `CHECK (<expr>)` — every row must satisfy this boolean
    /// expression (ADR-0029). Simple-mode form (a typed `Expr`).
    pub check: Option<Expr>,
    /// Advanced-mode raw-SQL `DEFAULT` (ADR-0035 §4a.2): the
    /// expression text captured from a SQL `CREATE TABLE`, since
    /// `sql_expr` yields no `Expr`. When `Some`, it takes precedence
    /// over `default` in DDL emission. `None` in simple mode.
    pub default_sql: Option<String>,
    /// Advanced-mode raw-SQL `CHECK` (ADR-0035 §4a.2): the inner
    /// expression text (without the `CHECK ( … )` wrapper). When
    /// `Some`, it takes precedence over `check`. `None` in simple mode.
    pub check_sql: Option<String>,
}

impl ColumnSpec {
    /// A column spec carrying no constraints — the common case
    /// for callers and tests that do not exercise ADR-0029.
    #[must_use]
    pub fn new(name: impl Into<String>, ty: Type) -> Self {
        Self {
            name: name.into(),
            ty,
            not_null: false,
            unique: false,
            default: None,
            check: None,
            default_sql: None,
            check_sql: None,
        }
    }
}

/// A column-level constraint with its payload (ADR-0029 §3).
///
/// Produced by `add constraint <constraint> to <T>.<col>`.
/// `Default` / `Check` carry the value / expression; `NotNull`
/// and `Unique` are payload-free.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Constraint {
    NotNull,
    Unique,
    Default(Value),
    Check(Expr),
}

impl Constraint {
    /// The bare constraint kind, dropping any payload — used for
    /// the `[ok]` summary line and log output.
    #[must_use]
    pub const fn kind(&self) -> ConstraintKind {
        match self {
            Self::NotNull => ConstraintKind::NotNull,
            Self::Unique => ConstraintKind::Unique,
            Self::Default(_) => ConstraintKind::Default,
            Self::Check(_) => ConstraintKind::Check,
        }
    }
}

/// The kind of a column-level constraint, without a payload.
///
/// Produced by `drop constraint <kind> from <T>.<col>`
/// (ADR-0029 §3) — naming the kind is enough, since at most one
/// constraint of each kind exists per column.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintKind {
    NotNull,
    Unique,
    Default,
    Check,
}

impl ConstraintKind {
    /// Upper-case SQL-style label for user-facing messages
    /// (`NOT NULL`, `UNIQUE`, `DEFAULT`, `CHECK`).
    #[must_use]
    pub const fn label(self) -> &'static str {
        match self {
            Self::NotNull => "NOT NULL",
            Self::Unique => "UNIQUE",
            Self::Default => "DEFAULT",
            Self::Check => "CHECK",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Command {
    CreateTable {
        name: String,
        /// Columns to create, in declaration order.
        columns: Vec<ColumnSpec>,
        /// Names of columns forming the primary key. Length 1 is
        /// a single PK; length >= 2 is a compound PK; length 0
        /// indicates no primary key (a future grammar option,
        /// not produced by today's parser).
        primary_key: Vec<String>,
    },
    DropTable {
        name: String,
    },
    /// Advanced-mode SQL `DROP TABLE [IF EXISTS] <name>` (ADR-0035 §4,
    /// sub-phase 4c). Executes through the same `do_drop_table`
    /// machinery as [`Self::DropTable`] (cascade / inbound-relationship
    /// refusal / metadata cleanup); `if_exists` turns an absent table
    /// into a no-op-with-note rather than an error.
    SqlDropTable {
        name: String,
        if_exists: bool,
    },
    /// Advanced-mode SQL `CREATE TABLE` (ADR-0035 §1, sub-phase 4a).
    /// Its own command, but executed **structurally** through the
    /// same `do_create_table` machinery as [`Self::CreateTable`] —
    /// the columns / PK reuse `ColumnSpec`. `if_not_exists` makes an
    /// already-existing table a no-op-with-note rather than an error
    /// (ADR-0035 §4). 4a carries only `NOT NULL` / `UNIQUE` /
    /// `PRIMARY KEY`; `DEFAULT` / `CHECK` are the 4a.2 slice.
    SqlCreateTable {
        name: String,
        columns: Vec<ColumnSpec>,
        primary_key: Vec<String>,
        /// Composite (multi-column) `UNIQUE (a, b)` table constraints
        /// (ADR-0035 §4a.2). Single-column table-level `UNIQUE` is
        /// folded into the column's `unique` flag instead.
        unique_constraints: Vec<Vec<String>>,
        /// Table-level `CHECK (<expr>)` constraints, in declaration
        /// order, as raw SQL text (ADR-0035 §4a.3). A multi-column
        /// CHECK has no column to hang on and the engine reports no
        /// CHECKs, so it round-trips via a dedicated metadata table.
        check_constraints: Vec<String>,
        /// Foreign keys (ADR-0035 §5, sub-phase 4b) — inline
        /// `REFERENCES` and table-level `FOREIGN KEY`, each created as
        /// an ADR-0013 named relationship in the same transaction as
        /// the table (one undo step).
        foreign_keys: Vec<SqlForeignKey>,
        if_not_exists: bool,
    },
    /// Add a column to an existing table. The column carries
    /// its constraints from the same suffix grammar as
    /// `create table` (ADR-0029); `check` is `None` until the
    /// CHECK grammar lands.
    AddColumn {
        table: String,
        column: String,
        ty: Type,
        not_null: bool,
        unique: bool,
        default: Option<Value>,
        check: Option<Expr>,
    },
    /// Remove a column from a table. Refused if the column is
    /// part of the primary key or is involved in a declared
    /// relationship — drop the relationship first. Refused, too,
    /// when an index covers the column, unless `cascade` is set
    /// (the `--cascade` flag), in which case the covering
    /// indexes are dropped alongside the column (ADR-0025).
    DropColumn {
        table: String,
        column: String,
        cascade: bool,
    },
    /// Rename a column. SQLite handles cascading renames in
    /// FK references on other tables; the executor mirrors
    /// the change into our `__rdbms_playground_columns` and
    /// `__rdbms_playground_relationships` metadata tables in
    /// the same transaction.
    RenameColumn {
        table: String,
        old: String,
        new: String,
    },
    /// Change a column's type. Implemented via the
    /// rebuild-table primitive (ADR-0013) since SQLite's
    /// ALTER TABLE does not support type changes.
    ///
    /// Per ADR-0017 the actual conversion model is a per-cell
    /// dry-run against a curated transformer matrix; the two
    /// optional flags carried in `mode` let the user opt into
    /// lossy conversions or skip the client-side layer entirely.
    ChangeColumnType {
        table: String,
        column: String,
        ty: Type,
        mode: ChangeColumnMode,
    },
    /// Establish a 1:n relationship: parent_table.parent_column
    /// is the primary-key side; child_table.child_column is the
    /// foreign-key side. `name` is optional — when `None`, the
    /// executor auto-generates one (`<Child>_<column>_to_<Parent>`).
    /// `create_fk` requests the child column be created
    /// automatically with the appropriate type if it is missing.
    AddRelationship {
        name: Option<String>,
        parent_table: String,
        /// Parent (referenced) PK column(s); one element for a
        /// single-column FK, ordered list for a compound FK
        /// (ADR-0043). Positionally paired with `child_columns`.
        parent_columns: Vec<String>,
        child_table: String,
        /// Child (referencing) column(s), positionally paired with
        /// `parent_columns`; equal, non-zero length.
        child_columns: Vec<String>,
        on_delete: ReferentialAction,
        on_update: ReferentialAction,
        create_fk: bool,
    },
    /// Convenience: generate a junction table for a many-to-many
    /// relationship between `t1` and `t2` (ADR-0045 / C4). The
    /// executor builds a table with one FK column per parent PK
    /// column (named `{table}_{pkcol}`, typed via `fk_target_type`),
    /// a compound PK over all of them, and two `CASCADE` 1:n
    /// relationships — all in one `create table` (one undo step).
    /// `name` overrides the auto-generated junction name `{t1}_{t2}`.
    CreateM2nRelationship {
        t1: String,
        t2: String,
        name: Option<String>,
    },
    /// Drop a relationship by either user-given/auto-generated
    /// name, or by positional reference to the FK endpoints.
    DropRelationship {
        selector: RelationshipSelector,
    },
    /// Create an index on one or more columns of a table
    /// (ADR-0025). `name` is optional — when `None`, the
    /// executor auto-generates `<Table>_<col…>_idx`.
    AddIndex {
        name: Option<String>,
        table: String,
        columns: Vec<String>,
    },
    /// Drop an index by name, or by positional reference to its
    /// table and exact column set (ADR-0025).
    DropIndex {
        selector: IndexSelector,
    },
    /// Advanced-mode SQL `DROP INDEX [IF EXISTS] <name>` (ADR-0035 §4,
    /// sub-phase 4d). Name-only (SQL has no positional column form — that
    /// is the simple `drop index on T(…)`). Executes through the same
    /// `do_drop_index` machinery as [`Self::DropIndex`]; `if_exists`
    /// turns an absent index into a no-op-with-note rather than an error.
    SqlDropIndex {
        name: String,
        if_exists: bool,
    },
    /// Advanced-mode SQL `CREATE [UNIQUE] INDEX [IF NOT EXISTS] [<name>]
    /// ON <table> (<col>, …)` (ADR-0035 §4d). Executes through the same
    /// `do_add_index` machinery as [`Self::AddIndex`] (the columns/
    /// auto-name reuse), plus the `unique` flag (simple mode has no
    /// `add unique index` — that stays deferred per ADR-0025). `name` is
    /// `None` for the unnamed form (auto-named at execution);
    /// `if_not_exists` makes an existing index name a no-op-with-note.
    SqlCreateIndex {
        name: Option<String>,
        table: String,
        columns: Vec<String>,
        unique: bool,
        if_not_exists: bool,
    },
    /// Advanced-mode SQL `ALTER TABLE <table> <action>` (ADR-0035 §4,
    /// sub-phase 4e). `alter` is advanced-only. Each action maps to an
    /// existing column executor — the runtime decomposes it to
    /// `add_column` / `drop_column` / `rename_column` (one undo step
    /// each). 4f/4g/4h extend [`AlterTableAction`].
    SqlAlterTable {
        table: String,
        action: AlterTableAction,
    },
    /// Add a column-level constraint to an existing column
    /// (ADR-0029 §2.2). Applied through the rebuild-table
    /// primitive after a §5 dry-run guards populated columns.
    AddConstraint {
        table: String,
        column: String,
        constraint: Constraint,
    },
    /// Remove a column-level constraint from an existing column
    /// (ADR-0029 §2.2). Naming the `kind` is enough — at most
    /// one constraint of each kind exists per column.
    DropConstraint {
        table: String,
        column: String,
        kind: ConstraintKind,
    },
    /// Re-display a table's structure in the output. Doesn't
    /// change schema; useful when the user wants to look at a
    /// table they aren't currently DDL'ing on.
    ShowTable {
        name: String,
    },
    /// Re-display a schema collection in the output (V5/V5a). With
    /// `name: None`, the whole collection — every table,
    /// relationship, or index (the list-all forms). With
    /// `name: Some(_)`, one named item's detail — `show
    /// relationship <name>` / `show index <name>` (V5a); the table
    /// singular is the separate `ShowTable`, so a named `Tables`
    /// never occurs. Read-only; pure display, no schema change.
    ShowList {
        kind: ShowListKind,
        name: Option<String>,
    },
    /// Insert a single row. `columns` is `None` for the natural-
    /// order short form (`insert into T values (...)`); the
    /// executor fills in the column list by walking the schema.
    Insert {
        table: String,
        columns: Option<Vec<String>>,
        values: Vec<Value>,
    },
    /// Update rows matching the WHERE clause (or all rows when
    /// `all_rows` is set, per ADR-0009 opt-in convention).
    Update {
        table: String,
        assignments: Vec<(String, Value)>,
        filter: RowFilter,
    },
    Delete {
        table: String,
        filter: RowFilter,
    },
    /// Render the rows of a table as a data view in the output.
    /// An optional `where` filters rows; an optional `limit`
    /// caps the row count (ADR-0026 §5). When `limit` is set the
    /// query is implicitly ordered by the table's primary key,
    /// so `limit n` is a stable "first n by primary key" rather
    /// than an arbitrary subset.
    ShowData {
        name: String,
        filter: Option<Expr>,
        limit: Option<u64>,
    },
    /// Populate a table with generated fake data (ADR-0048, SD1/SD2).
    /// `count` defaults to 20 when omitted; `rng_seed` (from the
    /// `--seed <n>` flag) makes generation reproducible.
    ///
    /// Phase 2 surfaces (ADR-0048 D1/D2):
    /// - `target_column` is `Some` for the **column-fill** form
    ///   `seed <table>.<column>` — fill one column across the table's
    ///   *existing* rows (an UPDATE), rather than generating new rows.
    /// - `overrides` carries the `set <col> …` clause: per-column pins
    ///   that take precedence over the heuristic generator (D2).
    Seed {
        table: String,
        /// `Some(col)` → column-fill mode (UPDATE existing rows);
        /// `None` → whole-row generation (INSERT new rows).
        target_column: Option<String>,
        count: Option<u64>,
        overrides: Vec<SeedOverride>,
        rng_seed: Option<u64>,
    },
    /// Replay a sequence of DSL commands from a file. Each line
    /// is parsed and dispatched through the same pipeline as
    /// interactive input. Blank lines and lines whose first
    /// non-whitespace character is `#` are skipped. Execution
    /// stops at the first failure (parse or runtime); previously
    /// applied commands are NOT rolled back — the partial state
    /// is left in place because that matches the "I'm replaying
    /// my history" mental model where a partial replay is a
    /// recoverable state.
    ///
    /// `path` is the literal user-typed path. The runtime
    /// resolves relative paths against the active project's root
    /// so that `replay history.log` works without ceremony from
    /// inside a project.
    Replay {
        path: String,
    },
    /// Capture and display the query plan for an explainable
    /// command without executing it (ADR-0028). The inner
    /// `Command` is an ordinary parsed `ShowData` / `Update` /
    /// `Delete`; the runtime recognizes the `Explain` wrapper
    /// and routes it to the plan path instead of normal
    /// execution. Because `EXPLAIN QUERY PLAN` never runs the
    /// statement, explaining a destructive command is safe.
    Explain {
        query: Box<Self>,
    },
    /// Run a SQL `SELECT` and render its result set (ADR-0030
    /// §6, ADR-0031). Advanced mode only. `sql` is the validated
    /// SQL statement text: a `SELECT` changes no schema, so it is
    /// carried and executed as text rather than lowered to a
    /// typed command (ADR-0030 §4). The walker has already
    /// confirmed it is in the supported subset.
    Select {
        sql: String,
    },
    /// Run a validated SQL `INSERT` (ADR-0033 §1, sub-phase 3b).
    /// Advanced mode only. Grammar-as-text (ADR-0030 §4): `sql` is
    /// the validated statement the worker executes verbatim;
    /// `target_table` is extracted from the parse so the worker can
    /// re-persist that table's CSV after a successful insert
    /// (ADR-0030 §11) without re-parsing the SQL.
    ///
    /// `listed_columns` is the user's explicit `(col, …)` list
    /// (empty when the form omits it); `row_source` is the
    /// `VALUES …` / `SELECT …` / `WITH … SELECT …` text. Both are
    /// captured for sub-phase 3d's `shortid` auto-fill: when the
    /// list omits a `shortid` column, the worker materialises the
    /// row source, generates fresh ids, and reinserts. `returning`
    /// (3g) is added by the sub-phase that reads it.
    SqlInsert {
        sql: String,
        target_table: String,
        listed_columns: Vec<String>,
        row_source: String,
        /// Whether a `RETURNING` clause matched (ADR-0033 §5,
        /// sub-phase 3g). The worker collects the returned rows as a
        /// `DataResult` when true; otherwise it surfaces the
        /// affected-row count (+ auto-show) as before.
        returning: bool,
        /// Captured literal values per `VALUES` row, per position
        /// (ADR-0036 Phase 1). `Some(v)` for a bare literal (incl. a
        /// signed number); `None` for an expression position (nothing
        /// static to validate). Empty when the row source is a
        /// `SELECT`/`WITH` query. The worker validates these against the
        /// column types before the (still verbatim) insert; the error
        /// enricher reads them to show the offending value. Execution
        /// itself is unchanged — these are *not* bound.
        literal_rows: Vec<Vec<Option<Value>>>,
    },
    /// A SQL `UPDATE` validated by the walker (ADR-0033 §2,
    /// advanced mode). Grammar-as-text: the worker executes `sql`
    /// and re-persists `target_table`'s CSV (ADR-0030 §11).
    /// `RETURNING` (3g) is added by the sub-phase that reads it.
    SqlUpdate {
        sql: String,
        target_table: String,
        /// Whether a `RETURNING` clause matched (ADR-0033 §5,
        /// sub-phase 3g).
        returning: bool,
        /// Captured literal RHS of each top-level `SET col = <literal>`
        /// assignment (ADR-0036 Phase 2). `(col, Some(v))` for a bare
        /// literal (incl. a signed number); `(col, None)` for an
        /// expression RHS (arithmetic, function call, scalar subquery,
        /// column ref — nothing static to validate). The worker validates
        /// the `Some` values against their column types before the (still
        /// verbatim) update; the error enricher reads them to name the
        /// offending value. Execution itself is unchanged — these are
        /// *not* bound. `WHERE` is deliberately excluded (ADR-0036 §2).
        set_literals: Vec<(String, Option<Value>)>,
    },
    /// A SQL `DELETE` validated by the walker (ADR-0033 §1/§7,
    /// advanced mode). Grammar-as-text: the worker executes `sql`,
    /// observes any FK cascade by row-count diffing (Amendment 2 —
    /// the same mechanism the DSL `do_delete` uses), and re-persists
    /// `target_table`'s CSV plus every cascade-affected child
    /// (ADR-0030 §11). The worker never inspects the WHERE clause, so
    /// no predicate is carried here. `RETURNING` (3g) is added by the
    /// sub-phase that reads it.
    SqlDelete {
        sql: String,
        target_table: String,
        /// Whether a `RETURNING` clause matched (ADR-0033 §5,
        /// sub-phase 3g). The cascade summary surfaces alongside the
        /// returned rows when true.
        returning: bool,
    },
    /// App-lifecycle command (per ADR-0003). These work in both
    /// simple and advanced modes; the dispatcher branches on the
    /// `Command::App(...)` variant before mode-specific routing.
    /// Folded into the DSL parser so they participate in Tab
    /// completion + parse-error usage templates alongside the
    /// data commands.
    App(AppCommand),
}

/// App-level commands surfaced through the DSL parser. These do
/// not touch the database schema or data — they affect app
/// lifecycle, mode, persistence, and verbosity.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AppCommand {
    /// Exit cleanly. Accepts the `q` alias.
    Quit,
    /// Show in-app help (H3). With no `topic`, the full command
    /// list + types reference; with a `topic` (a command entry
    /// word like `insert` / `create` / `show`, or `types`), the
    /// focused detail for that command (or command group sharing
    /// the entry word).
    Help { topic: Option<String> },
    /// Show a contextual tier-3 hint (H2 / ADR-0053). No argument:
    /// when submitted, it expands on the most recent runtime error
    /// (the buffer is empty post-submit). The live-input surface is
    /// the F1 keybinding, handled in `App::handle_key`, not here.
    Hint,
    /// Print the application version (ADR-0054): the in-app twin of the
    /// `--version` / `-V` CLI flag. Emits `CARGO_PKG_VERSION` — the same
    /// single source of truth — into the output panel.
    Version,
    /// Rebuild `playground.db` from `project.yaml` + data/, with
    /// confirmation modal.
    Rebuild,
    /// Save the current project under a name (modal-driven).
    Save,
    /// Save the current project as a copy under a new path
    /// (modal-driven).
    SaveAs,
    /// Close current, create a fresh temp project.
    New,
    /// Open the project picker modal.
    Load,
    /// Write a zip of project.yaml + data/. `path` is the user-
    /// typed target (may be a name under the data root or an
    /// absolute path). `None` opens the path prompt modal.
    Export { path: Option<String> },
    /// Unpack a zip into a new project and switch to it.
    /// `target` overrides the project name (default: taken from
    /// the zip).
    Import {
        path: String,
        target: Option<String>,
    },
    /// Switch the persistent input mode.
    Mode { value: ModeValue },
    /// Show or set the messages verbosity.
    Messages { value: Option<MessagesValue> },
    /// Undo the most recent change, restoring the previous snapshot
    /// after a confirmation prompt (ADR-0006 Amendment 1).
    Undo,
    /// Re-apply the most recently undone change, after confirmation.
    Redo,
    /// Copy the output panel to the system clipboard (ADR-0041).
    /// `copy` / `copy all` copy the whole panel; `copy last` copies
    /// the most recent command's output.
    Copy { scope: CopyScope },
}

/// Which slice of the output panel `copy` targets (ADR-0041).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CopyScope {
    /// The entire output buffer (`copy` bare, or `copy all`).
    All,
    /// From the most recent echo line to the end (`copy last`).
    Last,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeValue {
    Simple,
    Advanced,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessagesValue {
    Short,
    Verbose,
}

/// Conversion mode for `change column …` (ADR-0017 §5).
///
/// `Default` runs the per-cell dry-run and refuses on lossy or
/// incompatible cells. `ForceConversion` accepts lossy cells but
/// still refuses incompatibles. `DontConvert` skips the entire
/// client-side layer and lets the database's STRICT typing
/// decide. `ForceConversion` and `DontConvert` are mutually
/// exclusive at the grammar level.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChangeColumnMode {
    Default,
    ForceConversion,
    DontConvert,
}

/// How an UPDATE / DELETE selects which rows to operate on.
/// `Where` is the default safe form. `AllRows` is the explicit
/// `--all-rows` flag opt-in for unfiltered operations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RowFilter {
    /// Operate on rows matching this WHERE expression
    /// (ADR-0026 — a full boolean expression, not just a single
    /// equality).
    Where(Expr),
    AllRows,
}

impl RowFilter {
    /// Build a `Where` filter for a single `column = value`
    /// equality. The pre-ADR-0026 grammar produced exactly this
    /// shape; the constructor stays as a convenience for
    /// callers and tests that only need simple equality.
    #[must_use]
    pub fn eq(column: impl Into<String>, value: Value) -> Self {
        Self::Where(Expr::Predicate(Predicate::Compare {
            left: Operand::Column {
                name: column.into(),
                span: Operand::NO_SPAN,
            },
            op: CompareOp::Eq,
            right: Operand::Literal {
                value,
                span: Operand::NO_SPAN,
            },
        }))
    }
}

/// One `set <col> …` override on a `seed` command (ADR-0048 D2, Phase 2).
///
/// The user can pin a column's generated values to a constant, a
/// pick-list, an explicit named generator, or a range — overriding the
/// per-column heuristic the executor would otherwise pick. `column` is
/// the user-typed column name (validated against the table at execution,
/// like every other column slot).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeedOverride {
    pub column: String,
    pub kind: SeedOverrideKind,
}

/// The four `set` override forms (ADR-0048 D2).
///
/// Values arrive as the DSL's `Value` (quoted text / unquoted number —
/// dates are quoted text per the D2 amendment); the `Generator` name is
/// a raw string validated at execution because `src/dsl` cannot depend
/// on `src/seed` (the curated vocabulary lives there).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SeedOverrideKind {
    /// `set status = 'pending'` — every row gets the constant.
    Fixed(Value),
    /// `set role in ('admin', 'editor')` — uniform pick from the list.
    PickList(Vec<Value>),
    /// `set work_addr as email` — force the named generator (D9).
    Generator(String),
    /// `set price between 10 and 100` — uniform in `[low, high]`;
    /// numeric or (quoted) date bounds per the destination column type.
    Range { low: Value, high: Value },
}

/// A complex WHERE expression (ADR-0026 §4).
///
/// Built by `grammar::expr::build_expr` from the flat
/// matched-terminal slice the walker produces for a `where`
/// clause. The recursion mirrors the stratified expression
/// grammar — `Or` / `And` are n-ary (a flat `a AND b AND c` is
/// one `And` of three children), and single-child precedence
/// tiers collapse so a bare predicate reached through the
/// `or → and → not` layers is just the `Predicate`, not three
/// wrappers.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
    /// `a OR b OR …` — at least two children.
    Or(Vec<Self>),
    /// `a AND b AND …` — at least two children.
    And(Vec<Self>),
    /// `NOT <expr>`.
    Not(Box<Self>),
    /// A leaf comparison / match test.
    Predicate(Predicate),
}

/// A single comparison or match test inside an [`Expr`]
/// (ADR-0026 §4). Operands are always a column reference or a
/// literal — never a nested expression.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Predicate {
    /// `<operand> <op> <operand>` — one of the six comparisons.
    Compare {
        left: Operand,
        op: CompareOp,
        right: Operand,
    },
    /// `<operand> [NOT] LIKE <operand>` — `%` / `_` wildcards.
    Like {
        target: Operand,
        pattern: Operand,
        negated: bool,
    },
    /// `<operand> [NOT] BETWEEN <operand> AND <operand>`.
    Between {
        target: Operand,
        low: Operand,
        high: Operand,
        negated: bool,
    },
    /// `<operand> [NOT] IN (<operand>[, …])`.
    In {
        target: Operand,
        items: Vec<Operand>,
        negated: bool,
    },
    /// `<operand> IS [NOT] NULL`.
    IsNull { target: Operand, negated: bool },
}

/// A comparison operand — a column reference or a literal
/// (ADR-0026 §1: operands are never nested expressions).
///
/// Each operand carries the byte `span` it occupied in the
/// source. The span feeds the precise per-literal WARNING
/// highlight (ADR-0027) and is otherwise editor metadata —
/// `PartialEq` is hand-written to **ignore** it, so two
/// operands are equal when their column / value match
/// regardless of where they were typed. This keeps `Command`
/// equality whitespace- and position-independent (the bulk of
/// the `Expr` test corpus relies on it).
#[derive(Debug, Clone, Eq)]
pub enum Operand {
    Column { name: String, span: (usize, usize) },
    Literal { value: Value, span: (usize, usize) },
}

impl Operand {
    /// Span used for operands built without a source position
    /// (the [`RowFilter::eq`] convenience constructor).
    pub const NO_SPAN: (usize, usize) = (0, 0);

    /// The byte range this operand occupied in the source —
    /// [`Operand::NO_SPAN`] for programmatically-built operands.
    #[must_use]
    pub const fn span(&self) -> (usize, usize) {
        match self {
            Self::Column { span, .. } | Self::Literal { span, .. } => *span,
        }
    }
}

impl PartialEq for Operand {
    /// Compares column name / literal value only — the source
    /// `span` is deliberately excluded (see the type docs).
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Column { name: a, .. }, Self::Column { name: b, .. }) => a == b,
            (Self::Literal { value: a, .. }, Self::Literal { value: b, .. }) => a == b,
            _ => false,
        }
    }
}

/// The six comparison operators. `<>` and `!=` both parse to
/// `NotEq` — `<>` is standard SQL, `!=` the common variant
/// (ADR-0026 §1).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
    Eq,
    NotEq,
    Lt,
    LtEq,
    Gt,
    GtEq,
}

/// How a `drop relationship` command identifies the relationship
/// to remove. Both forms are accepted; the executor resolves to
/// a single row in the metadata table.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RelationshipSelector {
    Named {
        name: String,
    },
    Endpoints {
        parent_table: String,
        parent_column: String,
        child_table: String,
        child_column: String,
    },
}

impl std::fmt::Display for RelationshipSelector {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Named { name } => write!(f, "{name}"),
            Self::Endpoints {
                parent_table,
                parent_column,
                child_table,
                child_column,
            } => write!(
                f,
                "from {parent_table}.{parent_column} to {child_table}.{child_column}"
            ),
        }
    }
}

/// How a `drop index` command identifies the index to remove
/// (ADR-0025). Both forms are accepted; the executor resolves to
/// a single index.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IndexSelector {
    Named { name: String },
    Columns { table: String, columns: Vec<String> },
}

/// Which schema collection a `show <kind>` list command displays (V5).
///
/// The bare plural forms list every item of the kind across the
/// project; the singular `show table <name>` (a separate
/// `Command::ShowTable`) shows one. The singular `show
/// relationship <name>` / `show index <name>` forms are not yet
/// provided — only the list-all forms land here.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShowListKind {
    /// `show tables` — every user table (internal `__rdbms_*`
    /// tables excluded, as in the items panel).
    Tables,
    /// `show relationships` — every declared FK relationship.
    Relationships,
    /// `show indexes` — every index across all tables.
    Indexes,
}

impl ShowListKind {
    /// The full command name for the `name()` / echo surface.
    #[must_use]
    pub const fn command_name(self) -> &'static str {
        match self {
            Self::Tables => "show tables",
            Self::Relationships => "show relationships",
            Self::Indexes => "show indexes",
        }
    }
}

/// The action of an advanced-mode `ALTER TABLE` (ADR-0035 §4).
///
/// Sub-phase 4e carries the column actions; 4f adds `AlterColumnType`;
/// 4g/4h add `AddConstraint`/`AddForeignKey`/`DropConstraint`, and
/// `RenameTo`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlterTableAction {
    /// `ADD COLUMN <name> <type> [NOT NULL] [UNIQUE] [DEFAULT …]
    /// [CHECK …]` — column constraints only (no PK / inline REFERENCES;
    /// those are create-table / 4g). Reuses `do_add_column`. Boxed so
    /// the large `ColumnSpec` doesn't bloat the enum (and `Command` /
    /// `Action` that embed it) — `clippy::large_enum_variant`.
    AddColumn(Box<ColumnSpec>),
    /// `DROP COLUMN <name>` — reuses `do_drop_column` (cascade = false:
    /// an index-covered column is refused, matching SQLite + the
    /// simple-mode default; there is no `--cascade` SQL spelling).
    DropColumn { column: String },
    /// `RENAME COLUMN <old> TO <new>` — reuses `do_rename_column`.
    RenameColumn { old: String, new: String },
    /// `ALTER COLUMN <name> TYPE <type>` — reuses `do_change_column_type`
    /// with `ChangeColumnMode::ForceConversion`, which is the ADR-0035 §7
    /// advanced-mode policy (lossy cells are *performed* with a note, no
    /// force flag; static-refused / incompatible still refuse). One undo
    /// step (the executor's rebuild). ADR-0035 §4f. The ISO synonym
    /// `SET DATA TYPE` (canonical) and the PostgreSQL `TYPE` shorthand
    /// both build this action (ADR-0035 Amendment 2).
    AlterColumnType { column: String, ty: Type },
    /// `ALTER COLUMN <name> SET NOT NULL` (ADR-0035 Amendment 2) — a
    /// documented PostgreSQL extension (ISO has no in-place NOT-NULL
    /// verb). Decomposes to `do_add_constraint(NotNull)` (ADR-0029).
    SetColumnNotNull { column: String },
    /// `ALTER COLUMN <name> DROP NOT NULL` (ADR-0035 Amendment 2).
    /// Decomposes to `do_drop_constraint(NotNull)`.
    DropColumnNotNull { column: String },
    /// `ALTER COLUMN <name> SET DEFAULT <expr>` (ADR-0035 Amendment 2,
    /// ISO). `default_sql` is the raw `sql_expr` text (the §4a.2 / §4e
    /// mechanism — `sql_expr` builds no AST, so the default cannot be a
    /// typed `Value`). Decomposes to the dedicated `do_set_column_default`
    /// executor (not `do_add_constraint`, which is `Value`-based).
    SetColumnDefault { column: String, default_sql: String },
    /// `ALTER COLUMN <name> DROP DEFAULT` (ADR-0035 Amendment 2, ISO).
    /// Decomposes to `do_drop_constraint(Default)`.
    DropColumnDefault { column: String },
    /// `ADD [CONSTRAINT <name>] (CHECK (…) | UNIQUE (…) | FOREIGN KEY
    /// (…) REFERENCES …)` — a table-level constraint (ADR-0035 §4g). The
    /// `name` is the `CONSTRAINT <name>` prefix (the FK carries its own
    /// `SqlForeignKey::name`, set from this prefix at build time). CHECK
    /// and FOREIGN KEY may be named; UNIQUE may not (composite UNIQUE is
    /// anonymous in our model — §4g). Boxed: the FK payload is sizeable
    /// (`clippy::large_enum_variant`).
    AddTableConstraint {
        name: Option<String>,
        constraint: Box<TableConstraint>,
    },
    /// `DROP CONSTRAINT <name>` — drops a named table-level CHECK or a
    /// named FK (relationship), resolved by name (ADR-0035 §4g).
    DropConstraint { name: String },
    /// `RENAME TO <new>` — rename the table (ADR-0035 §6, sub-phase 4h).
    /// The one genuinely new low-level op in Phase 4: a native table
    /// rename plus reconciliation of the CSV file name and every metadata
    /// row that names the table (columns, both relationship ends, table
    /// CHECKs, and any table-qualified CHECK *text*). Advanced-mode only.
    RenameTable { new: String },
}

/// A table-level constraint added via `ALTER TABLE … ADD [CONSTRAINT
/// <name>] …` (ADR-0035 §4g).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TableConstraint {
    /// `CHECK (<expr>)` — the expression as **raw SQL text** (the
    /// `sql_expr` grammar is validate-only; the builder captures the
    /// matched span — the 4a.2 / 4e mechanism).
    Check { expr_sql: String },
    /// `UNIQUE (<col>, …)` — a composite UNIQUE constraint.
    Unique { columns: Vec<String> },
    /// `FOREIGN KEY (<col>) REFERENCES <P>[(<col>)] [ON …]` — reuses the
    /// 4b `SqlForeignKey` shape; decomposed to `add_relationship`.
    ForeignKey(SqlForeignKey),
}

impl std::fmt::Display for IndexSelector {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Named { name } => write!(f, "{name}"),
            Self::Columns { table, columns } => {
                write!(f, "on {table} ({})", columns.join(", "))
            }
        }
    }
}

impl Command {
    /// Short label for log output and result rendering.
    #[must_use]
    pub const fn verb(&self) -> &'static str {
        match self {
            Self::CreateTable { .. } => "create table",
            Self::SqlCreateTable { .. } => "create table",
            Self::DropTable { .. } => "drop table",
            Self::SqlDropTable { .. } => "drop table",
            Self::AddColumn { .. } => "add column",
            Self::DropColumn { .. } => "drop column",
            Self::RenameColumn { .. } => "rename column",
            Self::ChangeColumnType { .. } => "change column",
            Self::AddRelationship { .. } => "add relationship",
            Self::CreateM2nRelationship { .. } => "create m:n relationship",
            Self::DropRelationship { .. } => "drop relationship",
            Self::AddIndex { .. } => "add index",
            Self::DropIndex { .. } => "drop index",
            Self::SqlDropIndex { .. } => "drop index",
            Self::SqlCreateIndex { .. } => "create index",
            Self::SqlAlterTable { .. } => "alter table",
            Self::AddConstraint { .. } => "add constraint",
            Self::DropConstraint { .. } => "drop constraint",
            Self::ShowTable { .. } => "show table",
            // A named item reports the singular verb; the list-all
            // forms report the plural (`kind.command_name()`).
            Self::ShowList {
                kind: ShowListKind::Relationships,
                name: Some(_),
            } => "show relationship",
            Self::ShowList {
                kind: ShowListKind::Indexes,
                name: Some(_),
            } => "show index",
            Self::ShowList { kind, .. } => kind.command_name(),
            Self::Insert { .. } => "insert into",
            Self::Seed { .. } => "seed",
            Self::Update { .. } => "update",
            Self::Delete { .. } => "delete from",
            Self::ShowData { .. } => "show data",
            Self::Replay { .. } => "replay",
            Self::Explain { .. } => "explain",
            Self::Select { .. } => "select",
            Self::SqlInsert { .. } => "insert into",
            Self::SqlUpdate { .. } => "update",
            Self::SqlDelete { .. } => "delete from",
            Self::App(app) => match app {
                AppCommand::Quit => "quit",
                AppCommand::Help { .. } => "help",
                AppCommand::Hint => "hint",
                AppCommand::Version => "version",
                AppCommand::Rebuild => "rebuild",
                AppCommand::Save => "save",
                AppCommand::SaveAs => "save as",
                AppCommand::New => "new",
                AppCommand::Load => "load",
                AppCommand::Export { .. } => "export",
                AppCommand::Import { .. } => "import",
                AppCommand::Mode { .. } => "mode",
                AppCommand::Messages { .. } => "messages",
                AppCommand::Undo => "undo",
                AppCommand::Redo => "redo",
                AppCommand::Copy { .. } => "copy",
            },
        }
    }

    /// The table whose structure most directly reflects the
    /// outcome of this command. For relationships this is the
    /// child table, since the FK constraint physically belongs
    /// there and our describe view shows both sides anyway.
    #[must_use]
    pub fn target_table(&self) -> &str {
        match self {
            Self::CreateTable { name, .. }
            | Self::SqlCreateTable { name, .. }
            | Self::DropTable { name }
            | Self::SqlDropTable { name, .. }
            | Self::ShowTable { name }
            | Self::ShowData { name, .. } => name,
            Self::AddColumn { table, .. }
            | Self::DropColumn { table, .. }
            | Self::RenameColumn { table, .. }
            | Self::ChangeColumnType { table, .. }
            | Self::AddConstraint { table, .. }
            | Self::DropConstraint { table, .. }
            | Self::Insert { table, .. }
            | Self::Seed { table, .. }
            | Self::Update { table, .. }
            | Self::Delete { table, .. } => table,
            // For relationships we focus on the parent (1-side):
            // the structure rendering after add/drop shows that
            // table's "Referenced by" entry, which is what the
            // user looks at to confirm the relationship.
            Self::AddRelationship { parent_table, .. } => parent_table,
            // For m:n we focus on the first table; the executor builds
            // and returns the junction's structure regardless.
            Self::CreateM2nRelationship { t1, .. } => t1,
            Self::DropRelationship { selector } => match selector {
                RelationshipSelector::Endpoints { parent_table, .. } => parent_table,
                // For a named drop we don't know the parent table
                // until the executor resolves it; the name itself
                // is a sensible fallback for logging.
                RelationshipSelector::Named { name } => name,
            },
            Self::AddIndex { table, .. } => table,
            Self::DropIndex { selector } => match selector {
                IndexSelector::Columns { table, .. } => table,
                // A named drop doesn't name the table until the
                // executor resolves it; the index name is a
                // sensible fallback for logging.
                IndexSelector::Named { name } => name,
            },
            // The SQL drop is name-only; the index name identifies it
            // until the executor resolves the table (mirrors the named
            // `DropIndex` / `SqlDropTable` fallback).
            Self::SqlDropIndex { name, .. } => name,
            Self::SqlCreateIndex { table, .. } => table,
            Self::SqlAlterTable { table, .. } => table,
            // Replay isn't tied to a single table; the path is
            // the most identifying thing for log output.
            Self::Replay { path } => path,
            // Explain forwards to the wrapped query — the table
            // the plan is about is the inner command's table.
            Self::Explain { query } => query.target_table(),
            // A SQL `SELECT` may read several tables (or none);
            // there is no single structure-target table. The
            // result renders as a data view, not a structure
            // view, so an empty target is correct here.
            Self::Select { .. } => "",
            // A `show <kind>` list spans every table (or none) —
            // there is no single structure-target table; it renders
            // as a list, not a structure view.
            Self::ShowList { .. } => "",
            // A SQL `INSERT` carries its parsed target table (for
            // CSV re-persistence and ok-summary subject).
            Self::SqlInsert { target_table, .. }
            | Self::SqlUpdate { target_table, .. }
            | Self::SqlDelete { target_table, .. } => target_table,
            // App commands aren't tied to schema entities — the
            // verb is the most identifying thing. The
            // display_subject override below provides a richer
            // form when one exists.
            Self::App(_) => "",
        }
    }

    /// Human-readable subject for the `[ok] <verb> <subject>`
    /// summary line. Most commands target a single table, but
    /// relationship commands are better described by their
    /// endpoints than by either side alone.
    #[must_use]
    pub fn display_subject(&self) -> String {
        match self {
            Self::AddRelationship {
                parent_table,
                parent_columns,
                child_table,
                child_columns,
                ..
            } => {
                // `from P.col to C.col` (single) or `from P.(a, b) to
                // C.(x, y)` (compound — ADR-0043), mirroring the DSL.
                let fmt = |cols: &[String]| {
                    if cols.len() == 1 {
                        cols[0].clone()
                    } else {
                        format!("({})", cols.join(", "))
                    }
                };
                format!(
                    "from {parent_table}.{} to {child_table}.{}",
                    fmt(parent_columns),
                    fmt(child_columns),
                )
            }
            Self::DropRelationship { selector } => match selector {
                RelationshipSelector::Named { name } => name.clone(),
                RelationshipSelector::Endpoints {
                    parent_table,
                    parent_column,
                    child_table,
                    child_column,
                } => format!("from {parent_table}.{parent_column} to {child_table}.{child_column}"),
            },
            // A constraint command's subject is the dotted
            // `<table>.<column>` it acts on (ADR-0029 §2.2).
            Self::AddConstraint { table, column, .. }
            | Self::DropConstraint { table, column, .. } => {
                format!("{table}.{column}")
            }
            _ => self.target_table().to_string(),
        }
    }
}
