/**
 * Pure helpers for the onboarding wizard. Co-located here so the wizard
 * itself can stay focused on orchestration, and so each helper can be
 * unit-tested without rendering React.
 */

export type StepKey = 'gsc' | 'analysis' | 'recommendations';

export interface OnboardingStep {
  label: string;
  key: StepKey;
}

/**
 * Canonical, ordered definition of the user-facing onboarding steps.
 *
 * NB: the server-side `OnboardingProgressService` defines six finer-grained
 * steps (welcome / connect_gsc / connect_wp / run_analysis /
 * first_opportunity / first_publish) for instrumentation and nudge emails.
 * Those names are deliberately not exposed to users — this three-step model
 * is the user-facing language. Email copy that references the granular
 * server steps should be reconciled to these labels.
 */
export const ONBOARDING_STEPS: ReadonlyArray<OnboardingStep> = [
  { label: 'Connect GSC', key: 'gsc' },
  { label: 'Run Analysis', key: 'analysis' },
  // COPY-004: changed from 'Get Your First Insights' (vague SaaS buzzword) to
  // outcome-focused 'See what to fix first' which speaks to the persona's
  // recovery goal rather than describing a feature.
  { label: 'See what to fix first', key: 'recommendations' },
];

/**
 * R6UXA-012: Collapsed persona branches to the single solo path. The v1
 * persona is `solo_tech_blogger_bulk_ai_rewriter` — agency/freelancer/
 * in-house branches introduced copy that the persona never encounters and
 * created a maintenance surface for copy that's impossible to A/B test.
 *
 * If the product ever expands to serve agency/in-house personas, restore
 * the switch or read the copy from a server-side role config so it stays
 * in sync with the UserRole enum.
 */
export function getAnalysisStepDescription(_userRole: string | null | undefined): string {
  // COPY-004: time anchor + outcome framing for the solo blogger persona.
  return 'Analyze your traffic data to find pages worth fixing — results in about 30 seconds.';
}

/**
 * The four broad categories of GSC sync failure we know how to react to.
 * Each maps to a typed CTA in the failure card:
 *   reconnect       — credentials/permissions are bad; user must re-auth.
 *   retry           — transient (rate limit, network); a fresh attempt will
 *                     usually succeed.
 *   contact_support — we don't recognise the error; route to support.
 */
export type GscErrorAction = 'reconnect' | 'retry' | 'contact_support';

export interface GscErrorClassification {
  likelyCause: string;
  whatToDo: GscErrorAction;
}

/**
 * Best-guess interpretation of a GSC `sync_error` string into a user-readable
 * "what likely happened" + suggested next step.
 *
 * Keyword matching against Google's exception messages is a soft contract —
 * if Google changes wording, the classifier silently falls through to the
 * `contact_support` default. The right long-term architecture is a typed
 * error code emitted by `GscSyncService` and persisted alongside `sync_error`
 * — until then this gives users a useful first hop.
 */
export function classifyGscError(rawError: string | null | undefined): GscErrorClassification {
  const err = (rawError ?? '').toLowerCase();
  if (!err) {
    return {
      likelyCause: "We didn't get a specific reason from Google.",
      whatToDo: 'retry',
    };
  }
  if (
    err.includes('invalid_grant') ||
    err.includes('token') ||
    err.includes('unauthorized') ||
    err.includes('401')
  ) {
    return {
      likelyCause: 'Your Google credentials have expired or been revoked.',
      whatToDo: 'reconnect',
    };
  }
  if (
    err.includes('insufficient_permission') ||
    err.includes('forbidden') ||
    err.includes('403') ||
    err.includes('scope')
  ) {
    return {
      likelyCause: "We don't have permission to read this Search Console property.",
      whatToDo: 'reconnect',
    };
  }
  if (err.includes('not found') || err.includes('404') || err.includes('no_property')) {
    return {
      likelyCause: "Search Console couldn't find the property — it may have been removed.",
      whatToDo: 'reconnect',
    };
  }
  if (err.includes('quota') || err.includes('rate') || err.includes('429')) {
    return {
      likelyCause: 'Google rate-limited us. This usually clears in a few minutes.',
      whatToDo: 'retry',
    };
  }
  if (err.includes('network') || err.includes('timeout') || err.includes('econnreset')) {
    return {
      likelyCause: "We couldn't reach Google. This is usually a transient network issue.",
      whatToDo: 'retry',
    };
  }
  return {
    likelyCause: 'Something on our side broke during sync.',
    whatToDo: 'contact_support',
  };
}

/**
 * Map a sync progress percentage (0–100) to a truthful stage description.
 *
 * Earlier versions cycled four stage strings on a 4-second timer regardless
 * of actual progress, which lied to the user (a 90%-complete sync would loop
 * back to "Authenticating…"). Now stage copy is a pure function of progress,
 * so the text always agrees with the bar.
 *
 * Boundaries are inclusive on the lower edge: < 33 → stage 1; < 67 → stage 2;
 * < 90 → stage 3; ≥ 90 → stage 4. Out-of-range values are clamped by the
 * caller before this function is invoked.
 */
export function syncStageCopy(progressPct: number): string {
  if (progressPct < 33) return 'Authenticating with Google and verifying permissions…';
  if (progressPct < 67) return 'Fetching your search history…';
  if (progressPct < 90) return 'Processing clicks, impressions, and position metrics…';
  return 'Building your performance baseline…';
}

/**
 * R6UXA-013: Shared setup-step labels — single source of truth for the copy
 * that appears in both the onboarding wizard AND the Billing page
 * (trial-recap modal + checkout-success checklist). Keeping them here means
 * the two surfaces always tell the same story. The keys match the `SetupState`
 * boolean flags the server sends to Billing and the wizard alike.
 */
export const SETUP_STEP_LABELS: Record<'has_gsc' | 'has_analysis' | 'has_ai_key', string> = {
  has_gsc: 'Connected Google Search Console',
  has_analysis: 'Ran your first traffic analysis',
  has_ai_key: 'Set up AI content generation',
};

/**
 * Cap the size of a free-form error string before we hand it to a `mailto:`
 * URL. Browsers cap mailto URLs around 2,000–8,000 characters; verbose
 * Google exceptions occasionally exceed that. Matches the truncation the
 * admin GSC connections controller already applies when surfacing errors.
 */
export const MAX_ERROR_LENGTH_FOR_MAILTO = 500;

/**
 * Trim and decorate a GSC `sync_error` string for embedding in a mailto body.
 * Returns the placeholder `'(none reported)'` when the input is empty so
 * support can distinguish "we tried and got nothing" from "we forgot to
 * include the field".
 */
export function formatErrorForSupport(errorMessage: string | null | undefined): string {
  if (!errorMessage) return '(none reported)';
  return errorMessage.length > MAX_ERROR_LENGTH_FOR_MAILTO
    ? `${errorMessage.slice(0, MAX_ERROR_LENGTH_FOR_MAILTO)}… (truncated)`
    : errorMessage;
}

/**
 * Canonical support inbox. Mirrors `config/support.php` (env: SUPPORT_EMAIL).
 * Matches the address used by `DashboardLayout`. If support routing changes,
 * update both surfaces or thread the value through Inertia shared props.
 */
export const SUPPORT_EMAIL = 'support@rankwiz.ai';
