/**
 * R2 structural-fields migration (2026-05-12).
 *
 * Backstory: when the encrypted store landed, the R2 credentials
 * (`r2.access_key_id`, `r2.secret_access_key`) moved into the registry
 * but the three structural fields — `account_id`, `bucket`, and
 * `public_url` — kept living in `workspace.json:r2_config.*`. That left
 * the Settings UI broken: users could enter credentials, hit save, and
 * AssetService still picked `local` because workspace-manager gates on
 * the structural fields from workspace.json. Until those fields were
 * also reachable via the store, the UI couldn't fully configure R2.
 *
 * Phase 12 (2026-05-12) already removed the first-run importer that
 * would have piggybacked the structural-field migration onto the
 * existing migrationSource pipeline. So this standalone one-shot fills
 * the gap: it runs at boot, copies pre-existing values from
 * `workspace.json:r2_config.*` into the store on first encounter, and
 * marks a meta flag so it never repeats.
 *
 * Operators who configured R2 via `studiograph r2 setup` before this
 * change see zero behavior difference. Operators who set R2 via the UI
 * post-change write directly to the store and never touch this code.
 *
 * Idempotent. Non-fatal on any single error — we'd rather boot with the
 * legacy fallback path than refuse to start because workspace.json went
 * sideways.
 */
import { existsSync, readFileSync } from 'fs';
const META_KEY = 'r2_structural_migrated_at';
/**
 * Map of registry key → dot-path inside workspace.json's r2_config block.
 * Mirrors the same shape the legacy `migrationSource` annotations encoded
 * for other fields before Phase 12b dropped them.
 */
const FIELD_MAP = [
    { key: 'r2.account_id', path: 'account_id' },
    { key: 'r2.bucket', path: 'bucket' },
    { key: 'r2.public_url', path: 'public_url' },
];
/**
 * Pull a nested string out of an object — same shape as dual-write's
 * readPath. Returns undefined for any missing intermediate or non-string
 * leaf. We intentionally only accept strings because the registry's
 * validators all expect string values for the R2 structural fields.
 */
function readString(obj, path) {
    if (!obj || typeof obj !== 'object')
        return undefined;
    const parts = path.split('.');
    let cur = obj;
    for (const p of parts) {
        if (cur == null || typeof cur !== 'object')
            return undefined;
        cur = cur[p];
    }
    return typeof cur === 'string' && cur.length > 0 ? cur : undefined;
}
export async function migrateR2Structural(opts) {
    if (opts.store.getMeta(META_KEY)) {
        return { skipped: true, written: [] };
    }
    // Stamp the marker even on the no-source path so we don't re-scan
    // workspace.json on every boot. This is the same shape scope-collapse
    // uses for fresh DBs.
    if (!existsSync(opts.workspaceJsonPath)) {
        opts.store.setMeta(META_KEY, String(Date.now()));
        return { skipped: false, written: [] };
    }
    let raw;
    try {
        raw = JSON.parse(readFileSync(opts.workspaceJsonPath, 'utf-8'));
    }
    catch {
        // Unparseable workspace.json — mark migrated anyway and let the
        // boot path surface the file error elsewhere. Trying to recover
        // from a corrupt config from inside a secrets migration would
        // mask the real problem.
        opts.store.setMeta(META_KEY, String(Date.now()));
        return { skipped: false, written: [] };
    }
    const r2Config = raw?.r2_config;
    const written = [];
    // Best-effort writes. Each field is independent — a bad URL on one
    // field shouldn't stop the others from migrating. The store's
    // workspace-setting writer doesn't run the registry validator at
    // write time, so a malformed URL would survive the write; the
    // Settings UI surfaces validation on subsequent edits.
    const actor = { type: 'system', systemId: 'r2-structural-migration' };
    for (const { key, path } of FIELD_MAP) {
        const value = readString(r2Config, path);
        if (!value)
            continue;
        // Don't clobber a value that's already in the store — the UI may
        // have written one before the importer ran on a freshly-redeployed
        // workspace where workspace.json got re-synced from a backup.
        if (opts.store.get(key) !== undefined)
            continue;
        try {
            await opts.store.set({ key, value }, actor);
            written.push(key);
        }
        catch {
            // Skip and continue — see module comment on non-fatal stance.
        }
    }
    opts.store.setMeta(META_KEY, String(Date.now()));
    return { skipped: false, written };
}
//# sourceMappingURL=r2-structural-migration.js.map