"use strict";
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.STEP_ARTIFACT_TYPES = void 0;
exports.handleStepArtifact = handleStepArtifact;
const fs_1 = __importDefault(require("fs"));
const readline_1 = __importDefault(require("readline"));
const child_process_1 = require("child_process");
const cheerio = __importStar(require("cheerio"));
const describe_1 = require("../../../commandUtil/describe");
const debugUtils_1 = require("./debugUtils");
const DISPATCH = {
    console: { artifact: debugUtils_1.ARTIFACT_TYPES.ConsoleLog, ext: 'json', json: true },
    dom: { artifact: debugUtils_1.ARTIFACT_TYPES.DomSnapshot, ext: 'html', json: false },
    network: { artifact: debugUtils_1.ARTIFACT_TYPES.Har, ext: 'har', json: true },
    screenshot: { artifact: debugUtils_1.ARTIFACT_TYPES.Screenshot, ext: 'png', json: false },
};
exports.STEP_ARTIFACT_TYPES = Object.keys(DISPATCH);
const POST_SCREENSHOT_FILENAME = 'screenshot.png';
const BEFORE_SCREENSHOT_FILENAME = 'before_screenshot.png';
const NON_VISIBLE_TAGS = ['script', 'style', 'noscript', 'link', 'meta'];
const ACCESSIBLE_NAME_PREVIEW = 80;
function resolveUrl(userLogs, opts) {
    const url = (0, debugUtils_1.resolveStepArtifact)(userLogs, opts.stepRunId, DISPATCH[opts.type].artifact);
    if (!url) {
        return undefined;
    }
    if (opts.type === 'screenshot' && opts.before) {
        return url.replace(POST_SCREENSHOT_FILENAME, BEFORE_SCREENSHOT_FILENAME);
    }
    return url;
}
function cacheFilename(opts) {
    const { ext } = DISPATCH[opts.type];
    if (opts.type === 'screenshot') {
        const suffix = opts.before ? 'before-screenshot' : 'screenshot';
        return `step-run-${opts.stepRunId}-${suffix}.${ext}`;
    }
    return `step-run-${opts.stepRunId}-${opts.type}.${ext}`;
}
function emitNote(opts) {
    const label = opts.type === 'screenshot' && opts.before ? 'before-screenshot' : opts.type;
    (0, describe_1.outputEntity)({
        step_run_id: opts.stepRunId,
        type: opts.type,
        ...(opts.before ? { before: true } : {}),
        note: `${label} not available for step run ${opts.stepRunId}`,
    }, opts.outputFormat);
}
function validateOptions(opts) {
    const modes = [];
    if (opts.print) {
        modes.push('--print');
    }
    if (opts.head !== undefined) {
        modes.push('--head');
    }
    if (opts.tail !== undefined) {
        modes.push('--tail');
    }
    if (opts.bytes !== undefined) {
        modes.push('--bytes');
    }
    if (opts.query) {
        modes.push('--query');
    }
    if (opts.textOnly) {
        modes.push('--text-only');
    }
    if (modes.length > 1) {
        throw new Error(`${modes.join(', ')} are mutually exclusive — pick exactly one`);
    }
    if (opts.query && !DISPATCH[opts.type].json) {
        throw new Error(`--query only supports JSON artifacts (console, network); got ${opts.type}`);
    }
    if (opts.textOnly && opts.type !== 'dom') {
        throw new Error('--text-only only supports the dom artifact');
    }
    if (opts.before && opts.type !== 'screenshot') {
        throw new Error(`--before only supports the screenshot artifact; got ${opts.type}`);
    }
    if (opts.type === 'screenshot' && modes.length > 0) {
        throw new Error(`${modes[0]} is not supported for binary artifacts (screenshot). Read the file at the returned path instead.`);
    }
}
async function streamHead(filePath, n) {
    const rl = readline_1.default.createInterface({
        input: fs_1.default.createReadStream(filePath, { encoding: 'utf-8' }),
        crlfDelay: Infinity,
    });
    let count = 0;
    let firstLine = true;
    for await (const line of rl) {
        if (count >= n) {
            break;
        }
        process.stdout.write(firstLine ? line : `\n${line}`);
        firstLine = false;
        count++;
    }
    rl.close();
    warnIfSingleLineFile(filePath, count, n, '--head');
}
function warnIfSingleLineFile(filePath, linesEmitted, requested, flag) {
    if (linesEmitted > 1 || requested <= 0) {
        return;
    }
    const size = fs_1.default.statSync(filePath).size;
    if (size <= 512) {
        return;
    }
    process.stderr.write(`warning: ${flag} N is line-based, but this artifact is a single ${size}-byte line ` +
        `— the whole blob was emitted. Use --query for JSON or --bytes for a fixed-byte prefix.\n`);
}
async function streamTail(filePath, n) {
    const rl = readline_1.default.createInterface({
        input: fs_1.default.createReadStream(filePath, { encoding: 'utf-8' }),
        crlfDelay: Infinity,
    });
    const ring = new Array(n);
    let total = 0;
    for await (const line of rl) {
        ring[total % n] = line;
        total++;
    }
    rl.close();
    const start = total <= n ? 0 : total % n;
    const len = Math.min(total, n);
    for (let i = 0; i < len; i++) {
        const line = ring[(start + i) % n];
        process.stdout.write(i === 0 ? line : `\n${line}`);
    }
    warnIfSingleLineFile(filePath, total, n, '--tail');
}
function streamBytes(filePath, n) {
    const buf = Buffer.alloc(n);
    const fd = fs_1.default.openSync(filePath, 'r');
    try {
        const read = fs_1.default.readSync(fd, buf, 0, n, 0);
        process.stdout.write(buf.subarray(0, read));
    }
    finally {
        fs_1.default.closeSync(fd);
    }
}
function runJq(filePath, expr, opts) {
    const result = (0, child_process_1.spawnSync)('jq', [expr, filePath], { encoding: 'utf-8' });
    if (result.error &&
        result.error.code === 'ENOENT') {
        throw new Error(`\`jq\` not found on PATH. Install jq (https://jqlang.github.io/jq/download/) to use --query, or read the cached file directly: ${filePath}`);
    }
    if (result.status !== 0) {
        throw new Error(`jq exited ${result.status ?? '?'} on ${filePath}: ${(result.stderr || '').trim()}`);
    }
    const trimmed = result.stdout.trim();
    if (trimmed.length === 0 || trimmed === '[]') {
        (0, describe_1.outputEntity)({
            step_run_id: opts.stepRunId,
            type: opts.type,
            ...(opts.before ? { before: true } : {}),
            query: expr,
            matches: 0,
            note: `--query produced no matches on ${opts.type}`,
        }, opts.outputFormat);
        return;
    }
    process.stdout.write(result.stdout);
}
function extractTextOnly(html) {
    const $ = cheerio.load(html);
    $(NON_VISIBLE_TAGS.join(', ')).remove();
    const out = [];
    $('body, body *').each((_, el) => {
        if (el.type !== 'tag') {
            return;
        }
        const $el = $(el);
        const tag = el.tagName?.toLowerCase() ?? '';
        const role = $el.attr('role') ?? (0, debugUtils_1.tagToRole)(tag);
        const ariaLabel = $el.attr('aria-label')?.trim();
        const id = $el.attr('id')?.trim();
        const testid = $el.attr('data-testid')?.trim();
        const ownText = $el
            .contents()
            .filter((_idx, n) => n.type === 'text')
            .text()
            .replace(/\s+/g, ' ')
            .trim()
            .slice(0, ACCESSIBLE_NAME_PREVIEW);
        if (!role && !ariaLabel && !id && !testid && !ownText) {
            return;
        }
        const parts = [role ?? tag];
        const name = ariaLabel ?? ownText;
        if (name) {
            parts.push(`'${name}'`);
        }
        if (id) {
            parts.push(`#${id}`);
        }
        if (testid) {
            parts.push(`[testid=${testid}]`);
        }
        out.push(parts.join(' '));
    });
    return out.join('\n');
}
async function handleStepArtifact(opts) {
    validateOptions(opts);
    const { apiClient, userLogs } = await (0, debugUtils_1.loadStepContext)(opts.apiKey, opts.testRunId, opts.stepRunId);
    const url = resolveUrl(userLogs, opts);
    if (!url) {
        emitNote(opts);
        return;
    }
    const cachePath = await (0, debugUtils_1.downloadOrUseCache)(apiClient, opts.testRunId, url, cacheFilename(opts));
    if (!cachePath) {
        emitNote(opts);
        return;
    }
    if (opts.query) {
        runJq(cachePath, opts.query, opts);
        return;
    }
    if (opts.textOnly) {
        process.stdout.write(extractTextOnly(fs_1.default.readFileSync(cachePath, 'utf-8')));
        return;
    }
    if (opts.head !== undefined) {
        await streamHead(cachePath, opts.head);
        return;
    }
    if (opts.tail !== undefined) {
        await streamTail(cachePath, opts.tail);
        return;
    }
    if (opts.bytes !== undefined) {
        streamBytes(cachePath, opts.bytes);
        return;
    }
    if (opts.print) {
        process.stdout.write(fs_1.default.readFileSync(cachePath, 'utf-8'));
        return;
    }
    (0, describe_1.outputEntity)({
        step_run_id: opts.stepRunId,
        type: opts.type,
        ...(opts.before ? { before: true } : {}),
        file: cachePath,
        size_bytes: fs_1.default.statSync(cachePath).size,
    }, opts.outputFormat);
}
