use crate::function::{Func, ScalarFunc};
use crate::schema::Schema;
use crate::translate::emitter::Resolver;
use crate::translate::expr::{sanitize_string, translate_expr};
use crate::translate::{ProgramBuilder, ProgramBuilderOpts};
use crate::util::normalize_ident;
use crate::vdbe::insn::Insn;
use crate::{Result, SymbolTable};
use turso_parser::ast::{Expr, Literal};

/// Translate ATTACH statement
/// SQLite implements ATTACH as a function call to sqlite_attach()
pub fn translate_attach(
    expr: &Expr,
    db_name: &Expr,
    key: &Option<Box<Expr>>,
    schema: &Schema,
    syms: &SymbolTable,
    mut program: ProgramBuilder,
) -> Result<ProgramBuilder> {
    // SQLite treats ATTACH as a function call to sqlite_attach(filename, dbname, key)
    // We'll allocate registers for the arguments and call the function

    program.extend(&ProgramBuilderOpts {
        num_cursors: 0,
        approx_num_insns: 10,
        approx_num_labels: 0,
    });

    let arg_reg = program.alloc_registers(4); // 3 for args + 1 for result
    let resolver = Resolver::new(schema, syms);

    // Load filename argument
    // Handle different expression types as string literals for filenames
    match expr {
        Expr::Literal(Literal::String(s)) => {
            // For ATTACH, string literals should be used directly (without quotes)
            program.emit_insn(Insn::String8 {
                value: sanitize_string(s),
                dest: arg_reg,
            });
        }
        Expr::Qualified(_, _) => {
            // For ATTACH, qualified expressions like "foo.db" should be treated as filename strings
            let filename = format!("{expr}");
            program.emit_insn(Insn::String8 {
                value: filename,
                dest: arg_reg,
            });
        }
        Expr::Id(id) => {
            // For ATTACH, identifiers should be treated as filename strings
            // Use normalize_ident to strip quotes from double-quoted identifiers
            program.emit_insn(Insn::String8 {
                value: normalize_ident(id.as_str()),
                dest: arg_reg,
            });
        }
        _ => {
            translate_expr(&mut program, None, expr, arg_reg, &resolver)?;
        }
    }

    // Load database name argument
    // Handle different expression types as string literals for database names
    match db_name {
        Expr::Literal(Literal::String(s)) => {
            // For ATTACH, string literals should be used directly (without quotes)
            program.emit_insn(Insn::String8 {
                value: sanitize_string(s),
                dest: arg_reg + 1,
            });
        }
        Expr::Qualified(_, _) => {
            // For ATTACH, qualified expressions should be treated as name strings
            let db_name_str = format!("{db_name}");
            program.emit_insn(Insn::String8 {
                value: db_name_str,
                dest: arg_reg + 1,
            });
        }
        Expr::Id(id) => {
            // For ATTACH, identifiers should be treated as name strings
            // Use normalize_ident to strip quotes from double-quoted identifiers
            program.emit_insn(Insn::String8 {
                value: normalize_ident(id.as_str()),
                dest: arg_reg + 1,
            });
        }
        _ => {
            translate_expr(&mut program, None, db_name, arg_reg + 1, &resolver)?;
        }
    }

    // Load key argument (NULL if not provided)
    if let Some(key_expr) = key {
        translate_expr(&mut program, None, key_expr, arg_reg + 2, &resolver)?;
    } else {
        program.emit_insn(Insn::Null {
            dest: arg_reg + 2,
            dest_end: None,
        });
    }

    // Call sqlite_attach function
    program.emit_insn(Insn::Function {
        constant_mask: 0,
        start_reg: arg_reg,
        dest: arg_reg + 3, // Result register (not used but required)
        func: crate::function::FuncCtx {
            func: Func::Scalar(ScalarFunc::Attach),
            arg_count: 3,
        },
    });

    Ok(program)
}

/// Translate DETACH statement
/// SQLite implements DETACH as a function call to sqlite_detach()
pub fn translate_detach(
    expr: &Expr,
    schema: &Schema,
    syms: &SymbolTable,
    mut program: ProgramBuilder,
) -> Result<ProgramBuilder> {
    // SQLite treats DETACH as a function call to sqlite_detach(dbname)

    program.extend(&ProgramBuilderOpts {
        num_cursors: 0,
        approx_num_insns: 5,
        approx_num_labels: 0,
    });

    let arg_reg = program.alloc_registers(2); // 1 for arg + 1 for result
    let resolver = Resolver::new(schema, syms);

    // Load database name argument
    // Handle different expression types as string literals for database names
    match expr {
        Expr::Literal(Literal::String(s)) => {
            // For DETACH, string literals should be used directly (without quotes)
            program.emit_insn(Insn::String8 {
                value: sanitize_string(s),
                dest: arg_reg,
            });
        }
        Expr::Qualified(_, _) => {
            // For DETACH, qualified expressions should be treated as name strings
            let db_name_str = format!("{expr}");
            program.emit_insn(Insn::String8 {
                value: db_name_str,
                dest: arg_reg,
            });
        }
        Expr::Id(id) => {
            // For DETACH, identifiers should be treated as name strings
            // Use normalize_ident to strip quotes from double-quoted identifiers
            program.emit_insn(Insn::String8 {
                value: normalize_ident(id.as_str()),
                dest: arg_reg,
            });
        }
        _ => {
            translate_expr(&mut program, None, expr, arg_reg, &resolver)?;
        }
    }

    // Call sqlite_detach function
    program.emit_insn(Insn::Function {
        constant_mask: 0,
        start_reg: arg_reg,
        dest: arg_reg + 1, // Result register (not used but required)
        func: crate::function::FuncCtx {
            func: Func::Scalar(ScalarFunc::Detach),
            arg_count: 1,
        },
    });

    Ok(program)
}
