import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
function scorePercent(score) {
    if (score == null)
        return null;
    return Number((score * 100).toFixed(1));
}
function extractScores(report) {
    const scores = {};
    for (const [id, category] of Object.entries(report.categories ?? {})) {
        scores[id] = scorePercent(category.score);
    }
    return scores;
}
function extractCategoryDetails(report) {
    return Object.entries(report.categories ?? {}).map(([id, category]) => ({
        id,
        title: category.title ?? id,
        description: category.description ?? null,
        score: scorePercent(category.score),
        auditCount: category.auditRefs?.length ?? 0,
    }));
}
function categoryForAudit(report, auditId) {
    for (const [categoryId, category] of Object.entries(report.categories ?? {})) {
        if (category.auditRefs?.some((ref) => ref.id === auditId))
            return categoryId;
    }
    return null;
}
function weightForAudit(report, auditId) {
    for (const category of Object.values(report.categories ?? {})) {
        const ref = category.auditRefs?.find((r) => r.id === auditId);
        if (ref?.weight != null)
            return ref.weight;
    }
    return 0;
}
function topOpportunities(report, limit = 12) {
    const audits = report.audits ?? {};
    const candidates = Object.entries(audits)
        .filter(([, audit]) => typeof audit.score === 'number' && (audit.score ?? 1) < 0.9)
        .filter(([, audit]) => audit.scoreDisplayMode !== 'informative' && audit.scoreDisplayMode !== 'manual')
        .map(([id, audit]) => {
        const details = audit.details;
        return {
            id,
            title: audit.title ?? id,
            category: categoryForAudit(report, id),
            score: audit.score ?? null,
            displayValue: audit.displayValue ?? null,
            weight: weightForAudit(report, id),
            savingsMs: details?.overallSavingsMs ?? null,
            savingsBytes: details?.overallSavingsBytes ?? null,
        };
    })
        .sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0) ||
        (b.savingsMs ?? 0) - (a.savingsMs ?? 0) ||
        (a.score ?? 1) - (b.score ?? 1));
    return candidates.slice(0, limit);
}
function failingByCategory(report, perCategory = 5) {
    const byCategory = {};
    for (const opportunity of topOpportunities(report, 50)) {
        const cat = opportunity.category ?? 'other';
        if (!byCategory[cat])
            byCategory[cat] = [];
        if (byCategory[cat].length < perCategory)
            byCategory[cat].push(opportunity);
    }
    return byCategory;
}
export function parseLighthouseOutput(raw) {
    // Lighthouse may print non-JSON noise before/after the JSON blob when run via npx.
    const start = raw.indexOf('{');
    const end = raw.lastIndexOf('}');
    if (start === -1 || end === -1 || end <= start) {
        throw new Error('Could not parse Lighthouse JSON output.');
    }
    const report = JSON.parse(raw.slice(start, end + 1));
    return {
        scores: extractScores(report),
        categories: extractCategoryDetails(report),
        opportunities: topOpportunities(report),
        failingByCategory: failingByCategory(report),
    };
}
export function registerLighthouseAuditTool(server) {
    server.registerTool('lighthouse_audit', {
        title: 'Lighthouse Audit',
        description: 'Run a headless Lighthouse audit against a URL (prefer localhost / the running dev server). Returns per-category ' +
            'scores and titles, top opportunities across categories (with savings when available), and failing audits grouped by category.',
        inputSchema: {
            url: z.string().url().describe('URL to audit, e.g. http://127.0.0.1:5173/'),
            categories: z
                .array(z.enum(['performance', 'accessibility', 'best-practices', 'seo', 'pwa']))
                .optional()
                .describe('Optional subset of Lighthouse categories. Defaults to performance, accessibility, best-practices, seo (add pwa explicitly if needed).'),
        },
    }, async ({ url, categories }) => {
        try {
            const selected = categories && categories.length > 0
                ? categories
                : ['performance', 'accessibility', 'best-practices', 'seo'];
            // Prefer npx so lighthouse need not be globally installed; npx is already allow-listed.
            const result = await runCommand('npx', [
                '--yes',
                'lighthouse',
                url,
                '--output=json',
                '--quiet',
                '--chrome-flags=--headless --no-sandbox',
                ...selected.flatMap((category) => ['--only-categories', category]),
            ], {
                cwd: config.workspaceRoot,
                timeoutMs: config.scaffoldCommandTimeoutMs,
            });
            if (result.failed && !result.output.includes('{')) {
                return errorResult(`Lighthouse failed (exit ${result.exitCode}). Ensure Chrome/Chromium is available.\n${result.output}`);
            }
            const parsed = parseLighthouseOutput(result.output);
            return jsonResult({
                url,
                requestedCategories: selected,
                scores: parsed.scores,
                categories: parsed.categories,
                opportunities: parsed.opportunities,
                failingByCategory: parsed.failingByCategory,
                timedOut: result.timedOut,
                exitCode: result.exitCode,
            });
        }
        catch (error) {
            return errorResult(toErrorMessage(error));
        }
    });
}
//# sourceMappingURL=lighthouse.js.map