import type { Buffer } from 'node:buffer';
import type { AscApp, AscDistributionCert, AscProfileSummary } from '../apple-api.js';
import type { ApiKeyData, CertificateData, EnrichedIdentityAvailability, OnboardingProgress, OnboardingStep, ProfileData } from '../types.js';
import type { AsyncCommandRunner, CiSecretDiscovery, CiSecretEntry, CiSecretTarget, CommandRunner } from '../ci-secrets.js';
import type { DiscoveredProfile, ExportedP12, IdentityProfileMatch, SigningIdentity } from '../macos-signing.js';
import type { MobileprovisionDetail } from '../../mobileprovision-parser.js';
import type { BuildCredentials } from '../../../schemas/build.js';
import type { BuildLogger, BuildRequestOptions, BuildRequestResult } from '../../request.js';
import type { EnvExportOpts, EnvExportResult } from '../env-export.js';
import type { GeneratedWorkflow, WorkflowGeneratorOpts } from '../workflow-generator.js';
import type { WorkflowWriteOptions, WorkflowWriteResult } from '../workflow-writer.js';
import type { TailTransient } from '../tail/flow.js';
import type { AppVerifyResult, AscAppLike, GatePath } from '../app-verification.js';
import type { DetectedBundleIds } from '../bundle-id-detector.js';
/**
 * Stable reason an identity has no usable matching profile. Drives the
 * `import-no-match-recovery` menu variant. (Mirrors the `noMatchReason` enum.)
 */
export type IosNoMatchReason = 'apple-no-cert-match' | 'apple-no-profiles-linked' | 'apple-bundle-mismatch' | 'apple-distribution-mismatch' | 'apple-other' | 'no-profile-on-disk';
/**
 * A duplicate Capgo provisioning profile (creating-profile / import-create).
 * Matches the `{ id, name, profileType }` triple returned by apple-api's
 * findCapgoProfiles() and carried on DuplicateProfileError.profiles. Derived
 * from the real AscProfileSummary so it tracks any future field additions.
 */
export type IosDuplicateProfile = Pick<AscProfileSummary, 'id' | 'name' | 'profileType'>;
export type IosStepKind = 'auto' | 'input' | 'choice' | 'done' | 'error';
export interface IosStepOption {
    value: string;
    label?: string;
    note?: string;
}
export interface IosStepView {
    step: OnboardingStep;
    kind: IosStepKind;
    title?: string;
    prompt?: string;
    collect?: string[];
    options?: IosStepOption[];
    message?: string;
}
/**
 * Per-step runtime context the driver supplies to the view builder AND threads
 * back through `IosEffectResult.transient` between effects. EVERY field is
 * OPTIONAL so a caller that only passes `{ appId }` still gets a usable view.
 *
 * This is the iOS "ephemeral inventory" — driver-held transient state that is
 * NEVER persisted to progress.json (it carries Apple-side selections + raw
 * cert/profile/keychain payloads). The total resume function (getIosResumeStep)
 * NEVER produces a step that depends on these — on resume the driver re-runs the
 * silent inventory (import-scanning) and re-renders the picker. See the audit's
 * "Ephemeral inventory" section for the producer/consumer map.
 */
export interface IosStepCtx {
    appId?: string;
    /** Selected signing identity (import-pick-identity). REQUIRED by import-exporting. */
    chosenIdentity?: SigningIdentity;
    /** Selected provisioning profile (import-pick-profile). REQUIRED by import-exporting. */
    chosenProfile?: DiscoveredProfile;
    /** Discovery result list from import-scanning (identities + on-disk profiles). */
    importMatches?: IdentityProfileMatch[];
    /** Scanned on-disk profiles (paired with importMatches). */
    importProfiles?: DiscoveredProfile[];
    /** Per-identity Apple-side availability (import-validating-all-certs). */
    identityAvailability?: Record<string, EnrichedIdentityAvailability>;
    /** Per-identity prefetched Apple profiles (parallel prefetch after validation). */
    profilePrefetch?: Record<string, DiscoveredProfile[]>;
    /** Apple cert resource id for the chosen identity (import-checking-apple-cert). */
    _appleCertIdForChosen?: string;
    /** Why the chosen identity has no usable profile — drives the recovery menu. */
    noMatchReason?: IosNoMatchReason;
    /** Duplicate Capgo profiles (creating-profile / import-create-profile-only). */
    duplicateProfiles?: IosDuplicateProfile[];
    /** Existing Apple certs offered for revocation when the cert limit is hit. */
    existingCerts?: AscDistributionCert[];
    /** The user's revoke selection (cert-limit-prompt → revoking-certificate). */
    certToRevoke?: AscDistributionCert;
    /**
     * Whether the host can show a native file picker — gates the
     * import-no-match-recovery / import-portal-explanation "use a .mobileprovision
     * from disk" option (the TUI's canUseFilePicker(), app.tsx:3570/3733). The
     * DRIVER threads the host capability here; the view defaults it to true (the
     * macOS-first onboarding target) so a caller that only passes { appId } still
     * gets the file-picker recovery option.
     */
    canUseFilePicker?: boolean;
    /** Resolved certificate data (creating-profile create-new / import-exporting). */
    certData?: CertificateData;
    /** Resolved profile data (creating-profile create-new / import-exporting). */
    profileData?: ProfileData;
    /** Apple team id resolved alongside certData/profileData. */
    teamId?: string;
    /** Keychain export password (import-exporting). Transient only. */
    importedP12Password?: string;
    /** Buffer of .p8 file content during validation (only the PATH is persisted). */
    p8Content?: Buffer;
    /**
     * True once the p8-method-select file-picker effect has opened the native
     * dialog this drive — the engine returns it in transient and the driver
     * threads it back as `deps.carried.pickerOpened` so a re-render does NOT
     * re-open the picker. Mirrors the TUI's `pickerOpenedRef` guard.
     */
    pickerOpened?: boolean;
    /**
     * True once the import-provide-profile-path .mobileprovision picker has opened
     * the native dialog this drive — returned in transient and threaded back as
     * `deps.carried.profilePickerOpened` so a re-render does NOT re-open the
     * picker. SEPARATE from `pickerOpened` (the .p8 picker). Mirrors the TUI's
     * `mobileprovisionPickerOpenedRef` guard (app.tsx:1689).
     */
    profilePickerOpened?: boolean;
    /** Verified key id + issuer id (mirror of completedSteps.apiKeyVerified). */
    apiKey?: ApiKeyData;
    /** ASC apps fetched by the verify-app effect (picker source + Path B re-poll). */
    verifyApps?: AscApp[];
    /** Registered Developer-portal bundle ids (diagnostic — sharpens Path B wording). */
    verifyRegisteredIds?: string[];
    /** The authoritative Release build id, re-detected FRESH from disk ('' = unresolved). */
    verifyReleaseBundleId?: string;
    /** The Debug-config bundle id when it differs from Release (else ''). */
    verifyDebugBundleId?: string;
    /** True when Debug + Release literal ids both exist AND differ (awareness note + telemetry). */
    verifyDebugReleaseDiffer?: boolean;
    /**
     * The verify-app classification (the pure classifyAppVerification result),
     * widened with the two pass-through outcomes ('fetch-failed' /
     * 'no-release-config') so the driver's Result telemetry mirrors the TUI's.
     * Present once the initial fetch has run — its absence is what makes
     * iosViewForStep render verify-app as an AUTO effect instead of the gate.
     */
    verifyResult?: AppVerifyResult | 'fetch-failed' | 'no-release-config';
    /** Which gate path the user is on (null = the picker). */
    verifyPath?: GatePath | null;
    /** The existing app picked in Path A (its bundleId is the target to match). */
    verifyChosenApp?: AscAppLike | null;
    /** 1-based count of blocked Continue attempts (drives the escalating warning). */
    verifyAttempt?: number;
    /** Path B: ask before re-opening the browser after a blocked re-poll. */
    verifyAskReopen?: boolean;
    /** Where verify-app routes on pass/pass-through (set by verifying-key on import). */
    pendingVerifyNext?: OnboardingStep;
    /** Human-readable error message the error view renders (failing step's message). */
    error?: string;
    /** The step to re-run when the user picks "Try again" (absent = no retry offered). */
    retryStep?: OnboardingStep;
    ciSecretEntries?: TailTransient['ciSecretEntries'];
    savedCredentials?: TailTransient['savedCredentials'];
    ciSecretTargets?: TailTransient['ciSecretTargets'];
    ciSecretSetupAdvice?: TailTransient['ciSecretSetupAdvice'];
    ciSecretRepoLabel?: TailTransient['ciSecretRepoLabel'];
    ciSecretExistingKeys?: TailTransient['ciSecretExistingKeys'];
    ciSecretUploadSummary?: TailTransient['ciSecretUploadSummary'];
    envExportPath?: TailTransient['envExportPath'];
    workflowFilePath?: TailTransient['workflowFilePath'];
    buildUrl?: TailTransient['buildUrl'];
    buildOutput?: TailTransient['buildOutput'];
    aiJobId?: TailTransient['aiJobId'];
    availableScripts?: TailTransient['availableScripts'];
    recommendedScript?: TailTransient['recommendedScript'];
    envExportError?: TailTransient['envExportError'];
    ciSecretError?: TailTransient['ciSecretError'];
}
/**
 * Async dependencies the iOS effects need (Apple API client, CSR + keychain
 * export, mobileprovision parsing, persistence, the shared tail helpers, and
 * status/log callbacks). EVERY helper is OPTIONAL and ADDITIVE so a driver can
 * inject only what the path it drives needs and the skeleton's stubs keep
 * type-checking. Data types are the REAL exports from apple-api / macos-signing /
 * mobileprovision-parser / csr; only the call-shape envelopes are engine-local.
 */
export interface IosEffectDeps {
    appId?: string;
    /** Verify an ASC API key (keyId + issuerId via the .p8). */
    verifyApiKey?: (args: {
        keyId: string;
        issuerId: string;
        p8Content: Buffer;
    }) => Promise<{
        teamId?: string;
    }>;
    /**
     * Create a distribution certificate from a CSR. Returns the RAW Apple cert
     * response (mirrors the real apple-api `createCertificate` helper): the cert
     * resource id, the base64 DER `certificateContent`, the expiry, and the team
     * id. The engine pairs `certificateContent` + the CSR private key via
     * `createP12` to produce the final .p12 — keeping the IO-free engine in charge
     * of assembling the CertificateData credential. Throws CertificateLimitError
     * (carrying the existing certs) when Apple's per-team cert limit is hit.
     */
    createCertificate?: (args: {
        csr: string;
        accessToken?: string;
    }) => Promise<{
        certificateId: string;
        certificateContent: string;
        expirationDate: string;
        teamId: string;
    }>;
    /** Revoke an existing certificate (cert-limit recovery). */
    revokeCertificate?: (certificateId: string) => Promise<void>;
    /** Create a provisioning profile for a bundle id + cert. */
    createProfile?: (args: {
        bundleId: string;
        certificateId: string;
        distribution?: string;
    }) => Promise<ProfileData>;
    /** Delete a provisioning profile (duplicate-profile recovery). */
    deleteProfile?: (profileId: string) => Promise<void>;
    /** Resolve the Apple cert resource id from a local cert SHA-1. */
    findCertIdBySha1?: (sha1: string) => Promise<string | null>;
    /** Classify a cert's Apple-side availability (import-validating-all-certs). */
    classifyCertAvailability?: (identity: SigningIdentity) => Promise<EnrichedIdentityAvailability>;
    /** List the team's distribution certificates (cert-limit prompt). */
    listCertificates?: () => Promise<AscDistributionCert[]>;
    /** Check for duplicate Capgo profiles for a bundle id. */
    checkDuplicateProfiles?: (bundleId: string) => Promise<IosDuplicateProfile[]>;
    /** Ensure the bundle id exists on Apple (import-create-profile-only). */
    ensureBundleId?: (bundleId: string) => Promise<void>;
    /**
     * List the Apple profiles linked to a cert (import-checking-apple-cert).
     *
     * Returns the RAW Apple shape (AscProfileSummary[]) exactly as the real
     * apple-api `listProfilesForCert` helper does — id / name / profileType /
     * profileContent / expirationDate / bundleIdentifier. The engine itself
     * synthesizes each summary into a DiscoveredProfile (populating profileBase64
     * + certificateSha1s=[identity.sha1]) via `synthesizeProfileFromAscSummary`,
     * byte-for-byte mirroring the TUI's inline mapping at app.tsx:1556 / :1460.
     * Keeping the dep at the raw Apple shape means the driver pre-binds nothing
     * more than the real helper.
     */
    listProfilesForCert?: (certificateId: string) => Promise<AscProfileSummary[]>;
    /** List every ASC app visible to the API key (verify-app fetch + Path B re-poll). */
    listApps?: () => Promise<AscApp[]>;
    /** List every registered bundle-id identifier (verify-app diagnostics). */
    listBundleIds?: () => Promise<string[]>;
    /**
     * FRESH bundle-id detection from disk (verify-app + the Path-A re-check). The
     * driver pre-binds the real `detectIosBundleIds({ cwd, iosDir, capacitorAppId })`
     * — the engine reads `releaseResolved`/`pbxproj` for the authoritative Release
     * id, `debug`/`debugReleaseDiffer` for the awareness note, and `capacitor` for
     * the persisted iosBundleIdContextAppId snapshot. Called PER CHECK so an edit
     * the user made since the wizard started is picked up (the TUI bypasses its
     * memo the same way, app.tsx:1522/3088).
     */
    detectBundleIds?: () => DetectedBundleIds;
    /**
     * Rewrite the Release PRODUCT_BUNDLE_IDENTIFIER assignments equal to `fromId`
     * to `toId` in the Xcode project (the Path-A auto-fix). The driver pre-binds
     * the real `writeReleaseBundleId(cwd, iosDir, …)`; returns the number of
     * replaced assignments (0 = nothing matched). Throws only on an FS error.
     */
    writeReleaseBundleId?: (fromId: string, toId: string) => {
        changed: number;
    };
    /** Generate a CSR + private key PEM. */
    generateCsr?: (args?: {
        commonName?: string;
    }) => {
        csr: string;
        privateKeyPem: string;
    };
    /** Build a .p12 from a cert + private key. Returns base64. */
    createP12?: (args: {
        certificatePem: string;
        privateKeyPem: string;
        password: string;
    }) => string;
    /** List the Mac's code-signing identities (import-scanning). */
    listSigningIdentities?: () => Promise<SigningIdentity[]>;
    /** Scan the Mac's on-disk provisioning profiles (import-scanning). */
    scanProvisioningProfiles?: () => Promise<DiscoveredProfile[]>;
    /**
     * Export a .p12 (cert + key) from the Keychain for the chosen identity
     * (import-exporting). Signature mirrors the REAL macos-signing helper VERBATIM:
     * takes the identity's SHA-1 and resolves to { base64, passphrase } (the
     * auto-generated wrap passphrase becomes the transient importedP12Password the
     * saving-credentials handoff reads — NEVER persisted, risk #2 / D-iOS-3).
     */
    exportP12FromKeychain?: (targetSha1: string) => Promise<ExportedP12>;
    /** Parse a `.mobileprovision` file in detail (import-provide-profile-path). */
    parseMobileprovisionDetailed?: (bytes: Buffer) => MobileprovisionDetail;
    loadProgress?: (appId: string) => Promise<OnboardingProgress | null>;
    saveProgress?: (appId: string, progress: OnboardingProgress) => Promise<void>;
    deleteProgress?: (appId: string) => Promise<void>;
    /** Persist the saved build-credential map (saving-credentials). */
    updateSavedCredentials?: (appId: string, platform: 'ios' | 'android', credentials: Record<string, string>) => Promise<void>;
    loadSavedCredentials?: (appId: string) => Promise<unknown>;
    readFile?: (path: string) => Promise<Buffer>;
    copyFile?: (src: string, dest: string) => Promise<void>;
    /**
     * Open the native .p8 file picker (p8-method-select). Resolves to the chosen
     * absolute path, or null when the user cancels. The driver pre-binds the real
     * `openFilePicker` here; tests inject a canned path/null. Mirrors the TUI's
     * `openFilePicker()` call inside the p8-method-select effect.
     */
    openP8FilePicker?: () => Promise<string | null>;
    /**
     * Open the native .mobileprovision file picker (import-provide-profile-path).
     * Resolves to the chosen absolute path, or null when the user cancels. The
     * driver pre-binds the real `openMobileprovisionPicker` here; tests inject a
     * canned path/null. Mirrors the TUI's `openMobileprovisionPicker()` call
     * inside the import-provide-profile-path effect (app.tsx:1696). The bytes are
     * then read via `deps.readFile` and parsed via `deps.parseMobileprovisionDetailed`.
     */
    openProfilePicker?: () => Promise<string | null>;
    /**
     * Whether the host can show a native file picker. Gates the
     * import-no-match-recovery / import-portal-explanation "use a .mobileprovision
     * from disk" option exactly as the TUI's `canUseFilePicker()` does
     * (app.tsx:3570/3733). Defaults to true when omitted (the macOS-first target).
     */
    canUseFilePicker?: () => boolean;
    /**
     * Open a URL in the host's default browser (import-portal-explanation's
     * "open the portal anyway" branch). Best-effort — the driver pre-binds the
     * real `open` helper; tests inject a recorder/no-op. Mirrors the TUI's
     * `open(...)` call at app.tsx:3749. A failure must NOT abort recovery.
     */
    openExternal?: (url: string) => Promise<void> | void;
    /**
     * Whether the host is macOS. Gates the post-backup fork: on macOS the user is
     * offered import-vs-create at `setup-method-select`; off-macOS the import
     * sub-flow is unavailable so backing-up routes straight to the create-new
     * `api-key-instructions`. Mirrors the TUI's `isMacOS()` branch. Defaults to
     * true when omitted (the macOS-first onboarding target).
     */
    isMacOS?: () => boolean;
    createCiSecretEntries?: (credentials: Partial<BuildCredentials>, apiKey?: string) => CiSecretEntry[];
    detectCiSecretTargets?: (runner?: CommandRunner) => CiSecretDiscovery;
    getCiSecretRepoLabelAsync?: (target: CiSecretTarget, runner?: AsyncCommandRunner) => Promise<string | null>;
    listExistingCiSecretKeysAsync?: (target: CiSecretTarget, keys: string[], runner?: AsyncCommandRunner) => Promise<string[]>;
    uploadCiSecretsAsync?: (target: CiSecretTarget, entries: CiSecretEntry[], existingKeys?: string[], runner?: AsyncCommandRunner, onProgress?: (current: number, total: number, keyName: string) => void) => Promise<void>;
    exportCredentialsToEnv?: (opts: EnvExportOpts) => EnvExportResult;
    defaultExportPath?: (appId: string, platform: 'ios' | 'android') => string;
    generateWorkflow?: (opts: WorkflowGeneratorOpts) => GeneratedWorkflow;
    writeWorkflowFile?: (opts: WorkflowGeneratorOpts, writeOptions?: WorkflowWriteOptions) => WorkflowWriteResult;
    requestBuildInternal?: (appId: string, options: BuildRequestOptions, silent?: boolean, logger?: BuildLogger) => Promise<BuildRequestResult>;
    /** The streaming BuildLogger threaded into requestBuildInternal (4th arg). */
    logger?: BuildLogger;
    /** The build VIEWER sink (FullscreenBuildOutput), distinct from onLog. */
    onBuildOutput?: (line: string) => void;
    /** Resolves the Capgo API key for the build request (CLI-flag-over-saved). */
    resolveApikey?: () => string | undefined;
    /** Per-key CI-secret upload progress (uploadCiSecretsAsync 5th arg). */
    onCiSecretUploadProgress?: (current: number, total: number, keyName: string) => void;
    /** The 2-phase checking-ci-secrets status text. */
    onCiSecretCheckPhase?: (phase: string) => void;
    /** The ci-secrets-failed reason. */
    onCiSecretError?: (message: string) => void;
    /** Reads the project's package.json scripts map (with-workflow preload). */
    getPackageScripts?: () => Record<string, string>;
    /** Detects the web-framework project type (best-effort; may resolve null). */
    findProjectType?: (options?: {
        quiet?: boolean;
    }) => Promise<string | null>;
    /** Maps a detected project type to its recommended build script name. */
    findBuildCommandForProjectType?: (projectType: string) => Promise<string | null>;
    /** Workflow-file telemetry hook (e.g. 'workflow-file-written'). */
    trackWorkflowEvent?: (event: string, options?: {
        decision?: string;
    }) => void;
    /**
     * DRIVER-HELD transient tail state threaded back into each post-save effect.
     * The TUI resolves these ONCE (at saving-credentials) and keeps them in React
     * state; a headless driver mirrors that by capturing the matching
     * IosEffectResult.transient and passing it back here on the NEXT effect.
     * NEVER persisted to progress.json. When absent (crash-recovery resume) the
     * effect falls back to a single lossy re-derivation from progress.
     */
    carried?: {
        savedCredentials?: Record<string, string>;
        ciSecretEntries?: CiSecretEntry[];
        ciSecretExistingKeys?: string[];
        /**
         * Whether the workflow file did NOT exist when previewed (the TUI's
         * `previewIsNew`, resolved at preview-workflow-file via existsSync). The
         * writing-workflow-file effect logs '✔ Wrote' vs '✔ Overwrote' from it.
         * Absent defaults to NEW ('Wrote'). EPHEMERAL — never persisted.
         */
        workflowIsNew?: boolean;
        /** The chosen signing identity (lossy re-scan source on resume). */
        chosenIdentity?: SigningIdentity;
        /** The chosen provisioning profile (lossy re-scan source on resume). */
        chosenProfile?: DiscoveredProfile;
        /**
         * The import-scanning discovery inventory (identity↔on-disk-profile matches +
         * the raw scanned profiles), threaded forward so the NEXT import effect can
         * read it without a re-scan. Produced by import-scanning into transient; the
         * driver mirrors it back here for import-validating-all-certs (which batches
         * classifyCertAvailability over importMatches) and the pickers. EPHEMERAL —
         * never persisted; on a crash-recovery resume the engine re-lands on
         * import-scanning and re-populates it.
         */
        importMatches?: IdentityProfileMatch[];
        importProfiles?: DiscoveredProfile[];
        /** Resolved cert/profile/team export payloads carried into saving-credentials. */
        certData?: CertificateData;
        profileData?: ProfileData;
        teamId?: string;
        /**
         * The validated .p8 file content (ASC private key) the driver carries
         * between the .p8 input chain and `verifying-key`. ONLY the p8Path is
         * persisted to progress.json — the raw key bytes ride this transient
         * channel, mirroring the TUI's `p8ContentRef`. The verifying-key effect
         * reads it from here; when absent (crash-recovery resume) it falls back to
         * re-reading the file at `progress.p8Path` via `deps.readFile`.
         */
        p8Content?: Buffer;
        /**
         * Tracks that the p8-method-select file-picker effect already ran, so a
         * re-render does NOT re-open the native picker. Mirrors the TUI's
         * `pickerOpenedRef`. The driver threads the returned `pickerOpened: true`
         * transient back here on the next call.
         */
        pickerOpened?: boolean;
        /**
         * Tracks that the import-provide-profile-path .mobileprovision file-picker
         * effect already ran this attempt, so a re-render / re-drive does NOT re-open
         * the native picker. SEPARATE from `pickerOpened` (the .p8 picker guard) so
         * the two file pickers never cross-suppress each other — mirrors the TUI's
         * distinct `mobileprovisionPickerOpenedRef` (app.tsx:1689). The driver threads
         * the returned `profilePickerOpened: true` transient back here; it RESETS the
         * flag (to false) before routing into import-provide-profile-path from the
         * recovery menu, exactly as the TUI clears the ref at app.tsx:3593.
         */
        profilePickerOpened?: boolean;
        /**
         * Keychain export passphrase for the IMPORT path's .p12 (import-exporting).
         * Transient only — the import-exporting effect never persists it, so the
         * saving-credentials handoff reads it from carried. Absent on the create-new
         * path (which uses the well-known DEFAULT_P12_PASSWORD) and on a crash-recovery
         * resume that lost the in-memory state.
         */
        importedP12Password?: string;
        /**
         * The cert the user picked at `cert-limit-prompt` (cert-limit recovery). The
         * choice is EPHEMERAL — `applyIosInput` persists nothing; the driver records
         * the picked AscDistributionCert here and re-drives the prompt as a resolver
         * effect, exactly as the TUI stashes `certToRevoke` in React state before
         * advancing to `revoking-certificate` (app.tsx:3923). The resolver returns
         * `revoking-certificate` when present, `error` when absent (the user exited).
         * Mirrors the BATCH 2 ephemeral-branching mechanism (`pickerOpened` /
         * `chosenIdentity`): the selection lives in carried, never in progress.json.
         */
        certToRevoke?: AscDistributionCert;
        /**
         * The duplicate Capgo profiles surfaced at `duplicate-profile-prompt`
         * (duplicate-profile recovery). Produced by `creating-profile` /
         * `import-create-profile-only` into transient; the driver threads the list
         * back here so `deleting-duplicate-profiles` knows which profiles to delete.
         * NEVER persisted (only `duplicateProfileOrigin` is — see types.ts).
         */
        duplicateProfiles?: IosDuplicateProfile[];
        /**
         * The user's confirm/exit decision at `duplicate-profile-prompt`. EPHEMERAL —
         * `applyIosInput` persists nothing (per the audit's sequencing model); the
         * driver records the choice here and re-drives the prompt as a resolver
         * effect. `true` → `deleting-duplicate-profiles`; falsy (the user exited) →
         * `error` (mirroring app.tsx:3942's delete-vs-exitOnboarding branch).
         */
        confirmDeleteDuplicates?: boolean;
        /**
         * The user's pick at `import-no-match-recovery` (the 5-way HUB). EPHEMERAL —
         * `applyIosInput` persists nothing; the driver records the choice here and
         * re-drives the prompt as a resolver effect. 'create' →
         * import-create-profile-only (with an ASC key) or api-key-instructions
         * (without); 'provide-profile-path' → import-provide-profile-path; 'browser'
         * → import-portal-explanation; 'back' → import-pick-identity. Mirrors the
         * TUI's recovery-menu onChange (app.tsx:3579).
         */
        recoveryAction?: 'create' | 'provide-profile-path' | 'browser' | 'back';
        /**
         * The user's pick at `import-portal-explanation` (the manual-portal
         * walkthrough). EPHEMERAL — the driver records the choice here and re-drives
         * the step as a resolver. 'use-create' → import-create-profile-only;
         * 'use-file' → import-provide-profile-path; 'open-anyway' / 'back' →
         * import-no-match-recovery. Mirrors app.tsx:3738.
         */
        portalAction?: 'use-create' | 'open-anyway' | 'use-file' | 'back';
        /**
         * The user's pick at `import-export-warning` (the heads-up before the one
         * Keychain dialog). EPHEMERAL — `applyIosInput` persists nothing; the driver
         * records the choice here and re-drives the step as a resolver. 'go' →
         * import-exporting (the precompiled signed helper is resolved + verified in
         * the export step itself — PR #2458 removed the swiftc compile step);
         * 'back' → import-pick-profile; 'exit'/absent → exit onboarding. Mirrors
         * app.tsx:3769 onChange.
         */
        exportWarningAction?: 'go' | 'back' | 'exit';
        /**
         * The STICKY no-match reason set by the step that ROUTED into recovery
         * (import-pick-identity / import-checking-apple-cert). The recovery resolver
         * + the import-provide-profile-path cancel branch thread it back so a
         * re-entry from a file-picker cancel / portal "open anyway" does NOT
         * recompute or overwrite it (risk #8) — the menu keeps showing the SAME
         * variant. Mirrors the TUI leaving `noMatchReason` untouched on back-nav.
         */
        noMatchReason?: IosNoMatchReason;
        /**
         * The step to re-run when the user picks "Try again" on the error screen
         * (BATCH 8). EPHEMERAL — set by the failing effect into transient.retryStep,
         * threaded back here by the driver, and read by the error RESOLVER
         * (runIosEffect('error')) to route a retry. NEVER persisted: an error is
         * transient runtime state, so a crash-recovery resume re-enters the failing
         * phase fresh (getIosResumeStep never returns 'error'). Mirrors the TUI's
         * setRetryStep + the ErrorStep 'retry' branch (app.tsx:1116 / 4468).
         */
        retryStep?: OnboardingStep;
        /**
         * The user's pick on the error screen (BATCH 8). EPHEMERAL — `applyIosInput`
         * persists nothing; the driver records the choice here and re-drives the step
         * as a resolver. 'retry' → re-run carried.retryStep (the failing step);
         * 'restart' → welcome (a fresh reset); 'exit'/absent → stay on 'error' (the
         * terminal exit sink — the driver leaves onboarding, mirroring the TUI's
         * exitOnboarding at app.tsx:4482). NEVER persisted.
         */
        errorAction?: 'retry' | 'restart' | 'exit';
        /**
         * Where verify-app routes once the invariant holds (or on a pass-through
         * exit): the import continuation (import-validating-all-certs /
         * import-pick-identity) on the import app_store path, absent on create-new
         * (verify-app falls back to 'creating-certificate'). Produced by
         * verifying-key into transient.pendingVerifyNext; the driver threads it back
         * here. NEVER persisted — a fresh mount has none, so a resume re-entering
         * verify-app always falls back to creating-certificate (matching the TUI's
         * pendingVerifyNext React state + getResumeStep's verify-app comment).
         */
        pendingVerifyNext?: OnboardingStep;
        /**
         * The user's pick on the PARKED verify-app step (the picker or one of the
         * two gates). EPHEMERAL — `applyIosInput` persists nothing; the driver
         * records the pick here and re-drives verify-app as a resolver effect:
         * 'pick' (+ verifyChosenApp) / 'create-new' route the picker; 'autofix' /
         * 'continue' drive the Path-A fix-build-id gate; 'recheck' / 'open' /
         * 'reopen' drive the Path-B create-app gate; 'back' resets to the picker;
         * 'cancel' exits via the error sink. Mirrors the TUI Select onChange values
         * (app.tsx:3246/3283/3323/3360). The driver MUST clear it after each
         * resolver run so a later re-entry runs the initial fetch.
         */
        verifyAction?: 'pick' | 'create-new' | 'autofix' | 'continue' | 'recheck' | 'open' | 'reopen' | 'back' | 'cancel';
        /** The existing ASC app picked in the verify-app picker (Path A target). */
        verifyChosenApp?: AscAppLike | null;
        /** The ASC apps fetched by the initial verify-app effect (picker source + re-poll). */
        verifyApps?: AscApp[];
        /** Registered Developer-portal bundle ids (Path B wording sharpener). */
        verifyRegisteredIds?: string[];
        /** The authoritative Release build id resolved by the verify-app fresh detect. */
        verifyReleaseBundleId?: string;
        /** The Debug-config bundle id when it differs from Release (else ''). */
        verifyDebugBundleId?: string;
        /** Which gate path the user is on (null = the picker). */
        verifyPath?: GatePath | null;
        /** 1-based count of blocked Continue attempts (the escalation driver). */
        verifyAttempt?: number;
        /** Path B: ask before re-opening the browser after a blocked re-poll. */
        verifyAskReopen?: boolean;
    };
    onStatus?: (message: string) => void;
    onLog?: (message: string, color?: string) => void;
    /** Internal-only diagnostic line → the support internal log (main PR #2406). Optional; no-op when absent. */
    onInternalLog?: (line: string) => void;
    signal?: AbortSignal;
}
export interface IosEffectResult {
    /** Updated progress after the effect ran (matches what was persisted). */
    progress: OnboardingProgress;
    /** Explicit next step when not derivable from progress alone (★ transitions). */
    next?: OnboardingStep;
    /** Transient runtime data that lives in the driver but is NOT persisted. */
    transient?: Partial<IosStepCtx>;
}
/**
 * The create-new choice/input vocabulary. Mirrors android's `AndroidInput`:
 * one variant per choice/input step that records (or routes) state. The iOS
 * `applyIosInput` signature still accepts `unknown`, so callers cast to this.
 * Navigation-only choices (api-key-instructions) are included for completeness
 * but return progress unchanged.
 */
export type IosInput = {
    step: 'setup-method-select';
    value: 'create' | 'import';
} | {
    step: 'api-key-instructions';
    value: 'picker' | 'manual';
} | {
    step: 'input-p8-path';
    value: string;
} | {
    step: 'input-key-id';
    value: string;
} | {
    step: 'input-issuer-id';
    value: string;
} | {
    step: 'cert-limit-prompt';
    value: string;
} | {
    step: 'duplicate-profile-prompt';
    value: 'delete' | 'exit';
} | {
    step: 'verify-app';
    value: string;
} | {
    step: 'import-distribution-mode';
    value: 'app_store' | 'ad_hoc' | '__cancel__';
} | {
    step: 'import-pick-identity';
    value: string;
} | {
    step: 'import-pick-profile';
    value: string;
} | {
    step: 'import-no-match-recovery';
    value: 'create' | 'provide-profile-path' | 'browser' | 'back';
} | {
    step: 'import-portal-explanation';
    value: 'use-create' | 'open-anyway' | 'use-file' | 'back';
} | {
    step: 'import-export-warning';
    value: 'go' | 'back' | 'exit';
};
/**
 * Build the view-model for a given step. Post-save tail steps delegate to the
 * shared neutral view (adapted back to IosStepView). The create-new choice/input
 * steps (setup-method fork + .p8 chain) return real per-step views mirroring the
 * TUI prompts/options (ui/steps/ios-credentials.tsx). All other steps return a
 * minimal placeholder 'auto' view echoing the step (real per-step views land in
 * later batches).
 */
export declare function iosViewForStep(step: OnboardingStep, progress: OnboardingProgress, ctx?: IosStepCtx): IosStepView;
/**
 * Apply a user input to progress. Post-save tail choice/input steps delegate
 * the reducer to the shared neutral module. The create-new choice/input steps
 * persist their field(s) exactly as the TUI's onSubmit/onChange handlers do
 * (ui/app.tsx). All other steps return progress unchanged (real per-step
 * mutations land in later batches).
 *
 * PURE — no IO. The .p8 file read + keyId extraction + Apple verification are
 * effect-boundary concerns (p8-method-select / verifying-key); the reducers here
 * only record the raw user input into progress.
 */
export declare function applyIosInput(step: OnboardingStep, progress: OnboardingProgress, input: unknown): OnboardingProgress;
/**
 * Run the async side-effect for a step. Post-save tail steps (incl.
 * saving-credentials) delegate to the shared neutral module via toTailDeps; the
 * neutral result maps 1:1 onto IosEffectResult (next is a wider OnboardingStep;
 * transient is a subset of IosStepCtx). All other steps are not implemented yet
 * — the real Apple-API / keychain / build effects land in later batches.
 */
export declare function runIosEffect(step: OnboardingStep, progress: OnboardingProgress, deps: IosEffectDeps): Promise<IosEffectResult>;
