use indexmap::IndexMap;
use text_size::TextRange;

use super::QualifiedName;

/// A `CREATE TABLE` definition.
#[derive(Debug)]
pub struct TableDef {
    /// Columns in declaration order.
    pub columns: IndexMap<String, ColumnDef>,
    /// Byte range of the `CREATE TABLE` statement in `schema.sql`.
    pub range: TextRange,
    /// Doc string from `COMMENT ON TABLE`.
    pub doc: Option<String>,
}

/// A single column within a table.
#[derive(Debug)]
pub struct ColumnDef {
    /// Human-readable type string, e.g. `"integer"`, `"text"`, `"numeric"`.
    pub ty: String,
    /// `false` if a `NOT NULL` constraint is present.
    pub nullable: bool,
    /// Byte range of the column definition within the `CREATE TABLE` statement.
    pub range: TextRange,
    /// Doc string from `COMMENT ON COLUMN`.
    pub doc: Option<String>,
    /// User-defined type reference. Set unconditionally; emitter gates on `model.types.contains_key`.
    pub type_ref: Option<QualifiedName>,
    /// FK target: (referenced table qualified name, referenced column name).
    pub fk_target: Option<(QualifiedName, String)>,
    /// Deparsed `DEFAULT` expression. `now()` = timestamp, `gen_random_uuid()` = stable id, literal = initial state.
    pub default_expr: Option<String>,
}

impl ColumnDef {
    /// `type [NOT NULL] [— comment]` for SCIP signature text.
    #[must_use]
    pub fn display_doc(&self) -> String {
        let null_suffix = if self.nullable { "" } else { " NOT NULL" };
        match &self.doc {
            Some(d) => format!("{}{null_suffix} — {d}", self.ty),
            None => format!("{}{null_suffix}", self.ty),
        }
    }
}

/// A `CREATE FUNCTION` definition.
#[derive(Debug)]
pub struct FunctionDef {
    /// Byte range of the `CREATE FUNCTION` statement.
    pub range: TextRange,
    /// Doc string from `COMMENT ON FUNCTION`.
    pub doc: Option<String>,
    /// User-defined return type. Set unconditionally; emitter gates on `model.types.contains_key`.
    pub return_type: Option<QualifiedName>,
    /// Qualified names of any user-defined parameter types.
    pub param_types: Vec<QualifiedName>,
    /// `(name, type_str)` pairs for non-TABLE params. Anonymous params have empty name.
    pub params: Vec<(String, String)>,
}

/// A `CREATE VIEW` definition.
#[derive(Debug)]
pub struct ViewDef {
    /// Byte range of the `CREATE VIEW` statement.
    pub range: TextRange,
    /// Doc string from `COMMENT ON VIEW`.
    pub doc: Option<String>,
    /// Tables/views from `RangeVar` nodes in the SELECT body. Emitter gates on model membership.
    pub source_tables: Vec<super::QualifiedName>,
}

/// A user-defined type (`CREATE TYPE`).
#[derive(Debug)]
pub struct TypeDef {
    /// Whether this is an enum or a composite type.
    pub kind: TypeKind,
    /// Byte range of the `CREATE TYPE` statement.
    pub range: TextRange,
    /// Doc string from `COMMENT ON TYPE`.
    pub doc: Option<String>,
    /// Fields for composite types in declaration order. Empty for enums.
    pub fields: IndexMap<String, ColumnDef>,
    /// Enum variant labels in declaration order. Empty for composite types.
    pub labels: Vec<String>,
}

/// Discriminates between enum and composite `CREATE TYPE` variants.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeKind {
    /// `CREATE TYPE … AS ENUM (…)`.
    Enum,
    /// `CREATE TYPE … AS (col type, …)`.
    Composite,
}

/// A `CREATE TRIGGER` definition.
#[derive(Debug)]
pub struct TriggerDef {
    /// The table this trigger is attached to.
    pub target: QualifiedName,
    /// Byte range of the `CREATE TRIGGER` statement.
    pub range: TextRange,
    /// `EXECUTE FUNCTION` target, if extractable from the AST.
    pub func: Option<QualifiedName>,
}

/// A `CREATE POLICY` definition.
#[derive(Debug)]
pub struct PolicyDef {
    /// The table this policy applies to.
    pub target: QualifiedName,
    /// Byte range of the `CREATE POLICY` statement.
    pub range: TextRange,
}

/// A `CREATE INDEX` definition.
#[derive(Debug)]
pub struct IndexDef {
    /// The table this index is on.
    pub target: QualifiedName,
    /// Byte range of the `CREATE INDEX` statement.
    pub range: TextRange,
    /// Access method (`btree`, `gin`, `gist`, `hash`, `brin`, `spgist`). Empty
    /// only when the parser supplied no value — `pg_query` normally emits
    /// `"btree"` even when omitted in source.
    pub method: String,
    /// Indexed columns and/or expressions in declaration order.
    pub targets: Vec<IndexTarget>,
    /// `true` for `CREATE UNIQUE INDEX`.
    pub unique: bool,
    /// Deparsed `WHERE` clause for partial indexes.
    pub predicate: Option<String>,
}

/// One target of a `CREATE INDEX` — either a column or an expression.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IndexTarget {
    /// Indexed column by name.
    Column(String),
    /// Indexed expression in canonical deparsed form (e.g. `lower(email)`).
    Expr(String),
}
