/**
 * EnrollmentWizard — mobile-first login orchestration (P2.1 of the Subscription
 * & Auth Standard). Sits on top of PendingLoginStore and turns a raw framework
 * login into a phone-friendly, expiry-resilient flow.
 *
 * The job (from the spec + the pi live-test specimen):
 *   - START a login: drive the framework's login flow, capture the PUBLIC
 *     device-code / auth URL, and store it with its TTL visible. (Codex →
 *     device-code; Claude → URL+paste-back-code.)
 *   - On EXPIRY: auto-reissue a fresh code/URL WITHOUT the operator asking — the
 *     exact gap the pi live-test exposed (the first code expired before Justin
 *     got to it and re-issuing took a manual round-trip).
 *   - COMPLETE when the operator approves at the provider + the account enrols.
 *
 * The interactive part — actually driving the framework's login CLI to obtain a
 * code/URL — is INJECTED (`driveLogin`), so this orchestrator is pure +
 * hermetically testable (no spawning, no network, no real OAuth). In production
 * the driver runs the framework's device-code/login flow and scrapes the code +
 * URL + TTL; here tests pass a stub. NEVER handles a credential — only the
 * public code/URL the operator types into the provider's own page.
 */
import { ensureInteractiveReady, } from './ensureInteractiveReady.js';
export class EnrollmentWizard {
    store;
    driveLogin;
    logger;
    ensureReady;
    constructor(cfg) {
        this.store = cfg.store;
        this.driveLogin = cfg.driveLogin;
        this.logger = cfg.logger ?? { log: () => { }, warn: () => { } };
        this.ensureReady = cfg.ensureReady ?? ensureInteractiveReady;
    }
    /** Default flow kind per provider: Codex/OpenAI = device-code (its endorsed
     *  flow); everyone else = url-code-paste (the phone-friendly Claude path). */
    static defaultKind(provider) {
        return provider === 'openai' ? 'device-code' : 'url-code-paste';
    }
    /**
     * Operator-facing heads-up about a flow's quirks, surfaced on the pending login.
     * The url-code-paste (Claude) flow on a brand-new account slot frequently issues
     * TWO codes in sequence: Anthropic first emails an email-VERIFICATION code, then
     * (after that's accepted) the page shows the sign-in code to paste back. Enrolling
     * is always a new slot, so the operator should expect — and not be confused by —
     * the two-step sequence (this confusion was flagged in live testing, topic 20905).
     * device-code (Codex) is a single code, so no notice. Returns undefined when there
     * is nothing to warn about.
     */
    static flowNotice(kind) {
        if (kind === 'url-code-paste') {
            return ('Heads up: a brand-new Claude login often asks for TWO codes in order — ' +
                'first an email-verification code Anthropic sends you, then the sign-in code ' +
                'shown after that. Enter the email code first; the sign-in code comes next.');
        }
        return undefined;
    }
    /**
     * Start an enrollment: drive the login, capture the public code/URL, store it
     * as a pending login (TTL visible). Returns the stored PendingLogin — the
     * surface the operator's phone shows.
     */
    async start(input) {
        const kind = input.kind ?? EnrollmentWizard.defaultKind(input.provider);
        const artifact = await this.driveLogin({
            provider: input.provider,
            framework: input.framework,
            kind,
            configHome: input.configHome,
        });
        const login = this.store.issue({
            id: input.id,
            label: input.label,
            provider: input.provider,
            framework: input.framework,
            kind,
            configHome: input.configHome,
            verificationUrl: artifact.verificationUrl,
            userCode: artifact.userCode,
            notice: EnrollmentWizard.flowNotice(kind),
            ttlMs: artifact.ttlMs,
        });
        this.logger.log(`[EnrollmentWizard] started ${kind} login for ${input.label} (${input.provider})`);
        return login;
    }
    /**
     * Auto-reissue every EXPIRED pending login without the operator asking — the
     * pi-live-test gap. For each expired login, re-drive the framework flow and
     * refresh the stored code/URL + TTL. Returns the reissued logins. Driver
     * failures are skipped (logged, left expired) so one bad re-drive doesn't
     * abort the sweep.
     */
    async reissueExpired() {
        const reissued = [];
        for (const login of this.store.expired()) {
            try {
                const fresh = await this.driveLogin({
                    provider: login.provider,
                    framework: login.framework,
                    kind: login.kind,
                    configHome: login.configHome,
                });
                const updated = this.store.reissue(login.id, {
                    verificationUrl: fresh.verificationUrl,
                    userCode: fresh.userCode,
                    ttlMs: fresh.ttlMs,
                });
                if (updated) {
                    reissued.push(updated);
                    this.logger.log(`[EnrollmentWizard] auto-reissued expired login ${login.id} (reissue #${updated.reissueCount})`);
                }
            }
            catch (err) {
                // @silent-fallback-ok: one re-drive failing must not abort the sweep;
                // the login stays expired and is retried next sweep.
                this.logger.warn(`[EnrollmentWizard] reissue of ${login.id} failed: ${err instanceof Error ? err.message : String(err)}`);
            }
        }
        return reissued;
    }
    /**
     * Mark a login completed once the operator approved + the account enrolled.
     * For a claude-code enrollment with a per-account config home, also seed the
     * interactive onboarding flags — the login itself was headless, so without
     * this the freshly-enrolled home wedges the first interactive session that
     * pins/swaps onto it (2026-06-09 incident). Fail-safe: a seeding failure is
     * logged, never blocks completion (the launch paths re-ensure defensively).
     */
    complete(id) {
        const login = this.store.complete(id);
        if (login && login.framework === 'claude-code' && login.configHome) {
            const ready = this.ensureReady(login.configHome);
            if (ready.patched) {
                this.logger.log(`[EnrollmentWizard] made ${login.configHome} interactive-ready (${ready.reason})`);
            }
            else if (ready.reason !== 'already interactive-ready') {
                this.logger.warn(`[EnrollmentWizard] could not verify ${login.configHome} interactive-ready — ${ready.reason}`);
            }
        }
        return login;
    }
    /** The phone surface: still-valid logins awaiting approval. */
    pending() {
        return this.store.active();
    }
}
//# sourceMappingURL=EnrollmentWizard.js.map