import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { BaseAdapter } from '../../orm/DatabaseAdapter.js';
import { isSqliteFamily } from '../enum/index.js';
import { MigrationBlueprint } from '../schema/Blueprint.js';
import { MigrationSchemaCompiler } from '../schema/SchemaCompiler.js';
const IDENT_RE = /^[A-Za-z_]\w*$/;
function assertIdentifier(label, value) {
    if (!IDENT_RE.test(value)) {
        throw ErrorFactory.createValidationError(`Invalid ${label} identifier: ${value}`);
    }
}
function isRecord(value) {
    return typeof value === 'object' && value !== null;
}
function getStringProp(value, key) {
    if (!isRecord(value))
        return null;
    const v = value[key];
    return typeof v === 'string' ? v : null;
}
function mapNames(rows) {
    return rows.map((r) => getStringProp(r, 'name') ?? '').filter((name) => name.length > 0);
}
function buildParameterized(db, sql, parameters) {
    const adapter = db.getAdapterInstance(false);
    return BaseAdapter.buildParameterizedQuery(sql, parameters, (i) => adapter.getPlaceholder(i));
}
async function queryExists(db, sql, parameters) {
    const built = buildParameterized(db, sql, parameters);
    const rows = await db.query(built.sql, built.parameters, true);
    return rows.length > 0;
}
async function runStatements(db, statements) {
    await statements
        .filter((sql) => sql.trim() !== '')
        .reduce(async (p, sql) => {
        await p;
        await db.query(sql, []);
    }, Promise.resolve());
}
async function schemaCreate(db, tableName, callback) {
    const blueprint = MigrationBlueprint.create(tableName);
    await callback(blueprint);
    const statements = MigrationSchemaCompiler.compileCreateTable(db.getType(), blueprint.getDefinition(), {
        ifNotExists: true,
    });
    await runStatements(db, statements);
}
async function schemaTable(db, tableName, callback) {
    const blueprint = MigrationBlueprint.create(tableName);
    await callback(blueprint);
    const def = blueprint.getDefinition();
    const plan = {
        addColumns: def.columns,
        dropColumns: blueprint.getDropColumns(),
        createIndexes: def.indexes,
        dropIndexes: blueprint.getDropIndexes(),
        addForeignKeys: def.foreignKeys,
        dropForeignKeys: blueprint.getDropForeignKeys(),
    };
    const statements = MigrationSchemaCompiler.compileAlterTable(db.getType(), tableName, plan);
    await runStatements(db, statements);
}
async function schemaDrop(db, tableName, ifExists) {
    const sql = MigrationSchemaCompiler.compileDropTable(db.getType(), tableName, { ifExists });
    await db.query(sql, []);
}
async function schemaHasTable(db, tableName) {
    const t = db.getType();
    if (isSqliteFamily(t)) {
        return queryExists(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1", [
            tableName,
        ]);
    }
    if (t === 'postgresql') {
        return queryExists(db, "SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name=? LIMIT 1", [tableName]);
    }
    if (t === 'mysql') {
        return queryExists(db, 'SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name=? LIMIT 1', [tableName]);
    }
    if (t === 'sqlserver') {
        return queryExists(db, 'SELECT 1 FROM sys.tables WHERE name=?', [tableName]);
    }
    throw ErrorFactory.createCliError(`Unsupported DB driver: ${t}`);
}
async function schemaHasColumn(db, tableName, columnName) {
    const t = db.getType();
    if (t === 'sqlite' || t === 'd1' || t === 'd1-remote') {
        assertIdentifier('table', tableName);
        assertIdentifier('column', columnName);
        const rows = await db.query(`PRAGMA table_info("${tableName}")`, [], true);
        return rows.some((r) => getStringProp(r, 'name') === columnName);
    }
    if (t === 'postgresql') {
        return queryExists(db, "SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name=? AND column_name=? LIMIT 1", [tableName, columnName]);
    }
    if (t === 'mysql') {
        return queryExists(db, 'SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name=? AND column_name=? LIMIT 1', [tableName, columnName]);
    }
    if (t === 'sqlserver') {
        return queryExists(db, 'SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(?) AND name=?', [
            tableName,
            columnName,
        ]);
    }
    throw ErrorFactory.createCliError(`Unsupported DB driver: ${t}`);
}
async function schemaGetAllTables(db) {
    const t = db.getType();
    if (t === 'sqlite' || t === 'd1' || t === 'd1-remote') {
        const rows = await db.query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'", [], true);
        return mapNames(rows);
    }
    if (t === 'postgresql') {
        const rows = await db.query("SELECT table_name AS name FROM information_schema.tables WHERE table_schema='public'", [], true);
        return mapNames(rows);
    }
    if (t === 'mysql') {
        const rows = await db.query('SELECT table_name AS name FROM information_schema.tables WHERE table_schema = DATABASE()', [], true);
        return mapNames(rows);
    }
    if (t === 'sqlserver') {
        const rows = await db.query('SELECT name FROM sys.tables', [], true);
        return mapNames(rows);
    }
    throw ErrorFactory.createCliError(`Unsupported DB driver: ${t}`);
}
function createSchemaBuilder(db) {
    return {
        create: async (tableName, callback) => schemaCreate(db, tableName, callback),
        table: async (tableName, callback) => schemaTable(db, tableName, callback),
        drop: async (tableName) => schemaDrop(db, tableName, false),
        dropIfExists: async (tableName) => schemaDrop(db, tableName, true),
        hasTable: async (tableName) => schemaHasTable(db, tableName),
        hasColumn: async (tableName, columnName) => schemaHasColumn(db, tableName, columnName),
        getAllTables: async () => schemaGetAllTables(db),
        getDb: () => db,
    };
}
export const Schema = Object.freeze({
    create: createSchemaBuilder,
});
