//! Matrix coverage for `insert into T (vals)` (Form C — bare
//! value list, no `values` keyword).
//!
//! Form C and Form B produce the identical AST and dispatch
//! identically (`Insert { columns: None, … }`). As of the
//! Form-C type-awareness work (handoff-14), Form C's paren is
//! resolved by the `insert_first_paren` lookahead: a value
//! literal as the first token routes the contents through the
//! typed `column_value_list` — the same per-column typed slots
//! Form B uses. So Form C values are now type-checked and
//! count-checked at parse time, not only at bind time.
//!
//! An identifier (column name) as the first token, or an empty
//! paren, routes to Form A instead — `insert into T (Name)`
//! still surfaces the "did you mean Form A?" recovery.

use crate::typing_surface::*;
use rdbms_playground::input_render::InputState;

// =========================================================
// Form C happy path: type-correct values parse to Insert.
// =========================================================

#[test]
fn form_c_text_pk_correct_values_parses() {
    // Items(Code:text, Title:text) — Form C expects two text
    // values (no auto-gen columns to skip).
    let schema = schema_text_pk();
    let a = assess_at_end("insert into Items ('SKU-1', 'Widget')", &schema);
    assert!(matches!(a.state, InputState::Valid));
    assert_eq!(a.parse_result.as_deref(), Ok("Insert"));
    crate::snap!("form_c_text_pk_valid", a);
}

#[test]
fn form_c_serial_pk_correct_values_parses() {
    // Customers(id:serial, Name:text, Email:text) — Form C
    // skips the serial `id`, expects two text values.
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers ('Alice', 'a@b.c')", &schema);
    assert!(matches!(a.state, InputState::Valid));
    assert_eq!(a.parse_result.as_deref(), Ok("Insert"));
    crate::snap!("form_c_serial_pk_valid", a);
}

#[test]
fn form_c_with_null_value_parses() {
    // null is type-compatible with any slot.
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers (null, 'a@b.c')", &schema);
    assert!(matches!(a.state, InputState::Valid));
    crate::snap!("form_c_null_value", a);
}

// =========================================================
// Form C is now type-aware (the §2.2 limitation is fixed).
// =========================================================

#[test]
fn form_c_rejects_number_for_text_column() {
    // `3.14` lands in the Name(text) slot — the typed slot
    // rejects it at parse time. Before Form-C type-awareness
    // this parsed Valid and only failed at bind time.
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers (3.14, 'a@b.c')", &schema);
    assert!(
        !matches!(a.state, InputState::Valid),
        "Form C should now type-check `3.14` against Name(text), got {:?}",
        a.state,
    );
    crate::snap!("form_c_type_mismatch", a);
}

#[test]
fn form_c_wrong_value_count_is_invalid() {
    // Customers Form C expects exactly two values (id:serial skipped).
    // Three values is a count mismatch. As of issue #17 it parses
    // structurally (so the friendly arity diagnostic fires); the
    // user-facing invalidity is the validity verdict (`[ERR]`).
    let schema = schema_serial_pk();
    let input = "insert into Customers ('Alice', 'a@b.c', 'extra')";
    let a = assess_at_end(input, &schema);
    assert_eq!(
        rdbms_playground::dsl::walker::input_verdict_in_mode(
            input,
            Some(&schema),
            rdbms_playground::mode::Mode::Simple,
        ),
        Some(rdbms_playground::dsl::walker::Severity::Error),
        "Form C with too many values must light the [ERR] verdict",
    );
    crate::snap!("form_c_wrong_count", a);
}

// =========================================================
// Form C typed-slot prose — the per-column hint Form B has
// is now available in Form C too.
// =========================================================

#[test]
fn form_c_second_slot_shows_typed_prose_for_column() {
    // First token `'Alice'` is a string literal → Form C. At
    // the second slot the hint names the Email column.
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers ('Alice', ", &schema);
    let prose = hint_prose(&a)
        .unwrap_or_else(|| panic!("expected Prose at Form C second slot, got {:?}", a.hint));
    assert!(
        prose.contains("Email"),
        "Form C second slot should name `Email`, got prose: {prose:?}",
    );
    crate::snap!("form_c_typed_prose", a);
}

// =========================================================
// In-progress Form C classifies as IncompleteAtEof.
// =========================================================

#[test]
fn form_c_in_progress_after_comma_is_incomplete() {
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers ('Alice', ", &schema);
    assert!(matches!(a.state, InputState::IncompleteAtEof));
    crate::snap!("form_c_in_progress_after_comma", a);
}

#[test]
fn form_c_in_progress_without_close_paren_is_incomplete() {
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers ('Alice', 'a@b.c'", &schema);
    assert!(matches!(a.state, InputState::IncompleteAtEof));
    crate::snap!("form_c_in_progress_no_close", a);
}

// =========================================================
// Form A recovery: a column-name identifier as the first
// paren token routes to Form A — `insert into T (Name)`
// without `values` flags as Form-A-in-progress.
// =========================================================

#[test]
fn form_c_with_column_shaped_item_flags_as_form_a_in_progress() {
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers (Name)", &schema);
    assert!(
        matches!(a.state, InputState::IncompleteAtEof),
        "expected IncompleteAtEof (Form A recovery), got {:?}",
        a.state,
    );
    assert_candidate_present(&a, &["values"]);
    crate::snap!("form_c_column_shaped_recovery", a);
}

#[test]
fn form_c_with_two_columns_flags_as_form_a_in_progress() {
    let schema = schema_serial_pk();
    let a = assess_at_end("insert into Customers (Name, Email)", &schema);
    assert!(matches!(a.state, InputState::IncompleteAtEof));
    assert_candidate_present(&a, &["values"]);
    crate::snap!("form_c_two_columns_recovery", a);
}
