"use strict";
/**
 * Vision Navigator Agent
 *
 * Autonomous agent that navigates UIs using vision-based understanding.
 * Observes -> Thinks -> Acts loop powered by visual AI.
 *
 * Communicates via Living Brain (context.json) following the blackboard pattern.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VisionNavigatorAgent = void 0;
const vision_element_finder_1 = require("../../services/vision-element-finder");
const ContextService_1 = require("../../services/ContextService");
const AICore_1 = require("../../core/ai/AICore");
const uuid_1 = require("uuid");
const chalk_1 = __importDefault(require("chalk"));
class VisionNavigatorAgent {
    constructor() {
        this.name = "VisionNavigatorAgent";
        this.description = "Autonomous navigator that understands UIs through vision and executes test missions";
        this.personality = `You are a meticulous AI agent that navigates web applications by
visually understanding the UI, just like a human would. You see the page, understand the context,
and make intelligent decisions about what to click, fill, or interact with. You ALWAYS validate
your actions against the project's Prime Objective to prevent mission drift.`;
        this.llmProvider = "gemini-vision"; // Uses Gemini for vision
        this.expertise = [
            "Vision-based UI understanding",
            "Autonomous navigation",
            "Intent validation",
            "Adaptive element finding",
            "Test execution",
        ];
    }
    async run(input) {
        const { page, mission, projectPath, maxSteps = 20 } = input;
        // Initialize services
        this.visionFinder = new vision_element_finder_1.VisionElementFinder(projectPath);
        this.contextService = new ContextService_1.ContextService(projectPath);
        await this.contextService.initialize();
        // Get Prime Objective from manifest (for gravity checking)
        const manifest = this.contextService.getManifest();
        const primeObjective = manifest?.phases?.functional_summary?.core_purpose ||
            "Complete the test mission";
        console.log(chalk_1.default.blue(`\n🤖 ${this.name} starting navigation...`));
        console.log(chalk_1.default.gray(`Mission: ${mission.mission}`));
        console.log(chalk_1.default.gray(`Prime Objective: ${primeObjective}\n`));
        // Write mission start to Living Brain
        await this.writeLivingBrain({
            timestamp: new Date().toISOString(),
            agentName: this.name,
            updateType: "test-start",
            data: {
                missionId: mission.id,
                mission: mission.mission,
                expectedOutcome: mission.expectedOutcome,
            },
            metadata: {
                testId: mission.id,
            },
        });
        const steps = [];
        let completed = false;
        let reason = "";
        let stepOrder = 0;
        try {
            // Main navigation loop: Observe -> Think -> Act
            while (!completed && stepOrder < maxSteps) {
                console.log(chalk_1.default.cyan(`\n📸 Step ${stepOrder + 1}: Observing...`));
                // 1. OBSERVE: Analyze current page state with vision
                const pageAnalysis = await this.visionFinder.analyzePage(page);
                console.log(chalk_1.default.gray(`   UI State: ${pageAnalysis.aiInterpretation}`));
                // Fail-fast: detect blocking toast/auth errors and stop with a clear assertion message.
                const blockerToast = await this.detectBlockingToast(page);
                if (blockerToast) {
                    const failedStep = {
                        id: (0, uuid_1.v4)(),
                        order: stepOrder++,
                        action: "assertion: Assertion B (sign-in) failed",
                        intent: "Detect login blocker and stop",
                        timestamp: new Date().toISOString(),
                        success: false,
                        error: `Assertion B failed: ${blockerToast}`,
                        visionDecision: {
                            action: "complete",
                            confidence: 100,
                            reasoning: `Blocking toast detected: ${blockerToast}`,
                            visualContext: pageAnalysis.aiInterpretation,
                            intent: "Fail fast on blocker",
                            alignsWithPrimeObjective: true,
                            gravityScore: 100,
                        },
                        metadata: { url: page.url() },
                        screenshot: pageAnalysis.screenshot,
                        visualAnalysis: {
                            componentsDetected: (pageAnalysis.elements || []).map((e) => e.description),
                            interactiveElements: (pageAnalysis.interactiveElements || []).map((e) => e.description),
                            textContent: [pageAnalysis.uiState?.mainContent || ""],
                            colorPalette: pageAnalysis.designSystem?.colors || [],
                            layoutStructure: pageAnalysis.layoutStructure?.type || "unknown",
                        },
                    };
                    steps.push(failedStep);
                    reason = failedStep.error || "Assertion failed";
                    break;
                }
                // ISSUE #4 FIX: Stuck-loop detection — detect semantic repetition
                // Normalizing action: Remove timestamps (24:60 etc) and specific UUIDs/IDs
                const normalizeAction = (a) => a.replace(/\d{2}:\d{2}:\d{2}/g, "HH:MM:SS")
                    .replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g, "UUID")
                    .replace(/Venue \d+/g, "Venue TIMESTAMP");
                const recentActions = steps.slice(-4).map(s => normalizeAction(s.action));
                const isStuckInLoop = recentActions.length >= 3 &&
                    recentActions.every(a => a === recentActions[0]);
                const previousStepDescriptions = steps.map((s) => `${s.action} (${s.success ? "✓" : "✗"})`);
                if (isStuckInLoop) {
                    console.log(chalk_1.default.yellow(`\n⚠️  Agent stuck in loop: "${recentActions[0]}". Injecting escape hint.`));
                    previousStepDescriptions.push(`SYSTEM: You have repeated the same action ("${recentActions[0]}") ${recentActions.length} times with no progress. You MUST try a completely different approach (e.g. refresh, different button, or check for overlays) or skip to the next mission step.`);
                }
                console.log(chalk_1.default.yellow(`\n🤔 Thinking: Deciding next action...`));
                // ISSUE #5 FIX: Wrap decideNextAction with retry + graceful fallback
                let decision;
                try {
                    decision = await this.visionFinder.decideNextAction(page, mission.mission, previousStepDescriptions, primeObjective, mission.testData);
                }
                catch (decideError) {
                    console.log(chalk_1.default.yellow(`   ⚠️  Decision failed: ${decideError.message?.substring(0, 80)}. Retrying...`));
                    await page.waitForTimeout(3000);
                    try {
                        decision = await this.visionFinder.decideNextAction(page, mission.mission, previousStepDescriptions, primeObjective, mission.testData);
                    }
                    catch (retryError) {
                        console.log(chalk_1.default.red(`   ❌ Persistent decision failure. Forcing 'wait' step.`));
                        decision = {
                            action: "wait",
                            confidence: 30,
                            reasoning: "Vision AI persistent failure - stabilizing state",
                            visualContext: "Decision engine error",
                            intent: "Wait for stabilizer",
                            alignsWithPrimeObjective: true,
                            gravityScore: 100
                        };
                    }
                }
                // Frustration Check (Self-Healing)
                const failureStreak = steps.filter(s => !s.success).length; // Simple proxy
                // Better: count consecutive failures from end
                let consecutiveFailures = 0;
                for (let i = steps.length - 1; i >= 0; i--) {
                    const step = steps[i];
                    if (step && !step.success)
                        consecutiveFailures++;
                    else
                        break;
                }
                if (consecutiveFailures >= 2) {
                    console.log(chalk_1.default.red.bold(`\n😡 Frustration limits reached! Forcing page refresh.`));
                    await page.reload({ waitUntil: "domcontentloaded" });
                    decision.action = "wait";
                    decision.reasoning = "Self-healing backoff due to repeating errors.";
                }
                else if (steps.length > 0) {
                    // Visual Delta Check: if action was successful but state is identical
                    const lastStep = steps[steps.length - 1];
                    if (lastStep && lastStep.success && lastStep.action.startsWith("click")) {
                        if (lastStep.visionDecision?.visualContext === pageAnalysis.aiInterpretation && lastStep.metadata?.url === page.url()) {
                            console.log(chalk_1.default.yellow(`   ⚠️  Visual state unchanged after click. Increasing frustration awareness.`));
                            // Injecting a hint for next step
                            previousStepDescriptions.push("SYSTEM: The previous click did not appear to change the UI state visually. The next action should be different.");
                        }
                    }
                }
                console.log(chalk_1.default.gray(`   Decision: ${decision.action}`));
                console.log(chalk_1.default.gray(`   Reasoning: ${decision.reasoning}`));
                console.log(chalk_1.default.gray(`   Confidence: ${decision.confidence}%`));
                // Guardrail: prevent premature "complete" unless end-state is plausible.
                // Models sometimes answer "complete" when unsure; we force a concrete next step.
                if (decision.action === "complete") {
                    const allowComplete = steps.length >= 6 ||
                        (/\/admin\b/.test(page.url()) &&
                            /create challenge|published|saved|success/i.test(pageAnalysis.aiInterpretation || "")) ||
                        (/\/admin\b/.test(page.url()) &&
                            /challenge/i.test(pageAnalysis.aiInterpretation || "") &&
                            /create|new|publish|saved|success/i.test(pageAnalysis.aiInterpretation || ""));
                    if (!allowComplete) {
                        console.log(chalk_1.default.yellow(`\n⚠️  Premature "complete" detected. Forcing re-decision with stricter guidance.`));
                        const stricterPrevious = [
                            ...previousStepDescriptions,
                            `SYSTEM: You attempted to end the mission ("complete") too early. You MUST pick a concrete next action that advances the mission (click/fill/navigate/wait). Do NOT return "complete" unless you can cite on-screen evidence that the final goal (challenge created + visible) is achieved.`,
                        ];
                        try {
                            decision = await this.visionFinder.decideNextAction(page, mission.mission, stricterPrevious, primeObjective, mission.testData);
                            console.log(chalk_1.default.gray(`   Re-decision: ${decision.action}`));
                            console.log(chalk_1.default.gray(`   Re-reasoning: ${decision.reasoning}`));
                        }
                        catch {
                            // As a safe fallback, keep the loop alive.
                            decision = {
                                action: "wait",
                                confidence: 20,
                                reasoning: "Model attempted premature complete; stabilizing and retrying.",
                                visualContext: pageAnalysis.aiInterpretation || "unknown",
                                intent: "Wait before retry",
                                alignsWithPrimeObjective: true,
                                gravityScore: 80,
                            };
                        }
                        // If the model STILL wants to "complete", force a deterministic first step.
                        if (decision.action === "complete") {
                            console.log(chalk_1.default.yellow(`   ⚠️  Still got "complete". Forcing a concrete start action (Register).`));
                            decision = {
                                action: "click",
                                confidence: 70,
                                reasoning: "Forced fallback: start mission by clicking the Register club/venue CTA on landing page.",
                                visualContext: pageAnalysis.aiInterpretation || "unknown",
                                targetElement: {
                                    description: "Register your club / Register club / Register venue",
                                    domSelector: "a:has-text(\"Register\"), button:has-text(\"Register\"), a[href*=\"/en/register\"], a[href$=\"/register\"]",
                                    confidence: 70,
                                },
                                intent: "Open club registration",
                                alignsWithPrimeObjective: true,
                                gravityScore: 100,
                            };
                        }
                    }
                }
                // Gravity Check: Validate alignment with Prime Objective
                if (!decision.alignsWithPrimeObjective) {
                    console.log(chalk_1.default.red(`\n🛑 GRAVITY ALERT: Decision deviates from Prime Objective!`));
                    console.log(chalk_1.default.yellow(`   Gravity Score: ${decision.gravityScore}%`));
                    // Record failed step
                    const failedStep = {
                        id: (0, uuid_1.v4)(),
                        order: stepOrder++,
                        action: `REJECTED: ${decision.action}`,
                        intent: decision.intent,
                        timestamp: new Date().toISOString(),
                        success: false,
                        error: `Gravity intervention: Action doesn't align with Prime Objective`,
                        visionDecision: decision,
                    };
                    steps.push(failedStep);
                    await this.writeLivingBrain({
                        timestamp: new Date().toISOString(),
                        agentName: this.name,
                        updateType: "test-step",
                        data: failedStep,
                        metadata: {
                            testId: mission.id,
                            stepId: failedStep.id,
                        },
                    });
                    // Ask AI to reconsider
                    console.log(chalk_1.default.yellow(`   Re-thinking with gravity awareness...`));
                    // For now, we'll break the loop
                    // In production, you'd retry with corrected context
                    reason = "Gravity intervention - mission drift prevented";
                    break;
                }
                // Check if mission is complete
                if (decision.action === "complete") {
                    completed = true;
                    reason = "Mission completed successfully";
                    console.log(chalk_1.default.green(`\n✅ Mission complete!`));
                    break;
                }
                // 3. ACT: Execute the decided action(s)
                console.log(chalk_1.default.magenta(`\n⚡ Acting: Executing ${decision.action}...`));
                if (decision.action === "multi_action" && decision.subActions && decision.subActions.length > 0) {
                    console.log(chalk_1.default.cyan(`   🔗 Processing batch of ${decision.subActions.length} sub-actions...`));
                    for (const subAction of decision.subActions) {
                        console.log(chalk_1.default.gray(`   ↳ ${subAction.action}: ${subAction.targetElement.description}`));
                        // Execute as a sub-step
                        const subStep = await this.executeAction(page, {
                            ...decision,
                            action: subAction.action,
                            targetElement: subAction.targetElement,
                            value: subAction.value
                        }, stepOrder++, mission);
                        subStep.screenshot = pageAnalysis?.screenshot;
                        steps.push(subStep);
                        // Write sub-step to Living Brain
                        await this.writeLivingBrain({
                            timestamp: new Date().toISOString(),
                            agentName: this.name,
                            updateType: "test-step",
                            data: subStep,
                            metadata: {
                                testId: mission.id,
                                executionId: `${mission.id}-${Date.now()}`,
                                stepId: subStep.id,
                            },
                        });
                        if (!subStep.success) {
                            console.log(chalk_1.default.red(`   ❌ Batch member failed: ${subStep.error}`));
                            break;
                        }
                    }
                }
                else {
                    const step = await this.executeAction(page, decision, stepOrder++, mission);
                    // Attach screenshot and visual analysis from the OBSERVE phase
                    step.screenshot = pageAnalysis?.screenshot;
                    step.visualAnalysis = {
                        componentsDetected: (pageAnalysis?.elements || []).map(e => e.description),
                        interactiveElements: (pageAnalysis?.interactiveElements || []).map(e => e.description),
                        textContent: [pageAnalysis?.uiState?.mainContent || ""],
                        colorPalette: pageAnalysis?.designSystem?.colors || [],
                        layoutStructure: pageAnalysis?.layoutStructure?.type || "unknown"
                    };
                    steps.push(step);
                    // Write step to Living Brain
                    await this.writeLivingBrain({
                        timestamp: new Date().toISOString(),
                        agentName: this.name,
                        updateType: "test-step",
                        data: step,
                        metadata: {
                            testId: mission.id,
                            executionId: `${mission.id}-${Date.now()}`,
                            stepId: step.id,
                        },
                    });
                    if (!step.success) {
                        console.log(chalk_1.default.red(`   ❌ Step failed: ${step.error}`));
                        reason = `Step failed: ${step.error}`;
                        break;
                    }
                }
                console.log(chalk_1.default.green(`   ✓ Step(s) successful`));
                // Brief pause for page to settle (Spinners can take a few hundred ms to appear)
                await page.waitForTimeout(1200);
            }
            // Check if we hit max steps
            if (stepOrder >= maxSteps && !completed) {
                reason = `Reached maximum steps (${maxSteps})`;
                console.log(chalk_1.default.yellow(`\n⚠️  ${reason}`));
            }
            const finalUrl = page.url();
            // Write completion to Living Brain
            await this.writeLivingBrain({
                timestamp: new Date().toISOString(),
                agentName: this.name,
                updateType: "test-complete",
                data: {
                    completed,
                    totalSteps: steps.length,
                    successfulSteps: steps.filter((s) => s.success).length,
                    finalUrl,
                    reason,
                },
                metadata: {
                    testId: mission.id,
                },
            });
            console.log(chalk_1.default.blue(`\n🏁 Navigation complete: ${steps.length} steps executed`));
            return {
                steps,
                completed,
                finalUrl,
                reason,
            };
        }
        catch (error) {
            console.error(chalk_1.default.red(`\n❌ Navigation error: ${error.message}`));
            // Write error to Living Brain
            await this.writeLivingBrain({
                timestamp: new Date().toISOString(),
                agentName: this.name,
                updateType: "error",
                data: {
                    error: error.message,
                    stack: error.stack,
                    stepsCompleted: steps.length,
                },
                metadata: {
                    testId: mission.id,
                },
            });
            throw error;
        }
    }
    async detectBlockingToast(page) {
        try {
            const text = await page.evaluate(() => {
                const candidates = [];
                const pickText = (el) => (el.textContent || "")
                    .replace(/\s+/g, " ")
                    .trim();
                // Common toast / alert surfaces (framework-agnostic)
                const selectors = [
                    "[data-sonner-toast]",
                    "[data-toast]",
                    "[role='alert']",
                    "[role='status']",
                    "[aria-live='polite']",
                    "[aria-live='assertive']",
                    ".toast",
                    ".sonner-toast",
                ];
                for (const sel of selectors) {
                    document.querySelectorAll(sel).forEach((el) => {
                        const t = pickText(el);
                        if (t)
                            candidates.push(t);
                    });
                }
                // Known hard-blocker fallback (only on login; toast DOM can be transient)
                // Avoid false positives on other pages by gating on URL.
                const href = window.location.href;
                if (href.includes("/login")) {
                    const bodyText = pickText(document.body);
                    if (bodyText.includes("Email not authorized for admin access.")) {
                        candidates.push("Email not authorized for admin access.");
                    }
                }
                return candidates.length ? candidates[candidates.length - 1] : null;
            });
            if (!text)
                return null;
            const normalized = text.toLowerCase();
            if (normalized.includes("email not authorized for admin access") ||
                (normalized.includes("not authorized") && page.url().includes("/en/login"))) {
                return text;
            }
            return null;
        }
        catch {
            return null;
        }
    }
    /**
     * Execute an action decided by the AI
     */
    async executeAction(page, decision, order, mission) {
        const step = {
            id: (0, uuid_1.v4)(),
            order,
            action: `${decision.action}: ${decision.targetElement?.description || decision.value || decision.url || ""}`,
            intent: decision.intent,
            timestamp: new Date().toISOString(),
            success: false,
            visionDecision: decision,
        };
        try {
            switch (decision.action) {
                case "click":
                    if (!decision.targetElement) {
                        throw new Error("No target element specified for click action");
                    }
                    const clickSuccess = await this.visionFinder.clickElement(page, decision.targetElement);
                    if (!clickSuccess) {
                        throw new Error("Click action failed");
                    }
                    step.metadata = {
                        elementSelector: decision.targetElement.domSelector,
                        clickedElement: decision.targetElement.description,
                    };
                    step.success = true;
                    break;
                case "fill":
                    if (!decision.targetElement || !decision.value) {
                        throw new Error("No target element or value specified for fill action");
                    }
                    const fillSuccess = await this.visionFinder.fillElement(page, decision.targetElement, decision.value);
                    if (!fillSuccess) {
                        throw new Error("Fill action failed");
                    }
                    step.metadata = {
                        elementSelector: decision.targetElement.domSelector,
                        inputValue: decision.value,
                    };
                    step.success = true;
                    break;
                case "navigate":
                    if (!decision.url) {
                        throw new Error("No URL specified for navigate action");
                    }
                    await page.goto(decision.url);
                    step.metadata = {
                        navigationTarget: decision.url,
                        url: page.url(),
                    };
                    step.success = true;
                    break;
                case "scroll":
                    await page.evaluate(() => {
                        window.scrollBy(0, window.innerHeight);
                    });
                    step.success = true;
                    break;
                case "wait":
                    await page.waitForTimeout(2000);
                    step.success = true;
                    break;
                case "upload":
                    // File upload would be handled here
                    // Similar to the existing implementation
                    step.error = "Upload action not yet implemented in VisionNavigatorAgent";
                    break;
                default:
                    step.error = `Unknown action: ${decision.action}`;
            }
        }
        catch (error) {
            step.error = error.message;
            step.success = false;
        }
        return step;
    }
    /**
     * Write update to Living Brain (context.json)
     */
    async writeLivingBrain(update) {
        try {
            // Read current context using AICore
            const aiCore = AICore_1.AICore.getInstance();
            const currentContext = (await aiCore.getLivingContext()) || {};
            // Add to test execution history
            if (!currentContext.testExecutionHistory) {
                currentContext.testExecutionHistory = [];
            }
            currentContext.testExecutionHistory.push(update);
            // Keep only last 100 updates to prevent bloat
            if (currentContext.testExecutionHistory.length > 100) {
                currentContext.testExecutionHistory = currentContext.testExecutionHistory.slice(-100);
            }
            // Save back to context.json using AICore
            await aiCore.saveLivingContext(currentContext);
        }
        catch (error) {
            console.warn("Failed to write to Living Brain:", error);
            // Don't throw - Living Brain writes are best-effort
        }
    }
    async validate(input) {
        return !!(input.page && input.mission && input.projectPath);
    }
    async getStatus() {
        return {
            name: this.name,
            status: "idle",
            errorCount: 0,
            successCount: 0,
        };
    }
}
exports.VisionNavigatorAgent = VisionNavigatorAgent;
//# sourceMappingURL=VisionNavigatorAgent.js.map