import { Logger } from '../config/logger.js';
import { ErrorFactory } from '../exceptions/ZintrustError.js';
import { QueryBuilder } from '../orm/QueryBuilder.js';
const poolsByInstance = new WeakMap();
const configByInstance = new WeakMap();
function getAdapterConfig(adapter) {
    const config = configByInstance.get(adapter);
    if (config === undefined) {
        throw ErrorFactory.createConfigError('PostgresAdapter not initialized');
    }
    return config;
}
function getPools(adapter) {
    const pools = poolsByInstance.get(adapter);
    if (pools === undefined) {
        throw ErrorFactory.createConfigError('PostgresAdapter not initialized');
    }
    return pools;
}
// Function-constructor implementation (no `class` keyword) but still constructable via `new`.
function PostgresAdapterImpl(config) {
    const adapterConfig = {
        max: 20,
        idleTimeoutMillis: 30000,
        connectionTimeoutMillis: 2000,
        isolation: 'shared',
        ...config,
    };
    poolsByInstance.set(this, new Map());
    configByInstance.set(this, adapterConfig);
}
PostgresAdapterImpl.prototype.getPoolKey = function getPoolKey() {
    const adapterConfig = getAdapterConfig(this);
    if (adapterConfig.isolation === 'isolated' && adapterConfig.serviceName !== undefined) {
        return `${adapterConfig.host}:${adapterConfig.port}/${adapterConfig.serviceName}`;
    }
    return `${adapterConfig.host}:${adapterConfig.port}/${adapterConfig.database}`;
};
PostgresAdapterImpl.prototype.connect = async function connect() {
    const adapterConfig = getAdapterConfig(this);
    const pools = getPools(this);
    return runConnect(adapterConfig, pools, () => this.getPoolKey());
};
PostgresAdapterImpl.prototype.getPool = function getPool() {
    const pools = getPools(this);
    return runGetPool(pools, () => this.getPoolKey());
};
PostgresAdapterImpl.prototype.query = async function query(sql, params) {
    return runQuery(this.getPool(), sql, params);
};
PostgresAdapterImpl.prototype.execute = async function execute(sql, params) {
    return runExecute(this.getPool(), sql, params);
};
PostgresAdapterImpl.prototype.transaction = async function transaction(callback) {
    return runTransaction(this.getPool(), callback);
};
PostgresAdapterImpl.prototype.getPoolStats = function getPoolStats() {
    return runGetPoolStats(this.getPool());
};
PostgresAdapterImpl.prototype.disconnect = async function disconnect() {
    const pools = getPools(this);
    return runDisconnect(pools, () => this.getPoolKey());
};
PostgresAdapterImpl.prototype.disconnectAll = async function disconnectAll() {
    const pools = getPools(this);
    return runDisconnectAll(pools);
};
PostgresAdapterImpl.prototype.createServiceSchema = async function createServiceSchema(schemaName) {
    const adapterConfig = getAdapterConfig(this);
    return runCreateServiceSchema(this, adapterConfig.isolation, schemaName);
};
PostgresAdapterImpl.prototype.runMigrations = async function runMigrationsPublic(migrations) {
    return runMigrations(this.getPool(), migrations);
};
PostgresAdapterImpl.prototype.healthCheck = async function healthCheck() {
    return runHealthCheck(this.getPool());
};
export const PostgresAdapter = PostgresAdapterImpl;
PostgresAdapter.create = (config) => new PostgresAdapter(config);
/**
 * Internal function to get connection pool
 */
function runGetPool(pools, getPoolKey) {
    const poolKey = getPoolKey();
    const pool = pools.get(poolKey);
    if (pool === undefined) {
        throw ErrorFactory.createConnectionError('Connection pool not initialized. Call connect() first.', { poolKey });
    }
    return pool;
}
/**
 * Internal function to get pool statistics
 */
function runGetPoolStats(pool) {
    return {
        totalConnections: pool.totalCount,
        idleConnections: pool.idleCount,
        waitingRequests: pool.waitingCount,
    };
}
/**
 * Internal function to disconnect a pool
 */
async function runDisconnect(pools, getPoolKey) {
    const poolKey = getPoolKey();
    const pool = pools.get(poolKey);
    if (pool !== undefined) {
        await pool.end();
        pools.delete(poolKey);
        Logger.info(`🔌 PostgreSQL disconnected: ${poolKey}`);
    }
}
/**
 * Internal function to disconnect all pools
 */
async function runDisconnectAll(pools) {
    const promises = Array.from(pools.values()).map(async (pool) => pool.end());
    await Promise.all(promises);
    pools.clear();
    Logger.info('🔌 All PostgreSQL pools disconnected');
}
function isMissingEsmPackage(error, packageName) {
    if (typeof error !== 'object' || error === null)
        return false;
    const maybe = error;
    const code = typeof maybe.code === 'string' ? maybe.code : '';
    const message = typeof maybe.message === 'string' ? maybe.message : '';
    // Some runners/wrappers preserve `code` but sanitize/omit the message.
    if (code === 'ERR_MODULE_NOT_FOUND' && message.length === 0)
        return true;
    if (code === 'ERR_MODULE_NOT_FOUND' && message.includes(`'${packageName}'`))
        return true;
    if (message.includes(`Cannot find package '${packageName}'`))
        return true;
    return false;
}
/**
 * Internal function to connect to PostgreSQL
 */
async function runConnect(adapterConfig, pools, getPoolKey) {
    const poolKey = getPoolKey();
    if (pools.has(poolKey) === true) {
        Logger.info(`♻️  Reusing existing connection pool: ${poolKey}`);
        return;
    }
    try {
        // Dynamic import to keep core zero-dependency
        const { Pool } = await import('pg');
        const pool = new Pool({
            host: adapterConfig.host,
            port: adapterConfig.port,
            database: adapterConfig.database,
            user: adapterConfig.user,
            password: adapterConfig.password,
            max: adapterConfig.max,
            idleTimeoutMillis: adapterConfig.idleTimeoutMillis,
            connectionTimeoutMillis: adapterConfig.connectionTimeoutMillis,
        });
        pool.on('error', (err) => {
            Logger.error(`Unexpected error on idle client for ${poolKey}`, err);
        });
        const client = await pool.connect();
        Logger.info(`✅ PostgreSQL connected: ${adapterConfig.host}:${adapterConfig.port}/${adapterConfig.database}`);
        client.release();
        pools.set(poolKey, pool);
        Logger.info(`🐘 PostgreSQL pool initialized: ${poolKey}`);
    }
    catch (error) {
        if (isMissingEsmPackage(error, 'pg')) {
            throw ErrorFactory.createConfigError("PostgreSQL pool requires the 'pg' package (run `zin add db:postgres` or `zin plugin install db:postgres`).");
        }
        throw ErrorFactory.createTryCatchError(`Failed to initialize PostgreSQL pool: ${error.message}`, error);
    }
}
/**
 * Internal function to execute a query
 */
async function runQuery(pool, sql, params) {
    try {
        const result = await pool.query(sql, params);
        return result.rows;
    }
    catch (error) {
        Logger.error(`PostgreSQL query failed: ${sql}`, error);
        throw error;
    }
}
/**
 * Internal function to execute a query and return full result
 */
async function runExecute(pool, sql, params) {
    try {
        const result = await pool.query(sql, params);
        return {
            rows: result.rows,
            rowCount: result.rowCount ?? 0,
        };
    }
    catch (err) {
        Logger.error(`PostgreSQL execute failed: ${sql}`, err);
        throw err;
    }
}
/**
 * Internal function to run a transaction
 */
async function runTransaction(pool, callback) {
    const client = await pool.connect();
    try {
        await client.query('BEGIN');
        const result = await callback(client);
        await client.query('COMMIT');
        return result;
    }
    catch (err) {
        Logger.error('Transaction failed, rolling back', err);
        await client.query('ROLLBACK');
        throw err;
    }
    finally {
        client.release();
    }
}
/**
 * Internal function to create service schema
 */
async function runCreateServiceSchema(adapter, isolation, schemaName) {
    if (isolation !== 'isolated') {
        Logger.info('ℹ️  Shared database mode: skipping schema creation');
        return;
    }
    try {
        await adapter.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`);
        Logger.info(`✅ Schema created: ${schemaName}`);
    }
    catch (err) {
        Logger.error(`Schema creation failed: ${schemaName}`, err);
    }
}
/**
 * Internal function to run migrations
 */
async function runMigrations(pool, migrations) {
    const client = await pool.connect();
    try {
        // Migrations must run sequentially to preserve ordering.
        /* eslint-disable no-await-in-loop */
        for (const migration of migrations) {
            await migration.up(client);
        }
        /* eslint-enable no-await-in-loop */
        Logger.info(`✅ Migrations completed`);
    }
    finally {
        client.release();
    }
}
/**
 * Internal function for health check
 */
async function runHealthCheck(pool) {
    try {
        const pingSql = QueryBuilder.create('').select('1').toSQL();
        const result = await pool.query(pingSql);
        return result.rows.length > 0;
    }
    catch (error) {
        Logger.error('PostgreSQL health check failed', error);
        return false;
    }
}
/**
 * Global PostgreSQL adapter instance management
 */
const instances = new Map();
/**
 * Get or create adapter instance
 */
export function getInstance(config, key = 'default') {
    const existing = instances.get(key);
    if (existing !== undefined) {
        return existing;
    }
    const created = PostgresAdapter.create(config);
    instances.set(key, created);
    return created;
}
/**
 * Get all instances
 */
export function getAllInstances() {
    return Array.from(instances.values());
}
/**
 * Disconnect all instances
 */
export async function disconnectAll() {
    await Promise.all(Array.from(instances.values()).map(async (adapter) => adapter.disconnectAll()));
    instances.clear();
}
export const PostgresAdapterManager = Object.freeze({
    getInstance,
    getAllInstances,
    disconnectAll,
});
export default PostgresAdapter;
