"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runPublishCommand = runPublishCommand;
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const control_plane_config_1 = require("../config/control-plane-config");
const command_1 = require("../core/command");
const factory_1 = require("../git/factory");
const native_git_cli_1 = require("../git/native-git-cli");
const remote_base_branch_1 = require("../git/remote-base-branch");
const catalog_1 = require("../i18n/catalog");
const factory_2 = require("../platform/auth/factory");
const provider_resolution_1 = require("../platform/provider-resolution");
const factory_3 = require("../platform/repository-host/factory");
const login_support_1 = require("../platform/repository-host/login-support");
const factory_4 = require("../platform/ticket-tracker/factory");
const runtime_1 = require("../worker/runtime");
const base_workspace_1 = require("../workspaces/base-workspace");
const issue_create_1 = require("./issue-create");
const locale_1 = require("./locale");
const publish_pr_body_1 = require("./publish-pr-body");
const prompts_1 = require("./prompts");
const install_1 = require("./service/install");
const publish_local_restore_1 = require("./publish-local-restore");
const DEFAULT_PUBLISH_MESSAGE = "chore: publish local changes";
async function runPublishCommand(cwd, options, deps = {}) {
    const locale = await (0, locale_1.loadCommandLocale)(cwd);
    const { config } = (0, provider_resolution_1.resolveSupportedProviders)(await (deps.loadConfig ?? control_plane_config_1.loadConfig)(cwd));
    const baseBranch = config.repositoryHost.baseBranch;
    const remoteName = config.repositoryHost.remoteName;
    const reviewers = (0, login_support_1.normalizeLoginListValues)(options.reviewers ?? []);
    const requestReview = reviewers.length > 0;
    const noMerge = options.noMerge === true;
    if (requestReview && options.restartService === true) {
        throw new Error((0, catalog_1.translate)(locale, "publish.command.reviewersRestartServiceConflict"));
    }
    if (noMerge && options.restartService === true) {
        throw new Error((0, catalog_1.translate)(locale, "publish.command.noMergeRestartServiceConflict"));
    }
    const authProvider = (0, factory_2.createAuthProvider)(config);
    const gitRepository = deps.gitRepository ?? (0, factory_1.createGitRepositoryClient)(config, authProvider);
    const repositoryHost = deps.repositoryHost ??
        (await (0, factory_3.createRepositoryHostClient)(config, authProvider));
    const run = deps.runCommand ?? command_1.runCommand;
    const runPostPublish = createPostPublishRunner({
        config,
        gitRepository,
        authProvider,
        cwd,
        locale,
        restartService: options.restartService === true,
        syncBaseWorkspace: deps.syncBaseWorkspace ?? base_workspace_1.ensureBaseWorkspace,
        runServiceRestart: deps.runServiceRestart ?? install_1.runServiceRestartCommand,
    });
    const currentBranch = await readCurrentBranch(cwd, run, locale);
    if (currentBranch === null) {
        throw new Error((0, catalog_1.translate)(locale, "publish.command.detachedHead"));
    }
    const pathspecs = normalizePublishPathspecs(cwd, options.paths, locale);
    const scopedPathspecs = pathspecs.length > 0 ? pathspecs : undefined;
    const noChangesMessage = pathspecs.length > 0
        ? "publish.command.noPathChanges"
        : "publish.command.noChanges";
    const hasChanges = scopedPathspecs
        ? await gitRepository.hasChanges(cwd, scopedPathspecs)
        : await gitRepository.hasChanges(cwd);
    const missingPathspecs = scopedPathspecs
        ? await findPublishPathspecsMissingFromGitAndDisk({
            cwd,
            pathspecs: scopedPathspecs,
            run,
            locale,
        })
        : [];
    const branchAheadOfBase = await gitRepository.isBranchAheadOfBase(cwd, baseBranch);
    if (!hasChanges && !branchAheadOfBase) {
        console.log((0, catalog_1.translate)(locale, noChangesMessage));
        warnMissingPublishPathspecs(locale, missingPathspecs);
        if (requestReview) {
            throw new Error((0, catalog_1.translate)(locale, "publish.command.reviewersNeedPr"));
        }
        if (noMerge) {
            return 0;
        }
        return runPostPublish();
    }
    warnMissingPublishPathspecs(locale, missingPathspecs);
    const commitPathspecs = excludePathspecs(pathspecs, missingPathspecs);
    const scopedCommitPathspecs = commitPathspecs.length > 0 ? commitPathspecs : undefined;
    const branch = await resolvePublishBranch({
        cwd,
        currentBranch,
        baseBranch,
        locale,
        run,
        now: deps.now ?? (() => new Date()),
    });
    return publishResolvedBranch({
        cwd,
        config,
        authProvider,
        gitRepository,
        repositoryHost,
        run,
        branch,
        currentBranch,
        baseBranch,
        remoteName,
        locale,
        pathspecs,
        scopedPathspecs: scopedCommitPathspecs,
        hasChanges,
        message: options.message,
        yes: options.yes === true,
        prompter: deps.prompter,
        ticketTracker: deps.ticketTracker,
        createWorkerRuntime: deps.createWorkerRuntime ?? runtime_1.createConfiguredWorkerRuntime,
        now: deps.now ?? (() => new Date()),
        reviewers,
        noMerge,
        runPostPublish,
    });
}
async function publishResolvedBranch(input) {
    const restoreCredentials = await (0, remote_base_branch_1.tryResolveRemoteBaseBranchGitCredentials)(input.authProvider, input.config.repositoryHost);
    const restoreInput = {
        cwd: input.cwd,
        baseBranch: input.baseBranch,
        remoteName: input.remoteName,
        currentBranch: input.currentBranch,
        locale: input.locale,
        run: input.run,
        branchCreated: input.branch.created,
        preserveLocalChanges: input.pathspecs.length > 0,
        credentials: restoreCredentials,
    };
    let restoreIncompletePublish = true;
    try {
        const confirmed = await confirmPublish({
            branchName: input.branch.branchName,
            baseBranch: input.baseBranch,
            yes: input.yes,
            prompter: input.prompter,
            locale: input.locale,
        });
        if (!confirmed) {
            return 1;
        }
        if (input.branch.created) {
            console.log((0, catalog_1.translate)(input.locale, "publish.command.createdBranch", {
                branchName: input.branch.branchName,
            }));
        }
        const message = normalizeMessage(input.message);
        const assignees = await resolvePublishPullRequestAssignees({
            config: input.config,
            authProvider: input.authProvider,
            ticketTracker: input.ticketTracker,
        });
        await commitPublishChanges({
            cwd: input.cwd,
            gitRepository: input.gitRepository,
            hasChanges: input.hasChanges,
            locale: input.locale,
            message,
            scopedPathspecs: input.scopedPathspecs,
        });
        await input.gitRepository.pushBranch(input.cwd, input.branch.branchName, {
            hookContext: "publish",
        });
        console.log((0, catalog_1.translate)(input.locale, "publish.command.pushed", {
            branchName: input.branch.branchName,
        }));
        const pullRequest = await (0, publish_pr_body_1.openPublishPullRequest)({
            cwd: input.cwd,
            config: input.config,
            repositoryHost: input.repositoryHost,
            branchName: input.branch.branchName,
            baseBranch: input.baseBranch,
            remoteName: input.remoteName,
            message,
            run: input.run,
            locale: input.locale,
            createWorkerRuntime: input.createWorkerRuntime,
            now: input.now,
        });
        restoreIncompletePublish = false;
        return await finishPublish({
            cwd: input.cwd,
            baseBranch: input.baseBranch,
            remoteName: input.remoteName,
            currentBranch: input.currentBranch,
            locale: input.locale,
            repositoryHost: input.repositoryHost,
            run: input.run,
            branchCreated: input.branch.created,
            preserveLocalChanges: input.pathspecs.length > 0,
            pullRequest: pullRequest.pullRequest,
            assignees,
            reviewers: input.reviewers,
            noMerge: input.noMerge,
            runPostPublish: input.runPostPublish,
            credentials: restoreCredentials,
        });
    }
    finally {
        if (restoreIncompletePublish) {
            await restoreBaseBranchAfterIncompletePublish(restoreInput);
        }
    }
}
async function finishPublish(input) {
    if (input.reviewers.length > 0) {
        return finishReviewPublish(input);
    }
    if (input.noMerge) {
        await finishUnmergedPublish(input);
        return 0;
    }
    return finishMergedPublish(input);
}
async function finishMergedPublish(input) {
    try {
        await syncPublishPullRequestAssignees(input);
        await mergePublishPullRequest(input);
    }
    finally {
        await restoreBaseBranchAfterPublish(input);
    }
    return input.runPostPublish();
}
async function resolvePublishPullRequestAssignees(input) {
    const ticketTracker = input.ticketTracker ??
        (await (0, factory_4.createTicketTrackerClient)(input.config, input.authProvider));
    return (0, issue_create_1.resolveIssueCreateAssignees)(ticketTracker, []);
}
async function syncPublishPullRequestAssignees(input) {
    await input.repositoryHost.syncPullRequestAssignees(input.pullRequest.number, input.assignees);
}
function createPostPublishRunner(input) {
    return () => runPostPublishActions(input);
}
async function commitPublishChanges(input) {
    if (!input.hasChanges) {
        return;
    }
    if (input.scopedPathspecs) {
        await input.gitRepository.commitAllChanges(input.cwd, input.message, input.scopedPathspecs, { hookContext: "publish" });
    }
    else {
        await input.gitRepository.commitAllChanges(input.cwd, input.message, [], {
            hookContext: "publish",
        });
    }
    console.log((0, catalog_1.translate)(input.locale, "publish.command.committed"));
}
async function mergePublishPullRequest(input) {
    await input.repositoryHost.mergePullRequest(input.pullRequest.number, "squash");
    console.log((0, catalog_1.translate)(input.locale, "publish.command.merged", {
        baseBranch: input.baseBranch,
        url: input.pullRequest.url,
    }));
}
async function confirmPublish(input) {
    if (input.yes) {
        return true;
    }
    const prompter = input.prompter ?? (0, prompts_1.createCliPrompter)({ locale: input.locale });
    if (!prompter.interactive) {
        console.error((0, catalog_1.translate)(input.locale, "publish.command.requiresYes"));
        return false;
    }
    const confirmed = await prompter.confirm({
        label: (0, catalog_1.translate)(input.locale, "publish.command.confirm", {
            branchName: input.branchName,
            baseBranch: input.baseBranch,
        }),
        defaultValue: false,
    });
    if (!confirmed) {
        console.log((0, catalog_1.translate)(input.locale, "publish.command.canceled"));
    }
    return confirmed;
}
async function finishReviewPublish(input) {
    const reviewerResult = await syncReviewersAndRestoreBase(input);
    if (reviewerResult.eligibleReviewers.length === 0) {
        throw new Error((0, catalog_1.translate)(input.locale, "publish.command.noAssignableReviewers", {
            reviewers: input.reviewers.join(", "),
        }));
    }
    console.log((0, catalog_1.translate)(input.locale, "publish.command.reviewersReady", {
        reviewers: reviewerResult.eligibleReviewers.join(", "),
    }));
    console.log((0, catalog_1.translate)(input.locale, "publish.command.leftUnmergedForReview", {
        url: input.pullRequest.url,
    }));
    return 0;
}
async function finishUnmergedPublish(input) {
    try {
        await syncPublishPullRequestAssignees(input);
    }
    finally {
        await restoreBaseBranchAfterPublish(input);
    }
    console.log((0, catalog_1.translate)(input.locale, "publish.command.leftUnmerged", {
        url: input.pullRequest.url,
    }));
}
async function syncReviewersAndRestoreBase(input) {
    try {
        await syncPublishPullRequestAssignees(input);
        return await input.repositoryHost.syncPullRequestReviewers(input.pullRequest.number, input.reviewers);
    }
    finally {
        await restoreBaseBranchAfterPublish(input);
    }
}
async function restoreBaseBranchAfterPublish(input) {
    if (!input.branchCreated || input.currentBranch !== input.baseBranch) {
        return;
    }
    await (0, publish_local_restore_1.restoreBaseBranch)({
        cwd: input.cwd,
        baseBranch: input.baseBranch,
        remoteName: input.remoteName,
        run: input.run,
        locale: input.locale,
        preserveLocalChanges: input.preserveLocalChanges,
        credentials: input.credentials,
    });
}
async function restoreBaseBranchAfterIncompletePublish(input) {
    if (!input.branchCreated || input.currentBranch !== input.baseBranch) {
        return;
    }
    try {
        await (0, native_git_cli_1.runNativeGitWithLegacyRunner)(input.run, {
            args: ["switch", input.baseBranch],
            cwd: input.cwd,
            locale: input.locale,
            suppressHooks: true,
        });
    }
    catch (error) {
        console.warn((0, catalog_1.translate)(input.locale, "publish.command.restoreBaseFailed", {
            baseBranch: input.baseBranch,
            message: error instanceof Error ? error.message : String(error),
        }));
    }
}
async function runPostPublishActions(input) {
    let workspacePath;
    try {
        const workspace = await input.syncBaseWorkspace({
            config: input.config,
            gitClient: input.gitRepository,
            authProvider: input.authProvider,
            repositoryRoot: input.cwd,
        });
        workspacePath = workspace.path;
    }
    catch (error) {
        throw new Error((0, catalog_1.translate)(input.locale, "publish.command.baseSyncFailed", {
            message: error instanceof Error ? error.message : String(error),
        }));
    }
    console.log((0, catalog_1.translate)(input.locale, "publish.command.baseSynced", {
        path: workspacePath,
    }));
    if (!input.restartService) {
        return 0;
    }
    return input.runServiceRestart(undefined, { cwd: input.cwd });
}
async function readCurrentBranch(cwd, run, locale) {
    const result = await (0, native_git_cli_1.runNativeGitWithLegacyRunner)(run, {
        args: ["branch", "--show-current"],
        cwd,
        locale,
    });
    const branchName = result.stdout.trim();
    return branchName.length === 0 ? null : branchName;
}
async function resolvePublishBranch(input) {
    if (input.currentBranch !== input.baseBranch) {
        return { branchName: input.currentBranch, created: false };
    }
    const branchName = buildPublishBranchName(input.now());
    await (0, native_git_cli_1.runNativeGitWithLegacyRunner)(input.run, {
        args: ["switch", "-c", branchName],
        cwd: input.cwd,
        locale: input.locale,
        suppressHooks: true,
    });
    return { branchName, created: true };
}
function buildPublishBranchName(now) {
    const timestamp = now
        .toISOString()
        .replaceAll(/[-:]/g, "")
        .replace(/\.\d{3}Z$/, "Z");
    return `alpaca/publish-${timestamp}`;
}
function normalizeMessage(message) {
    const normalized = message?.trim();
    return normalized ? normalized : DEFAULT_PUBLISH_MESSAGE;
}
function normalizePublishPathspecs(cwd, paths, locale) {
    if (!paths || paths.length === 0) {
        return [];
    }
    const cwdAbsolute = node_path_1.default.resolve(cwd);
    const normalized = [];
    const seen = new Set();
    for (const rawPath of paths) {
        const trimmed = rawPath.trim();
        if (!trimmed) {
            throw new Error((0, catalog_1.translate)(locale, "publish.command.invalidPath"));
        }
        const absolutePath = node_path_1.default.resolve(cwdAbsolute, trimmed);
        const relativePath = node_path_1.default.relative(cwdAbsolute, absolutePath);
        if (relativePath === ".." ||
            relativePath.startsWith(`..${node_path_1.default.sep}`) ||
            node_path_1.default.isAbsolute(relativePath)) {
            throw new Error((0, catalog_1.translate)(locale, "publish.command.pathOutsideRepository", {
                path: rawPath,
            }));
        }
        const pathspec = relativePath.length === 0 ? "." : relativePath.split(node_path_1.default.sep).join("/");
        if (!seen.has(pathspec)) {
            seen.add(pathspec);
            normalized.push(pathspec);
        }
    }
    return normalized;
}
async function findPublishPathspecsMissingFromGitAndDisk(input) {
    const missing = [];
    for (const pathspec of input.pathspecs) {
        if (await publishPathExistsOnDisk(input.cwd, pathspec)) {
            continue;
        }
        if (await publishPathIsKnownToGit(input.cwd, pathspec, input.run, input.locale)) {
            continue;
        }
        missing.push(pathspec);
    }
    return missing;
}
function warnMissingPublishPathspecs(locale, pathspecs) {
    for (const pathspec of pathspecs) {
        console.warn((0, catalog_1.translate)(locale, "publish.command.pathNotTrackedOrPresent", {
            path: pathspec,
        }));
    }
}
function excludePathspecs(pathspecs, excludedPathspecs) {
    if (excludedPathspecs.length === 0) {
        return [...pathspecs];
    }
    const excluded = new Set(excludedPathspecs);
    return pathspecs.filter((pathspec) => !excluded.has(pathspec));
}
async function publishPathExistsOnDisk(cwd, pathspec) {
    try {
        await (0, promises_1.access)(node_path_1.default.resolve(cwd, pathspec));
        return true;
    }
    catch {
        return false;
    }
}
async function publishPathIsKnownToGit(cwd, pathspec, run, locale) {
    try {
        const result = await (0, native_git_cli_1.runNativeGitWithLegacyRunner)(run, {
            args: ["ls-files", "--", pathspec],
            cwd,
            locale,
            allowNonZeroExit: true,
        });
        return result.stdout.trim().length > 0;
    }
    catch {
        return true;
    }
}
