/**
 * studiograph settings — CLI for the non-secret half of the encrypted store.
 *
 *   studiograph settings list                    Inventory of stored settings + values
 *   studiograph settings get <key>               One value
 *   studiograph settings set <key> <value>       Write or replace a setting
 *   studiograph settings delete <key>            Clear (reverts to registry default)
 *   studiograph settings schema                  Show every registered key, default, and constraint
 *
 * Targets registry workspace-setting keys (model.provider, model.id,
 * r2.account_id, r2.bucket, r2.public_url, search.provider,
 * search.google.cx, …) — the same surface the Web Providers panel
 * writes via `PUT /api/settings/:key`. Secrets go through
 * `studiograph secrets`; this command refuses non-setting classifications.
 *
 * Mirrors the shape of `studiograph secrets` (list/get/set/delete) so
 * operators already familiar with that command don't have to relearn
 * anything. Values are JSON-serialized in the store; for enum/string
 * settings the value is what you'd type at the prompt. Numeric and
 * boolean values are accepted as their plain typed form (`true`,
 * `false`, `42`) — set converts to a JSON value before storage.
 *
 * Authentication: same model as `studiograph secrets` — the master key
 * is the gate. If you can read `~/.studiograph/master-key`, you can
 * read/write the store.
 */
import { Command } from 'commander';
import { log } from '@clack/prompts';
import { homedir } from 'os';
import { existsSync } from 'fs';
import { join } from 'path';
import { SqliteEncryptedStore, loadMasterKey, loadPreviousMasterKey, setSecretStore, getKeyDef, listWorkspaceSettingsSchema, } from '../../../core/secrets/index.js';
function openStore() {
    const loaded = loadMasterKey();
    const previous = loadPreviousMasterKey();
    const sgRoot = process.env.RAILWAY_ENVIRONMENT && existsSync('/workspace')
        ? '/workspace'
        : join(homedir(), '.studiograph');
    const dbPath = join(sgRoot, 'secrets.db');
    if (!existsSync(dbPath)) {
        log.error(`Settings store not found at ${dbPath}`);
        log.info('Run `studiograph init` first, or start `studiograph serve` to initialize the store.');
        process.exit(1);
    }
    const store = new SqliteEncryptedStore({
        dbPath,
        masterKey: loaded.key,
        previousMasterKey: previous,
    });
    setSecretStore(store);
    return store;
}
function actor() {
    return { type: 'system', systemId: 'cli' };
}
function fmtTs(ms) {
    if (!ms)
        return '—';
    return new Date(ms).toISOString();
}
function fmtTable(rows) {
    if (rows.length === 0)
        return '';
    const cols = Object.keys(rows[0]);
    const widths = cols.map((c) => Math.max(c.length, ...rows.map((r) => r[c]?.length ?? 0)));
    const header = cols.map((c, i) => c.padEnd(widths[i])).join('  ');
    const sep = widths.map((w) => '─'.repeat(w)).join('  ');
    const body = rows.map((r) => cols.map((c, i) => (r[c] ?? '').padEnd(widths[i])).join('  ')).join('\n');
    return `${header}\n${sep}\n${body}`;
}
/**
 * Reject classifications that aren't workspace-setting. Mirrors the
 * server-side guard in routes/settings-api.ts so the CLI presents the
 * same error contract: secret keys are explicitly routed to
 * `studiograph secrets`, bootstrap keys are env-only and unsettable.
 */
function classificationError(key) {
    const def = getKeyDef(key);
    if (!def)
        return `Unknown key "${key}". Run \`studiograph settings schema\` to list available keys.`;
    if (def.classification === 'workspace-secret') {
        return `Key "${key}" is a secret. Use \`studiograph secrets set ${key}\` instead.`;
    }
    if (def.classification === 'bootstrap') {
        return `Key "${key}" is bootstrap-class — set it via environment variables, not the store.`;
    }
    if (def.classification.startsWith('user-')) {
        return `Key "${key}" is user-scoped — managed per-user via the API, not workspace-wide CLI.`;
    }
    if (def.classification !== 'workspace-setting') {
        return `Key "${key}" classification "${def.classification}" is not settable via this command.`;
    }
    return null;
}
/**
 * Coerce a CLI string token into a bare primitive (number, boolean) for
 * registry validators that demand a non-string type. Only consulted as
 * a fallback after the raw string has already failed validation — this
 * is important because string-typed registry entries (e.g.
 * `r2.bucket: z.string()`) may legitimately accept values that look
 * like primitives (a bucket named `2026`, `true`, `42.0`). Eagerly
 * coercing those would reject valid input before zod sees the right
 * type.
 */
function coerceCliPrimitive(raw) {
    if (raw === 'true')
        return true;
    if (raw === 'false')
        return false;
    if (/^-?\d+$/.test(raw))
        return parseInt(raw, 10);
    if (/^-?\d+\.\d+$/.test(raw))
        return parseFloat(raw);
    return raw;
}
export const settingsCommand = new Command('settings')
    .description('Manage workspace settings stored in the encrypted store (model.provider, r2.bucket, search.google.cx, …)');
// ── settings list ─────────────────────────────────────────────────────────
settingsCommand
    .command('list')
    .description('List every setting currently stored, with values')
    .option('--format <format>', 'Output format: table (default) or json', 'table')
    .action(async (opts) => {
    const store = openStore();
    try {
        const items = store.list();
        if (opts.format === 'json') {
            console.log(JSON.stringify(items.map((s) => {
                let value;
                try {
                    value = JSON.parse(s.valueJson);
                }
                catch {
                    value = s.valueJson;
                }
                return { key: s.key, value, updated_at: s.updatedAt, updated_by: s.updatedBy };
            }), null, 2));
            return;
        }
        if (items.length === 0) {
            log.info('No settings stored.');
            return;
        }
        const rows = items.map((s) => {
            let value;
            try {
                value = JSON.parse(s.valueJson);
            }
            catch {
                value = s.valueJson;
            }
            return {
                key: s.key,
                value: typeof value === 'string' ? value : JSON.stringify(value),
                updated: fmtTs(s.updatedAt),
                updated_by: s.updatedBy,
            };
        });
        console.log(fmtTable(rows));
    }
    finally {
        store.close();
    }
});
// ── settings get <key> ────────────────────────────────────────────────────
settingsCommand
    .command('get <key>')
    .description('Show the current value of a setting')
    .option('--format <format>', 'Output format: value (default) or json', 'value')
    .action(async (key, opts) => {
    const store = openStore();
    try {
        const value = store.get(key);
        if (value === undefined) {
            const def = getKeyDef(key);
            if (def?.classification === 'workspace-setting' && def.default !== undefined) {
                if (opts.format === 'json') {
                    console.log(JSON.stringify({ key, value: def.default, source: 'default' }, null, 2));
                }
                else {
                    console.log(def.default);
                }
                return;
            }
            log.error(`No value stored for "${key}" (and no registry default).`);
            process.exit(1);
        }
        if (opts.format === 'json') {
            console.log(JSON.stringify({ key, value }, null, 2));
        }
        else {
            console.log(typeof value === 'string' ? value : JSON.stringify(value));
        }
    }
    finally {
        store.close();
    }
});
// ── settings set <key> <value> ────────────────────────────────────────────
settingsCommand
    .command('set <key> <value>')
    .description('Write or replace a setting. Validates against the registry constraint (enum/string/number).')
    .action(async (key, rawValue) => {
    const err = classificationError(key);
    if (err) {
        log.error(err);
        process.exit(1);
    }
    const def = getKeyDef(key);
    // Try the raw string first — most registry settings (model.provider,
    // r2.bucket, search.google.cx, …) are z.string() / z.enum() and the
    // CLI token IS the value. Falling through to primitive coercion
    // would spuriously reject string values that look numeric or
    // boolean (e.g. `studiograph settings set r2.bucket 2026`).
    let parsed = def.validate.safeParse(rawValue);
    if (!parsed.success) {
        // Raw form rejected — try coerced primitive for genuinely typed
        // schemas (z.number(), z.boolean()). Skip the retry when coercion
        // wouldn't change anything: the result would be identical and we'd
        // surface the same failure twice.
        const coerced = coerceCliPrimitive(rawValue);
        if (coerced !== rawValue) {
            parsed = def.validate.safeParse(coerced);
        }
    }
    if (!parsed.success) {
        log.error(`Validation failed: ${parsed.error.issues.map((i) => i.message).join('; ')}`);
        process.exit(1);
    }
    const store = openStore();
    try {
        const meta = await store.set({ key, value: parsed.data }, actor());
        log.success(`Stored ${key}=${JSON.stringify(parsed.data)}. updated_at=${fmtTs(meta.updatedAt)}`);
    }
    finally {
        store.close();
    }
});
// ── settings delete <key> ─────────────────────────────────────────────────
settingsCommand
    .command('delete <key>')
    .description('Clear a setting (reverts to the registry default, or empty if no default). Audited.')
    .action(async (key) => {
    const err = classificationError(key);
    if (err) {
        log.error(err);
        process.exit(1);
    }
    const store = openStore();
    try {
        const removed = await store.delete(key, actor());
        if (removed)
            log.success(`Cleared ${key}.`);
        else
            log.warn(`No stored value for ${key} (nothing to clear).`);
    }
    finally {
        store.close();
    }
});
// ── settings schema ───────────────────────────────────────────────────────
settingsCommand
    .command('schema')
    .description('Show every registered workspace-setting key with default value, env alias, and constraint')
    .option('--format <format>', 'Output format: table (default) or json', 'table')
    .action(async (opts) => {
    const schema = listWorkspaceSettingsSchema();
    if (opts.format === 'json') {
        console.log(JSON.stringify(schema, null, 2));
        return;
    }
    const rows = schema.map((s) => ({
        key: s.key,
        default: s.default !== undefined ? JSON.stringify(s.default) : '—',
        constraint: s.constraint
            ? s.constraint.type === 'enum'
                ? `enum: ${s.constraint.values.join(' | ')}`
                : s.constraint.type
            : '—',
        env: s.env_aliases?.join(', ') ?? '—',
    }));
    console.log(fmtTable(rows));
});
//# sourceMappingURL=settings.js.map