mod tvf;
mod values;
mod views;

use crate::ast::{SelectStmt, TableFactor, TableRef};
use crate::catalog::{Catalog, ColumnDef, TableDef};
use crate::error::DbError;
use crate::storage::StoredRow;

use super::super::clock::Clock;
use super::super::context::ExecutionContext;
use super::super::model::BoundTable;
use super::super::query::QueryExecutor;
use super::super::result::QueryResult;

pub(crate) fn bind_table(
    catalog: &dyn Catalog,
    storage: &dyn crate::storage::Storage,
    clock: &dyn Clock,
    tref: TableRef,
    ctx: &mut ExecutionContext,
    executor: &QueryExecutor<'_>,
) -> Result<BoundTable, DbError> {
    if let TableFactor::Derived(ref select) = tref.factor {
        return bind_derived_subquery(tref.alias.clone(), *select.clone(), ctx, executor);
    }

    if let Some(bound_tvf) = tvf::bind_builtin_tvf(catalog, storage, clock, &tref, ctx)? {
        return Ok(bound_tvf);
    }
    if let Some(bound_tvf) = tvf::bind_inline_tvf(catalog, storage, clock, &tref, ctx, executor)? {
        return Ok(bound_tvf);
    }
    if let Some(bound_view) = views::bind_view(catalog, storage, clock, &tref, ctx, executor)? {
        return Ok(bound_view);
    }
    if let Some(obj) = tref.name_as_object() {
        let schema = obj.schema_or_dbo();
        if let Some(synonym) = catalog.find_synonym(schema, &obj.name) {
            if ctx.frame.depth > 16 {
                return Err(DbError::Execution(
                    "Synonym recursion limit exceeded".into(),
                ));
            }
            ctx.frame.depth += 1;
            let mut resolved_tref = tref.clone();
            resolved_tref.factor = TableFactor::Named(synonym.base_object.clone());
            let res = bind_table(catalog, storage, clock, resolved_tref, ctx, executor);
            ctx.frame.depth -= 1;
            return res;
        }
    }
    values::bind_plain_table(tref, catalog, ctx)
}

fn bind_derived_subquery(
    alias: Option<String>,
    select: SelectStmt,
    ctx: &mut ExecutionContext,
    executor: &QueryExecutor<'_>,
) -> Result<BoundTable, DbError> {
    let alias =
        alias.ok_or_else(|| DbError::Semantic("subquery in FROM must have an alias".into()))?;
    let result = executor.execute_select(select.into(), ctx)?;
    Ok(query_result_to_bound_table(alias.clone(), alias, result))
}

pub(super) fn query_result_to_bound_table(
    alias: String,
    table_name: String,
    result: QueryResult,
) -> BoundTable {
    let table_def = TableDef {
        id: 0,
        schema_id: 1,
        schema_name: "dbo".to_string(),
        name: table_name,
        columns: result
            .columns
            .iter()
            .enumerate()
            .map(|(i, cname)| ColumnDef {
                id: (i + 1) as u32,
                name: cname.clone(),
                data_type: result.column_types[i].clone(),
                nullable: true,
                primary_key: false,
                unique: false,
                identity: None,
                default: None,
                default_constraint_name: None,
                check: None,
                check_constraint_name: None,
                computed_expr: None,
                ansi_padding_on: true,
            })
            .collect(),
        check_constraints: vec![],
        foreign_keys: vec![],
    };
    let rows = result
        .rows
        .into_iter()
        .map(|values| StoredRow {
            values,
            deleted: false,
        })
        .collect::<Vec<_>>();
    BoundTable {
        alias,
        table: table_def,
        virtual_rows: Some(rows),
    }
}
