import {
  AlertTriangle,
  ArrowRight,
  Bell,
  Check,
  Clock,
  Copy,
  ExternalLink,
  Loader2,
  PlayCircle,
  Sparkles,
  TrendingUp,
  UserPlus,
  X,
} from 'lucide-react';
import { toast } from 'sonner';

import { useEffect, useRef, useState } from 'react';

import { Link, router, useForm, usePage } from '@inertiajs/react';

import { DraftOutputTeaser } from '@/Components/Ai/DraftOutputTeaser';
import { TrialExpiryCard } from '@/Components/billing/TrialExpiryCard';
import { Badge } from '@/Components/ui/badge';
import { Button } from '@/Components/ui/button';
import { Progress } from '@/Components/ui/progress';
import { useExperiment } from '@/hooks/useExperiment';
import { usePolling } from '@/hooks/usePolling';
import { trackEvent, trackProductEvent } from '@/lib/analytics';
import {
  ANALYZE_NOW_CLICKED,
  BYOK_NUDGE_DEFERRED,
  DEMO_MODE_ENTERED,
  GSC_CONNECT_CLICKED,
  GSC_SYNC_COMPLETED,
  GSC_SYNC_ESCAPE,
  GSC_SYNC_STARTED,
  GSC_SYNC_WAIT_ABANDONED,
  ONBOARDING_ABANDONED,
  ONBOARDING_COMPLETED,
  ONBOARDING_STEP_VIEWED,
  ONBOARDING_WIZARD_VIEWED,
  REFERRAL_CTA_SHOWN,
  REFERRAL_LINK_COPIED,
  TEAM_INVITE_SENT,
  UPGRADE_PROMPT_CLICKED,
  WIZARD_AI_STEP_SHOWN,
  WIZARD_SEE_RECOMMENDATIONS_CLICKED,
  WP_CONNECT_CLICKED,
  WP_INSTALL_STARTED,
  WP_PLUGIN_DETECTED,
} from '@/lib/event-catalog';
import { cn } from '@/lib/utils';
import { PageProps, SiteBasic } from '@/types';

interface GscConnection {
  property_url: string;
  last_synced_at: string | null;
  status: string;
  sync_progress_pct?: number | null;
}

interface WpConnection {
  wp_url: string;
  setup_token?: string;
  status: string;
  plugin_version?: string;
  capabilities?: Record<string, boolean>;
  last_webhook_at?: string | null;
  last_sync_at?: string | null;
}

interface LatestRun {
  id: number;
  status: string;
  before_start: string | null;
  before_end: string | null;
  after_start: string | null;
  after_end: string | null;
  summary: {
    overall: {
      clicks_after: number;
      clicks_delta_pct: number;
      impressions_after: number;
      impressions_delta_pct: number;
      ctr_after: number;
      ctr_delta_pct: number;
      position_after: number;
      position_delta_pct: number;
    };
  } | null;
}

interface OnboardingWizardProps {
  site: SiteBasic & { is_demo: boolean };
  gscConnection: GscConnection | null;
  wpConnection: WpConnection | null;
  gscDataRange?: { earliest: string | null; latest: string | null; coverage_days?: number | null };
  latestRun?: LatestRun | null;
  winnersCount?: number;
  losersCount?: number;
  recommendationsCount?: number;
  referralUrl?: string | null;
  productOverviewVideoUrl?: string | null;
  demoDataAvailable?: boolean;
  /** ACT-003: User role for persona-conditional messaging. Matches UserRole enum values. */
  userRole?: string | null;
}

/** ACT-003: Return a role-specific description for the Run Analysis card. */
function getAnalysisStepDescription(userRole: string | null | undefined): string {
  switch (userRole) {
    case 'agency_owner':
      return 'Analyze your GSC data to surface SEO wins across client properties. Generate team-ready action plans and shareable reports.';
    case 'blogger':
      return 'Analyze your GSC data to find underperforming posts and content gaps. Get AI-powered rewrite suggestions for your highest-traffic pages.';
    case 'ecommerce_manager':
      return 'Analyze your GSC data to surface product page and category SEO opportunities. Find quick wins to drive more organic traffic to your store.';
    case 'freelancer':
      return 'Analyze your GSC data to surface client SEO opportunities quickly. Generate polished reports to share insights with clients.';
    case 'in_house_seo':
      return 'Analyze your GSC data to surface competitive opportunities and traffic trends. Track performance across your full site architecture.';
    default:
      return 'Analyze your GSC data to surface SEO opportunities and recommendations.';
  }
}

interface Step {
  label: string;
  key: 'gsc' | 'analysis' | 'recommendations';
}

const steps: Step[] = [
  { label: 'Connect GSC', key: 'gsc' },
  { label: 'Run Analysis', key: 'analysis' },
  { label: 'Review Recommendations', key: 'recommendations' },
];

const SYNC_STAGES = [
  'Authenticating with Google and verifying permissions…',
  'Fetching your last 16 months of search data…',
  'Processing clicks, impressions, and position metrics…',
  'Building your performance baseline…',
] as const;

/** Sample cards shown during first sync so the user sees what results will look like.
 * @param domain - The user's site domain, used to personalise the sample URL. */
function SyncWaitPreview({ domain }: { domain?: string }) {
  const bareDomain = domain ? domain.replace(/^https?:\/\//, '') : '';
  const samplePath = bareDomain
    ? `https://${bareDomain}/blog/your-first-post`
    : '/blog/example-post';
  return (
    <div className="mt-4 space-y-2">
      <p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
        Here's what you'll see after sync
      </p>
      {/* Sample finding card */}
      <div
        className="rounded-md border border-border/60 bg-muted/30 p-3 space-y-1 select-none"
        aria-hidden="true"
      >
        <div className="flex items-center gap-1.5">
          <TrendingUp className="h-3.5 w-3.5 text-destructive shrink-0" />
          <span className="text-xs font-medium truncate">Traffic drop: {samplePath}</span>
        </div>
        <p className="text-xs text-muted-foreground">
          Clicks fell <span className="font-medium text-destructive">−38%</span> in the last 28 days
          vs prior period.
        </p>
      </div>
      {/* Sample recommendation card */}
      <div
        className="rounded-md border border-border/60 bg-muted/30 p-3 space-y-1 select-none"
        aria-hidden="true"
      >
        <div className="flex items-center gap-1.5">
          <Sparkles className="h-3.5 w-3.5 text-primary shrink-0" />
          <span className="text-xs font-medium truncate">Recommendation: Refresh content</span>
        </div>
        <p className="text-xs text-muted-foreground">
          Update introduction and add an FAQ section targeting 3 related queries.
        </p>
      </div>
    </div>
  );
}

/** Persistent banner shown when the site is running in demo mode (ACT-003). Disappears once a real GSC connection exists. */
function DemoModeBanner({ siteId, onDismiss }: { siteId: number; onDismiss?: () => void }) {
  return (
    <div
      role="status"
      aria-label="Demo mode active"
      className="flex items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-950/30"
    >
      <Sparkles
        className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5"
        aria-hidden="true"
      />
      <div className="flex-1 min-w-0">
        <p className="text-sm font-medium text-amber-800 dark:text-amber-300">
          You're exploring with sample data
        </p>
        <p className="text-xs text-amber-700 dark:text-amber-400 mt-0.5">
          Connect Google Search Console to replace the sample data with your real SEO metrics.
        </p>
        <a
          href={route('gsc.connect', { site: siteId })}
          className="mt-2 inline-flex items-center gap-1.5 text-xs font-medium text-amber-800 underline underline-offset-2 hover:text-amber-900 dark:text-amber-300 dark:hover:text-amber-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
          onClick={() =>
            trackEvent(GSC_CONNECT_CLICKED, { site_id: String(siteId), source: 'demo_banner' })
          }
        >
          Connect real data
          <ArrowRight className="h-3 w-3" aria-hidden="true" />
        </a>
      </div>
      {onDismiss && (
        <button
          type="button"
          onClick={onDismiss}
          aria-label="Dismiss demo mode banner"
          className="shrink-0 text-amber-500 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
        >
          <X className="h-3.5 w-3.5" aria-hidden="true" />
        </button>
      )}
    </div>
  );
}

/** Compact non-blocking sync indicator shown inline inside the GSC card. */
function GscSyncStatusCompact({
  syncProgressPct = 0,
  productOverviewVideoUrl,
  propertyUrl,
  isFirstSync = false,
  domain,
  siteId,
}: {
  syncProgressPct?: number;
  productOverviewVideoUrl?: string | null;
  propertyUrl?: string;
  isFirstSync?: boolean;
  domain?: string;
  siteId?: number;
}) {
  const [stageIndex, setStageIndex] = useState(0);

  useEffect(() => {
    const stageInterval = setInterval(() => {
      setStageIndex((prev) => (prev + 1) % SYNC_STAGES.length);
    }, 4000);
    return () => clearInterval(stageInterval);
  }, []);

  const displayPct = Math.min(100, Math.max(0, syncProgressPct));

  let notifSettingsHref: string | null = null;
  if (siteId) {
    try {
      notifSettingsHref = route('notification-settings.show', { site: siteId });
    } catch {
      // route name not registered in Ziggy manifest (e.g. feature flag off)
    }
  }

  return (
    <div className="space-y-2">
      {propertyUrl && <p className="text-sm text-success">Connected: {propertyUrl}</p>}
      <div className="flex items-center gap-2">
        <Loader2 className="h-4 w-4 text-primary animate-spin shrink-0" aria-hidden="true" />
        <div>
          <span className="text-sm font-medium">Syncing in background</span>
          <span className="ml-1.5 text-xs text-muted-foreground" aria-live="polite">
            — {displayPct}% complete
          </span>
        </div>
      </div>
      <div
        role="progressbar"
        aria-valuenow={displayPct}
        aria-valuemin={0}
        aria-valuemax={100}
        aria-label="GSC sync progress"
      >
        <Progress value={displayPct} className="h-1.5" />
      </div>
      <p aria-live="polite" className="text-xs text-muted-foreground">
        {SYNC_STAGES[stageIndex]}
      </p>
      {typeof Notification !== 'undefined' && Notification.permission === 'default' && (
        <Button
          type="button"
          size="sm"
          variant="outline"
          onClick={() => Notification.requestPermission()}
          className="h-7 gap-1.5 text-xs"
        >
          <Bell className="h-3.5 w-3.5" aria-hidden="true" />
          Notify me when ready
        </Button>
      )}
      {(typeof Notification === 'undefined' || Notification.permission === 'denied') &&
        (notifSettingsHref ? (
          <a
            href={notifSettingsHref}
            className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
          >
            <Bell className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
            Email me when ready →
          </a>
        ) : (
          <p className="text-xs text-muted-foreground flex items-center gap-1.5">
            <Bell className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
            Check back soon — this usually takes a few minutes.
          </p>
        ))}
      {productOverviewVideoUrl && (
        <a
          href={productOverviewVideoUrl}
          target="_blank"
          rel="noopener noreferrer"
          className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
        >
          <PlayCircle className="h-3.5 w-3.5" aria-hidden="true" />
          60-sec product overview
        </a>
      )}
      {isFirstSync && <SyncWaitPreview domain={domain} />}
    </div>
  );
}

function WizardStepper({
  completedKeys,
  currentStep,
}: {
  completedKeys: Set<string>;
  currentStep: number;
}) {
  return (
    <ol role="list" aria-label="Setup progress" className="flex items-center w-full">
      {steps.map((step, index) => {
        const stepNumber = index + 1;
        const isCompleted = completedKeys.has(step.key);
        const isCurrent = stepNumber === currentStep && !isCompleted;
        const isUpcoming = !isCompleted && !isCurrent;
        const isLast = index === steps.length - 1;

        return (
          <li
            key={step.key}
            aria-current={isCurrent ? 'step' : undefined}
            className="flex items-center flex-1 last:flex-none"
          >
            {/* Circle + label */}
            <div className="flex flex-col items-center gap-1">
              <div className="relative flex items-center justify-center">
                {isCurrent && (
                  <span className="absolute inline-flex h-full w-full rounded-full bg-primary opacity-40 animate-pulse motion-reduce:animate-none" />
                )}
                <div
                  className={cn(
                    'relative flex h-8 w-8 items-center justify-center rounded-full border-2 text-sm font-semibold transition-colors',
                    isCompleted && 'border-success bg-success text-white',
                    isCurrent && 'border-primary bg-primary text-primary-foreground',
                    isUpcoming && 'border-muted-foreground/40 bg-transparent text-muted-foreground',
                  )}
                >
                  {isCompleted ? <Check className="h-4 w-4" aria-hidden="true" /> : stepNumber}
                </div>
              </div>
              <span
                className={cn(
                  'text-xs font-medium max-w-[80px] text-center leading-tight',
                  isCompleted && 'text-success',
                  isCurrent && 'text-foreground',
                  isUpcoming ? 'text-muted-foreground' : '',
                )}
              >
                <span className="sr-only">
                  {isCompleted ? 'Completed: ' : isCurrent ? 'Current step: ' : 'Upcoming: '}
                </span>
                {step.label}
              </span>
            </div>

            {/* Connector line */}
            {!isLast && (
              <div
                className={cn(
                  'flex-1 h-0.5 mx-2 mb-5 transition-colors',
                  isCompleted ? 'bg-success' : 'bg-muted-foreground/20',
                )}
              />
            )}
          </li>
        );
      })}
    </ol>
  );
}

function PluginInstallHelper({ siteId, domain }: { siteId: number; domain?: string }) {
  const [open, setOpen] = useState(false);
  const wpAdminUploadUrl = domain
    ? `${domain.replace(/\/$/, '')}/wp-admin/plugin-install.php?tab=upload`
    : null;

  const handleToggle = () => {
    const next = !open;
    setOpen(next);
    if (next) {
      trackEvent(WP_INSTALL_STARTED, { site_id: String(siteId) });
    }
  };

  return (
    <div className="mt-3 rounded-md border border-border/60 bg-muted/30">
      <button
        type="button"
        onClick={handleToggle}
        aria-expanded={open}
        aria-controls="plugin-install-steps"
        className="flex w-full items-center justify-between px-3 py-2 text-sm text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md transition-colors"
      >
        <span>Need to install the plugin first?</span>
        <span
          aria-hidden="true"
          className={cn('text-xs transition-transform duration-200', open ? 'rotate-180' : '')}
        >
          ▾
        </span>
      </button>

      {open && (
        <div
          id="plugin-install-steps"
          className="border-t border-border/60 px-3 pb-3 pt-2 space-y-3 animate-in slide-in-from-top-1 fade-in-0 duration-150"
        >
          <ol className="space-y-1.5 text-sm text-muted-foreground list-decimal list-inside">
            <li>Download the plugin zip using the button below.</li>
            <li>
              {wpAdminUploadUrl ? (
                <>
                  <a
                    href={wpAdminUploadUrl}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="inline-flex items-center gap-1 text-primary underline underline-offset-2 hover:text-primary/80"
                  >
                    Open WP Plugin Installer
                    <ExternalLink className="h-3 w-3" aria-hidden="true" />
                  </a>{' '}
                  (or go to Plugins → Add New → Upload Plugin).
                </>
              ) : (
                'In your WordPress admin, go to Plugins → Add New → Upload Plugin.'
              )}
            </li>
            <li>Choose the downloaded zip file and click Install Now.</li>
            <li>Activate the plugin.</li>
            <li>Return here and enter your WordPress URL to connect.</li>
          </ol>
          <a
            href="/downloads/wp-plugin"
            download
            className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 active:scale-[0.98] transition-transform focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          >
            Download Plugin
          </a>
        </div>
      )}
    </div>
  );
}

// Defined at module scope for stable useEffect dependency array reference (INST-003).
const ACTIVE_STEPS = [
  'gsc_connect',
  'gsc_syncing',
  'analysis_ready',
  'analysis_running',
  'recommendations',
  'healthy_complete',
] as const;

export default function OnboardingWizard({
  site,
  gscConnection,
  wpConnection,
  gscDataRange,
  latestRun,
  recommendationsCount,
  referralUrl,
  productOverviewVideoUrl,
  demoDataAvailable = false,
  userRole,
}: OnboardingWizardProps) {
  const {
    polling_interval_ms,
    trial,
    plan,
    features,
    ai_status: aiStatus,
  } = usePage<PageProps>().props;

  // ACT-002: A/B experiment — test whether showing demo CTA earlier improves activation
  // for users without a GSC connection. variant_a: demo card shown above the GSC section.
  const demoPlacementVariant = useExperiment('onboarding_demo_placement');

  const gscStatus = gscConnection?.status ?? null;
  const isSyncing = gscStatus === 'syncing' || gscStatus === 'pending';
  // ACT-001: Highlight Analyze Now when server reports 100% progress (may precede status flip to 'synced')
  const syncProgressPct = gscConnection?.sync_progress_pct ?? 0;
  const syncReadyEarly = isSyncing && syncProgressPct >= 100;

  // ACT-011: Compute data coverage days for insufficient-data warning
  const dataCoverageDays = gscDataRange?.coverage_days ?? null;

  // Inline WP connection form
  const {
    data: wpData,
    setData: setWpData,
    post: postWp,
    processing: wpProcessing,
    errors: wpErrors,
  } = useForm({ wp_url: '' });
  const [detecting, setDetecting] = useState(false);
  const [detectResult, setDetectResult] = useState<'found' | 'not-found' | null>(null);
  const [wpTokenCopied, setWpTokenCopied] = useState(false);
  const [referralCopied, setReferralCopied] = useState(false);
  const [syncWaitReferralCopied, setSyncWaitReferralCopied] = useState(false);

  const handleWpConnect = (e: React.FormEvent) => {
    e.preventDefault();
    trackEvent(WP_CONNECT_CLICKED, { site_id: String(site.id) });
    postWp(route('wp.connect', { site: site.id }));
  };

  const handleDetectPlugin = async () => {
    const url = wpData.wp_url.trim().replace(/\/$/, '');
    if (!url) return;
    setDetecting(true);
    setDetectResult(null);
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 10_000);
    try {
      const response = await fetch(`${url}/wp-json/rankwiz/v1/status`, {
        method: 'HEAD',
        signal: controller.signal,
      });
      if (response.ok) {
        setDetectResult('found');
        trackEvent(WP_PLUGIN_DETECTED, { site_id: String(site.id) });
      } else {
        setDetectResult('not-found');
      }
    } catch {
      setDetectResult('not-found');
    } finally {
      clearTimeout(timeout);
      setDetecting(false);
    }
  };

  const copySetupToken = async (token: string) => {
    await navigator.clipboard.writeText(token);
    setWpTokenCopied(true);
    setTimeout(() => setWpTokenCopied(false), 2000);
  };

  // ACT-003: Detect auto-triggered analysis (from AutomationOrchestrationService)
  const isAnalysisPending = latestRun?.status === 'pending' || latestRun?.status === 'processing';

  // ACT-001: Track wizard viewed on mount
  const syncStartRef = useRef<number | null>(null);
  // Tracks explicit escape via "Go to Dashboard" CTA during sync — suppresses ONBOARDING_ABANDONED on unmount
  const escapedViaSyncCtaRef = useRef(false);
  const [isEscapingSync, setIsEscapingSync] = useState(false);
  useEffect(() => {
    trackEvent(ONBOARDING_WIZARD_VIEWED, { site_id: String(site.id) });
  }, [site.id]);

  // Poll when syncing, analysis pending, or WP setup token is displayed (waiting for plugin activation)
  usePolling(
    isSyncing || isAnalysisPending || !!wpConnection?.setup_token,
    polling_interval_ms ?? 5000,
    ['gsc_connection', 'latest_run', 'wp_connection'],
  );

  // ACT-001: Track GSC sync start
  useEffect(() => {
    if (isSyncing && syncStartRef.current === null) {
      syncStartRef.current = Date.now();
      trackEvent(GSC_SYNC_STARTED, { site_id: String(site.id) });
    }
  }, [isSyncing, site.id]);

  // ACT-008: Track referral CTA shown during sync wait
  useEffect(() => {
    if (isSyncing && referralUrl) {
      trackEvent(REFERRAL_CTA_SHOWN, { source: 'sync_wait_card', site_id: String(site.id) });
    }
  }, [isSyncing, referralUrl, site.id]);

  // Show a toast when sync finishes (status transitions away from syncing/pending)
  const prevStatusRef = useRef(gscStatus);
  useEffect(() => {
    const prev = prevStatusRef.current;
    const wasSyncing = prev === 'syncing' || prev === 'pending';
    const nowSynced = gscStatus === 'synced';
    if (wasSyncing && nowSynced) {
      toast.success('Your GSC data is ready — run your first analysis now!');
      // ACT-001: Track GSC sync completed with duration
      const durationMs = syncStartRef.current ? Date.now() - syncStartRef.current : undefined;
      trackEvent(GSC_SYNC_COMPLETED, {
        site_id: String(site.id),
        ...(durationMs !== undefined && { duration_ms: durationMs }),
      });
      syncStartRef.current = null;

      // ACT-004: Fire browser notification if user navigated away
      if (
        typeof Notification !== 'undefined' &&
        Notification.permission === 'granted' &&
        document.hidden
      ) {
        new Notification('GSC data is ready!', {
          body: `${site.name} is ready for analysis. Run your first analysis now.`,
          icon: '/favicon.ico',
        });
      }
    }
    prevStatusRef.current = gscStatus;
  }, [gscStatus, site.id, site.name]);

  const analysisCompleted = latestRun?.status === 'completed';

  const hasRecommendations = (recommendationsCount ?? 0) > 0;
  // A healthy site with 0 recommendations is a valid completion — no urgent issues found.
  const isHealthySite = analysisCompleted && (recommendationsCount ?? 0) === 0;
  const step3Complete = hasRecommendations || isHealthySite;
  const totalSteps = 3;
  const stepsCompleted =
    (gscConnection ? 1 : 0) + (analysisCompleted ? 1 : 0) + (step3Complete ? 1 : 0);
  const currentStep = Math.min(stepsCompleted + 1, totalSteps);

  // Granular wizard sub-steps for per-step drop-off measurement (INST-003).
  // These 6 states capture every distinct UI experience a user can see during the wizard.
  // Defined outside component to keep stable reference for useEffect dependency array.
  type OnboardingActiveStep = (typeof ACTIVE_STEPS)[number];

  const activeStep: OnboardingActiveStep = (() => {
    if (isHealthySite) return 'healthy_complete';
    if (hasRecommendations) return 'recommendations';
    if (isAnalysisPending) return 'analysis_running';
    if (gscConnection && gscStatus === 'synced') return 'analysis_ready';
    if (isSyncing) return 'gsc_syncing';
    return 'gsc_connect';
  })();

  // Track step views — fires on every distinct activeStep transition so per-step
  // abandon rates are measurable. 6 distinct events fire across a full walkthrough.
  const stepStartRef = useRef(Date.now());
  useEffect(() => {
    trackProductEvent(ONBOARDING_STEP_VIEWED, {
      step_name: activeStep,
      step_index: ACTIVE_STEPS.indexOf(activeStep) + 1,
      total_steps: ACTIVE_STEPS.length,
      site_id: String(site.id),
      plan_tier: plan ?? 'free',
      // ACT-002: experiment variant for demo placement (null = not enrolled or loading)
      ...(demoPlacementVariant !== null && { experiment_demo_variant: demoPlacementVariant }),
      // ACT-003: user role for persona-based conversion analysis
      ...(userRole !== null && userRole !== undefined && { user_role: userRole }),
    });
    stepStartRef.current = Date.now();
  }, [activeStep, site.id, plan, demoPlacementVariant, userRole]);

  // Track onboarding completion server-side (consent-free) + client-side supplement
  const completionTrackedRef = useRef(false);
  useEffect(() => {
    if (stepsCompleted === totalSteps && !completionTrackedRef.current) {
      completionTrackedRef.current = true;
      // Server-side: fires in audit_logs regardless of analytics consent (INST-012)
      router.post(
        route('onboarding.complete', { site: site.id }),
        {},
        { preserveState: true, preserveScroll: true },
      );
      // Client-side supplement: GA4 / PostHog client (may be consent-blocked for new users)
      // ACT-003: include user role for persona-based conversion analysis
      trackProductEvent(ONBOARDING_COMPLETED, {
        site_id: String(site.id),
        ...(userRole !== null && userRole !== undefined && { user_role: userRole }),
      });
      trackEvent(REFERRAL_CTA_SHOWN, { site_id: String(site.id) });
    }
  }, [stepsCompleted, totalSteps, site.id, userRole]);

  // Keep a ref up-to-date with the latest values needed for unmount cleanup (avoids stale closure)
  const abandonmentRef = useRef({
    site_id: String(site.id),
    step_key: activeStep,
    step_name: activeStep,
    stepsCompleted,
    isSyncing,
  });
  abandonmentRef.current = {
    site_id: String(site.id),
    step_key: activeStep,
    step_name: activeStep,
    stepsCompleted,
    isSyncing,
  };

  // Fire ONBOARDING_ABANDONED on unmount when wizard is not yet complete.
  // Suppressed when user explicitly clicked "Go to Dashboard" during sync (escapedViaSyncCtaRef),
  // since that fires GSC_SYNC_ESCAPE instead and is not an unintentional abandonment.
  useEffect(() => {
    return () => {
      const {
        site_id,
        step_key,
        step_name,
        stepsCompleted: sc,
        isSyncing: syncing,
      } = abandonmentRef.current;
      if (sc < totalSteps && !escapedViaSyncCtaRef.current) {
        trackEvent(ONBOARDING_ABANDONED, {
          site_id,
          step_key,
          step_name,
          time_on_step_ms: Date.now() - stepStartRef.current,
          was_syncing: syncing,
        });
        if (syncing && syncStartRef.current !== null) {
          trackEvent(GSC_SYNC_WAIT_ABANDONED, {
            site_id,
            wait_time_ms: Date.now() - syncStartRef.current,
          });
        }
      }
    };
  }, []);

  const progressPct = Math.round((stepsCompleted / totalSteps) * 100);

  const completedKeys = new Set<string>([
    ...(gscConnection ? ['gsc'] : []),
    ...(analysisCompleted ? ['analysis'] : []),
    ...(step3Complete ? ['recommendations'] : []),
  ]);

  return (
    <div className="container py-8">
      <div className="max-w-2xl mx-auto space-y-6">
        <h1 className="text-3xl font-bold">Set up your SEO insights</h1>
        <p className="text-muted-foreground">
          {site.name} &middot; {site.domain}
        </p>

        <div className="mb-6">
          <div className="flex justify-between text-sm text-muted-foreground mb-2">
            <span>
              Step {currentStep} of {totalSteps}
            </span>
            <span>
              {gscConnection && gscStatus === 'synced'
                ? '~2 minutes to first insights'
                : 'Get your first insights in ~10 minutes'}
            </span>
          </div>
          <Progress value={progressPct} className="h-2" />
          {trial?.active && trial.daysRemaining !== null && (
            <div className="mt-2 flex items-center gap-1.5">
              <Clock className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
              <Badge
                variant={trial.daysRemaining <= 3 ? 'destructive' : 'secondary'}
                className="text-xs"
              >
                {trial.daysRemaining} day{trial.daysRemaining !== 1 ? 's' : ''} left in Pro trial
              </Badge>
            </div>
          )}
          {/* ACT-006: Upgrade nudge for trial users approaching expiry (≤5 days) */}
          {features?.billing &&
            trial?.active &&
            trial.daysRemaining !== null &&
            trial.daysRemaining <= 5 && (
              <div className="mt-3">
                <TrialExpiryCard daysRemaining={trial.daysRemaining} source="onboarding_wizard" />
              </div>
            )}
        </div>

        {/* Step visual indicator */}
        <div className="px-2">
          <WizardStepper completedKeys={completedKeys} currentStep={currentStep} />
        </div>

        <div className="space-y-4">
          {/* ACT-003: Demo mode banner — shown when site is running on seeded data with no real GSC */}
          {site.is_demo && !gscConnection && <DemoModeBanner siteId={site.id} />}

          {/* ACT-002: variant_a — demo CTA promoted above GSC card for users without a connection */}
          {demoDataAvailable && !gscConnection && demoPlacementVariant === 'variant_a' && (
            <div className="rounded-lg border border-primary/30 bg-primary/5 p-4 space-y-2">
              <h2 className="font-semibold text-sm">See RankWiz in action instantly</h2>
              <p className="text-xs text-muted-foreground">
                Not ready to connect GSC yet? Explore with sample data and see real SEO insights
                before linking your account.
              </p>
              <button
                type="button"
                className="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                onClick={() => {
                  trackEvent(DEMO_MODE_ENTERED, {
                    site_id: String(site.id),
                    source: 'demo_early_card',
                  });
                  router.post(
                    route('onboarding.demo', { site: site.id }),
                    {},
                    { preserveScroll: false },
                  );
                }}
              >
                <Sparkles className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
                Explore with sample data
              </button>
            </div>
          )}

          {/* GSC card — compact non-blocking status when syncing */}
          <div className="rounded-lg border p-4">
            <h2 className="font-semibold mb-2">Google Search Console</h2>
            {isSyncing ? (
              <GscSyncStatusCompact
                syncProgressPct={gscConnection?.sync_progress_pct ?? 5}
                productOverviewVideoUrl={productOverviewVideoUrl}
                propertyUrl={gscConnection?.property_url}
                isFirstSync={!gscDataRange?.coverage_days}
                domain={site.domain}
                siteId={site.id}
              />
            ) : gscConnection ? (
              <p className="text-sm text-success">Connected: {gscConnection.property_url}</p>
            ) : (
              <div className="space-y-3">
                <div className="flex items-center justify-between gap-3">
                  <p className="text-sm text-muted-foreground">Not connected</p>
                  <a
                    href={route('gsc.connect', { site: site.id })}
                    className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring shrink-0"
                    onClick={() => trackEvent(GSC_CONNECT_CLICKED, { site_id: String(site.id) })}
                  >
                    Connect Google Search Console
                  </a>
                </div>
                {/* ACT-002: control/null path — show demo CTA inside GSC card.
                    variant_a shows it as a prominent card above, so hide it here. */}
                {demoDataAvailable && demoPlacementVariant !== 'variant_a' && (
                  <div className="flex items-center gap-2">
                    <div className="h-px flex-1 bg-border" />
                    <span className="text-xs text-muted-foreground">or</span>
                    <div className="h-px flex-1 bg-border" />
                  </div>
                )}
                {demoDataAvailable && demoPlacementVariant !== 'variant_a' && (
                  <button
                    type="button"
                    className="w-full inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                    onClick={() => {
                      trackEvent(DEMO_MODE_ENTERED, { site_id: String(site.id) });
                      router.post(
                        route('onboarding.demo', { site: site.id }),
                        {},
                        { preserveScroll: false },
                      );
                    }}
                  >
                    <Sparkles className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
                    Explore with sample data
                  </button>
                )}
              </div>
            )}
          </div>

          {/* CRO-003: Escape card — promoted above the locked analysis button so users see it
              without scrolling. Primary styling makes it the primary call to action during sync.
              Users who navigate away will receive a browser notification when sync completes. */}
          {isSyncing && (
            <div className="rounded-lg border border-primary/40 bg-primary/5 p-4 space-y-3">
              <div>
                <h2 className="font-semibold text-sm mb-1">No need to wait here</h2>
                <p className="text-xs text-muted-foreground mb-3">
                  Your data is syncing in the background. Explore RankWiz or come back later — the
                  page will update automatically when sync finishes.
                </p>
                <button
                  type="button"
                  disabled={isEscapingSync}
                  className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-70"
                  onClick={() => {
                    if (isEscapingSync) return;

                    setIsEscapingSync(true);
                    const waitMs = syncStartRef.current ? Date.now() - syncStartRef.current : 0;
                    let visitSucceeded = false;

                    trackEvent(GSC_SYNC_ESCAPE, {
                      site_id: String(site.id),
                      step_key: 'gsc',
                      wait_time_ms: waitMs,
                    });

                    router.visit(route('dashboard'), {
                      onSuccess: () => {
                        visitSucceeded = true;
                        escapedViaSyncCtaRef.current = true;
                      },
                      onError: () => {
                        setIsEscapingSync(false);
                      },
                      onCancel: () => {
                        setIsEscapingSync(false);
                      },
                      onFinish: () => {
                        if (!visitSucceeded) {
                          setIsEscapingSync(false);
                        }
                      },
                    });
                  }}
                >
                  Go to Dashboard
                </button>
              </div>

              {/* ACT-008: Referral nudge during sync wait — high attentional availability */}
              {referralUrl && (
                <div className="border-t border-border/40 pt-3">
                  <p className="text-xs text-muted-foreground mb-2">
                    Know someone who needs SEO insights?{' '}
                    <span className="text-foreground font-medium">
                      Invite them while you wait — you both get 5 extra AI drafts.
                    </span>
                  </p>
                  <button
                    type="button"
                    onClick={async () => {
                      await navigator.clipboard.writeText(referralUrl);
                      setSyncWaitReferralCopied(true);
                      trackEvent(REFERRAL_LINK_COPIED, {
                        source: 'sync_wait_card',
                        site_id: String(site.id),
                      });
                      setTimeout(() => setSyncWaitReferralCopied(false), 2000);
                    }}
                    className="inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:text-primary/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
                    aria-label="Copy referral link"
                  >
                    {syncWaitReferralCopied ? (
                      <Check className="h-3.5 w-3.5" aria-hidden="true" />
                    ) : (
                      <Copy className="h-3.5 w-3.5" aria-hidden="true" />
                    )}
                    {syncWaitReferralCopied ? 'Link copied!' : 'Copy referral link'}
                  </button>
                </div>
              )}
            </div>
          )}

          {/* Run First Analysis card — shown before WP so core flow is GSC → Analysis → WP */}
          <div className="rounded-lg border p-4">
            <h2 className="font-semibold mb-2">Run First Analysis</h2>
            {/* ACT-003: Persona-conditional description based on user role */}
            <p className="text-sm text-muted-foreground mb-3">
              {getAnalysisStepDescription(userRole)}
            </p>
            {/* ACT-003: Show waiting state when analysis was auto-triggered */}
            {isAnalysisPending ? (
              <div className="flex items-center gap-2 text-sm text-primary">
                <Loader2 className="h-4 w-4 animate-spin" />
                <span>
                  Analysis is running — we&apos;ll show your results when it&apos;s done...
                </span>
              </div>
            ) : gscConnection && gscStatus === 'synced' && !analysisCompleted ? (
              <div className="space-y-3">
                {/* ACT-011: Non-blocking warning when GSC history < 56 days */}
                {dataCoverageDays !== null && dataCoverageDays < 56 && (
                  <div
                    role="alert"
                    className="flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-300"
                  >
                    <AlertTriangle className="h-4 w-4 shrink-0 mt-0.5" aria-hidden="true" />
                    <span>
                      Limited data: {dataCoverageDays} day{dataCoverageDays !== 1 ? 's' : ''} of
                      history. First analysis may show fewer results — best with 60+ days.
                    </span>
                  </div>
                )}
                <Link
                  href={route('analyze.index', { site: site.id })}
                  className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring animate-pulse motion-reduce:animate-none"
                  onClick={() =>
                    trackEvent(ANALYZE_NOW_CLICKED, {
                      site_id: String(site.id),
                      source: 'onboarding_wizard',
                      ...(dataCoverageDays !== null
                        ? { data_coverage_days: dataCoverageDays }
                        : {}),
                    })
                  }
                >
                  <PlayCircle className="h-4 w-4" aria-hidden="true" />
                  Analyze Now
                </Link>
              </div>
            ) : gscConnection && isSyncing && syncReadyEarly ? (
              <div className="space-y-3">
                <Link
                  href={route('analyze.index', { site: site.id })}
                  className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring animate-pulse motion-reduce:animate-none"
                  onClick={() =>
                    trackEvent(ANALYZE_NOW_CLICKED, {
                      site_id: String(site.id),
                      source: 'onboarding_wizard_sync_ready_early',
                    })
                  }
                >
                  <PlayCircle className="h-4 w-4" aria-hidden="true" />
                  Analyze Now
                </Link>
              </div>
            ) : gscConnection && isSyncing ? (
              <div className="flex items-center gap-2">
                <button
                  type="button"
                  disabled
                  aria-label="Run Analysis — available when sync completes"
                  className="inline-flex items-center gap-1.5 rounded-md bg-primary/40 px-3 py-1.5 text-sm font-medium text-primary-foreground cursor-not-allowed"
                >
                  <PlayCircle className="h-4 w-4" aria-hidden="true" />
                  Run Analysis
                </button>
                <span className="text-xs text-muted-foreground">Available when sync completes</span>
              </div>
            ) : gscConnection && gscStatus === 'failed' ? (
              <div className="space-y-2">
                <p className="text-sm text-destructive">
                  GSC sync failed. Please retry the connection.
                </p>
                <a
                  href={route('gsc.connect', { site: site.id })}
                  className="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                >
                  Retry Connection
                </a>
              </div>
            ) : (
              <p className="text-sm text-muted-foreground italic">
                Connect Google Search Console first to run an analysis.
              </p>
            )}
          </div>
        </div>

        {/* ACT-003: Healthy site card — shown when analysis completes with 0 recommendations */}
        {isHealthySite && (
          <div className="rounded-lg border border-success/30 bg-success/5 p-4">
            <div className="flex items-start gap-3">
              <Check className="h-5 w-5 text-success shrink-0 mt-0.5" aria-hidden="true" />
              <div className="flex-1 min-w-0">
                <p className="text-sm font-semibold text-foreground">
                  Your site looks healthy — no urgent issues found in your GSC data.
                </p>
                <p className="text-xs text-muted-foreground mt-1">
                  Here are some growth paths to explore:
                </p>
                <div className="flex flex-wrap gap-2 mt-3">
                  <Link
                    href={route('opportunity-map.index', { site: site.id })}
                    className="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                  >
                    Keyword Opportunities
                  </Link>
                  <Link
                    href={route('topic-clusters.index', { site: site.id })}
                    className="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                  >
                    Topic Clusters
                  </Link>
                  <Link
                    href={route('freshness.index', { site: site.id })}
                    className="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                  >
                    Content Freshness
                  </Link>
                </div>
              </div>
            </div>
          </div>
        )}

        {/* ACT-007: Prominent "See Your Recommendations" CTA — primary action after analysis completes */}
        {analysisCompleted && hasRecommendations && (
          <div className="rounded-lg border border-primary/40 bg-primary/5 p-4">
            <div className="flex items-start gap-3">
              <Sparkles className="h-5 w-5 text-primary shrink-0 mt-0.5" aria-hidden="true" />
              <div className="flex-1 min-w-0">
                <p className="text-sm font-semibold text-foreground">
                  Your recommendations are ready
                </p>
                <p className="text-xs text-muted-foreground mt-1">
                  {recommendationsCount ?? 0} recommendation{(recommendationsCount ?? 0) !== 1 ? 's' : ''} found — review them to start improving your traffic.
                </p>
                <div className="mt-3">
                  <Link
                    href={route('recommendations.index', { site: site.id }) + '?from=wizard'}
                    className="inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                    onClick={() =>
                      trackEvent(WIZARD_SEE_RECOMMENDATIONS_CLICKED, {
                        site_id: String(site.id),
                        recommendations_count: String(recommendationsCount ?? 0),
                      })
                    }
                  >
                    See Your Recommendations
                    <ArrowRight className="h-4 w-4" aria-hidden="true" />
                  </Link>
                </div>
              </div>
            </div>
          </div>
        )}

        {/* ACT-001: AI draft nudge card — shown inline after analysis completes */}
        {analysisCompleted && (
          <AiDraftNudgeCard
            siteId={site.id}
            bundledRemaining={aiStatus?.bundled_remaining ?? null}
          />
        )}

        {/* CRO-008: Upgrade card — shown at highest-intent moment (analysis complete, free plan) */}
        {analysisCompleted && features?.billing && !trial?.active && !plan && (
          <OnboardingUpgradeCard
            siteId={site.id}
            recommendationsCount={recommendationsCount ?? 0}
          />
        )}

        {/* ACT-007: Team invite card — shown after analysis completes to seed B2B viral loop */}
        {analysisCompleted && <TeamInviteCard siteId={site.id} />}

        {/* CRO-014: Activation-complete upgrade prompt — highest-conversion moment for free users */}
        {stepsCompleted === totalSteps && features?.billing && !trial?.active && !plan && (
          <ActivationCompleteUpgradeCard siteId={site.id} />
        )}

        {/* ACT-002: Referral completion card — shown when all steps are done */}
        {stepsCompleted === totalSteps && referralUrl && (
          <ReferralCompletionCard
            referralUrl={referralUrl}
            referralCopied={referralCopied}
            onCopy={async () => {
              await navigator.clipboard.writeText(referralUrl);
              setReferralCopied(true);
              trackEvent(REFERRAL_LINK_COPIED, { site_id: String(site.id) });
              setTimeout(() => setReferralCopied(false), 2000);
            }}
          />
        )}

        {/* ACT-004: WP Plugin bonus step — post-activation only, never blocks core wizard progress */}
        {stepsCompleted === totalSteps && (
          <div
            className="rounded-lg border border-dashed border-border/60 p-4"
            id="wp-connect-card"
          >
            <h2 className="font-semibold mb-2">
              WordPress Plugin
              <Badge variant="outline" className="ml-2 text-xs font-normal">
                Bonus step
              </Badge>
            </h2>
            {wpConnection && !wpConnection.setup_token ? (
              <p className="text-sm text-success">Connected: {wpConnection.wp_url}</p>
            ) : wpConnection?.setup_token ? (
              /* Pending: connection created, polling will auto-detect activation */
              <div className="space-y-3">
                <div className="flex items-center gap-2 text-sm text-muted-foreground">
                  <Loader2 className="h-4 w-4 animate-spin shrink-0" aria-hidden="true" />
                  <span>Waiting for plugin activation…</span>
                </div>
                <p className="text-sm text-muted-foreground">
                  Paste this token in your WP admin → Settings → RankWiz AI:
                </p>
                <div className="flex items-center gap-2 rounded-md bg-muted p-3 font-mono text-sm">
                  <span className="flex-1 break-all">{wpConnection.setup_token}</span>
                  <button
                    type="button"
                    onClick={() => copySetupToken(wpConnection.setup_token!)}
                    aria-label="Copy setup token"
                    className="shrink-0 rounded p-1 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                  >
                    {wpTokenCopied ? (
                      <Check className="h-4 w-4 text-green-500 dark:text-green-400" />
                    ) : (
                      <Copy className="h-4 w-4" />
                    )}
                  </button>
                </div>
                {/* CRO-007: Skip consequence — content quality rules require WP content */}
                <p className="text-xs text-muted-foreground">
                  Connect WordPress to unlock full content analysis and publish AI drafts directly
                  from RankWiz.
                </p>
              </div>
            ) : (
              /* Not connected: inline URL form */
              <div className="space-y-3">
                <p className="text-sm text-muted-foreground">
                  Connect to publish AI drafts directly to your WordPress site and unlock full
                  content analysis.
                </p>
                <form onSubmit={handleWpConnect} className="space-y-2">
                  <div>
                    <label htmlFor="wp-url-wizard" className="sr-only">
                      WordPress site URL
                    </label>
                    <input
                      id="wp-url-wizard"
                      type="url"
                      value={wpData.wp_url}
                      onChange={(e) => {
                        setWpData('wp_url', e.target.value);
                        setDetectResult(null);
                      }}
                      placeholder="https://yoursite.com"
                      aria-invalid={!!wpErrors.wp_url}
                      aria-describedby={wpErrors.wp_url ? 'wp-url-wizard-error' : undefined}
                      className="w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                    />
                    {wpErrors.wp_url && (
                      <p id="wp-url-wizard-error" className="text-sm text-destructive mt-1">
                        {wpErrors.wp_url}
                      </p>
                    )}
                  </div>
                  {/* Deep link + auto-detect — shown once user types a URL */}
                  {wpData.wp_url && (
                    <div className="flex items-center gap-3 flex-wrap">
                      <a
                        href={`${wpData.wp_url.replace(/\/$/, '')}/wp-admin/plugin-install.php?tab=upload`}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground underline underline-offset-2"
                      >
                        <ExternalLink className="h-3 w-3" aria-hidden="true" />
                        Open WP plugin installer
                      </a>
                      <button
                        type="button"
                        onClick={handleDetectPlugin}
                        disabled={detecting}
                        className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm disabled:opacity-50"
                      >
                        {detecting ? (
                          <>
                            <Loader2 className="h-3 w-3 animate-spin" aria-hidden="true" />
                            Checking…
                          </>
                        ) : (
                          'Check if plugin is installed'
                        )}
                      </button>
                    </div>
                  )}
                  {detectResult === 'found' && (
                    <p className="text-xs text-success flex items-center gap-1">
                      <Check className="h-3 w-3" aria-hidden="true" />
                      Plugin detected — ready to connect!
                    </p>
                  )}
                  {detectResult === 'not-found' && (
                    <p className="text-xs text-muted-foreground">
                      Plugin not detected. Install it first using the link above, then connect.
                    </p>
                  )}
                  <div className="flex items-center gap-2 flex-wrap">
                    <button
                      type="submit"
                      disabled={wpProcessing || !wpData.wp_url}
                      className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
                    >
                      {wpProcessing && (
                        <Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
                      )}
                      Connect WordPress
                    </button>
                  </div>
                  {/* CRO-007: Skip consequence — content quality rules require WP content */}
                  <p className="text-xs text-muted-foreground">
                    Connect WordPress to unlock full content analysis and publish AI drafts directly
                    from RankWiz.
                  </p>
                </form>
                <PluginInstallHelper siteId={site.id} domain={wpData.wp_url} />
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

const BYOK_DEFERRED_KEY = 'byok_nudge_deferred';

interface AiDraftNudgeCardProps {
  siteId: number;
  bundledRemaining: number | null;
}

function AiDraftNudgeCard({ siteId, bundledRemaining }: AiDraftNudgeCardProps) {
  const { trial, plan, features } = usePage<PageProps>().props;
  const [deferred, setDeferred] = useState(() => {
    try {
      return localStorage.getItem(BYOK_DEFERRED_KEY) === '1';
    } catch {
      return false;
    }
  });

  useEffect(() => {
    trackEvent(WIZARD_AI_STEP_SHOWN, { site_id: String(siteId) });
    // intentional: fire once on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const hasCredits = bundledRemaining !== null && bundledRemaining > 0;
  const isTrialUser = trial?.active === true;
  const isFreePlan = !plan && !isTrialUser;

  if (deferred) return null;

  const handleDefer = () => {
    trackEvent(BYOK_NUDGE_DEFERRED, {
      site_id: String(siteId),
      plan: isTrialUser ? 'trial' : 'free',
    });
    try {
      localStorage.setItem(BYOK_DEFERRED_KEY, '1');
    } catch {
      // localStorage unavailable — ignore
    }
    setDeferred(true);
  };

  return (
    <div className="rounded-lg border border-primary/20 bg-primary/5 p-4">
      <div className="flex items-start gap-3">
        <TrendingUp className="h-5 w-5 text-primary shrink-0 mt-0.5" aria-hidden="true" />
        <div className="flex-1 min-w-0">
          <p className="text-sm font-semibold text-foreground">
            Generate your first AI draft to complete activation
          </p>
          {hasCredits ? (
            <>
              <p className="text-xs text-muted-foreground mt-1">
                You have{' '}
                <span className="font-medium text-foreground">
                  {bundledRemaining} bundled AI draft{bundledRemaining !== 1 ? 's' : ''}
                </span>{' '}
                available this month — no API key needed. WordPress is optional.
              </p>
              <Link
                href={route('recommendations.index', { site: siteId })}
                className="inline-flex items-center gap-1.5 mt-3 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                Generate Your First AI Draft
              </Link>
            </>
          ) : (
            <>
              {/* ACT-006: dual-path UI when bundled credits are exhausted */}
              {isTrialUser ? (
                // Trial users: show credit context + billing CTA
                <>
                  <p className="text-xs text-muted-foreground mt-1 mb-3">
                    You've used your bundled drafts for this month. Your Pro trial includes 30
                    drafts/month — upgrade to continue generating.
                  </p>
                  {features?.billing && (
                    <Link
                      href={route('billing.plans', {
                        plan: 'pro',
                        source: 'onboarding_byok_nudge_trial',
                      })}
                      className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                      onClick={() =>
                        trackEvent(UPGRADE_PROMPT_CLICKED, {
                          surface: 'onboarding_wizard',
                          source: 'byok_nudge_trial',
                          site_id: String(siteId),
                        })
                      }
                    >
                      Upgrade to Pro
                      <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
                    </Link>
                  )}
                  <p className="text-xs text-muted-foreground mt-2">
                    Or{' '}
                    <Link
                      href={route('settings.ai')}
                      className="underline underline-offset-2 hover:text-foreground"
                    >
                      connect your own OpenAI key
                    </Link>{' '}
                    to generate at cost price.
                  </p>
                </>
              ) : isFreePlan && features?.billing ? (
                // Free users: primary upgrade CTA, secondary BYOK option
                <>
                  <p className="text-xs text-muted-foreground mt-1 mb-3">
                    You've used all bundled drafts this month. Upgrade to Pro for 30 drafts/month,
                    or connect your own OpenAI key.
                  </p>
                  {/* ACT-002: taste-before-commitment teaser */}
                  <DraftOutputTeaser siteId={siteId} />
                  <Link
                    href={route('billing.plans', {
                      plan: 'pro',
                      source: 'onboarding_byok_nudge_free',
                    })}
                    className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                    onClick={() =>
                      trackEvent(UPGRADE_PROMPT_CLICKED, {
                        surface: 'onboarding_wizard',
                        source: 'byok_nudge_free',
                        site_id: String(siteId),
                      })
                    }
                  >
                    Upgrade to Pro — 14-Day Free Trial
                    <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
                  </Link>
                  <p className="text-xs text-muted-foreground mt-2">
                    Or{' '}
                    <Link
                      href={route('settings.ai')}
                      className="underline underline-offset-2 hover:text-foreground"
                    >
                      connect your own OpenAI key
                    </Link>{' '}
                    to generate unlimited drafts at cost price.
                  </p>
                </>
              ) : (
                // Fallback: BYOK-only path (billing disabled or unknown plan)
                <>
                  <p className="text-xs text-muted-foreground mt-1 mb-3">
                    {bundledRemaining === 0
                      ? "You've used all bundled drafts this month. Set up your OpenAI key to keep generating."
                      : 'Connect your own OpenAI key to generate unlimited AI drafts at cost price.'}
                  </p>
                  {/* ACT-002: taste-before-commitment teaser */}
                  <DraftOutputTeaser siteId={siteId} />
                  <Link
                    href={route('settings.ai')}
                    className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                  >
                    Unlock Full AI Drafts →
                  </Link>
                </>
              )}
              <div className="mt-3 flex items-center">
                <button
                  type="button"
                  onClick={handleDefer}
                  className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
                >
                  <X className="h-3 w-3" aria-hidden="true" />
                  Remind me later
                </button>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

function OnboardingUpgradeCard({
  siteId,
  recommendationsCount,
}: {
  siteId: number;
  recommendationsCount: number;
}) {
  return (
    <div className="rounded-lg border border-primary/30 bg-primary/5 p-4">
      <div className="flex items-start gap-3">
        <Sparkles className="h-5 w-5 text-primary shrink-0 mt-0.5" aria-hidden="true" />
        <div className="flex-1 min-w-0">
          <p className="text-sm font-semibold text-foreground">
            {recommendationsCount > 0
              ? `You found ${recommendationsCount} SEO opportunit${recommendationsCount === 1 ? 'y' : 'ies'}.`
              : 'Your first analysis is complete.'}
          </p>
          <p className="text-xs text-muted-foreground mt-1">
            Pro unlocks batch AI drafts, WordPress publishing, and ROI tracking — try free for 14
            days.
          </p>
          <Link
            href={route('billing.plans', { plan: 'pro', source: 'onboarding_analysis_complete' })}
            className="inline-flex items-center gap-1.5 mt-3 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
            onClick={() =>
              trackEvent(UPGRADE_PROMPT_CLICKED, {
                surface: 'onboarding_wizard',
                source: 'onboarding_analysis_complete',
                site_id: String(siteId),
              })
            }
          >
            Start Free 14-Day Trial
            <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
          </Link>
        </div>
      </div>
    </div>
  );
}

function ActivationCompleteUpgradeCard({ siteId }: { siteId: number }) {
  const [dismissed, setDismissed] = useState(false);

  if (dismissed) return null;

  return (
    <div className="rounded-lg border border-primary/30 bg-primary/5 p-4">
      <div className="flex items-start gap-3">
        <Sparkles className="h-5 w-5 text-primary shrink-0 mt-0.5" aria-hidden="true" />
        <div className="flex-1 min-w-0">
          <p className="text-sm font-semibold text-foreground">
            You've set up everything. Ready to scale?
          </p>
          <p className="text-xs text-muted-foreground mt-1">
            Pro gives you 30 AI drafts/month, analysis for up to 5 sites, and traffic anomaly alerts
            — try free for 14 days.
          </p>
          <div className="mt-3 flex items-center gap-3 flex-wrap">
            <Link
              href={route('billing.plans', {
                plan: 'pro',
                source: 'onboarding_all_steps_complete',
              })}
              className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              onClick={() =>
                trackEvent(UPGRADE_PROMPT_CLICKED, {
                  surface: 'onboarding_wizard',
                  source: 'onboarding_all_steps_complete',
                  site_id: String(siteId),
                })
              }
            >
              Start Free 14-Day Trial
              <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
            </Link>
            <button
              type="button"
              onClick={() => setDismissed(true)}
              className="text-xs text-muted-foreground hover:text-foreground underline underline-offset-2 focus-visible:outline-none"
            >
              I'll stick with free for now
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function TeamInviteCard({ siteId }: { siteId: number }) {
  const [sent, setSent] = useState(false);
  const { data, setData, post, processing, errors, reset } = useForm({ email: '', role: 'viewer' });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    post(route('site-members.store', { site: siteId }), {
      preserveScroll: true,
      onSuccess: () => {
        setSent(true);
        reset();
        trackEvent(TEAM_INVITE_SENT, { site_id: String(siteId) });
      },
    });
  };

  if (sent) {
    return (
      <div className="rounded-lg border border-success/30 bg-success/5 p-4 flex items-center gap-3">
        <Check className="h-4 w-4 text-success shrink-0" aria-hidden="true" />
        <p className="text-sm text-foreground">
          Invite sent! They'll receive an email with a link to join.
        </p>
      </div>
    );
  }

  return (
    <div className="rounded-lg border p-4">
      <div className="flex items-start gap-3 mb-3">
        <UserPlus className="h-5 w-5 text-primary shrink-0 mt-0.5" aria-hidden="true" />
        <div>
          <p className="text-sm font-semibold text-foreground">Invite a Teammate</p>
          <p className="text-xs text-muted-foreground mt-0.5">
            Collaborate on SEO recommendations with a colleague or client.
          </p>
        </div>
      </div>
      <form onSubmit={handleSubmit} className="space-y-2">
        <div>
          <label htmlFor="invite-email-wizard" className="sr-only">
            Teammate email address
          </label>
          <input
            id="invite-email-wizard"
            type="email"
            value={data.email}
            onChange={(e) => setData('email', e.target.value)}
            placeholder="colleague@company.com"
            aria-invalid={!!errors.email}
            aria-describedby={errors.email ? 'invite-email-error' : undefined}
            className="w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          />
          {errors.email && (
            <p id="invite-email-error" className="text-sm text-destructive mt-1">
              {errors.email}
            </p>
          )}
          <p className="text-xs text-muted-foreground mt-1">
            They'll get an email to join — no account needed yet.
          </p>
        </div>
        <div className="flex items-center gap-2">
          <button
            type="submit"
            disabled={processing || !data.email}
            className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {processing && <Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />}
            Send Invite
          </button>
          <span className="text-xs text-muted-foreground">
            Optional — do this later from Site Settings
          </span>
        </div>
      </form>
    </div>
  );
}

interface ReferralCompletionCardProps {
  referralUrl: string;
  referralCopied: boolean;
  onCopy: () => Promise<void>;
}

function ReferralCompletionCard({
  referralUrl,
  referralCopied,
  onCopy,
}: ReferralCompletionCardProps) {
  return (
    <div className="rounded-lg border p-4">
      <div className="flex items-start gap-2 mb-1">
        <Sparkles className="h-4 w-4 text-primary mt-0.5 shrink-0" aria-hidden="true" />
        <p className="text-sm font-semibold text-foreground">
          Invite a colleague — you both get 5 extra AI drafts
        </p>
      </div>
      <p className="text-xs text-muted-foreground mb-3">
        When a friend signs up with your link, you each receive 5 bonus AI draft credits
        automatically within 24 hours.
      </p>
      <div className="flex items-center gap-2 rounded-md bg-muted px-3 py-2 text-xs font-mono overflow-hidden">
        <span className="flex-1 truncate text-muted-foreground">{referralUrl}</span>
        <button
          type="button"
          onClick={onCopy}
          aria-label="Copy referral link"
          className="shrink-0 rounded p-1 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
        >
          {referralCopied ? (
            <Check className="h-3.5 w-3.5 text-green-500 dark:text-green-400" />
          ) : (
            <Copy className="h-3.5 w-3.5 text-muted-foreground" />
          )}
        </button>
      </div>
    </div>
  );
}
