import { sha256Base64 } from '../signing/hashing.js';
export const LIFECYCLE_TERMINAL_STATES = ['ready', 'failed', 'cancelled'];
export const LIFECYCLE_NON_TERMINAL_STATES = ['pending', 'provisioning', 'migrating', 'retiring'];
export const LIFECYCLE_STATES = [...LIFECYCLE_NON_TERMINAL_STATES, ...LIFECYCLE_TERMINAL_STATES];
export const LIFECYCLE_MIGRATION_STATES = ['pending', 'migrated', 'failed'];
export const LIFECYCLE_OPERATIONS = ['ensure_dev_database', 'ensure_prod_database', 'migrate_schema', 'retire_database'];
export const ENVIRONMENT_CLASSES = ['dev', 'staging', 'prod'];
export const DATABASE_ENGINES = ['postgres', 'snowflake', 'snowflake_postgres', 'lakebase'];
export const DATABASE_LIFECYCLE_MANAGED_BY = 'database_lifecycle';
export const CREDENTIAL_RESOLVERS = ['opa_local', 'aws_secrets_manager', 'gcp_secret_manager', 'vault', 'kubernetes_secret'];
export function isReadyDatabaseBinding(binding) {
    return binding.lifecycleState === 'ready';
}
export function computeRequirementKey(requirement) {
    return `${slugify(requirement.logicalName)}~${encodeURIComponent(requirement.logicalName)}:${requirement.engine}`;
}
export function computeBindingKey(input) {
    return [input.organizationId, ...bindingKeySegments(input)].join(':');
}
export function computeLegacyBindingKeyWithoutOrganization(input) {
    return bindingKeySegments(input).join(':');
}
function bindingKeySegments(input) {
    return [
        input.applicationId,
        input.environmentClass,
        `${slugify(input.environmentName)}~${encodeURIComponent(input.environmentName)}`,
        input.requirementKey
    ];
}
export async function computeDesiredSpecHash(requirement) {
    return await sha256Base64(JSON.stringify(canonicalize(requirement)));
}
export function isTerminalLifecycleState(state) {
    return LIFECYCLE_TERMINAL_STATES.includes(state);
}
export function redactLifecycleSecrets(value) {
    return redact(value);
}
function slugify(value) {
    return value
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-|-$/g, '');
}
function canonicalize(value) {
    if (Array.isArray(value)) {
        return value.map(canonicalize).sort((left, right) => codepointCompare(JSON.stringify(left), JSON.stringify(right)));
    }
    if (value && typeof value === 'object') {
        return Object.fromEntries(Object.entries(value)
            .filter(([, entryValue]) => entryValue !== undefined)
            .sort(([left], [right]) => codepointCompare(left, right))
            .map(([key, entryValue]) => [key, canonicalize(entryValue)]));
    }
    return value;
}
function codepointCompare(left, right) {
    if (left < right) {
        return -1;
    }
    if (left > right) {
        return 1;
    }
    return 0;
}
function redact(value, preserveCredentialRefs = false) {
    if (Array.isArray(value)) {
        return value.map((entry) => redact(entry, preserveCredentialRefs));
    }
    if (value && typeof value === 'object') {
        return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
            if (isCredentialRefKey(key)) {
                return [key, redact(entryValue, true)];
            }
            if (!preserveCredentialRefs && SECRET_KEY_PATTERN.test(key)) {
                return [key, '[REDACTED]'];
            }
            return [key, redact(entryValue, preserveCredentialRefs)];
        }));
    }
    return value;
}
// Shared secret-key tripwire: keys whose name strongly suggests a raw secret
// value. Used by `redactLifecycleSecrets` to scrub logs/error reporting and by
// the server-side `assertNoRawCredentialMaterial` guard to refuse callbacks
// that violate the typed-CredentialRef contract. NOT a security boundary --
// callers must still send `Record<string, string | number | boolean>` for
// `connectionMetadata` and `CredentialRef` for `credentialRefs`; this catches
// obvious misuse only.
export const SECRET_KEY_PATTERN = /password|secret|token|private[_-]?key|dsn/i;
// Schemes whose URI form embeds credentials before `@`. Used as a value-side
// tripwire alongside `SECRET_KEY_PATTERN` -- callers should never send
// connection strings; the typed `connectionMetadata` shape is the contract.
export const CREDENTIAL_BEARING_DSN_SCHEMES = [
    'postgres',
    'postgresql',
    'mysql',
    'mariadb',
    'mongodb',
    'mongodb+srv',
    'redis',
    'rediss',
    'amqp',
    'amqps',
    'kafka'
];
export function isSecretKey(key) {
    return SECRET_KEY_PATTERN.test(key);
}
export function containsCredentialMaterial(value) {
    if (DSN_WITH_INLINE_AUTH.test(value)) {
        return true;
    }
    if (CREDENTIAL_KV_PATTERN.test(value)) {
        return true;
    }
    if (JSON_ENCODED_SECRET_PATTERN.test(value)) {
        return true;
    }
    return false;
}
const DSN_SCHEME_GROUP = CREDENTIAL_BEARING_DSN_SCHEMES.join('|').replace(/\+/g, '\\+');
const DSN_WITH_INLINE_AUTH = new RegExp(`(?:${DSN_SCHEME_GROUP}):\\/\\/[^/@\\s:]+:[^/@\\s]+@`, 'i');
const CREDENTIAL_KV_PATTERN = /(?:password|pwd|token|secret|private[_-]?key|api[_-]?key)\s*=\s*[^;&\s"]+/i;
const JSON_ENCODED_SECRET_PATTERN = /["']\s*(?:password|secret|token|private[_-]?key|dsn)\s*["']\s*:/i;
function isCredentialRefKey(key) {
    return /credential[_-]?ref/i.test(key);
}
//# sourceMappingURL=index.js.map