/**
 * Golden Path Runner
 *
 * Executes user-defined flows against a running app using Playwright.
 * Flows are defined in YAML files under .vaspera/flows/
 *
 * @module scanners/runtime/golden-path-runner
 */
import { readFile, readdir, mkdir, writeFile } from "fs/promises";
import { join } from "path";
import { parse as parseYaml } from "yaml";
import { logger } from "../../logger.js";
import { GoldenPathFlowSchema, } from "./types.js";
const FLOWS_DIR = ".vaspera/flows";
const SCREENSHOTS_DIR = ".vaspera/screenshots";
/**
 * Load a golden path flow from a YAML file
 */
export async function loadFlow(flowPath) {
    const content = await readFile(flowPath, "utf-8");
    const parsed = parseYaml(content);
    return GoldenPathFlowSchema.parse(parsed);
}
/**
 * Discover all flows in a project
 */
export async function discoverFlows(projectPath) {
    const flowsDir = join(projectPath, FLOWS_DIR);
    const flows = [];
    try {
        const files = await readdir(flowsDir);
        for (const file of files) {
            if (file.endsWith(".yaml") || file.endsWith(".yml")) {
                try {
                    const flowPath = join(flowsDir, file);
                    const flow = await loadFlow(flowPath);
                    flows.push({ path: flowPath, flow });
                }
                catch (error) {
                    logger.warn("runtime.flow_parse_error", {
                        file,
                        error: error.message,
                    });
                }
            }
        }
    }
    catch {
        logger.debug("runtime.no_flows_directory", { path: flowsDir });
    }
    return flows;
}
/**
 * Execute a single step (without Playwright - basic HTTP/assertion version)
 * This is a lightweight version for initial implementation.
 * Full Playwright integration will be added as a separate module.
 */
async function executeStep(step, stepIndex, baseUrl, context) {
    const startTime = Date.now();
    try {
        switch (step.action) {
            case "navigate": {
                const url = step.url?.startsWith("http")
                    ? step.url
                    : `${baseUrl}${step.url}`;
                const response = await fetch(url, {
                    method: "GET",
                    signal: AbortSignal.timeout(step.timeout || 10000),
                });
                context.currentUrl = url;
                context.lastResponse = response;
                return {
                    step: stepIndex,
                    action: step.action,
                    success: response.ok,
                    duration: Date.now() - startTime,
                    error: response.ok ? undefined : `HTTP ${response.status}`,
                };
            }
            case "api": {
                const url = step.url?.startsWith("http")
                    ? step.url
                    : `${baseUrl}${step.url}`;
                const response = await fetch(url, {
                    method: step.method || "GET",
                    headers: {
                        "Content-Type": "application/json",
                        ...step.headers,
                    },
                    body: step.body ? JSON.stringify(step.body) : undefined,
                    signal: AbortSignal.timeout(step.timeout || 10000),
                });
                context.lastResponse = response;
                return {
                    step: stepIndex,
                    action: step.action,
                    success: response.ok,
                    duration: Date.now() - startTime,
                    error: response.ok ? undefined : `HTTP ${response.status}`,
                };
            }
            case "assert": {
                // For assertions, we need Playwright for DOM checks
                // For now, support URL assertions only
                if (step.url) {
                    const passed = context.currentUrl.includes(step.url);
                    return {
                        step: stepIndex,
                        action: step.action,
                        success: passed,
                        duration: Date.now() - startTime,
                        assertion: {
                            expected: step.url,
                            actual: context.currentUrl,
                            passed,
                        },
                    };
                }
                // DOM assertions require Playwright - return skipped
                return {
                    step: stepIndex,
                    action: step.action,
                    success: true,
                    duration: Date.now() - startTime,
                    error: "DOM assertions require Playwright (skipped)",
                };
            }
            case "wait": {
                const waitTime = step.timeout || 1000;
                await new Promise((resolve) => setTimeout(resolve, waitTime));
                return {
                    step: stepIndex,
                    action: step.action,
                    success: true,
                    duration: Date.now() - startTime,
                };
            }
            case "click":
            case "fill":
            case "select":
            case "screenshot": {
                // These require Playwright - return skipped
                return {
                    step: stepIndex,
                    action: step.action,
                    success: true,
                    duration: Date.now() - startTime,
                    error: `${step.action} requires Playwright (skipped)`,
                };
            }
            default:
                return {
                    step: stepIndex,
                    action: step.action,
                    success: false,
                    duration: Date.now() - startTime,
                    error: `Unknown action: ${step.action}`,
                };
        }
    }
    catch (error) {
        return {
            step: stepIndex,
            action: step.action,
            success: false,
            duration: Date.now() - startTime,
            error: error.message,
        };
    }
}
/**
 * Run a golden path flow
 *
 * By default uses lightweight HTTP-based execution.
 * Pass usePlaywright: true to use full browser automation.
 */
export async function runFlow(flow, baseUrl, options = {}) {
    // Try Playwright if requested
    if (options.usePlaywright) {
        try {
            const { isPlaywrightAvailable, runFlowWithPlaywright } = await import("./playwright-executor.js");
            if (await isPlaywrightAvailable()) {
                return runFlowWithPlaywright(flow, baseUrl, {
                    headless: options.headless,
                    slowMo: options.slowMo,
                    screenshotsDir: options.screenshotsDir,
                });
            }
            logger.warn("runtime.playwright_requested_but_unavailable");
        }
        catch {
            logger.warn("runtime.playwright_import_failed");
        }
    }
    // Fall back to HTTP-based execution
    const startTime = Date.now();
    const stepResults = [];
    const screenshots = [];
    let failed = false;
    const context = {
        currentUrl: baseUrl,
        lastResponse: undefined,
    };
    logger.info("runtime.flow_started", {
        name: flow.name,
        steps: flow.steps.length,
    });
    for (let i = 0; i < flow.steps.length; i++) {
        const step = flow.steps[i];
        if (failed) {
            // Skip remaining steps after a failure
            stepResults.push({
                step: i,
                action: step.action,
                success: false,
                duration: 0,
                error: "Skipped due to previous failure",
            });
            continue;
        }
        const result = await executeStep(step, i, baseUrl, context);
        stepResults.push(result);
        if (!result.success && !result.error?.includes("skipped")) {
            failed = true;
            logger.warn("runtime.step_failed", {
                flow: flow.name,
                step: i,
                action: step.action,
                error: result.error,
            });
        }
        else {
            logger.debug("runtime.step_passed", {
                flow: flow.name,
                step: i,
                action: step.action,
                duration: result.duration,
            });
        }
    }
    const passedSteps = stepResults.filter((r) => r.success && !r.error?.includes("skipped")).length;
    const failedSteps = stepResults.filter((r) => !r.success && !r.error?.includes("skipped")).length;
    logger.info("runtime.flow_completed", {
        name: flow.name,
        passed: passedSteps,
        failed: failedSteps,
        duration: Date.now() - startTime,
    });
    return {
        flowName: flow.name,
        success: !failed,
        totalSteps: flow.steps.length,
        passedSteps,
        failedSteps,
        duration: Date.now() - startTime,
        steps: stepResults,
        screenshots,
    };
}
/**
 * Run all flows in a project
 */
export async function runAllFlows(projectPath, baseUrl, options = {}) {
    const discoveredFlows = await discoverFlows(projectPath);
    const results = [];
    // Filter by priority if specified
    let flows = discoveredFlows;
    if (options.priority) {
        const priorities = [
            "critical",
            "high",
            "medium",
            "low",
        ];
        const minPriorityIndex = priorities.indexOf(options.priority);
        flows = flows.filter((f) => {
            const flowPriorityIndex = priorities.indexOf(f.flow.priority || "medium");
            return flowPriorityIndex <= minPriorityIndex;
        });
    }
    // Filter by tags if specified
    if (options.tags && options.tags.length > 0) {
        flows = flows.filter((f) => f.flow.tags?.some((tag) => options.tags?.includes(tag)));
    }
    // Sort by priority (critical first)
    const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
    flows.sort((a, b) => priorityOrder[a.flow.priority || "medium"] -
        priorityOrder[b.flow.priority || "medium"]);
    logger.info("runtime.running_flows", {
        total: flows.length,
        priority: options.priority,
    });
    for (const { flow } of flows) {
        const result = await runFlow(flow, flow.baseUrl || baseUrl, {
            usePlaywright: options.usePlaywright,
            headless: options.headless,
            slowMo: options.slowMo,
            screenshotsDir: options.screenshotsDir,
        });
        results.push(result);
        // Stop on critical flow failure
        if (!result.success && flow.priority === "critical") {
            logger.error("runtime.critical_flow_failed", { name: flow.name });
            break;
        }
    }
    return results;
}
/**
 * Generate a sample flow file
 */
export async function generateSampleFlow(projectPath, name = "sample") {
    const flowsDir = join(projectPath, FLOWS_DIR);
    // Ensure directory exists
    await mkdir(flowsDir, { recursive: true });
    const sampleFlow = `# ${name} Golden Path Flow
# Define user flows to verify your app works correctly

name: "${name}"
description: "Sample golden path flow - customize for your app"
priority: medium
tags:
  - sample
  - smoke-test

steps:
  # Navigate to the home page
  - action: navigate
    url: "/"
    description: "Load home page"

  # Verify home page loaded
  - action: assert
    url: "/"
    description: "Verify on home page"

  # Wait for any async content
  - action: wait
    timeout: 1000
    description: "Wait for page to settle"

  # Test an API endpoint
  - action: api
    url: "/api/health"
    method: GET
    description: "Check health endpoint"

  # Navigate to another page
  # - action: navigate
  #   url: "/about"
  #   description: "Go to about page"

  # Fill a form (requires Playwright)
  # - action: fill
  #   selector: "#email"
  #   value: "test@example.com"
  #   description: "Enter email"

  # Click a button (requires Playwright)
  # - action: click
  #   selector: "[data-testid='submit']"
  #   description: "Submit form"

  # Take a screenshot (requires Playwright)
  # - action: screenshot
  #   description: "Capture final state"
`;
    const flowPath = join(flowsDir, `${name}.yaml`);
    await writeFile(flowPath, sampleFlow, "utf-8");
    logger.info("runtime.sample_flow_created", { path: flowPath });
    return flowPath;
}
//# sourceMappingURL=golden-path-runner.js.map