import { execFileSync } from 'child_process';
import { readFile, writeFile } from 'fs/promises';
const zeroSha = /^0{40}$/;
const commitSeparator = '\n---EMULSIFY-COMMIT---\n';
export function parseVersion(version) {
    const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-.+)?$/.exec(version);
    if (!match) {
        throw new Error(`Unsupported version format: ${version}`);
    }
    return {
        major: Number(match[1]),
        minor: Number(match[2]),
        patch: Number(match[3]),
    };
}
export function formatVersion(version) {
    return `${version.major}.${version.minor}.${version.patch}`;
}
export function compareVersions(left, right) {
    if (left.major !== right.major) {
        return left.major > right.major ? 1 : -1;
    }
    if (left.minor !== right.minor) {
        return left.minor > right.minor ? 1 : -1;
    }
    if (left.patch !== right.patch) {
        return left.patch > right.patch ? 1 : -1;
    }
    return 0;
}
export function incrementVersion(version, releaseType) {
    if (releaseType === 'major') {
        return { major: version.major + 1, minor: 0, patch: 0 };
    }
    if (releaseType === 'minor') {
        return { major: version.major, minor: version.minor + 1, patch: 0 };
    }
    return {
        major: version.major,
        minor: version.minor,
        patch: version.patch + 1,
    };
}
export function getReleaseType(messages) {
    if (messages.some((message) => hasBreakingChange(message))) {
        return 'major';
    }
    if (messages.some((message) => /^feat(?:\([^)]+\))?:/m.test(message))) {
        return 'minor';
    }
    if (messages.some((message) => /^(?:fix|perf)(?:\([^)]+\))?:/m.test(message))) {
        return 'patch';
    }
    return undefined;
}
async function updatePackageMetadata(version) {
    const packageJson = JSON.parse(await readFile('package.json', 'utf8'));
    const packageLockJson = JSON.parse(await readFile('package-lock.json', 'utf8'));
    packageJson.version = version;
    packageLockJson.version = version;
    if (packageLockJson.packages?.['']) {
        packageLockJson.packages[''].version = version;
    }
    await Promise.all([
        writeFile('package.json', `${JSON.stringify(packageJson, null, 2)}\n`),
        writeFile('package-lock.json', `${JSON.stringify(packageLockJson, null, 2)}\n`),
    ]);
}
async function getCurrentPackageVersion() {
    const packageJson = JSON.parse(await readFile('package.json', 'utf8'));
    return parseVersion(packageJson.version);
}
function getLatestTag() {
    try {
        return execFileSync('git', ['describe', '--tags', '--abbrev=0'], {
            encoding: 'utf8',
        }).trim();
    }
    catch {
        return undefined;
    }
}
function getCommitMessages(before, after, latestTag) {
    const rangeStart = before && !zeroSha.test(before) ? before : latestTag;
    const range = rangeStart ? `${rangeStart}..${after}` : after;
    const output = execFileSync('git', ['log', '--format=%B' + commitSeparator, range], {
        encoding: 'utf8',
    }).trim();
    return output
        .split(commitSeparator)
        .map((message) => message.trim())
        .filter(Boolean);
}
function hasBreakingChange(message) {
    return (/^[a-z]+(?:\([^)]+\))?!:/m.test(message) ||
        /^BREAKING(?: CHANGE| CHANGES)?:/m.test(message));
}
const [before, after = 'HEAD'] = process.argv.slice(2);
const latestTag = getLatestTag();
const currentVersion = await getCurrentPackageVersion();
const baseVersion = latestTag ? parseVersion(latestTag) : currentVersion;
const releaseType = getReleaseType(getCommitMessages(before, after, latestTag));
if (!releaseType) {
    console.log('No semantic version change detected.');
    process.exitCode = 0;
}
else {
    const targetVersion = incrementVersion(baseVersion, releaseType);
    if (compareVersions(currentVersion, targetVersion) >= 0) {
        console.log(`Package version ${formatVersion(currentVersion)} is already at or ahead of ${formatVersion(targetVersion)}.`);
    }
    else {
        await updatePackageMetadata(formatVersion(targetVersion));
        console.log(`Updated package metadata to ${formatVersion(targetVersion)}.`);
    }
}
