import fs from 'fs';
import path from 'path';
import { db, getDatabase } from '../utils/db.js';
import { sql } from 'drizzle-orm';
import { schemaManager } from '../utils/schemaManager.js';
import { getProjectPath, toFileURL } from '../utils/dirname.js';
/**
 * Hooks Manager - Executes lifecycle hooks for collections
 *
 * Matches Sequelize implementation 1:1
 */
export class HooksManager {
    hooks = {};
    /**
     * Register a hook for a collection and event
     */
    registerHook(collection, event, hookFunction) {
        const key = `${collection}:${event}`;
        console.info(`Registering hook for ${key}`);
        if (!this.hooks[key]) {
            this.hooks[key] = [];
        }
        this.hooks[key].push(hookFunction);
    }
    /**
     * Get hooks for a collection and event
     * Returns both specific hooks and wildcard hooks (registered for all collections)
     */
    getHooks(collection, event) {
        // Get hooks for specific collection
        const specificHooks = this.hooks[`${collection}:${event}`] || [];
        // Get wildcard hooks (registered for all collections with *)
        const wildcardHooks = this.hooks[`*:${event}`] || [];
        // Combine both - wildcard hooks execute first
        return [...wildcardHooks, ...specificHooks];
    }
    /**
     * Execute hooks for a collection and action
     */
    async executeHooks(collection, event, accountability, context) {
        const hooks = this.getHooks(collection, event);
        let modifiedData = { ...context };
        // Execute each hook in sequence
        for (const hook of hooks) {
            const result = await hook({
                collection,
                accountability,
                db,
                ...modifiedData,
            });
            // Update modifiedData with hook result if provided
            if (result) {
                modifiedData = result;
            }
        }
        return modifiedData;
    }
    /**
     * Load hooks from extensions directory
     */
    async loadHooksFromDirectory(context, directory) {
        if (!directory) {
            directory = getProjectPath('extensions');
        }
        if (!fs.existsSync(directory)) {
            console.warn(`Hooks directory not found: ${directory}`);
            return;
        }
        const files = fs.readdirSync(directory);
        for (const file of files) {
            const filePath = path.join(directory, file);
            if (fs.statSync(filePath).isDirectory() && file.startsWith('baasix-hook-')) {
                const hookFile = path.join(filePath, 'index.js');
                if (fs.existsSync(hookFile)) {
                    try {
                        // Dynamic import for ES modules
                        // Convert to file:// URL for Windows compatibility
                        const hookModule = await import(toFileURL(hookFile));
                        if (typeof hookModule.default === 'function') {
                            await hookModule.default(this, context);
                            console.info(`Loaded hook: ${file}`);
                        }
                    }
                    catch (error) {
                        console.error(`Failed to load hook ${file}:`, error);
                    }
                }
            }
        }
    }
    /**
     * Load schedules from extensions directory
     */
    async loadSchedulesFromDirectory(context, schedule, directory) {
        if (!directory) {
            directory = getProjectPath('extensions');
        }
        if (!fs.existsSync(directory)) {
            console.warn(`Schedules directory not found: ${directory}`);
            return;
        }
        const files = fs.readdirSync(directory);
        for (const file of files) {
            const filePath = path.join(directory, file);
            if (fs.statSync(filePath).isDirectory() && file.startsWith('baasix-schedule-')) {
                const scheduleFile = path.join(filePath, 'index.js');
                if (fs.existsSync(scheduleFile)) {
                    try {
                        // Dynamic import for ES modules
                        // Convert to file:// URL for Windows compatibility
                        const scheduleModule = await import(toFileURL(scheduleFile));
                        if (typeof scheduleModule.default === 'function') {
                            await scheduleModule.default(schedule, context);
                            console.info(`Loaded schedule: ${file}`);
                        }
                    }
                    catch (error) {
                        console.error(`Failed to load schedule ${file}:`, error);
                    }
                }
            }
        }
    }
}
// Create singleton instance only if it doesn't exist
if (!globalThis.__baasix_hooksManager) {
    globalThis.__baasix_hooksManager = new HooksManager();
}
export const hooksManager = globalThis.__baasix_hooksManager;
// Register global beforeCreate hook for auto-sort functionality (only once)
if (!globalThis.__baasix_hooksManagerInitialized) {
    globalThis.__baasix_hooksManagerInitialized = true;
    hooksManager.registerHook('*', 'items.create', async (context) => {
        const { data, collection } = context;
        if (!data) {
            return context;
        }
        try {
            // Get the Drizzle table schema (using statically imported schemaManager)
            const table = schemaManager.getTable(collection);
            // Check if table has a 'sort' column by trying to access it
            if (table && table.sort) {
                // If sort is not provided or is null/undefined, auto-increment it
                if (data.sort === undefined || data.sort === null) {
                    const db = getDatabase();
                    // Query for max sort value
                    const result = await db.execute(sql `
            SELECT COALESCE(MAX("sort"), 0) as max_sort
            FROM "${sql.raw(collection)}"
          `);
                    const maxSort = result[0]?.max_sort || 0;
                    data.sort = Number(maxSort) + 1;
                    console.log(`[HooksManager] Auto-assigned sort value ${data.sort} for ${collection}`);
                }
            }
        }
        catch (error) {
            // If query fails, silently ignore (sort field might not exist)
            console.warn(`Failed to auto-increment sort for ${collection}:`, error.message);
        }
        return context;
    });
    // ── Internal hook: Invalidate user-role auth cache on baasix_UserRole mutations ──
    // This catches ALL code paths: direct API, nested relational updates, bulk, imports.
    const invalidateUserRoleCacheFromHook = async (context) => {
        try {
            const { invalidateUserRoleCache } = await import('../utils/common.js');
            const doc = context.document;
            const prev = context.previousDocument;
            // Invalidate current and previous user (in case user_Id itself changed)
            const userIds = new Set();
            if (doc?.user_Id)
                userIds.add(doc.user_Id);
            if (prev?.user_Id)
                userIds.add(prev.user_Id);
            for (const userId of userIds) {
                const tenantId = doc?.tenant_Id || prev?.tenant_Id;
                await invalidateUserRoleCache(userId, tenantId);
            }
        }
        catch (error) {
            console.error('[HooksManager] Failed to invalidate user role cache:', error.message);
        }
        return context;
    };
    hooksManager.registerHook('baasix_UserRole', 'items.create.after', invalidateUserRoleCacheFromHook);
    hooksManager.registerHook('baasix_UserRole', 'items.update.after', invalidateUserRoleCacheFromHook);
    hooksManager.registerHook('baasix_UserRole', 'items.delete.after', invalidateUserRoleCacheFromHook);
    // Evict session cache when a session record is deleted via ItemsService
    // This ensures auth/me returns 401 immediately instead of waiting for 30s TTL
    hooksManager.registerHook('baasix_Sessions', 'items.delete.after', async (context) => {
        try {
            const token = context.document?.token;
            if (!token)
                return context;
            const { getCache } = await import('../utils/cache.js');
            const cache = getCache();
            await cache.delete(`auth:session:${token}`);
        }
        catch (error) {
            console.error('[HooksManager] Failed to invalidate session cache:', error.message);
        }
        return context;
    });
}
export default hooksManager;
//# sourceMappingURL=HooksManager.js.map