import { z } from 'zod';
export const SCM_COMMIT_MESSAGE_MAX_LENGTH = 4096;
export const SCM_COMMIT_PATCH_MAX_COUNT = 256;
export const SCM_COMMIT_PATCH_MAX_LENGTH = 200_000;
export const SCM_OPERATION_ERROR_CODES = {
    NOT_REPOSITORY: 'NOT_REPOSITORY',
    INVALID_PATH: 'INVALID_PATH',
    INVALID_REQUEST: 'INVALID_REQUEST',
    COMMAND_FAILED: 'COMMAND_FAILED',
    CHANGE_APPLY_FAILED: 'CHANGE_APPLY_FAILED',
    COMMIT_REQUIRED: 'COMMIT_REQUIRED',
    CONFLICTING_WORKTREE: 'CONFLICTING_WORKTREE',
    REMOTE_AUTH_REQUIRED: 'REMOTE_AUTH_REQUIRED',
    REMOTE_UPSTREAM_REQUIRED: 'REMOTE_UPSTREAM_REQUIRED',
    REMOTE_NON_FAST_FORWARD: 'REMOTE_NON_FAST_FORWARD',
    REMOTE_FF_ONLY_REQUIRED: 'REMOTE_FF_ONLY_REQUIRED',
    REMOTE_REJECTED: 'REMOTE_REJECTED',
    REMOTE_NOT_FOUND: 'REMOTE_NOT_FOUND',
    FEATURE_UNSUPPORTED: 'FEATURE_UNSUPPORTED',
    BACKEND_UNAVAILABLE: 'BACKEND_UNAVAILABLE',
};
export const ScmOperationErrorCodeSchema = z.enum([
    SCM_OPERATION_ERROR_CODES.NOT_REPOSITORY,
    SCM_OPERATION_ERROR_CODES.INVALID_PATH,
    SCM_OPERATION_ERROR_CODES.INVALID_REQUEST,
    SCM_OPERATION_ERROR_CODES.COMMAND_FAILED,
    SCM_OPERATION_ERROR_CODES.CHANGE_APPLY_FAILED,
    SCM_OPERATION_ERROR_CODES.COMMIT_REQUIRED,
    SCM_OPERATION_ERROR_CODES.CONFLICTING_WORKTREE,
    SCM_OPERATION_ERROR_CODES.REMOTE_AUTH_REQUIRED,
    SCM_OPERATION_ERROR_CODES.REMOTE_UPSTREAM_REQUIRED,
    SCM_OPERATION_ERROR_CODES.REMOTE_NON_FAST_FORWARD,
    SCM_OPERATION_ERROR_CODES.REMOTE_FF_ONLY_REQUIRED,
    SCM_OPERATION_ERROR_CODES.REMOTE_REJECTED,
    SCM_OPERATION_ERROR_CODES.REMOTE_NOT_FOUND,
    SCM_OPERATION_ERROR_CODES.FEATURE_UNSUPPORTED,
    SCM_OPERATION_ERROR_CODES.BACKEND_UNAVAILABLE,
]);
export const ScmBackendIdSchema = z.enum(['git', 'sapling']);
export const ScmRepoModeSchema = z.enum(['.git', '.sl']);
export const ScmBackendPreferenceSchema = z.object({
    kind: z.literal('prefer'),
    backendId: ScmBackendIdSchema,
});
export const ScmDiffAreaSchema = z.enum(['included', 'pending', 'both']);
export const ScmChangeSetModelSchema = z.enum(['index', 'working-copy']);
const ScmCapabilitiesSchemaCore = z.object({
    readStatus: z.boolean(),
    readDiffFile: z.boolean(),
    readDiffCommit: z.boolean(),
    readLog: z.boolean(),
    readBranches: z.boolean().optional(),
    readStash: z.boolean().optional(),
    writeInclude: z.boolean(),
    writeExclude: z.boolean(),
    writeDiscard: z.boolean().optional(),
    writeCommit: z.boolean(),
    writeCommitPathSelection: z.boolean(),
    writeCommitLineSelection: z.boolean(),
    writeBackout: z.boolean(),
    writeBranchCreate: z.boolean().optional(),
    writeBranchCheckout: z.boolean().optional(),
    writeRemoteFetch: z.boolean(),
    writeRemotePull: z.boolean(),
    writeRemotePush: z.boolean(),
    writeRemotePublish: z.boolean().optional(),
    writeStash: z.boolean().optional(),
    worktreeCreate: z.boolean(),
    changeSetModel: ScmChangeSetModelSchema,
    supportedDiffAreas: z.array(ScmDiffAreaSchema).min(1),
    operationLabels: z
        .object({
        commit: z.string().optional(),
        include: z.string().optional(),
        exclude: z.string().optional(),
        backout: z.string().optional(),
        fetch: z.string().optional(),
        pull: z.string().optional(),
        push: z.string().optional(),
    })
        .optional(),
});
export const ScmCapabilitiesSchema = z.preprocess((value) => {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return value;
    }
    const record = value;
    if (record.worktreeCreate !== undefined || record.workspaceWorktreeCreate === undefined) {
        return value;
    }
    return {
        ...record,
        worktreeCreate: record.workspaceWorktreeCreate,
    };
}, ScmCapabilitiesSchemaCore);
export const ScmEntryKindSchema = z.enum([
    'modified',
    'added',
    'deleted',
    'renamed',
    'copied',
    'untracked',
    'conflicted',
]);
export const ScmPathStatsSchema = z.object({
    includedAdded: z.number().int().nonnegative(),
    includedRemoved: z.number().int().nonnegative(),
    pendingAdded: z.number().int().nonnegative(),
    pendingRemoved: z.number().int().nonnegative(),
    isBinary: z.boolean(),
});
export const ScmWorkingEntrySchema = z.object({
    path: z.string(),
    previousPath: z.string().nullable(),
    kind: ScmEntryKindSchema,
    includeStatus: z.string(),
    pendingStatus: z.string(),
    hasIncludedDelta: z.boolean(),
    hasPendingDelta: z.boolean(),
    stats: ScmPathStatsSchema,
});
export const ScmWorktreeSchema = z.object({
    path: z.string(),
    branch: z.string().nullable(),
    isCurrent: z.boolean(),
    isMain: z.boolean().optional(),
});
export const ScmWorkingSnapshotSchema = z.object({
    projectKey: z.string(),
    fetchedAt: z.number().int(),
    repo: z.object({
        isRepo: z.boolean(),
        rootPath: z.string().nullable(),
        backendId: ScmBackendIdSchema.nullable(),
        mode: ScmRepoModeSchema.nullable(),
        worktrees: z.array(ScmWorktreeSchema).default([]),
    }),
    capabilities: ScmCapabilitiesSchema,
    branch: z.object({
        head: z.string().nullable(),
        upstream: z.string().nullable(),
        ahead: z.number().int().nonnegative(),
        behind: z.number().int().nonnegative(),
        detached: z.boolean(),
    }),
    stashCount: z.number().int().nonnegative().optional(),
    hasConflicts: z.boolean(),
    entries: z.array(ScmWorkingEntrySchema),
    totals: z.object({
        includedFiles: z.number().int().nonnegative(),
        pendingFiles: z.number().int().nonnegative(),
        untrackedFiles: z.number().int().nonnegative(),
        includedAdded: z.number().int().nonnegative(),
        includedRemoved: z.number().int().nonnegative(),
        pendingAdded: z.number().int().nonnegative(),
        pendingRemoved: z.number().int().nonnegative(),
    }),
});
export const ScmRequestBaseSchema = z.object({
    cwd: z.string().optional(),
    backendPreference: ScmBackendPreferenceSchema.optional(),
});
export const ScmBackendDescribeRequestSchema = ScmRequestBaseSchema;
export const ScmBackendDescribeResponseSchema = z.object({
    success: z.boolean(),
    backendId: ScmBackendIdSchema.optional(),
    repoMode: ScmRepoModeSchema.optional(),
    isRepo: z.boolean().optional(),
    capabilities: ScmCapabilitiesSchema.optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmStatusSnapshotRequestSchema = ScmRequestBaseSchema;
export const ScmStatusSnapshotResponseSchema = z.object({
    success: z.boolean(),
    snapshot: ScmWorkingSnapshotSchema.optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmDiffFileRequestSchema = ScmRequestBaseSchema.extend({
    path: z.string(),
    area: ScmDiffAreaSchema.optional(),
});
export const ScmDiffFileResponseSchema = z.object({
    success: z.boolean(),
    diff: z.string().optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmDiffCommitRequestSchema = ScmRequestBaseSchema.extend({
    commit: z.string(),
});
export const ScmDiffCommitResponseSchema = z.object({
    success: z.boolean(),
    diff: z.string().optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmChangeApplyRequestSchema = ScmRequestBaseSchema.extend({
    paths: z.array(z.string()).optional(),
    patch: z.string().optional(),
});
export const ScmChangeApplyResponseSchema = z.object({
    success: z.boolean(),
    stdout: z.string().optional(),
    stderr: z.string().optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmChangeDiscardEntrySchema = z.object({
    path: z.string(),
    kind: ScmEntryKindSchema,
});
export const ScmChangeDiscardRequestSchema = ScmRequestBaseSchema.extend({
    entries: z.array(ScmChangeDiscardEntrySchema).min(1),
});
export const ScmChangeDiscardResponseSchema = z.object({
    success: z.boolean(),
    stdout: z.string().optional(),
    stderr: z.string().optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmCommitPatchSchema = z.object({
    path: z.string(),
    patch: z.string().min(1).max(SCM_COMMIT_PATCH_MAX_LENGTH),
});
export const ScmCommitCreateRequestSchema = ScmRequestBaseSchema.extend({
    message: z.string().max(SCM_COMMIT_MESSAGE_MAX_LENGTH),
    scope: z
        .union([
        z.object({
            kind: z.literal('all-pending'),
        }),
        z.object({
            kind: z.literal('paths'),
            include: z.array(z.string()).min(1),
            exclude: z.array(z.string()).optional(),
        }),
    ])
        .optional(),
    patches: z.array(ScmCommitPatchSchema).min(1).max(SCM_COMMIT_PATCH_MAX_COUNT).optional(),
});
export const ScmCommitCreateResponseSchema = z.object({
    success: z.boolean(),
    commitSha: z.string().optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
function normalizeScmPatchPathToken(raw) {
    let value = raw.trim();
    if (!value || value === '/dev/null')
        return null;
    if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
        value = value.slice(1, -1);
    }
    value = value.replace(/^([ab])\//, '').replace(/^\.\/+/, '').trim();
    if (!value || value === '/dev/null')
        return null;
    return value;
}
function tokenizeScmDiffHeader(value) {
    const tokens = [];
    let current = '';
    let inQuotes = false;
    for (let index = 0; index < value.length; index += 1) {
        const char = value[index] ?? '';
        if (char === '"') {
            inQuotes = !inQuotes;
            continue;
        }
        if (!inQuotes && /\s/.test(char)) {
            if (current) {
                tokens.push(current);
                current = '';
            }
            continue;
        }
        current += char;
    }
    if (current)
        tokens.push(current);
    return tokens;
}
function parseScmGitDiffHeaderPath(line) {
    const raw = line.slice('diff --git '.length).trim();
    if (!raw)
        return [];
    const tokens = tokenizeScmDiffHeader(raw);
    if (tokens.length < 2)
        return [];
    const left = normalizeScmPatchPathToken(tokens[0] ?? '');
    const right = normalizeScmPatchPathToken(tokens[1] ?? '');
    return [left, right].filter((value) => Boolean(value));
}
export function parseScmPatchPaths(patch) {
    const normalized = String(patch ?? '').replace(/\r\n/g, '\n');
    if (!normalized.trim())
        return [];
    const seen = new Set();
    for (const line of normalized.split('\n')) {
        if (line.startsWith('diff --git ')) {
            for (const path of parseScmGitDiffHeaderPath(line)) {
                seen.add(path);
            }
            continue;
        }
        if (line.startsWith('--- ') || line.startsWith('+++ ')) {
            const parsed = normalizeScmPatchPathToken(line.slice(4));
            if (parsed)
                seen.add(parsed);
        }
    }
    return Array.from(seen);
}
export function isScmPatchBoundToPath(path, patch) {
    const normalizedPath = normalizeScmPatchPathToken(path);
    if (!normalizedPath)
        return false;
    const parsedPaths = parseScmPatchPaths(patch);
    if (parsedPaths.length === 0)
        return false;
    return parsedPaths.every((parsedPath) => parsedPath === normalizedPath);
}
export const ScmLogEntrySchema = z.object({
    sha: z.string(),
    shortSha: z.string(),
    authorName: z.string(),
    authorEmail: z.string(),
    timestamp: z.number().int(),
    subject: z.string(),
    body: z.string(),
});
export const ScmLogListRequestSchema = ScmRequestBaseSchema.extend({
    limit: z.number().int().min(1).max(500).optional(),
    skip: z.number().int().min(0).optional(),
});
export const ScmLogListResponseSchema = z.object({
    success: z.boolean(),
    entries: z.array(ScmLogEntrySchema).optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmCommitBackoutRequestSchema = ScmRequestBaseSchema.extend({
    commit: z.string(),
});
export const ScmCommitBackoutResponseSchema = z.object({
    success: z.boolean(),
    stdout: z.string().optional(),
    stderr: z.string().optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export const ScmRemoteRequestSchema = ScmRequestBaseSchema.extend({
    remote: z.string().optional(),
    branch: z.string().optional(),
});
export const ScmRemoteResponseSchema = z.object({
    success: z.boolean(),
    stdout: z.string().optional(),
    stderr: z.string().optional(),
    error: z.string().optional(),
    errorCode: ScmOperationErrorCodeSchema.optional(),
});
export function parseScmUpstreamRef(upstream) {
    if (!upstream)
        return null;
    const slashIndex = upstream.indexOf('/');
    if (slashIndex <= 0 || slashIndex === upstream.length - 1) {
        return null;
    }
    return {
        remote: upstream.slice(0, slashIndex),
        branch: upstream.slice(slashIndex + 1),
    };
}
export function inferScmRemoteTarget(input) {
    const parsed = parseScmUpstreamRef(input.upstream);
    if (parsed)
        return parsed;
    return {
        remote: input.defaultRemote ?? 'origin',
        branch: input.allowHeadFallback ? (input.head ?? null) : null,
    };
}
const CONTROL_CHAR_REGEX = /[\u0000-\u001F\u007F]/;
function hasUnsupportedRemoteRefSyntax(value, label) {
    if (CONTROL_CHAR_REGEX.test(value))
        return true;
    if (value.includes('\\'))
        return true;
    if (value.includes('//'))
        return true;
    if (value.startsWith('/') || value.endsWith('/'))
        return true;
    if (value.includes('@{') || value.includes('..'))
        return true;
    if (label === 'Remote name') {
        return value.includes(':');
    }
    return (value.startsWith('+') ||
        value.startsWith('.') ||
        value.endsWith('.') ||
        value.endsWith('.lock') ||
        value.includes(':') ||
        value.includes('^') ||
        value.includes('~') ||
        value.includes('?') ||
        value.includes('*') ||
        value.includes('['));
}
function normalizeRemoteRefValue(value, label) {
    if (value === undefined) {
        return { ok: true, value: undefined };
    }
    const normalized = value.trim();
    if (!normalized) {
        return { ok: true, value: undefined };
    }
    if (normalized.startsWith('-')) {
        return { ok: false, error: `${label} cannot start with "-"` };
    }
    if (/\s/.test(normalized)) {
        return { ok: false, error: `${label} must not contain whitespace` };
    }
    if (normalized.includes('\0')) {
        return { ok: false, error: `${label} contains unsupported characters` };
    }
    if (hasUnsupportedRemoteRefSyntax(normalized, label)) {
        return { ok: false, error: `${label} contains unsupported syntax` };
    }
    return { ok: true, value: normalized };
}
export function normalizeScmRemoteRequest(request) {
    const remote = normalizeRemoteRefValue(request.remote, 'Remote name');
    if (!remote.ok) {
        return remote;
    }
    const branch = normalizeRemoteRefValue(request.branch, 'Branch name');
    if (!branch.ok) {
        return branch;
    }
    return {
        ok: true,
        request: {
            remote: remote.value,
            branch: branch.value,
        },
    };
}
export function hasAnyPendingScmChanges(snapshot) {
    return (snapshot.totals.includedFiles > 0 ||
        snapshot.totals.pendingFiles > 0 ||
        snapshot.totals.untrackedFiles > 0);
}
export function evaluateScmRemoteMutationPolicy(input) {
    const { kind, snapshot, hasExplicitTarget, policy } = input;
    if (kind === 'push' && policy.blockPushOnConflicts && snapshot.hasConflicts) {
        return { ok: false, reason: 'conflicts_present' };
    }
    if (policy.requireUpstreamWhenNoExplicitTarget && !hasExplicitTarget && !snapshot.branch.upstream) {
        return { ok: false, reason: 'upstream_required' };
    }
    if (snapshot.branch.detached || (policy.requireActiveHead && !snapshot.branch.head)) {
        return { ok: false, reason: 'detached_head' };
    }
    if (kind === 'push' && policy.blockPushWhenBehind && snapshot.branch.behind > 0) {
        return { ok: false, reason: 'branch_behind_remote' };
    }
    if (kind === 'pull' && policy.requireCleanPull && (snapshot.hasConflicts || hasAnyPendingScmChanges(snapshot))) {
        return { ok: false, reason: 'clean_worktree_required' };
    }
    return { ok: true };
}
export function classifyScmOperationErrorCode(errorCode) {
    switch (errorCode) {
        case SCM_OPERATION_ERROR_CODES.NOT_REPOSITORY:
            return 'repository';
        case SCM_OPERATION_ERROR_CODES.INVALID_PATH:
            return 'path';
        case SCM_OPERATION_ERROR_CODES.INVALID_REQUEST:
            return 'request';
        case SCM_OPERATION_ERROR_CODES.COMMAND_FAILED:
            return 'command';
        case SCM_OPERATION_ERROR_CODES.CHANGE_APPLY_FAILED:
            return 'change';
        case SCM_OPERATION_ERROR_CODES.COMMIT_REQUIRED:
            return 'commit';
        case SCM_OPERATION_ERROR_CODES.CONFLICTING_WORKTREE:
            return 'worktree';
        case SCM_OPERATION_ERROR_CODES.REMOTE_AUTH_REQUIRED:
        case SCM_OPERATION_ERROR_CODES.REMOTE_UPSTREAM_REQUIRED:
        case SCM_OPERATION_ERROR_CODES.REMOTE_NON_FAST_FORWARD:
        case SCM_OPERATION_ERROR_CODES.REMOTE_FF_ONLY_REQUIRED:
        case SCM_OPERATION_ERROR_CODES.REMOTE_REJECTED:
        case SCM_OPERATION_ERROR_CODES.REMOTE_NOT_FOUND:
            return 'remote';
        case SCM_OPERATION_ERROR_CODES.FEATURE_UNSUPPORTED:
            return 'capability';
        case SCM_OPERATION_ERROR_CODES.BACKEND_UNAVAILABLE:
            return 'backend';
        default:
            return 'unknown';
    }
}
export function mapSaplingScmErrorCode(stderr) {
    const lower = String(stderr ?? '').toLowerCase();
    if (lower.includes('no repository found') || lower.includes('not inside a repository')) {
        return SCM_OPERATION_ERROR_CODES.NOT_REPOSITORY;
    }
    if (lower.includes('authentication') || lower.includes('permission denied') || lower.includes('authorization')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_AUTH_REQUIRED;
    }
    if (lower.includes('bookmark') && lower.includes('not found')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_UPSTREAM_REQUIRED;
    }
    if (lower.includes("use '--to' to specify destination bookmark")) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_UPSTREAM_REQUIRED;
    }
    if (lower.includes('you must specify a destination for the update')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_UPSTREAM_REQUIRED;
    }
    if (lower.includes('does not have a name')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_NOT_FOUND;
    }
    if (lower.includes('non-fast-forward') || lower.includes('push creates new remote head')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_NON_FAST_FORWARD;
    }
    if (lower.includes('remote rejected')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_REJECTED;
    }
    return SCM_OPERATION_ERROR_CODES.COMMAND_FAILED;
}
export function mapGitScmErrorCode(stderr) {
    const lower = String(stderr ?? '').toLowerCase();
    if (lower.includes('not a git repository')) {
        return SCM_OPERATION_ERROR_CODES.NOT_REPOSITORY;
    }
    if (lower.includes('no such remote') || lower.includes('does not appear to be a git repository')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_NOT_FOUND;
    }
    if (lower.includes('authentication failed') ||
        lower.includes('permission denied') ||
        lower.includes('could not read username') ||
        lower.includes('terminal prompts disabled') ||
        lower.includes('support for password authentication was removed')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_AUTH_REQUIRED;
    }
    if (lower.includes('no upstream configured') ||
        lower.includes('has no upstream branch') ||
        lower.includes('no tracking information for the current branch')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_UPSTREAM_REQUIRED;
    }
    if (lower.includes('non-fast-forward') ||
        lower.includes('fetch first') ||
        lower.includes('tip of your current branch is behind')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_NON_FAST_FORWARD;
    }
    if (lower.includes('not possible to fast-forward') || (lower.includes('ff-only') && lower.includes('aborting'))) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_FF_ONLY_REQUIRED;
    }
    if (lower.includes('remote rejected') ||
        lower.includes('pre-receive hook declined') ||
        lower.includes('protected branch hook declined') ||
        lower.includes('remote: error: gh006') ||
        lower.includes('remote: error: gh013')) {
        return SCM_OPERATION_ERROR_CODES.REMOTE_REJECTED;
    }
    return SCM_OPERATION_ERROR_CODES.COMMAND_FAILED;
}
//# sourceMappingURL=scm.js.map