function hasExtension(filePath, extension) {
    return filePath.toLowerCase().endsWith(extension);
}
export function resolveInvocation(cliBin) {
    if (hasExtension(cliBin, '.js')) {
        return { command: 'node', prefixArgs: [cliBin] };
    }
    if (hasExtension(cliBin, '.ts')) {
        return { command: 'bun', prefixArgs: [cliBin] };
    }
    return { command: cliBin, prefixArgs: [] };
}
/**
 * Build the CLI argument vector for an operation invocation.
 *
 * Key design choices vs old SDK:
 * - changeMode injection BEFORE argv loop, not after. changeMode is already a
 *   param in operationSpec.params (envelope param for mutations). Appending after
 *   the loop would duplicate it.
 * - Booleans encoded as `--flag true`/`--flag false` explicitly, matching current CLI.
 */
export function buildOperationArgv(operation, params, options, runtimeTimeoutMs, defaultChangeMode, user) {
    // Inject defaultChangeMode into params BEFORE encoding — single source of truth.
    let normalizedParams = defaultChangeMode != null && params.changeMode == null && operation.params.some((p) => p.name === 'changeMode')
        ? { ...params, changeMode: defaultChangeMode }
        : params;
    // Inject user identity for doc.open when not already specified.
    if (user != null && operation.operationId === 'doc.open') {
        if (normalizedParams.userName == null && user.name) {
            normalizedParams = { ...normalizedParams, userName: user.name };
        }
        if (normalizedParams.userEmail == null && user.email) {
            normalizedParams = { ...normalizedParams, userEmail: user.email };
        }
    }
    // Legacy alias: tables.split renamed atRowIndex → rowIndex (SD-2132).
    if (operation.operationId === 'doc.tables.split' && normalizedParams.atRowIndex !== undefined) {
        if (normalizedParams.rowIndex !== undefined && normalizedParams.rowIndex !== normalizedParams.atRowIndex) {
            throw new Error('tables.split: cannot provide both rowIndex and atRowIndex with different values.');
        }
        const { atRowIndex, ...rest } = normalizedParams;
        normalizedParams = { ...rest, rowIndex: atRowIndex };
    }
    const argv = [...operation.commandTokens];
    for (const spec of operation.params) {
        const value = normalizedParams[spec.name];
        if (value == null)
            continue;
        const flag = `--${spec.flag ?? spec.name}`;
        switch (spec.kind) {
            case 'doc':
                argv.push(String(value));
                break;
            case 'flag':
                if (spec.type === 'boolean') {
                    // Explicit true/false — matches current CLI operation-executor.ts.
                    argv.push(flag, value === true ? 'true' : 'false');
                }
                else if (spec.type === 'string[]') {
                    if (Array.isArray(value)) {
                        for (const entry of value)
                            argv.push(flag, String(entry));
                    }
                }
                else {
                    argv.push(flag, String(value));
                }
                break;
            case 'jsonFlag':
                // CLI always parses --*-json values via JSON.parse, so even scalar
                // strings must be serialized as JSON string literals.
                argv.push(flag, JSON.stringify(value));
                break;
        }
    }
    const timeoutMs = options.timeoutMs ?? runtimeTimeoutMs;
    if (timeoutMs != null) {
        argv.push('--timeout-ms', String(timeoutMs));
    }
    argv.push('--output', 'json');
    return argv;
}
