//! The VDBE bytecode code generator.
//!
//! This module is responsible for translating the SQL AST into a sequence of
//! instructions for the VDBE. The VDBE is a register-based virtual machine that
//! executes bytecode instructions. This code generator is responsible for taking
//! the SQL AST and generating the corresponding VDBE instructions. For example,
//! a SELECT statement will be translated into a sequence of instructions that
//! will read rows from the database and filter them according to a WHERE clause.

pub(crate) mod aggregation;
pub(crate) mod delete;
pub(crate) mod emitter;
pub(crate) mod expr;
pub(crate) mod group_by;
pub(crate) mod index;
pub(crate) mod insert;
pub(crate) mod main_loop;
pub(crate) mod optimizer;
pub(crate) mod order_by;
pub(crate) mod plan;
pub(crate) mod planner;
pub(crate) mod pragma;
pub(crate) mod result_row;
pub(crate) mod schema;
pub(crate) mod select;
pub(crate) mod subquery;
pub(crate) mod transaction;
pub(crate) mod update;

use crate::fast_lock::SpinLock;
use crate::schema::Schema;
use crate::storage::pager::Pager;
use crate::storage::sqlite3_ondisk::DatabaseHeader;
use crate::translate::delete::translate_delete;
use crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode};
use crate::vdbe::Program;
use crate::{bail_parse_error, Connection, LimboError, Result, SymbolTable};
use fallible_iterator::FallibleIterator as _;
use index::{translate_create_index, translate_drop_index};
use insert::translate_insert;
use limbo_sqlite3_parser::ast::{self, Delete, Insert};
use limbo_sqlite3_parser::lexer::sql::Parser;
use schema::{
    translate_create_table, translate_create_virtual_table, translate_drop_table, ParseSchema,
    SQLITE_TABLEID,
};
use select::translate_select;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use transaction::{translate_tx_begin, translate_tx_commit};
use update::translate_update;

/// Translate SQL statement into bytecode program.
pub fn translate(
    schema: &Schema,
    stmt: ast::Stmt,
    database_header: Arc<SpinLock<DatabaseHeader>>,
    pager: Rc<Pager>,
    connection: Weak<Connection>,
    syms: &SymbolTable,
    query_mode: QueryMode,
) -> Result<Program> {
    let mut change_cnt_on = false;

    let program = match stmt {
        ast::Stmt::AlterTable(a) => {
            let (table_name, alter_table) = a.as_ref();

            match alter_table {
                ast::AlterTableBody::RenameTo(name) => {
                    let rename = &name.0;
                    let name = &table_name.name.0;

                    let Some(table) = schema.tables.get(name) else {
                        return Err(LimboError::ParseError(format!("no such table: {name}")));
                    };

                    if schema.tables.contains_key(rename) {
                        return Err(LimboError::ParseError(format!(
                            "there is already another table or index with this name: {rename}"
                        )));
                    };

                    let Some(btree) = table.btree() else { todo!() };

                    let mut btree = (*btree).clone();
                    btree.name = rename.clone();

                    let sql = btree.to_sql();

                    let stmt = format!(
                        r#"
                            UPDATE {SQLITE_TABLEID}
                            SET name = '{rename}'
                              , tbl_name = '{rename}'
                              , sql = '{sql}'
                            WHERE tbl_name = '{name}'
                        "#,
                    );

                    let mut parser = Parser::new(stmt.as_bytes());
                    let Some(ast::Cmd::Stmt(ast::Stmt::Update(mut update))) = parser.next()? else {
                        unreachable!();
                    };

                    translate_update(
                        QueryMode::Normal,
                        schema,
                        &mut update,
                        syms,
                        ParseSchema::Reload,
                    )?
                }
                _ => todo!(),
            }
        }
        ast::Stmt::Analyze(_) => bail_parse_error!("ANALYZE not supported yet"),
        ast::Stmt::Attach { .. } => bail_parse_error!("ATTACH not supported yet"),
        ast::Stmt::Begin(tx_type, tx_name) => translate_tx_begin(tx_type, tx_name)?,
        ast::Stmt::Commit(tx_name) => translate_tx_commit(tx_name)?,
        ast::Stmt::CreateIndex {
            unique,
            if_not_exists,
            idx_name,
            tbl_name,
            columns,
            ..
        } => {
            change_cnt_on = true;
            translate_create_index(
                query_mode,
                (unique, if_not_exists),
                &idx_name.name.0,
                &tbl_name.0,
                &columns,
                schema,
            )?
        }
        ast::Stmt::CreateTable {
            temporary,
            if_not_exists,
            tbl_name,
            body,
        } => translate_create_table(
            query_mode,
            tbl_name,
            temporary,
            *body,
            if_not_exists,
            schema,
        )?,
        ast::Stmt::CreateTrigger { .. } => bail_parse_error!("CREATE TRIGGER not supported yet"),
        ast::Stmt::CreateView { .. } => bail_parse_error!("CREATE VIEW not supported yet"),
        ast::Stmt::CreateVirtualTable(vtab) => {
            translate_create_virtual_table(*vtab, schema, query_mode, &syms)?
        }
        ast::Stmt::Delete(delete) => {
            let Delete {
                tbl_name,
                where_clause,
                limit,
                ..
            } = *delete;
            change_cnt_on = true;
            translate_delete(query_mode, schema, &tbl_name, where_clause, limit, syms)?
        }
        ast::Stmt::Detach(_) => bail_parse_error!("DETACH not supported yet"),
        ast::Stmt::DropIndex {
            if_exists,
            idx_name,
        } => translate_drop_index(query_mode, &idx_name.name.0, if_exists, schema)?,
        ast::Stmt::DropTable {
            if_exists,
            tbl_name,
        } => translate_drop_table(query_mode, tbl_name, if_exists, schema)?,
        ast::Stmt::DropTrigger { .. } => bail_parse_error!("DROP TRIGGER not supported yet"),
        ast::Stmt::DropView { .. } => bail_parse_error!("DROP VIEW not supported yet"),
        ast::Stmt::Pragma(name, body) => pragma::translate_pragma(
            query_mode,
            schema,
            &name,
            body.map(|b| *b),
            database_header.clone(),
            pager,
        )?,
        ast::Stmt::Reindex { .. } => bail_parse_error!("REINDEX not supported yet"),
        ast::Stmt::Release(_) => bail_parse_error!("RELEASE not supported yet"),
        ast::Stmt::Rollback { .. } => bail_parse_error!("ROLLBACK not supported yet"),
        ast::Stmt::Savepoint(_) => bail_parse_error!("SAVEPOINT not supported yet"),
        ast::Stmt::Select(select) => translate_select(query_mode, schema, *select, syms)?,
        ast::Stmt::Update(mut update) => {
            translate_update(query_mode, schema, &mut update, syms, ParseSchema::None)?
        }
        ast::Stmt::Vacuum(_, _) => bail_parse_error!("VACUUM not supported yet"),
        ast::Stmt::Insert(insert) => {
            let Insert {
                with,
                or_conflict,
                tbl_name,
                columns,
                body,
                returning,
            } = *insert;
            change_cnt_on = true;
            translate_insert(
                query_mode,
                schema,
                &with,
                &or_conflict,
                &tbl_name,
                &columns,
                &body,
                &returning,
                syms,
            )?
        }
    };

    Ok(program.build(database_header, connection, change_cnt_on))
}
