import type { Buffer } from 'node:buffer';
import type { AndroidOnboardingProgress, AndroidOnboardingStep } from './types.js';
import type { KeystoreOptions, KeystoreResult, ListAliasesResult, ProbeKeyPasswordResult } from './keystore.js';
import type { ValidateOptions, ValidationResult } from './service-account-validation.js';
import type { GcpProject, GcpServiceAccount, GcpServiceAccountKey } from './gcp-api.js';
import type { GoogleOAuthTokens, GoogleUserInfo, PendingOAuthSession, RunOAuthFlowOptions } from './oauth-google.js';
import type { BuildCredentials } from '../../../schemas/build.js';
import type { BuildLogger, BuildRequestOptions, BuildRequestResult } from '../../request.js';
import type { AsyncCommandRunner, CiSecretDiscovery, CiSecretEntry, CiSecretSetupAdvice, CiSecretTarget, CommandRunner } from '../ci-secrets.js';
import type { EnvExportOpts, EnvExportResult } from '../env-export.js';
import type { BuildScriptChoice, GeneratedWorkflow, PackageManager, WorkflowGeneratorOpts } from '../workflow-generator.js';
import type { WorkflowWriteOptions, WorkflowWriteResult } from '../workflow-writer.js';
/**
 * Pure helper: given persisted progress, validated OAuth tokens and the user's
 * profile, return a NEW progress object (immutable spread) with:
 *   - `_oauthRefreshToken` set to `tokens.refreshToken`
 *   - `completedSteps.googleSignInComplete` set to `{ email, googleSubject, scope }`
 *
 * This is the single canonical place where Google sign-in state is written to
 * progress — shared by the Ink core effect (`google-sign-in-running`) and the
 * MCP bridge so both produce identical progress objects.
 */
export declare function applyGoogleSignIn(progress: AndroidOnboardingProgress, tokens: GoogleOAuthTokens, info: GoogleUserInfo): AndroidOnboardingProgress;
/**
 * Apply a BROKER (access-token) Google sign-in. The MCP path receives a short-lived access token from the
 * Capgo OAuth broker and CANNOT refresh it (the token is issued to the broker's confidential Web client), so
 * it stores the access token + its expiry and re-signs-in on expiry — no `_oauthRefreshToken`. Mirrors
 * applyGoogleSignIn for the refresh-token (TUI loopback) path.
 */
export declare function applyGoogleSignInBroker(progress: AndroidOnboardingProgress, accessToken: string, expiresAt: number | null, info: GoogleUserInfo): AndroidOnboardingProgress;
export type AndroidStepKind = 'auto' | 'input' | 'choice' | 'done' | 'error';
export interface AndroidStepOption {
    value: string;
    label?: string;
    note?: string;
}
export interface AndroidStepView {
    step: AndroidOnboardingStep;
    kind: AndroidStepKind;
    title?: string;
    prompt?: string;
    collect?: string[];
    options?: AndroidStepOption[];
    message?: string;
}
export interface AndroidStepCtx {
    appId: string;
    detectedPackageIds?: string[];
    gcpProjects?: {
        projectId: string;
        name: string;
        projectNumber?: string;
    }[];
    detectedAliases?: string[];
    saValidation?: {
        ok: false;
        kind: string;
        message: string;
    } | {
        ok: true;
    };
    /**
     * Task 3 — keystore-existing-key-password prompt boundary.
     * Set to true in AndroidEffectResult.transient when the auto-probe could not
     * resolve the key password and the driver should show the manual input.
     * After the user submits, applyAndroidInput records keystoreKeyPassword and
     * re-running the effect finds it set and completes the phase.
     */
    needsKeyPasswordPrompt?: boolean;
    /**
     * Task 4 — fresh access token returned from google-sign-in-running so the
     * driver can seed its token cache and avoid an immediate refresh on the next
     * step. If the driver ignores it, deps.getAccessToken() will mint one —
     * behaviorally identical.
     */
    accessToken?: string;
    /**
     * keystore-existing-detecting-alias wrong-password signal.
     * Set to true in AndroidEffectResult.transient when listKeystoreAliases returns
     * { ok: false, reason: 'wrong-password' }. The driver (app.tsx) maps this to
     * the original UX: setError + setRetryStep('keystore-existing-store-password')
     * + setStep('error') WITHOUT calling handleError (no retryCount bump).
     */
    wrongPassword?: boolean;
    /** CI-secret entries built at saving-credentials (key/value/masked). */
    ciSecretEntries?: CiSecretEntry[];
    /**
     * Full saved-credential map written at saving-credentials (the 5 build-cred
     * fields, NO CAPGO_TOKEN). The Ink TUI holds the same in its `savedCredentials`
     * React state and the env-export effects write it verbatim. Transient only —
     * never persisted to progress.json (it carries the raw keystore/SA secrets).
     */
    savedCredentials?: Record<string, string>;
    /** CI-secret destinations discovered at detecting-ci-secrets. */
    ciSecretTargets?: CiSecretTarget[];
    /** Per-destination setup advice surfaced when no target is reachable. */
    ciSecretSetupAdvice?: CiSecretSetupAdvice[];
    /** Resolved owner/repo (GitHub) the CLI will push secrets to. */
    ciSecretRepoLabel?: string | null;
    /** Which secret keys already exist on the remote (checking-ci-secrets). */
    ciSecretExistingKeys?: string[];
    /** Human summary of the upload (uploading-ci-secrets). */
    ciSecretUploadSummary?: string;
    /** Absolute path of the written .env file (exporting-env). */
    envExportPath?: string;
    /** Set when env-export found nothing to write or threw — routed to build-complete, never thrown (exporting-env / overwrite-and-export-env). */
    envExportError?: string;
    /** Absolute path of the written workflow file (writing-workflow-file). */
    workflowFilePath?: string;
    /** The queued build URL (requesting-build). */
    buildUrl?: string;
    /** Streamed build-request log lines (requesting-build). */
    buildOutput?: string[];
    /** Captured AI-analysis job id surfaced on a failed build (requesting-build). */
    aiJobId?: string;
    /** Detected package manager from the project's lockfile (pick-package-manager). */
    detectedPackageManager?: string;
    /** All scripts from package.json (pick-build-script picker). */
    availableScripts?: Record<string, string>;
    /** Project-type recommendation surfaced at the top of pick-build-script. */
    recommendedScript?: string | null;
    /** Default `.env` export path shown at ask-export-env (defaultExportPath). */
    defaultEnvExportPath?: string;
}
export declare const KIND_TABLE: Record<AndroidOnboardingStep, AndroidStepKind>;
/**
 * Pure function: given an explicit step name, persisted progress (or null),
 * and the current runtime context, return a UI-framework-neutral description
 * of the step.
 *
 * This is the primary entry-point for drivers that already know the step
 * (e.g. the MCP bridge). `androidStepView` is a thin wrapper that resolves
 * the step from progress first.
 *
 * Dynamic kind for `android-package-select`:
 *   - ctx.detectedPackageIds === undefined  → 'auto'  (preload not yet done)
 *   - ctx.detectedPackageIds.length > 0     → 'choice' (options = the ids)
 *   - ctx.detectedPackageIds.length === 0   → 'input'  (user must type it)
 *
 * All other steps use KIND_TABLE[step] unchanged.
 *
 * No I/O; no mutation of progress.
 */
export declare function androidViewForStep(step: AndroidOnboardingStep, progress: AndroidOnboardingProgress | null, ctx: AndroidStepCtx): AndroidStepView;
export type AndroidInput = {
    step: 'credentials-exist';
    value: 'backup' | 'cancel';
} | {
    step: 'keystore-method-select';
    value: 'existing' | 'generate' | 'learn';
} | {
    step: 'keystore-existing-path';
    path: string;
} | {
    step: 'keystore-existing-store-password';
    password: string;
} | {
    step: 'keystore-existing-alias-select';
    alias: string;
} | {
    step: 'keystore-existing-alias';
    alias: string;
} | {
    step: 'keystore-existing-key-password';
    password: string;
} | {
    step: 'keystore-new-alias';
    alias: string;
} | {
    step: 'keystore-new-password-method';
    value: 'random' | 'manual';
} | {
    step: 'keystore-new-store-password';
    password: string;
} | {
    step: 'keystore-new-key-password';
    password: string;
} | {
    step: 'keystore-new-cn';
    cn: string;
} | {
    step: 'service-account-method-select';
    value: 'generate' | 'existing';
} | {
    step: 'sa-json-existing-path';
    path: string;
} | {
    step: 'sa-json-validation-failed';
    value: 'retry' | 'oauth';
} | {
    step: 'sa-json-validation-failed';
    value: 'save-anyway';
    serviceAccountKeyBase64: string;
} | {
    step: 'play-developer-id-input';
    rawDeveloperIdOrUrl: string;
} | {
    step: 'gcp-projects-select';
    gcpProject: {
        projectId: string;
        name: string;
        projectNumber?: string;
    };
} | {
    step: 'gcp-project-create-name';
    displayName: string;
} | {
    step: 'android-package-select';
    packageName: string;
    source: 'gradle' | 'capacitor-config' | 'user-input';
    serviceAccountMethod: 'generate' | 'existing';
} | {
    step: 'ci-secrets-setup';
    value: 'retry' | 'skip';
} | {
    step: 'ci-secrets-target-select';
    ciSecretTarget: CiSecretTarget | null;
} | {
    step: 'ask-ci-secrets';
    value: 'yes' | 'no';
} | {
    step: 'confirm-ci-secret-overwrite';
    value: 'replace' | 'skip';
} | {
    step: 'ci-secrets-failed';
    value: 'retry' | 'continue';
} | {
    step: 'ask-github-actions-setup';
    value: 'with-workflow' | 'secrets-only' | 'declined';
} | {
    step: 'confirm-secrets-push';
    value: 'confirm' | 'cancel';
} | {
    step: 'ask-export-env';
    value: 'no';
} | {
    step: 'ask-export-env';
    value: 'yes';
    envExportTargetPath: string;
} | {
    step: 'confirm-env-export-overwrite';
    value: 'replace' | 'skip';
} | {
    step: 'pick-package-manager';
    selectedPackageManager: PackageManager;
} | {
    step: 'pick-build-script';
    value: '__custom__';
} | {
    step: 'pick-build-script';
    buildScriptChoice: BuildScriptChoice;
} | {
    step: 'pick-build-script-custom';
    command: string;
} | {
    step: 'preview-workflow-file';
    value: 'write' | 'view' | 'cancel';
} | {
    step: 'view-workflow-diff';
    value: 'close';
} | {
    step: 'ask-build';
    value: 'yes' | 'no';
};
export declare function applyAndroidInput(step: AndroidOnboardingStep, progress: AndroidOnboardingProgress, input: AndroidInput): AndroidOnboardingProgress;
export interface AndroidEffectDeps {
    generateKeystore: (opts: KeystoreOptions) => KeystoreResult;
    listKeystoreAliases: (bytes: Uint8Array, password: string) => ListAliasesResult;
    tryUnlockPrivateKey: (bytes: Uint8Array, password: string) => ProbeKeyPasswordResult;
    validateServiceAccountJson: (opts: ValidateOptions) => Promise<ValidationResult>;
    updateSavedCredentials: (appId: string, platform: 'ios' | 'android', credentials: Record<string, string>) => Promise<void>;
    loadSavedCredentials: (appId: string) => Promise<unknown>;
    saveAndroidProgress: (appId: string, progress: AndroidOnboardingProgress) => Promise<void>;
    loadAndroidProgress: (appId: string) => Promise<AndroidOnboardingProgress | null>;
    deleteAndroidProgress: (appId: string) => Promise<void>;
    readFile: (path: string) => Promise<Buffer>;
    copyFile: (src: string, dest: string) => Promise<void>;
    /**
     * Run the full browser OAuth flow. The driver pre-binds OAuth client config
     * (clientId, clientSecret, scopes) so the core never sees credentials —
     * config/scope policy lives in the driver.
     *
     * Signature mirrors RunOAuthFlowOptions from oauth-google.ts (minus
     * timeoutMs/signal which the driver controls internally).
     */
    runOAuthFlow: (callbacks: Pick<RunOAuthFlowOptions, 'onAuthUrl' | 'onStatus'>) => Promise<GoogleOAuthTokens>;
    /**
     * Non-blocking OAuth starter (MCP fire-and-poll). Opens the browser and
     * starts the loopback listener, then returns a PendingOAuthSession immediately
     * without waiting for sign-in to complete. The MCP bridge uses this to
     * avoid blocking a single tool call on the full OAuth round-trip.
     *
     * Optional — only provided by the MCP driver. The Ink driver uses the
     * blocking `runOAuthFlow` instead and does not need this dep.
     *
     * The driver pre-binds OAuth client config (clientId, clientSecret, scopes).
     */
    startOAuthFlow?: (callbacks?: Pick<RunOAuthFlowOptions, 'onAuthUrl' | 'onStatus'>) => Promise<PendingOAuthSession>;
    /**
     * Open a URL in the user's default browser — used by the MCP broker sign-in to (optionally) open the
     * sign-in link for the user. Best-effort: the engine falls back to showing the link if this throws or is
     * absent. Optional (the Ink driver never uses it).
     */
    openBrowser?: (url: string) => Promise<void>;
    /** Fetch the signed-in user's profile (email, sub). */
    fetchUserInfo: (accessToken: string) => Promise<GoogleUserInfo>;
    /**
     * Mint a fresh access token from the stored refresh token. Called before
     * each cloud step that needs one. The driver owns the token cache and
     * handles expiry.
     */
    getAccessToken: () => Promise<string>;
    /**
     * Revoke a Google OAuth refresh token. Best-effort — the core swallows
     * failures (non-fatal; the token expires on its own).
     */
    revokeToken: (refreshToken: string) => Promise<void>;
    /**
     * List GCP projects the user has access to.
     * Mirrors gcp-api.ts: listProjects(accessToken).
     */
    listProjects: (accessToken: string) => Promise<GcpProject[]>;
    /**
     * Create a GCP project and wait for the operation to finish.
     * Mirrors gcp-api.ts: createProject(accessToken, projectId, displayName).
     */
    createProject: (accessToken: string, projectId: string, displayName: string) => Promise<GcpProject>;
    /**
     * Enable an API on a project (idempotent).
     * Mirrors gcp-api.ts: enableService(accessToken, projectId, serviceName).
     */
    enableService: (accessToken: string, projectId: string, serviceName: string) => Promise<void>;
    /**
     * Find or create the Capgo service account in a project.
     * Mirrors gcp-api.ts: ensureServiceAccount(args).
     */
    ensureServiceAccount: (args: {
        accessToken: string;
        projectId: string;
        accountId: string;
        displayName?: string;
        description?: string;
    }) => Promise<{
        account: GcpServiceAccount;
        created: boolean;
    }>;
    /**
     * Create a JSON key for a service account.
     * Mirrors gcp-api.ts: createServiceAccountKey(args).
     */
    createServiceAccountKey: (args: {
        accessToken: string;
        projectId: string;
        serviceAccountEmail: string;
    }) => Promise<GcpServiceAccountKey>;
    /**
     * Invite the service account into the Play Console developer account.
     * Mirrors play-api.ts: inviteServiceAccount(args).
     */
    inviteServiceAccount: (args: {
        accessToken: string;
        developerId: string;
        serviceAccountEmail: string;
        developerAccountPermissions?: readonly string[];
        grants?: ReadonlyArray<{
            packageName: string;
            permissions: readonly string[];
        }>;
    }) => Promise<void>;
    /**
     * Find applicationId values in the Android Gradle build files.
     * The driver pre-binds `androidDir` so this dep is argless from the core's
     * perspective. The driver calls findAndroidApplicationIds(androidDir) under
     * the hood.
     */
    findAndroidApplicationIds: () => Promise<string[]>;
    /**
     * Build the CI-secret entries (key/value/masked) from the saved credentials.
     * Mirrors ci-secrets.ts: createCiSecretEntries(credentials, apiKey?).
     */
    createCiSecretEntries?: (credentials: Partial<BuildCredentials>, apiKey?: string) => CiSecretEntry[];
    /**
     * Detect which CI-secret destinations (GitHub/GitLab) are reachable.
     * Mirrors ci-secrets.ts: detectCiSecretTargets(runner?). The driver pre-binds
     * the command runner, so the core calls this with no args.
     */
    detectCiSecretTargets?: (runner?: CommandRunner) => CiSecretDiscovery;
    /**
     * Resolve the concrete `owner/repo` (GitHub) or `group/project` (GitLab) the
     * CLI will target, so the user can confirm before any secret is overwritten.
     * Mirrors ci-secrets.ts: getCiSecretRepoLabelAsync(target, runner?).
     */
    getCiSecretRepoLabelAsync?: (target: CiSecretTarget, runner?: AsyncCommandRunner) => Promise<string | null>;
    /**
     * List which of `keys` already exist as secrets/variables on the remote.
     * Mirrors ci-secrets.ts: listExistingCiSecretKeysAsync(target, keys, runner?).
     */
    listExistingCiSecretKeysAsync?: (target: CiSecretTarget, keys: string[], runner?: AsyncCommandRunner) => Promise<string[]>;
    /**
     * Push the CI-secret entries to the target, reporting per-key progress.
     * Mirrors ci-secrets.ts: uploadCiSecretsAsync(target, entries, existingKeys?, runner?, onProgress?).
     */
    uploadCiSecretsAsync?: (target: CiSecretTarget, entries: CiSecretEntry[], existingKeys?: string[], runner?: AsyncCommandRunner, onProgress?: (current: number, total: number, keyName: string) => void) => Promise<void>;
    /**
     * Write the credentials to a local 0o600 `.env` file (no git operation).
     * Mirrors env-export.ts: exportCredentialsToEnv(opts).
     */
    exportCredentialsToEnv?: (opts: EnvExportOpts) => EnvExportResult;
    /**
     * Resolve the default `.env` export path for an app + platform (pure).
     * Mirrors env-export.ts: defaultExportPath(appId, platform).
     */
    defaultExportPath?: (appId: string, platform: 'ios' | 'android') => string;
    /**
     * Lockfile-based package-manager detection (pure, read-only). Drives the
     * pick-package-manager 'recommended — matches your lockfile' note.
     * Mirrors @capgo/find-package-manager: findPackageManagerType(cwd, 'npm').
     */
    detectPackageManager?: () => string;
    /**
     * Generate the GitHub Actions workflow YAML (pure).
     * Mirrors workflow-generator.ts: generateWorkflow(opts).
     */
    generateWorkflow?: (opts: WorkflowGeneratorOpts) => GeneratedWorkflow;
    /**
     * Generate + write the workflow file to `.github/workflows/capgo-build.yml`.
     * Mirrors workflow-writer.ts: writeWorkflowFile(opts, writeOptions?).
     */
    writeWorkflowFile?: (opts: WorkflowGeneratorOpts, writeOptions?: WorkflowWriteOptions) => WorkflowWriteResult;
    /**
     * Fire the actual `capgo build request`. The driver pre-binds the logger /
     * silent flag it owns; the core supplies appId + options.
     * Mirrors request.ts: requestBuildInternal(appId, options, silent?, logger?).
     */
    requestBuildInternal?: (appId: string, options: BuildRequestOptions, silent?: boolean, logger?: BuildLogger) => Promise<BuildRequestResult>;
    /**
     * The streaming BuildLogger the TUI threads into requestBuildInternal (the 4th
     * arg) so every build line streams into `setBuildOutput`. Forwarded verbatim.
     */
    logger?: BuildLogger;
    /**
     * The build VIEWER sink — the android TUI feeds this into `setBuildOutput` (the
     * dedicated build output pane, DISTINCT from the `onLog` side-log). The shared
     * tail writes the build header / blank+queued / ⚠ failure / no-key UX / catch
     * lines here. Forwarded verbatim through `toTailDeps`. No-op when absent.
     */
    onBuildOutput?: (line: string) => void;
    /**
     * Resolves the Capgo API key the build request should use, mirroring the
     * android tail's CLI-flag-over-saved precedence. Returns undefined when no key
     * is resolvable (the no-key UX finishes at build-complete). Forwarded verbatim.
     */
    resolveApikey?: () => string | undefined;
    /**
     * Per-key CI-secret upload progress, forwarded as the 5th arg of
     * uploadCiSecretsAsync. The android tail feeds this into
     * `setCiSecretUploadProgress`. No-op when absent.
     */
    onCiSecretUploadProgress?: (current: number, total: number, keyName: string) => void;
    /**
     * The 2-phase checking-ci-secrets status text. The android tail feeds this into
     * `setCiSecretCheckPhase`. No-op when absent.
     */
    onCiSecretCheckPhase?: (phase: string) => void;
    /**
     * The ci-secrets-failed reason (repo-null / catch in checking-ci-secrets). The
     * android tail feeds this into `setCiSecretError` (rendered by the
     * CiSecretsFailedStep). Forwarded verbatim through `toTailDeps`. No-op when absent.
     */
    onCiSecretError?: (message: string) => void;
    /** Reads the project's package.json scripts map (workflow-builder 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'). The android tail
     * calls `trackWorkflowEvent`. No-op when absent.
     */
    trackWorkflowEvent?: (event: string, options?: {
        decision?: string;
    }) => void;
    /**
     * DRIVER-HELD transient tail state, threaded back into each post-save tail
     * effect. The Ink TUI resolves these ONCE (at `saving-credentials`) and keeps
     * them in React state (`savedCredentials` / `ciSecretEntries` /
     * `ciSecretExistingKeys`); a headless driver mirrors that by capturing the
     * matching `AndroidEffectResult.transient` from each effect and passing it
     * back here on the NEXT effect. The engine NEVER persists these to
     * progress.json — they are secrets/credentials/entries that must stay in
     * memory only. When a field is absent (e.g. a crash-recovery resume where the
     * driver lost its in-memory state) the effect falls back to a SINGLE lossy
     * re-derivation from progress (rebuildTailCredentials / createCiSecretEntries)
     * rather than resolving the Capgo API key a second time.
     */
    carried?: {
        /** Full saved credentials map written at saving-credentials (5 fields, no CAPGO_TOKEN). */
        savedCredentials?: Record<string, string>;
        /** CI-secret entries resolved ONCE at saving-credentials (creds + Capgo API key → CAPGO_TOKEN). */
        ciSecretEntries?: CiSecretEntry[];
        /** Which secret keys already exist on the remote, resolved at checking-ci-secrets. */
        ciSecretExistingKeys?: string[];
        /** Whether the workflow file did NOT exist at preview (app.tsx's `previewIsNew`); drives 'Wrote' vs 'Overwrote'. Defaults to NEW when absent. */
        workflowIsNew?: 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;
    onAuthUrl?: (url: string) => void;
    signal?: AbortSignal;
}
export interface AndroidEffectResult {
    /** Updated progress after the effect ran (matches what was persisted). */
    progress: AndroidOnboardingProgress;
    /** Explicit next step when not derivable from progress alone (★ transitions). */
    next?: AndroidOnboardingStep;
    /** Transient runtime data that lives in the driver but is NOT persisted. */
    transient?: Partial<AndroidStepCtx>;
}
export declare function runAndroidEffect(step: AndroidOnboardingStep, progress: AndroidOnboardingProgress, deps: AndroidEffectDeps): Promise<AndroidEffectResult>;
