import * as fs from 'fs';
import * as path from 'path';
import { chromium } from 'playwright';
import { Logger } from '../utils/logger.js';
import { Generator } from './generator.js';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { DSLExecutor } from '../dslExecutor/executor.js';
import { ExecutionContext } from '../core/context.js';
import { SelectorResolver } from '../core/selectorResolver.js';
import { EventEmitter } from 'events';
import { ConfigManager } from '../utils/configManager.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export class Recorder {
    static emitter = new EventEmitter();
    static lastCapturedEvents = [];
    /**
     * Starts a recording session.
     */
    static async start(url, name = 'recorded_scenario', config = { mode: 'scrape', dbEnabled: false }) {
        let capturedEvents = [];
        let isStopped = false;
        let isDiscarded = false;
        let tempLoopSteps = [];
        let isRecordingLoop = false;
        let docTypeFromClient;
        Logger.log(`Starting modular recording session for ${url} (Scenario: ${name})`);
        const browser = await chromium.launch({
            headless: false,
            args: ['--start-maximized']
        });
        // Ensure selector registry is loaded for bootstrap/resume steps
        if (config.token) {
            ConfigManager.getInstance().setRemoteToken(config.token);
        }
        await SelectorResolver.init();
        const context = await browser.newContext({ viewport: null });
        const page = await context.newPage();
        // 1. Entry-point Navigation
        const hasNavigated = config.initialSteps && config.initialSteps.some(s => s.action === 'navigate');
        if (!hasNavigated) {
            Logger.log(`Navigating to entry point: ${url}`);
            await page.goto(url, { waitUntil: 'networkidle' });
        }
        // 2. Pre-execute initial steps (Bootstrap mode)
        if (config.initialSteps && config.initialSteps.length > 0) {
            Logger.log(`Bootstrap Mode: Executing ${config.initialSteps.length} initial steps...`);
            const executionContext = new ExecutionContext({
                mode: config.mode,
                baseUrl: url,
                flows: [],
                database: { enabled: config.dbEnabled, collection: config.dbCollection }
            });
            executionContext.page = page;
            executionContext.browserContext = context;
            // Seed context with existing scenario inputs if provided
            if (config.inputData) {
                executionContext.inputs = { ...config.inputData };
            }
            // Load shared data for variable resolution during bootstrap
            if (config.sharedDataRefs && config.sharedDataRefs.length > 0) {
                try {
                    const { TestRegistry } = await import('../core/registry.js');
                    const registry = await TestRegistry.getInstance();
                    for (const ref of config.sharedDataRefs) {
                        const shared = await registry.getSharedData(ref, config.organizationId);
                        if (shared && shared.data) {
                            const records = Array.isArray(shared.data) ? shared.data : [shared.data];
                            if (records.length > 0) {
                                const mainRecord = records[0];
                                // Merge flat keys
                                executionContext.inputs = { ...executionContext.inputs, ...mainRecord };
                                // Also merge with prefix (e.g., credlix.email) for explicit resolution
                                for (const [k, v] of Object.entries(mainRecord)) {
                                    executionContext.inputs[`${ref}.${k}`] = v;
                                }
                            }
                        }
                    }
                    Logger.log(`[Recorder:Bootstrap] Resolution context initialized with ${Object.keys(executionContext.inputs).length} keys`);
                }
                catch (err) {
                    Logger.warn(`[Recorder:Bootstrap] Failed to load shared data: ${err.message}`);
                }
            }
            // Fallback: load sample record from collection if inputs still empty
            if (Object.keys(executionContext.inputs).length === 0 && config.dbEnabled && config.dbCollection) {
                try {
                    const { TestRegistry } = await import('../core/registry.js');
                    const registry = await TestRegistry.getInstance();
                    const sample = await registry.getSampleData(config.dbCollection);
                    if (sample) {
                        executionContext.inputs = { ...executionContext.inputs, ...sample };
                        Logger.log(`[Recorder:Bootstrap] Loaded sample record from collection ${config.dbCollection} for resolution`);
                    }
                }
                catch (err) {
                    // Silent fallback
                }
            }
            try {
                await DSLExecutor.executeFlow(executionContext, config.initialSteps, async (step, index) => {
                    Logger.log(`[Recorder:Bootstrap] Executing step ${index + 1}/${config.initialSteps.length}: ${step.action} on ${step.target}`);
                });
                // Pre-fill events if resuming
                if (config.isResuming) {
                    capturedEvents = config.initialSteps.map(s => {
                        // Resolve the value for parametrized steps from the context we just loaded
                        let resolvedValue = s.value;
                        if (resolvedValue === undefined && s.inputKey) {
                            const val = executionContext.inputs[s.inputKey];
                            resolvedValue = val !== undefined ? String(val) : undefined;
                        }
                        if (resolvedValue === undefined && s.target && s.target.includes('.')) {
                            const val = executionContext.inputs[s.target];
                            resolvedValue = val !== undefined ? String(val) : undefined;
                        }
                        return {
                            type: s.action,
                            selector: s.target,
                            altSelectors: [],
                            value: resolvedValue,
                            label: s.inputKey || s.mapTo,
                            attribute: s.attribute,
                            paginateTarget: s.paginateTarget,
                            url: page.url(),
                            timestamp: Date.now(),
                            isBootstrap: true,
                            originalStep: s
                        };
                    });
                }
            }
            catch (err) {
                Logger.error('Bootstrap phase failed. Proceeding to manual recording.', err);
            }
        }
        let isOverwriteFromClient = false;
        let onlyCacheFromClient = false;
        // 3. Expose bindings
        await context.exposeBinding('recordEvent', async ({ page }, event) => {
            if (event.type === 'stop') {
                Logger.log('Stop signal received from UI.');
                docTypeFromClient = event.docType;
                isOverwriteFromClient = event.isOverwrite || false;
                onlyCacheFromClient = event.onlyCache || false;
                if (isRecordingLoop && tempLoopSteps.length > 0) {
                    Logger.log(`Auto-finalizing active loop with ${tempLoopSteps.length} steps before stop.`);
                    const loopStep = {
                        type: 'scrapeDeep',
                        selector: this.currentLoopSelector || 'auto_finalized',
                        altSelectors: [],
                        label: this.currentLoopLabel || 'auto_loop',
                        value: JSON.stringify(tempLoopSteps),
                        paginateTarget: this.currentPaginateTarget,
                        url: page.url(),
                        timestamp: Date.now()
                    };
                    capturedEvents.push(loopStep);
                }
                isStopped = true;
                return;
            }
            if (event.type === 'preview') {
                Logger.log('Preview signal received from UI.');
                const generated = await Generator.generate(name, capturedEvents, { ...config, url, docType: event.docType, onlyCache: true });
                Recorder.emitter.emit('preview', { generated });
                return;
            }
            if (event.type === 'discard') {
                Logger.log('Discard signal received from UI. Session will be abandoned.');
                isDiscarded = true;
                isStopped = true;
                return;
            }
            if (event.type === 'startLoop') {
                Logger.log(`Deep Loop session started for ${event.selector || 'unknown'}`);
                isRecordingLoop = true;
                tempLoopSteps = [];
                // Store these for auto-finalization safety
                this.currentLoopSelector = event.selector;
                this.currentPaginateTarget = event.paginateTarget;
                this.currentLoopLabel = event.label || 'deep_loop';
                return;
            }
            if (event.type === 'finalizeLoop') {
                Logger.log(`Deep Loop finalized with ${tempLoopSteps.length} sub-steps.`);
                const loopStep = {
                    type: 'scrapeDeep',
                    selector: event.selector,
                    altSelectors: event.altSelectors || [],
                    label: event.label || 'deep_extraction',
                    value: JSON.stringify(tempLoopSteps),
                    paginateTarget: event.paginateTarget,
                    url: event.url,
                    timestamp: event.timestamp
                };
                capturedEvents.push(loopStep);
                isRecordingLoop = false;
                tempLoopSteps = [];
                return;
            }
            Logger.log(`[Recorder${isRecordingLoop ? ':Loop' : ''}] Event: ${event.type} | Target: ${event.label || event.selector || 'N/A'}`);
            if (isRecordingLoop) {
                tempLoopSteps.push(event);
            }
            else {
                capturedEvents.push(event);
            }
        });
        await context.exposeBinding('removeEvent', async ({ page }, { index, isLoopStep }) => {
            if (isLoopStep) {
                if (index >= 0 && index < tempLoopSteps.length) {
                    const removed = tempLoopSteps.splice(index, 1);
                    Logger.log(`[Recorder:Loop] Removed step: ${removed[0].type} at index ${index}`);
                }
            }
            else {
                if (index >= 0 && index < capturedEvents.length) {
                    const removed = capturedEvents.splice(index, 1);
                    Logger.log(`[Recorder] Removed step: ${removed[0].type} at index ${index}`);
                }
            }
        });
        await context.exposeBinding('getLatestEvents', async () => {
            return {
                capturedEvents,
                isRecordingLoop,
                tempLoopSteps
            };
        });
        // 4. Inject client script...
        const clientScriptPath = path.resolve(__dirname, './scripts/client.js');
        const clientScript = fs.readFileSync(clientScriptPath, 'utf-8');
        // Inject session metadata for the client UI
        const metadataScript = `
        window.ag_session_meta = ${JSON.stringify({
            project: config.project,
            tags: config.tags,
            mode: config.mode,
            docType: config.doc_type || config.docType,
            initialEvents: capturedEvents
        })};
    `;
        await context.addInitScript(metadataScript + clientScript);
        // Force a reload or small wait to ensure script injection on current page if needed
        // But since bootstrap likely navigated, we are on a page where client.js will load on next interaction/refresh
        // Actually, we should evaluate the script once manually to show the overlay IMMEDIATELY
        await page.addScriptTag({ content: metadataScript + clientScript });
        return new Promise((resolve) => {
            const interval = setInterval(async () => {
                if (isStopped || page.isClosed()) {
                    clearInterval(interval);
                    if (isStopped && !isDiscarded) {
                        Logger.log(`Committing recording session (${capturedEvents.length} events)`);
                        await Generator.generate(name, capturedEvents, { ...config, url, docType: docTypeFromClient, isOverwrite: isOverwriteFromClient });
                    }
                    else {
                        Logger.log('Session discarded or browser closed without stop signal. No files generated.');
                    }
                    Recorder.lastCapturedEvents = capturedEvents;
                    await browser.close();
                    resolve();
                }
            }, 1000);
        });
    }
    static async saveRecording() {
        // Legacy method - functionality moved to Generator
    }
    static getCapturedEvents() {
        return this.lastCapturedEvents;
    }
}
