"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AUTO_WORKSPACE_SETUP_WATCH_PATHS = void 0;
exports.ensureWorkspaceSetup = ensureWorkspaceSetup;
exports.buildWorkspaceSetupFingerprint = buildWorkspaceSetupFingerprint;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const command_1 = require("../core/command");
const native_git_cli_1 = require("../git/native-git-cli");
const log_text_1 = require("../run/log-text");
const SETUP_METADATA_VERSION = 1;
const WORKSPACE_SETUP_LOG_OUTPUT_MAX_CHARS = 4_000;
exports.AUTO_WORKSPACE_SETUP_WATCH_PATHS = [
    "package.json",
    "package-lock.json",
    "npm-shrinkwrap.json",
    "pnpm-lock.yaml",
    "pnpm-workspace.yaml",
    "yarn.lock",
    ".yarnrc.yml",
    "bun.lock",
    "bun.lockb",
    "pyproject.toml",
    "poetry.lock",
    "uv.lock",
    "requirements.txt",
    "requirements",
    "Pipfile",
    "Pipfile.lock",
    "environment.yml",
    "environment.yaml",
    "conda-lock.yml",
    "composer.json",
    "composer.lock",
    "Gemfile",
    "Gemfile.lock",
    "go.mod",
    "go.sum",
    "Cargo.toml",
    "Cargo.lock",
    "deno.json",
    "deno.lock",
];
async function ensureWorkspaceSetup(input) {
    const setupCommand = input.config.setupCommand?.trim();
    if (!setupCommand) {
        return;
    }
    const safeSetupCommand = redactWorkspaceSetupOutput(setupCommand);
    const run = input.runCommand ?? command_1.runCommand;
    const now = input.now ?? (() => new Date());
    const metadataPath = await resolveWorkspaceSetupMetadataPath(input.workspacePath);
    const metadata = await readWorkspaceSetupMetadata(metadataPath);
    const fingerprint = input.config.setupFrequency === "when_changed"
        ? await buildWorkspaceSetupFingerprintWithWarnings(input.workspacePath, input.config)
        : null;
    const decision = shouldRunWorkspaceSetup({
        config: input.config,
        metadata,
        fingerprint: fingerprint?.hash ?? null,
    });
    if (!decision.run) {
        await input.logger.appendSection((0, log_text_1.issueLogSection)("workspaceSetup"), [
            `${log_text_1.ISSUE_LOG_TEXT.field.outcome}: skipped`,
            `${log_text_1.ISSUE_LOG_TEXT.field.reason}: ${decision.reason}`,
            `${log_text_1.ISSUE_LOG_TEXT.field.command}: ${safeSetupCommand}`,
            `${log_text_1.ISSUE_LOG_TEXT.field.cwd}: ${input.workspacePath}`,
            ...renderWorkspaceSetupWarnings(fingerprint?.warnings ?? []),
        ].join("\n"));
        return;
    }
    await input.logger.appendSection((0, log_text_1.issueLogSection)("workspaceSetup"), [
        `${log_text_1.ISSUE_LOG_TEXT.field.outcome}: started`,
        `${log_text_1.ISSUE_LOG_TEXT.field.reason}: ${decision.reason}`,
        `${log_text_1.ISSUE_LOG_TEXT.field.command}: ${safeSetupCommand}`,
        `${log_text_1.ISSUE_LOG_TEXT.field.cwd}: ${input.workspacePath}`,
        ...renderWorkspaceSetupWarnings(fingerprint?.warnings ?? []),
    ].join("\n"));
    const result = await run({
        command: "bash",
        args: ["-lc", setupCommand],
        cwd: input.workspacePath,
        timeoutMs: input.config.setupTimeoutSec * 1000,
        allowNonZeroExit: true,
    });
    if (result.exitCode !== 0) {
        await input.logger.appendSection((0, log_text_1.issueLogSection)("workspaceSetup"), renderWorkspaceSetupResult("failed", result));
        throw new Error(`Workspace setup failed with exit code ${result.exitCode}: ${safeSetupCommand}`);
    }
    const successfulFingerprint = input.config.setupFrequency === "when_changed"
        ? (await buildWorkspaceSetupFingerprintWithWarnings(input.workspacePath, input.config)).hash
        : null;
    await writeWorkspaceSetupMetadata(metadataPath, {
        version: SETUP_METADATA_VERSION,
        lastSuccessfulFingerprint: successfulFingerprint,
        lastSuccessfulAt: now().toISOString(),
        setupCommand,
        setupFrequency: input.config.setupFrequency,
    });
    await input.logger.appendSection((0, log_text_1.issueLogSection)("workspaceSetup"), renderWorkspaceSetupResult("succeeded", result));
}
async function buildWorkspaceSetupFingerprint(workspacePath, config) {
    return (await buildWorkspaceSetupFingerprintWithWarnings(workspacePath, config)).hash;
}
async function buildWorkspaceSetupFingerprintWithWarnings(workspacePath, config) {
    const hash = (0, node_crypto_1.createHash)("sha256");
    hash.update(`version:${SETUP_METADATA_VERSION}\n`);
    hash.update(`command:${config.setupCommand ?? ""}\n`);
    hash.update(`watch:${config.setupWatchPaths === "auto" ? "auto" : JSON.stringify(config.setupWatchPaths)}\n`);
    const watchPaths = config.setupWatchPaths === "auto"
        ? exports.AUTO_WORKSPACE_SETUP_WATCH_PATHS
        : config.setupWatchPaths;
    const collection = await collectWorkspaceSetupFingerprintEntries(workspacePath, watchPaths, { warnOnSkippedPaths: config.setupWatchPaths !== "auto" });
    for (const entry of collection.entries) {
        hash.update(`path:${entry.relativePath}\n`);
        hash.update(entry.content);
        hash.update("\n");
    }
    return { hash: hash.digest("hex"), warnings: collection.warnings };
}
async function collectWorkspaceSetupFingerprintEntries(workspacePath, watchPaths, options) {
    const entries = [];
    const warnings = [];
    for (const watchPath of watchPaths) {
        const absolutePath = node_path_1.default.resolve(workspacePath, watchPath);
        const relativePath = normalizeWorkspaceRelativePath(workspacePath, absolutePath);
        if (relativePath === null) {
            continue;
        }
        await collectFingerprintEntriesForPath(absolutePath, relativePath, entries, warnings, options);
    }
    return {
        entries: entries.sort((left, right) => left.relativePath.localeCompare(right.relativePath)),
        warnings: warnings.sort((left, right) => left.relativePath.localeCompare(right.relativePath)),
    };
}
async function collectFingerprintEntriesForPath(absolutePath, relativePath, entries, warnings, options) {
    let fileStat;
    try {
        fileStat = await (0, promises_1.stat)(absolutePath);
    }
    catch {
        if (options.warnOnSkippedPaths) {
            warnings.push({ relativePath, reason: "missing" });
        }
        return;
    }
    if (fileStat.isDirectory()) {
        let children;
        try {
            children = await (0, promises_1.readdir)(absolutePath);
        }
        catch {
            if (options.warnOnSkippedPaths) {
                warnings.push({ relativePath, reason: "not_readable" });
            }
            return;
        }
        await Promise.all(children.map((child) => collectFingerprintEntriesForPath(node_path_1.default.join(absolutePath, child), `${relativePath}/${child}`, entries, warnings, options)));
        return;
    }
    if (!fileStat.isFile()) {
        return;
    }
    try {
        entries.push({
            relativePath,
            content: await (0, promises_1.readFile)(absolutePath),
        });
    }
    catch {
        if (options.warnOnSkippedPaths) {
            warnings.push({ relativePath, reason: "not_readable" });
        }
    }
}
function normalizeWorkspaceRelativePath(workspacePath, absolutePath) {
    const relativePath = node_path_1.default.relative(workspacePath, absolutePath);
    if (relativePath === ".." ||
        relativePath.startsWith(`..${node_path_1.default.sep}`) ||
        node_path_1.default.isAbsolute(relativePath)) {
        return null;
    }
    return relativePath.split(node_path_1.default.sep).join("/");
}
async function resolveWorkspaceSetupMetadataPath(workspacePath) {
    const gitPath = (await (0, native_git_cli_1.readNativeGit)(workspacePath, [
        "rev-parse",
        "--git-path",
        "alpaca/workspace-setup.json",
    ])).trim();
    return node_path_1.default.resolve(workspacePath, gitPath);
}
async function readWorkspaceSetupMetadata(metadataPath) {
    try {
        const parsed = JSON.parse(await (0, promises_1.readFile)(metadataPath, "utf8"));
        if (!isWorkspaceSetupMetadata(parsed)) {
            return null;
        }
        return parsed;
    }
    catch {
        return null;
    }
}
async function writeWorkspaceSetupMetadata(metadataPath, metadata) {
    await (0, promises_1.mkdir)(node_path_1.default.dirname(metadataPath), { recursive: true });
    await (0, promises_1.writeFile)(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
}
function isWorkspaceSetupMetadata(value) {
    if (value === null || typeof value !== "object") {
        return false;
    }
    const metadata = value;
    return (metadata.version === SETUP_METADATA_VERSION &&
        (typeof metadata.lastSuccessfulFingerprint === "string" ||
            metadata.lastSuccessfulFingerprint === null) &&
        (typeof metadata.lastSuccessfulAt === "string" ||
            metadata.lastSuccessfulAt === null) &&
        typeof metadata.setupCommand === "string" &&
        typeof metadata.setupFrequency === "string");
}
function shouldRunWorkspaceSetup(input) {
    if (input.config.setupFrequency === "always") {
        return { run: true, reason: "setupFrequency=always" };
    }
    if (input.metadata === null || input.metadata.lastSuccessfulAt === null) {
        return { run: true, reason: "no successful setup metadata" };
    }
    if (input.config.setupFrequency === "once") {
        return { run: false, reason: "setupFrequency=once already completed" };
    }
    if (input.metadata.lastSuccessfulFingerprint !== input.fingerprint) {
        return { run: true, reason: "setup fingerprint changed" };
    }
    return { run: false, reason: "setup fingerprint unchanged" };
}
function renderWorkspaceSetupWarnings(warnings) {
    return warnings.map((warning) => `${log_text_1.ISSUE_LOG_TEXT.field.warning}: setup watch path skipped: ${warning.relativePath} (${warning.reason})`);
}
function renderWorkspaceSetupResult(outcome, result) {
    const stdout = summarizeWorkspaceSetupOutput(result.stdout);
    const stderr = summarizeWorkspaceSetupOutput(result.stderr);
    return [
        `${log_text_1.ISSUE_LOG_TEXT.field.outcome}: ${outcome}`,
        `exitCode: ${result.exitCode}`,
        `${log_text_1.ISSUE_LOG_TEXT.field.stdout}: ${stdout || log_text_1.ISSUE_LOG_TEXT.none}`,
        `${log_text_1.ISSUE_LOG_TEXT.field.stderr}: ${stderr || log_text_1.ISSUE_LOG_TEXT.none}`,
    ].join("\n");
}
function summarizeWorkspaceSetupOutput(text) {
    const redacted = redactWorkspaceSetupOutput(text).trim();
    if (redacted.length <= WORKSPACE_SETUP_LOG_OUTPUT_MAX_CHARS) {
        return redacted;
    }
    const omittedChars = redacted.length - WORKSPACE_SETUP_LOG_OUTPUT_MAX_CHARS;
    return `${redacted.slice(0, WORKSPACE_SETUP_LOG_OUTPUT_MAX_CHARS)}\n[truncated ${omittedChars} chars]`;
}
function redactWorkspaceSetupOutput(text) {
    return text
        .replace(/([a-z][a-z0-9+.-]*:\/\/)([^/\s:@]+):([^/\s@]+)@/giu, "$1<redacted>@")
        .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/giu, "$1 <redacted>")
        .replace(/(^|[\s;])((?:_?auth[_-]?token|api[_-]?key|access[_-]?token|auth[_-]?token|password|passwd|secret|token)(?:\s*[:=]\s*|\s+))([^\s"'`]+)/giu, "$1$2<redacted>")
        .replace(/(--(?:_?auth[-_]?token|authToken|api[-_]?key|access[-_]?token|password|passwd|secret|token)(?:=|\s+))([^\s"'`]+)/giu, "$1<redacted>");
}
