export function analyzeRgbaFrameVisualMetrics(pixels, width, height, options = {}) {
    if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
        throw new RangeError("Frame visual metrics require positive integer width and height.");
    }
    if (pixels.length !== width * height * 4) {
        throw new RangeError("Frame visual metrics require RGBA pixels with width * height * 4 bytes.");
    }
    const darkLumaThreshold = options.darkLumaThreshold ?? 18;
    const salientLumaThreshold = options.salientLumaThreshold ?? 24;
    const bucketShift = options.bucketShift ?? 4;
    const edgeLumaThreshold = options.edgeLumaThreshold ?? 42;
    const buckets = new Map();
    let nonDarkPixels = 0;
    let salientPixels = 0;
    let darkPixels = 0;
    let lumaSum = 0;
    let minLuma = Number.POSITIVE_INFINITY;
    let maxLuma = 0;
    let minX = width;
    let minY = height;
    let maxX = -1;
    let maxY = -1;
    const quadrants = new Set();
    for (let y = 0; y < height; y += 1) {
        for (let x = 0; x < width; x += 1) {
            const index = (y * width + x) * 4;
            const r = pixels[index] ?? 0;
            const g = pixels[index + 1] ?? 0;
            const b = pixels[index + 2] ?? 0;
            const luma = framePixelLuma(r, g, b);
            lumaSum += luma;
            minLuma = Math.min(minLuma, luma);
            maxLuma = Math.max(maxLuma, luma);
            if (luma > darkLumaThreshold)
                nonDarkPixels += 1;
            else
                darkPixels += 1;
            if (luma > salientLumaThreshold) {
                salientPixels += 1;
                minX = Math.min(minX, x);
                minY = Math.min(minY, y);
                maxX = Math.max(maxX, x);
                maxY = Math.max(maxY, y);
                quadrants.add((x >= width / 2 ? 1 : 0) + (y >= height / 2 ? 2 : 0));
            }
            const bucket = `${r >> bucketShift}:${g >> bucketShift}:${b >> bucketShift}`;
            buckets.set(bucket, (buckets.get(bucket) ?? 0) + 1);
        }
    }
    let edgePixels = 0;
    let flatPixels = 0;
    let localContrastPixels = 0;
    for (let y = 1; y < height; y += 1) {
        for (let x = 1; x < width; x += 1) {
            const index = (y * width + x) * 4;
            const luma = rgbaPixelLuma(pixels, index);
            const left = rgbaPixelLuma(pixels, index - 4);
            const up = rgbaPixelLuma(pixels, index - width * 4);
            const delta = Math.abs(luma - left) + Math.abs(luma - up);
            if (delta > edgeLumaThreshold) {
                edgePixels += 1;
            }
            if (delta <= 2) {
                flatPixels += 1;
            }
            if (delta >= 8) {
                localContrastPixels += 1;
            }
        }
    }
    const pixelCount = width * height;
    const dominantBucket = Math.max(0, ...buckets.values());
    const bounds = maxX >= minX && maxY >= minY
        ? {
            minX,
            minY,
            maxX,
            maxY,
            width: maxX - minX + 1,
            height: maxY - minY + 1
        }
        : undefined;
    const occupiedAreaRatio = bounds ? (bounds.width * bounds.height) / pixelCount : 0;
    const meanLuma = lumaSum / pixelCount;
    return {
        width,
        height,
        pixelCount,
        nonDarkPixels,
        nonDarkRatio: nonDarkPixels / pixelCount,
        salientPixels,
        salientRatio: salientPixels / pixelCount,
        occupiedAreaRatio,
        occupiedQuadrants: quadrants.size,
        meanLuma,
        averageLuma: meanLuma,
        minLuma: Number.isFinite(minLuma) ? minLuma : 0,
        maxLuma,
        darkPixelRatio: darkPixels / pixelCount,
        colorBuckets: buckets.size,
        dominantBucketRatio: dominantBucket / pixelCount,
        edgePixels,
        edgePixelRatio: edgePixels / pixelCount,
        flatPixels,
        flatPixelRatio: flatPixels / pixelCount,
        localContrastPixels,
        localContrastRatio: localContrastPixels / pixelCount,
        ...(bounds ? { bounds } : {})
    };
}
export function evaluateFrameVisualQuality(metrics, thresholds) {
    const failures = [
        thresholdFailure("nonDarkRatio", metrics.nonDarkRatio, thresholds.minNonDarkRatio, "min"),
        thresholdFailure("salientRatio", metrics.salientRatio, thresholds.minSalientRatio, "min"),
        thresholdFailure("occupiedAreaRatio", metrics.occupiedAreaRatio, thresholds.minOccupiedAreaRatio, "min"),
        thresholdFailure("occupiedQuadrants", metrics.occupiedQuadrants, thresholds.minOccupiedQuadrants, "min"),
        thresholdFailure("meanLuma", metrics.meanLuma, thresholds.minMeanLuma, "min"),
        thresholdFailure("maxLuma", metrics.maxLuma, thresholds.minMaxLuma, "min"),
        thresholdFailure("colorBuckets", metrics.colorBuckets, thresholds.minColorBuckets, "min"),
        thresholdFailure("darkPixelRatio", metrics.darkPixelRatio, thresholds.maxDarkPixelRatio, "max"),
        thresholdFailure("dominantBucketRatio", metrics.dominantBucketRatio, thresholds.maxDominantBucketRatio, "max"),
        thresholdFailure("edgePixelRatio", metrics.edgePixelRatio, thresholds.minEdgePixelRatio, "min"),
        thresholdFailure("flatPixelRatio", metrics.flatPixelRatio, thresholds.maxFlatPixelRatio, "max"),
        thresholdFailure("localContrastRatio", metrics.localContrastRatio, thresholds.minLocalContrastRatio, "min")
    ].filter((failure) => Boolean(failure));
    return { ok: failures.length === 0, failures };
}
function thresholdFailure(name, actual, expected, direction) {
    if (expected === undefined)
        return undefined;
    if (direction === "min" && actual < expected)
        return `${name} ${roundMetric(actual)} < ${expected}`;
    if (direction === "max" && actual > expected)
        return `${name} ${roundMetric(actual)} > ${expected}`;
    return undefined;
}
function rgbaPixelLuma(pixels, index) {
    return framePixelLuma(pixels[index] ?? 0, pixels[index + 1] ?? 0, pixels[index + 2] ?? 0);
}
function framePixelLuma(r, g, b) {
    return r * 0.2126 + g * 0.7152 + b * 0.0722;
}
function roundMetric(value) {
    return Math.round(value * 10_000) / 10_000;
}
//# sourceMappingURL=FrameVisualMetrics.js.map