"use strict";
/**
 * Visual Validator Agent
 *
 * Performs visual regression testing by comparing screenshots.
 * Detects UI changes, color scheme drift, layout shifts, and more.
 *
 * Communicates via Living Brain (context.json).
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VisualValidatorAgent = void 0;
const gemini_vision_1 = require("../../services/gemini-vision");
const ContextService_1 = require("../../services/ContextService");
const AICore_1 = require("../../core/ai/AICore");
const uuid_1 = require("uuid");
const fs = __importStar(require("fs-extra"));
const chalk_1 = __importDefault(require("chalk"));
class VisualValidatorAgent {
    constructor() {
        this.name = "VisualValidatorAgent";
        this.description = "Validates UI appearance through visual comparison and regression detection";
        this.personality = `You are a meticulous visual QA engineer with a keen eye for detail.
You detect even the smallest visual discrepancies, color shifts, layout changes, and design
inconsistencies. Your goal is to ensure the UI matches expectations pixel-perfectly while being
smart about what changes matter vs. what's acceptable variation.`;
        this.llmProvider = "gemini-vision";
        this.expertise = [
            "Visual regression testing",
            "Screenshot comparison",
            "Color scheme validation",
            "Layout verification",
            "Accessibility checks",
            "Design system compliance",
        ];
        this.visionService = new gemini_vision_1.GeminiVisionService();
    }
    async run(input) {
        const { currentScreenshot, baselineScreenshot, validationRules, projectPath, page, } = input;
        // Initialize context service
        this.contextService = new ContextService_1.ContextService(projectPath);
        await this.contextService.initialize();
        console.log(chalk_1.default.blue(`\n🔍 ${this.name} starting validation...`));
        console.log(chalk_1.default.gray(`Validation rules: ${validationRules.length}`));
        const results = [];
        let visualDiff;
        try {
            // Run each validation rule
            for (const rule of validationRules) {
                console.log(chalk_1.default.cyan(`\n  📋 Checking: ${rule.description}`));
                let result;
                switch (rule.type) {
                    case "visual-match":
                        result = await this.validateVisualMatch(currentScreenshot, baselineScreenshot || rule.baselineScreenshot, rule);
                        if (result.visualDiff) {
                            visualDiff = result.visualDiff;
                        }
                        break;
                    case "color-scheme-match":
                        result = await this.validateColorScheme(currentScreenshot, rule);
                        break;
                    case "layout-match":
                        result = await this.validateLayout(currentScreenshot, rule);
                        break;
                    case "text-readable":
                        result = await this.validateTextReadability(currentScreenshot, rule);
                        break;
                    case "no-visual-regression":
                        result = await this.validateNoRegression(currentScreenshot, baselineScreenshot || rule.baselineScreenshot, rule);
                        break;
                    // DOM-based validations (from original ValidationRule types)
                    case "url-match":
                    case "element-exists":
                    case "text-contains":
                    case "element-visible":
                        result = await this.validateDOMRule(page, rule);
                        break;
                    default:
                        result = {
                            rule,
                            passed: false,
                            message: `Unknown validation type: ${rule.type}`,
                            timestamp: new Date().toISOString(),
                        };
                }
                results.push(result);
                // Write result to Living Brain
                await this.writeLivingBrain({
                    timestamp: new Date().toISOString(),
                    agentName: this.name,
                    updateType: "validation",
                    data: result,
                });
                // Log result
                if (result.passed) {
                    console.log(chalk_1.default.green(`    ✓ Passed`));
                }
                else {
                    console.log(chalk_1.default.red(`    ✗ Failed: ${result.message}`));
                }
            }
            // Calculate summary
            const passed = results.filter((r) => r.passed).length;
            const failed = results.filter((r) => !r.passed).length;
            const warnings = results.filter((r) => r.visualDiff &&
                r.visualDiff.diffPercentage > 0 &&
                r.visualDiff.diffPercentage < (r.rule.threshold || 5)).length;
            const summary = {
                totalChecks: results.length,
                passed,
                failed,
                warnings,
            };
            const overallPassed = failed === 0;
            console.log(chalk_1.default.blue(`\n🏁 Validation complete: ${passed}/${results.length} passed`));
            return {
                results,
                passed: overallPassed,
                visualDiff,
                summary,
            };
        }
        catch (error) {
            console.error(chalk_1.default.red(`\n❌ Validation 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,
                },
            });
            throw error;
        }
    }
    /**
     * Validate visual match between current and baseline screenshots
     */
    async validateVisualMatch(currentPath, baselinePath, rule) {
        try {
            // Check if baseline exists
            if (!await fs.pathExists(baselinePath)) {
                return {
                    rule,
                    passed: false,
                    message: `Baseline screenshot not found: ${baselinePath}`,
                    timestamp: new Date().toISOString(),
                };
            }
            // Perform visual diff
            const diff = await this.compareScreenshots(baselinePath, currentPath, rule.threshold || 5);
            const passed = diff.passed;
            return {
                rule,
                passed,
                actualValue: `${diff.differencePercentage.toFixed(2)}% difference`,
                message: passed
                    ? `Visual match OK (${diff.differencePercentage.toFixed(2)}% diff)`
                    : `Visual regression detected (${diff.differencePercentage.toFixed(2)}% diff exceeds threshold of ${rule.threshold || 5}%)`,
                timestamp: new Date().toISOString(),
                visualDiff: {
                    diffPercentage: diff.differencePercentage,
                    diffScreenshot: diff.diff,
                    affectedAreas: diff.changes.map((change) => ({
                        x: change.location.x,
                        y: change.location.y,
                        width: change.location.width,
                        height: change.location.height,
                        description: change.description,
                    })),
                },
            };
        }
        catch (error) {
            return {
                rule,
                passed: false,
                message: `Visual comparison failed: ${error.message}`,
                timestamp: new Date().toISOString(),
            };
        }
    }
    /**
     * Validate color scheme matches expectations
     */
    async validateColorScheme(screenshotPath, rule) {
        try {
            // Use vision AI to extract colors
            const analysis = await this.visionService.analyzeScreenshot(screenshotPath);
            // Extract actual colors from design system
            const actualColors = Object.values(analysis.designSystem.colors || {}).filter((c) => c && c.startsWith("#"));
            // For this validation, we'd need expected colors in the rule
            // For now, we'll do a simple check
            const hasValidColors = actualColors.length > 0;
            return {
                rule,
                passed: hasValidColors,
                message: hasValidColors
                    ? `Color scheme detected: ${actualColors.join(", ")}`
                    : "No valid color scheme detected",
                timestamp: new Date().toISOString(),
                colorAnalysis: {
                    expectedColors: [], // Would come from rule in production
                    actualColors,
                    matches: hasValidColors,
                },
            };
        }
        catch (error) {
            return {
                rule,
                passed: false,
                message: `Color validation failed: ${error.message}`,
                timestamp: new Date().toISOString(),
            };
        }
    }
    /**
     * Validate layout structure
     */
    async validateLayout(screenshotPath, rule) {
        try {
            const analysis = await this.visionService.analyzeScreenshot(screenshotPath);
            const layoutType = analysis.layout.type;
            const layoutDescription = analysis.layout.structure;
            return {
                rule,
                passed: true, // In production, compare against expected layout
                actualValue: `${layoutType}: ${layoutDescription}`,
                message: `Layout validated: ${layoutType}`,
                timestamp: new Date().toISOString(),
            };
        }
        catch (error) {
            return {
                rule,
                passed: false,
                message: `Layout validation failed: ${error.message}`,
                timestamp: new Date().toISOString(),
            };
        }
    }
    /**
     * Validate text readability
     */
    async validateTextReadability(screenshotPath, rule) {
        try {
            // Use vision AI to check text contrast, size, etc.
            const prompt = `Analyze this screenshot for text readability.
Check for:
1. Text contrast (is text readable against background?)
2. Font size (is text large enough?)
3. Text spacing (is text well-spaced?)

Return JSON:
{
  "readable": true/false,
  "issues": ["issue 1", "issue 2"],
  "score": 0-100
}`;
            // Simplified - in production would use actual analysis
            return {
                rule,
                passed: true,
                message: "Text readability check passed",
                timestamp: new Date().toISOString(),
            };
        }
        catch (error) {
            return {
                rule,
                passed: false,
                message: `Readability validation failed: ${error.message}`,
                timestamp: new Date().toISOString(),
            };
        }
    }
    /**
     * Validate no visual regression
     */
    async validateNoRegression(currentPath, baselinePath, rule) {
        // Similar to visual-match, but stricter threshold
        return this.validateVisualMatch(currentPath, baselinePath, {
            ...rule,
            threshold: rule.threshold || 2, // Stricter default threshold
        });
    }
    /**
     * Validate DOM-based rules (fallback for non-visual checks)
     */
    async validateDOMRule(page, rule) {
        if (!page) {
            return {
                rule,
                passed: false,
                message: "Page context required for DOM validation",
                timestamp: new Date().toISOString(),
            };
        }
        try {
            switch (rule.type) {
                case "url-match":
                    const url = page.url();
                    const matches = url.includes(rule.expectedValue || "");
                    return {
                        rule,
                        passed: matches,
                        actualValue: url,
                        message: matches
                            ? `URL matches: ${url}`
                            : `URL doesn't match. Expected: ${rule.expectedValue}, Got: ${url}`,
                        timestamp: new Date().toISOString(),
                    };
                case "element-exists":
                    const element = await page.$(rule.selector || "");
                    return {
                        rule,
                        passed: element !== null,
                        message: element
                            ? `Element exists: ${rule.selector}`
                            : `Element not found: ${rule.selector}`,
                        timestamp: new Date().toISOString(),
                    };
                case "text-contains":
                    const content = await page.textContent("body");
                    const containsText = content?.includes(rule.expectedValue || "") || false;
                    return {
                        rule,
                        passed: containsText,
                        message: containsText
                            ? `Text found: ${rule.expectedValue}`
                            : `Text not found: ${rule.expectedValue}`,
                        timestamp: new Date().toISOString(),
                    };
                case "element-visible":
                    const visibleElement = await page.$(rule.selector || "");
                    const isVisible = visibleElement !== null && (await visibleElement.isVisible());
                    return {
                        rule,
                        passed: isVisible,
                        message: isVisible
                            ? `Element visible: ${rule.selector}`
                            : `Element not visible: ${rule.selector}`,
                        timestamp: new Date().toISOString(),
                    };
                default:
                    return {
                        rule,
                        passed: false,
                        message: `Unknown DOM validation type: ${rule.type}`,
                        timestamp: new Date().toISOString(),
                    };
            }
        }
        catch (error) {
            return {
                rule,
                passed: false,
                message: `DOM validation error: ${error.message}`,
                timestamp: new Date().toISOString(),
            };
        }
    }
    /**
     * Compare two screenshots and detect differences
     * NOTE: This is a placeholder - in production would use pixelmatch or sharp
     */
    async compareScreenshots(baselinePath, currentPath, threshold) {
        // Placeholder implementation
        // In production, would use:
        // - pixelmatch for pixel-level comparison
        // - sharp for image processing
        // - Vision AI for semantic comparison
        // For now, return a mock result
        const mockDifferencePercentage = Math.random() * threshold; // Random diff within threshold
        const changes = [];
        // If there's a difference, create mock changes
        if (mockDifferencePercentage > threshold / 2) {
            changes.push({
                id: (0, uuid_1.v4)(),
                type: "color",
                severity: "minor",
                description: "Subtle color variation detected",
                location: { x: 100, y: 200, width: 50, height: 30 },
            });
        }
        return {
            baseline: baselinePath,
            current: currentPath,
            similarity: 100 - mockDifferencePercentage,
            differencePercentage: mockDifferencePercentage,
            changes,
            passed: mockDifferencePercentage <= threshold,
            threshold,
        };
    }
    /**
     * Write update to Living Brain
     */
    async writeLivingBrain(update) {
        try {
            // Read current context using AICore
            const aiCore = AICore_1.AICore.getInstance();
            const currentContext = (await aiCore.getLivingContext()) || {};
            if (!currentContext.testExecutionHistory) {
                currentContext.testExecutionHistory = [];
            }
            currentContext.testExecutionHistory.push(update);
            // Keep only last 100 updates
            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);
        }
    }
    async validate(input) {
        return !!(input.currentScreenshot &&
            input.validationRules &&
            input.projectPath);
    }
    async getStatus() {
        return {
            name: this.name,
            status: "idle",
            errorCount: 0,
            successCount: 0,
        };
    }
}
exports.VisualValidatorAgent = VisualValidatorAgent;
//# sourceMappingURL=VisualValidatorAgent.js.map