guideUpgrade method

Future<BillingCheckResult> guideUpgrade({
  1. String? projectId,
  2. int maxLoops = 3,
  3. bool interactive = true,
})

Walk the user through upgrading to Blaze.

Prints the consequence of staying on Spark, the upgrade URL, then prompts the user. After they confirm completion the status is re-checked. The loop runs at most maxLoops times so a misclick can never hang the wizard.

Returns the final BillingCheckResult.

Implementation

Future<BillingCheckResult> guideUpgrade({
  String? projectId,
  int maxLoops = 3,
  bool interactive = true,
}) async {
  final String pid = projectId ?? this.projectId;
  final String url = upgradeUrl(pid);

  UserPrompt.printDivider(title: 'Upgrade to Blaze (pay-as-you-go)');
  UserPrompt.printList(<String>[
    'Spark plan covers: Hosting, Firestore (small), Auth, Storage (small).',
    'Blaze plan is required for: Cloud Run, Artifact Registry cleanup,',
    '  scheduled jobs, callable functions, and most production workloads.',
    'Open: $url',
    'Sign in with an account that owns the project and link a billing',
    '  account. The upgrade is reversible — you can downgrade at any time.',
  ]);
  print('');

  if (!interactive) {
    info('Non-interactive mode: skipping Blaze upgrade hand-off for $pid');
    return BillingCheckResult(
      status: BlazeStatus.unknown,
      message:
          'Run `oracular check billing` after upgrading to Blaze at $url',
    );
  }

  for (int i = 0; i < maxLoops; i++) {
    final bool ready = await UserPrompt.askYesNo(
      i == 0
          ? 'Have you completed the Blaze upgrade in the browser?'
          : 'Re-check billing now? (will run gcloud billing describe)',
      defaultValue: i == 0,
    );
    if (!ready) {
      warn('Skipping Blaze verification for $pid.');
      return const BillingCheckResult(
        status: BlazeStatus.unknown,
        message: 'User skipped Blaze verification',
      );
    }

    final BillingCheckResult check = await checkBlazeStatus(projectId: pid);
    switch (check.status) {
      case BlazeStatus.enabled:
        success('Blaze plan confirmed for $pid.');
        if (check.billingAccountName != null) {
          info('Linked billing account: ${check.billingAccountName}');
        }
        return check;
      case BlazeStatus.notEnabled:
        warn(
          'Project $pid is still on Spark. The upgrade may not have completed — refresh the Firebase Console and try again.',
        );
        continue;
      case BlazeStatus.unknown:
        warn(
          'Could not verify billing for $pid: ${check.message ?? 'unknown error'}',
        );
        continue;
    }
  }

  return BillingCheckResult(
    status: BlazeStatus.unknown,
    message:
        'Could not confirm Blaze upgrade after $maxLoops attempts. Run `oracular check billing` later, or skip Blaze-only steps for now.',
  );
}