/**
 * Cloudflare D1 Database Adapter
 */
import { Cloudflare } from '../../config/cloudflare.js';
import { FeatureFlags } from '../../config/features.js';
import { Logger } from '../../config/logger.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { isObject } from '../../helper/index.js';
import { AdaptersEnum } from '../../migrations/enum/index.js';
import { QueryBuilder } from '../QueryBuilder.js';
const isRecord = (value) => isObject(value);
const toNumber = (value) => {
    if (typeof value === 'number' && Number.isFinite(value))
        return value;
    return null;
};
const toInsertId = (value) => {
    if (typeof value === 'number' || typeof value === 'string' || typeof value === 'bigint') {
        return value;
    }
    return undefined;
};
const isMutatingSql = (sql) => {
    const normalized = sql.trimStart().toLowerCase();
    return (normalized.startsWith('insert') ||
        normalized.startsWith('update') ||
        normalized.startsWith('delete') ||
        normalized.startsWith('create') ||
        normalized.startsWith('drop') ||
        normalized.startsWith('alter') ||
        normalized.startsWith('replace'));
};
const extractMeta = (value) => {
    if (!isRecord(value))
        return { changes: 0 };
    const changes = toNumber(value['changes']) ??
        toNumber(value['rows_written']) ??
        toNumber(value['rows_read']) ??
        0;
    const lastInsertId = toInsertId(value['lastRowId']) ??
        toInsertId(value['last_row_id']) ??
        toInsertId(value['lastInsertRowid']) ??
        toInsertId(value['last_insert_rowid']);
    return { changes, lastInsertId };
};
/**
 * Get D1 binding from config or global environment
 */
function getD1Binding(_config) {
    return Cloudflare.getD1Binding(_config);
}
/**
 * D1 adapter implementation
 */
export const D1Adapter = Object.freeze({
    /**
     * Create a new D1 adapter instance
     */
    // eslint-disable-next-line max-lines-per-function
    create(_config) {
        let connected = false;
        return {
            // eslint-disable-next-line @typescript-eslint/require-await
            async connect() {
                connected = true;
                Logger.info('✓ D1 connected');
            },
            // eslint-disable-next-line @typescript-eslint/require-await
            async disconnect() {
                connected = false;
                Logger.info('✓ D1 disconnected');
            },
            async query(sql, parameters) {
                if (!connected)
                    throw ErrorFactory.createConnectionError('Database not connected');
                const db = getD1Binding(_config);
                if (db === null) {
                    throw ErrorFactory.createConfigError('D1 database binding not found');
                }
                try {
                    const stmt = db.prepare(sql);
                    if (isMutatingSql(sql)) {
                        const runResult = await stmt.bind(...parameters).run();
                        const runRecord = runResult;
                        const meta = extractMeta(runRecord.meta);
                        return {
                            rows: [],
                            rowCount: meta.changes,
                            lastInsertId: meta.lastInsertId,
                        };
                    }
                    const result = await stmt.bind(...parameters).all();
                    const rawResult = result;
                    const rows = rawResult.results ?? [];
                    const metaValue = rawResult.meta;
                    const meta = extractMeta(metaValue);
                    return {
                        rows,
                        rowCount: rows.length > 0 ? rows.length : meta.changes,
                        lastInsertId: meta.lastInsertId,
                    };
                }
                catch (error) {
                    throw ErrorFactory.createTryCatchError(`D1 query failed: ${sql}`, error);
                }
            },
            async queryOne(sql, parameters) {
                if (!connected)
                    throw ErrorFactory.createConnectionError('Database not connected');
                const db = getD1Binding(_config);
                if (db === null) {
                    throw ErrorFactory.createConfigError('D1 database binding not found');
                }
                try {
                    const stmt = db.prepare(sql);
                    const result = await stmt.bind(...parameters).first();
                    return result ?? null;
                }
                catch (error) {
                    throw ErrorFactory.createTryCatchError(`D1 queryOne failed: ${sql}`, error);
                }
            },
            async ping() {
                if (!connected)
                    throw ErrorFactory.createConnectionError('Database not connected');
                const db = getD1Binding(_config);
                if (db === null) {
                    throw ErrorFactory.createConfigError('D1 database binding not found');
                }
                try {
                    // Use a minimal, side-effect-free query.
                    await db.prepare(QueryBuilder.create('').select('1').toSQL()).bind().first();
                }
                catch (error) {
                    throw ErrorFactory.createTryCatchError('D1 ping failed', error);
                }
            },
            async transaction(callback) {
                if (!connected)
                    throw ErrorFactory.createConnectionError('Database not connected');
                try {
                    const result = await callback(this);
                    return result;
                }
                catch (error) {
                    throw ErrorFactory.createTryCatchError('Transaction failed', error);
                }
            },
            getType() {
                return AdaptersEnum.d1;
            },
            isConnected() {
                return connected;
            },
            async rawQuery(sql, parameters) {
                if (!FeatureFlags.isRawQueryEnabled()) {
                    throw ErrorFactory.createConfigError('Raw SQL queries are disabled');
                }
                if (!connected) {
                    throw ErrorFactory.createConnectionError('Database not connected');
                }
                const db = getD1Binding(_config);
                if (db === null) {
                    throw ErrorFactory.createConfigError('D1 database binding not found');
                }
                try {
                    Logger.warn(`Raw SQL Query executed: ${sql}`, { parameters });
                    const stmt = db.prepare(sql);
                    const result = await stmt.bind(...(parameters ?? [])).all();
                    return result.results ?? [];
                }
                catch (error) {
                    throw ErrorFactory.createTryCatchError(`Raw SQL query failed: ${sql}`, error);
                }
            },
            getPlaceholder(_index) {
                return '?';
            },
        };
    },
});
export default D1Adapter;
