/**
 * @license
 * Copyright 2025 ReverseCraft
 * SPDX-License-Identifier: Apache-2.0
 */
import { logger } from './logger.js';
import { clearParseResultCache } from '../tools/analysis.js';
import { getCdpSession } from './cdp.js';
import { cacheScript, clearScriptCache } from './smart-breakpoint-utils.js';
const debuggerStates = new WeakMap();
export function getDebuggerState(page) {
    let state = debuggerStates.get(page);
    if (!state) {
        state = {
            enabled: false,
            isPaused: false,
            activeBreakpoints: new Map(),
            userDisabled: false,
        };
        debuggerStates.set(page, state);
    }
    return state;
}
/**
 * Get all active breakpoints from CDP.
 * Since CDP doesn't have a direct "list all breakpoints" API, we track them
 * when they are set and verify they still exist.
 *
 * @param page - The page to get breakpoints for
 * @returns Array of active breakpoint information
 */
export async function getActiveBreakpoints(page) {
    const state = getDebuggerState(page);
    const activeBreakpoints = [];
    for (const [bpId, storedInfo] of state.activeBreakpoints) {
        // Parse the breakpoint ID to extract URL pattern
        // CDP breakpoint IDs from setBreakpointByUrl have format like: "1:14:0:.*app\\.js.*"
        // Format: lineNumber:columnNumber:scriptHash:urlRegex
        const parts = bpId.split(':');
        let urlRegex = '(unknown)';
        if (parts.length >= 4) {
            urlRegex = parts.slice(3).join(':'); // URL regex might contain colons
        }
        activeBreakpoints.push({
            breakpointId: bpId,
            url: urlRegex,
            lineNumber: storedInfo.lineNumber,
            columnNumber: storedInfo.columnNumber ?? 0,
            condition: storedInfo.condition,
        });
    }
    return activeBreakpoints;
}
/**
 * Track a newly set breakpoint with its metadata.
 *
 * @param page - The page the breakpoint is set on
 * @param breakpointId - The CDP breakpoint ID
 * @param info - The breakpoint metadata (line number, column, condition)
 */
export function trackBreakpoint(page, breakpointId, info) {
    const state = getDebuggerState(page);
    state.activeBreakpoints.set(breakpointId, info);
}
/**
 * Untrack a removed breakpoint
 */
export function untrackBreakpoint(page, breakpointId) {
    const state = getDebuggerState(page);
    state.activeBreakpoints.delete(breakpointId);
}
/**
 * Get count of tracked breakpoints
 */
export function getBreakpointCount(page) {
    const state = getDebuggerState(page);
    return state.activeBreakpoints.size;
}
/**
 * Clear all tracked breakpoints (used after clear_all_breakpoints)
 */
export function clearTrackedBreakpoints(page) {
    const state = getDebuggerState(page);
    state.activeBreakpoints.clear();
}
// Track whether we've set up navigation listeners per page
const navigationListenerSetup = new WeakSet();
/**
 * Re-enable debugger after navigation.
 * Note: CDP breakpoints set via setBreakpointByUrl persist across navigations automatically,
 * so we don't need to manually restore them. We just need to ensure the debugger is enabled.
 *
 * Respects user's explicit disable_debugger call - if userDisabled is true,
 * the debugger will NOT be re-enabled after navigation.
 */
export async function restoreBreakpointsAfterNavigation(page, session) {
    const state = getDebuggerState(page);
    // Respect user's explicit disable_debugger call
    if (state.userDisabled) {
        logger('[debugger] Debugger was explicitly disabled by user, skipping re-enable after navigation');
        return;
    }
    // Re-enable debugger after navigation
    try {
        await session.send('Debugger.enable');
        state.enabled = true;
        logger('[debugger] Debugger re-enabled after navigation');
    }
    catch {
        // Debugger might already be enabled, ignore
    }
    // CDP breakpoints set via setBreakpointByUrl are URL-based and persist across navigations.
    // They will automatically resolve to new script locations when matching scripts are loaded.
    // No manual restoration needed.
}
/**
 * Initialize the debugger for a page proactively.
 * This should be called when a page is created or selected to ensure
 * that the debugger is enabled before scripts load.
 *
 * @param page - The page to initialize debugger for
 * @param options - Optional configuration
 * @param options.forceEnable - When true, resets userDisabled flag to allow re-enabling
 *                              the debugger even if it was previously disabled by the user.
 *                              Use this for explicit user actions like set_breakpoint.
 */
export async function initializeDebuggerForPage(page, options) {
    const session = await getCdpSession(page);
    const state = getDebuggerState(page);
    // Only reset userDisabled flag when explicitly requested (e.g., by set_breakpoint)
    // This preserves the user's choice to disable debugger via navigate_page or disable_debugger
    if (options?.forceEnable) {
        state.userDisabled = false;
    }
    // Respect user's explicit disable_debugger call
    if (state.userDisabled) {
        return session;
    }
    if (!state.enabled) {
        await session.send('Debugger.enable');
        state.enabled = true;
        // Listen for script parsed events to populate the script cache
        session.on('Debugger.scriptParsed', async (params) => {
            const scriptId = params.scriptId;
            const url = params.url || '';
            // Cache script metadata for breakpoint utilities
            cacheScript(session, {
                scriptId,
                url,
                startLine: params.startLine,
                startColumn: params.startColumn,
                endLine: params.endLine,
                endColumn: params.endColumn,
            });
            logger(`[debugger] Script parsed: ${url || scriptId}`);
        });
        // Listen for debugger paused events
        session.on('Debugger.paused', (params) => {
            state.isPaused = true;
            state.pausedCallFrames = params.callFrames?.map((frame) => ({
                callFrameId: frame.callFrameId,
                functionName: frame.functionName || '(anonymous)',
                location: {
                    scriptId: frame.location.scriptId,
                    lineNumber: frame.location.lineNumber,
                    columnNumber: frame.location.columnNumber,
                },
                url: frame.url || '',
                scopeChain: frame.scopeChain?.map((scope) => ({
                    type: scope.type,
                    object: {
                        type: scope.object?.type,
                        objectId: scope.object?.objectId,
                    },
                    name: scope.name,
                })) || [],
            }));
            logger('[debugger] Execution paused at breakpoint');
        });
        session.on('Debugger.resumed', () => {
            state.isPaused = false;
            state.pausedCallFrames = undefined;
            logger('[debugger] Execution resumed');
        });
    }
    // Set up navigation listener once per page to restore breakpoints after page refresh
    if (!navigationListenerSetup.has(page)) {
        navigationListenerSetup.add(page);
        // Get the main frame ID via CDP for accurate comparison
        let mainFrameId;
        try {
            const frameTree = await session.send('Page.getFrameTree');
            mainFrameId = frameTree.frameTree?.frame?.id;
        }
        catch {
            // Fallback: will restore breakpoints for all frame loads
        }
        // Listen for frame STARTED loading events
        session.on('Page.frameStartedLoading', async (params) => {
            let isMainFrame = !mainFrameId || params.frameId === mainFrameId;
            if (!isMainFrame) {
                try {
                    const frameTree = await session.send('Page.getFrameTree');
                    const currentMainFrameId = frameTree.frameTree?.frame?.id;
                    if (params.frameId === currentMainFrameId) {
                        isMainFrame = true;
                        mainFrameId = currentMainFrameId;
                    }
                }
                catch {
                    isMainFrame = true;
                }
            }
            if (isMainFrame) {
                // Clear caches on navigation
                clearScriptCache(session);
                clearParseResultCache(session);
                logger('[debugger] Script cache and parse result cache cleared on navigation');
                // Always restore breakpoints (even if empty to ensure debugger is enabled and ready)
                logger('[debugger] Main frame started loading, initializing debugger/breakpoints...');
                try {
                    await restoreBreakpointsAfterNavigation(page, session);
                }
                catch (err) {
                    logger(`[debugger] Error restoring breakpoints: ${err}`);
                }
                // Update main frame ID
                try {
                    const newFrameTree = await session.send('Page.getFrameTree');
                    mainFrameId = newFrameTree.frameTree?.frame?.id;
                }
                catch {
                    // Ignore
                }
            }
        });
        // Enable Page domain to receive frameStartedLoading events
        try {
            await session.send('Page.enable');
        }
        catch {
            // Ignore
        }
    }
    return session;
}
/**
 * Get a summary of active breakpoints for a page.
 * This is useful for displaying breakpoint info in list_pages, new_page, navigate_page responses.
 *
 * @param page - The page to get breakpoint summary for
 * @returns Array of formatted breakpoint summary strings, or empty array if no breakpoints
 */
export async function getBreakpointSummary(page) {
    const breakpoints = await getActiveBreakpoints(page);
    const lines = [];
    if (breakpoints.length === 0) {
        return lines;
    }
    lines.push(`🔴 Active breakpoints (${breakpoints.length}):`);
    for (const bp of breakpoints) {
        const location = `line ${bp.lineNumber}, col ${bp.columnNumber}`;
        const condition = bp.condition ? ` [condition: ${bp.condition}]` : '';
        lines.push(`   • ${bp.breakpointId}: ${bp.url} @ ${location}${condition}`);
    }
    return lines;
}
// ============================================================================
// XHR Breakpoint Tracking
// ============================================================================
// Store XHR breakpoint URL patterns per page
// CDP does not provide a way to list XHR breakpoints, so we track them ourselves
const xhrBreakpointsByPage = new WeakMap();
/**
 * Track an XHR breakpoint URL pattern for a page.
 *
 * @param page - The page the breakpoint is set on
 * @param urlPattern - The URL substring pattern for the breakpoint
 */
export function trackXhrBreakpoint(page, urlPattern) {
    let patterns = xhrBreakpointsByPage.get(page);
    if (!patterns) {
        patterns = new Set();
        xhrBreakpointsByPage.set(page, patterns);
    }
    patterns.add(urlPattern);
}
/**
 * Untrack an XHR breakpoint URL pattern for a page.
 *
 * @param page - The page the breakpoint was set on
 * @param urlPattern - The URL substring pattern to remove
 * @returns true if the pattern was found and removed, false otherwise
 */
export function untrackXhrBreakpoint(page, urlPattern) {
    const patterns = xhrBreakpointsByPage.get(page);
    if (!patterns) {
        return false;
    }
    return patterns.delete(urlPattern);
}
/**
 * Get all tracked XHR breakpoint URL patterns for a page.
 *
 * @param page - The page to get breakpoints for
 * @returns Array of URL substring patterns
 */
export function getTrackedXhrBreakpoints(page) {
    const patterns = xhrBreakpointsByPage.get(page);
    if (!patterns) {
        return [];
    }
    return Array.from(patterns);
}
/**
 * Clear all tracked XHR breakpoints for a page.
 *
 * @param page - The page to clear breakpoints for
 */
export function clearTrackedXhrBreakpoints(page) {
    const patterns = xhrBreakpointsByPage.get(page);
    if (patterns) {
        patterns.clear();
    }
}
//# sourceMappingURL=debugger-utils.js.map