import { AlertTriangle, ChevronDown, ExternalLink, Eye, EyeOff, Info, Lock, Sparkles, XCircle } from 'lucide-react';
import { toast } from 'sonner';

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

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

import InputError from '@/Components/InputError';
import EditorialPageHeader from '@/Components/layout/EditorialPageHeader';
import { Alert, AlertDescription, AlertTitle } from '@/Components/ui/alert';
import { Badge } from '@/Components/ui/badge';
import { Button } from '@/Components/ui/button';
import { ConfirmDialog } from '@/Components/ui/confirm-dialog';
import { Input } from '@/Components/ui/input';
import { LoadingButton } from '@/Components/ui/loading-button';
import { useFlashToasts } from '@/hooks/useFlashToasts';
import { useUnsavedChanges } from '@/hooks/useUnsavedChanges';
import DashboardLayout from '@/Layouts/DashboardLayout';
import { trackProductEvent } from '@/lib/analytics';
import { BYOK_SETTINGS_EXPLAINER, BYOK_SETTINGS_SECTION_HEADING } from '@/lib/copy/byok';
import {
  BYOK_KEY_SAVED,
  FEATURE_USED,
  SETTINGS_AI_KEY_VIEWED,
  UPGRADE_PROMPT_CLICKED,
} from '@/lib/event-catalog';
import { formatCurrency, formatDateTime, formatProviderName } from '@/lib/format';
import { DocsHelpLink } from '@/Pages/Marketing/Docs/components/DocsHelpLink';
import type { PageProps } from '@/types';

import { getValidationInfo, validationInfoMap } from './validationInfo';

interface KeyValidation {
  status: string;
  message: string | null;
  at: string | null;
  consecutiveFailures: number;
}

// R8PROD-004: per-provider stored key shape (from stored_keys prop)
interface StoredKey {
  provider: string;
  masked_key: string;
  is_validated: boolean;
  validated_at: string | null;
  validation: KeyValidation;
}

interface Props {
  // R8PROD-004: multi-key list — one entry per stored provider.
  stored_keys: StoredKey[];
  // Backward-compat scalar props (derived from first stored key or null)
  has_key: boolean;
  masked_key: string | null;
  provider: string | null;
  is_validated: boolean;
  validated_at: string | null;
  key_validation: KeyValidation | null;
  // Whether this user already has Pro-level access (subscribed, on trial, or in
  // grace period — see AiSettingsController::canAccessByok). Trial/subscribed
  // users must NOT see the "Upgrade to Pro" nudge: they can already use BYOK.
  can_use_byok: boolean;
  // INTAI-301: active AI mode ('bundled' | 'byok') — surfaced so the user can
  // see at a glance which budget their next draft spends.
  ai_mode?: string | null;
  // Bundled quota data (present when bundled_ai feature is enabled)
  bundled_quota?: {
    used: number;
    limit: number;
    reset_date: string;
    is_first_month: boolean;
  } | null;
  // AI-BYOK-R14-02: model-accurate cost display. Both null for bundled users or
  // BYOK users without a validated key — UI falls back to the flat-band copy.
  active_model?: string | null;
  per_draft_estimate?: number | null;
}

// INTAI-301: Perplexity removed — pplx- keys were accepted but AiProviderResolver
// only handles openai/anthropic, so any Perplexity key was a dead end.
const PROVIDER_CONFIG: Record<string, { label: string; placeholder: string; helpUrl: string; helpLabel: string }> = {
  openai: {
    label: 'OpenAI',
    placeholder: 'sk-…',
    helpUrl: 'https://platform.openai.com/api-keys',
    helpLabel: 'platform.openai.com/api-keys',
  },
  anthropic: {
    label: 'Anthropic',
    placeholder: 'sk-ant-…',
    helpUrl: 'https://console.anthropic.com/settings/keys',
    helpLabel: 'console.anthropic.com/settings/keys',
  },
};


const validationBadgeVariant: Record<string, 'success' | 'destructive' | 'default'> = {
  valid: 'success',
  invalid_auth: 'destructive',
  quota_exceeded: 'destructive',
  rate_limited: 'default',
  network_error: 'default',
  provider_error: 'default',
};

const statusLabelMap: Record<string, string> = {
  valid: 'Active',
  invalid_auth: 'Invalid Key',
  quota_exceeded: 'Quota Exceeded',
  rate_limited: 'Rate Limited',
  network_error: 'Connection Error',
  provider_error: 'Provider Error',
};

/**
 * MED-006: Map each validation status to a prominent banner severity.
 *
 *   destructive — terminal failure that requires user action (key rejected,
 *                 quota exceeded). Same severity as the dunning banner.
 *   warning     — transient failure that may recover on its own (network,
 *                 rate limit, provider error).
 *   null        — no banner (valid key, or no validation attempted yet).
 *
 * The Badge + inline message inside the card stays as a secondary surface;
 * the banner is the prominent error surface so users notice immediately on
 * page load that AI generation is broken.
 */
type BannerSeverity = 'destructive' | 'warning';

const validationBannerSeverity: Record<string, BannerSeverity> = {
  invalid_auth: 'destructive',
  quota_exceeded: 'destructive',
  rate_limited: 'warning',
  network_error: 'warning',
  provider_error: 'warning',
};

// R8PROD-004: provider-aware banner titles (fallback to generic if unknown provider)
const getValidationBannerTitle = (status: string, providerName: string): string => {
  const map: Record<string, string> = {
    invalid_auth: `Your ${providerName} key was rejected`,
    quota_exceeded: `${providerName} account has run out of credits`,
    rate_limited: `${providerName} is throttling your account`,
    network_error: `Could not reach ${providerName}`,
    provider_error: `${providerName} returned an unexpected error`,
  };
  return map[status] ?? `${providerName} key error`;
};

export default function AiKeySettings({
  stored_keys,
  has_key,
  // masked_key and validated_at retained for backward-compat Inertia prop contract;
  // display now comes from stored_keys cards. Prefixed to satisfy no-unused-vars.
  masked_key: _masked_key,
  provider,
  is_validated,
  validated_at: _validated_at,
  key_validation,
  can_use_byok,
  ai_mode,
  bundled_quota,
  active_model,
  per_draft_estimate,
}: Props) {
  const { features, ai_status, active_jobs } = usePage<PageProps>().props;
  const isOnBundledTier = !ai_status || ai_status.mode === 'bundled';
  // Gate the upgrade nudge on the SAME can-use-byok / trial state the controller
  // computes. A Pro-trial user is on the bundled tier until they add a key, so
  // without this guard they'd see "Upgrade to Pro" despite already having Pro
  // access. Only show the nudge to users who genuinely cannot use BYOK yet.
  const showUpgradeCard =
    !has_key && isOnBundledTier && !can_use_byok && (features.billing ?? false);

  // INTAI-301 (AI-P3-5): derive the effective ai_mode label for the budget banner
  const effectiveMode = ai_mode ?? ai_status?.mode ?? 'bundled';
  const isByok = effectiveMode === 'byok';

  // INTUX-R15-01: single-source the per-draft cost label so the BYOK banner and the
  // Cost-Transparency education block never disagree. Null when model or estimate is
  // absent (bundled user, unvalidated key, resolver threw) → both locations fall back
  // to the generic "$0.01–$0.05" band copy.
  const perDraftLabel =
    active_model != null && per_draft_estimate != null
      ? per_draft_estimate < 0.01
        ? `$${per_draft_estimate.toFixed(4)}`
        : formatCurrency(per_draft_estimate)
      : null;

  useEffect(() => {
    trackProductEvent(SETTINGS_AI_KEY_VIEWED);
    trackProductEvent(FEATURE_USED, { feature: 'settings_ai_key' });
  }, []);

  // QA-002: per-provider delete dialog state prevents all cards opening simultaneously
  const [showDeleteFor, setShowDeleteFor] = useState<string | null>(null);
  const [educationExpanded, setEducationExpanded] = useState(!has_key);
  // F4UXA-002: reveal toggle for BYOK key input — default hidden
  const [showKey, setShowKey] = useState(false);
  // F5UXA-014: collapsed replace-key form — hidden until user clicks "Replace Key"
  const [replaceKeyExpanded, setReplaceKeyExpanded] = useState(!has_key);
  // INTAI-301: per-provider validate state
  const [validatingProvider, setValidatingProvider] = useState<string | null>(null);
  const [validateProcessing, setValidateProcessing] = useState(false);
  // Double-submit guard: ref is set synchronously (before React state flush) so a
  // second rapid click cannot sneak in between setValidateProcessing(true) and the
  // next render. Mirrors the useMutationButton ref pattern (R6FE-008).
  const validateInFlightRef = useRef(false);

  const { data, setData, post, processing, errors, isDirty, reset } = useForm({
    provider: 'openai',
    api_key: '',
  });

  useUnsavedChanges(isDirty);
  // R3UXA-009: flash toasts from the validate controller (success/error channel).
  useFlashToasts();

  const submitKey = (e: React.FormEvent) => {
    e.preventDefault();
    post(route('settings.ai.store-key'), {
      onSuccess: () => {
        // R3FE-012: clear the secret input after a successful save.
        reset('api_key');
        trackProductEvent(BYOK_KEY_SAVED, { provider: data.provider, source: 'settings_page' });
      },
    });
  };

  // INTAI-301: accept provider param so every stored key card gets a working Test Key button.
  // router.post is fire-and-forget (gotcha #11); we track loading state manually.
  // Double-submit guard: ref prevents a second click from firing before the React
  // state flush that would disable the button (mirrors R6FE-008 / useMutationButton).
  const handleValidate = (targetProvider: string) => {
    if (validateInFlightRef.current) return;
    validateInFlightRef.current = true;
    setValidatingProvider(targetProvider);
    setValidateProcessing(true);
    router.post(
      route('settings.ai.validate-key'),
      { provider: targetProvider },
      {
        onFinish: () => {
          validateInFlightRef.current = false;
          setValidatingProvider(null);
          setValidateProcessing(false);
        },
      },
    );
  };

  const validationState = key_validation?.status ?? (is_validated ? 'valid' : null);
  const bannerSeverity =
    validationState && validationState in validationBannerSeverity
      ? validationBannerSeverity[validationState]
      : null;
  const bannerTitle = validationState
    ? getValidationBannerTitle(validationState, formatProviderName(provider ?? 'openai'))
    : null;
  const bannerMessage =
    key_validation?.message ?? (validationState ? validationInfoMap[validationState]?.message : null);
  const showValidationBanner = has_key && bannerSeverity !== null && bannerTitle && bannerMessage;

  return (
    <>
      <Head title="AI Settings" />

      <div className="container py-8">
        <EditorialPageHeader
          title="AI Settings"
          subtitle="Bring your own key from OpenAI or Anthropic — or use ours. Drafts stay grounded in your GSC data."
          actions={
            <div className="flex items-center gap-2">
              <DocsHelpLink
                slug="bringing-your-own-openai-key"
                topic="bringing your own OpenAI key"
              />
              <a
                href="/ai-transparency"
                target="_blank"
                rel="noopener noreferrer"
                aria-label="How we use AI (opens in a new tab)"
                className="text-xs text-muted-foreground hover:text-foreground transition-colors underline underline-offset-2"
              >
                How we use AI
              </a>
            </div>
          }
        />

        {/* BILL-001: Upgrade nudge for free-tier users without a BYOK key */}
        {showUpgradeCard && (
          <div className="max-w-2xl mt-6 rounded-lg border border-primary/30 bg-primary/5 p-4 flex items-start gap-3">
            <Sparkles className="size-5 text-primary mt-0.5 shrink-0" />
            <div className="flex-1 min-w-0">
              <p className="text-sm font-medium">
                Get {ai_status?.bundled_limit ?? 100} AI drafts/month without your own API key
              </p>
              <p className="text-xs text-muted-foreground mt-0.5">
                Upgrade to Pro to use RankWizAI&apos;s built-in AI — no OpenAI account required.
              </p>
            </div>
            <Button
              asChild
              size="sm"
              className="shrink-0"
              onClick={() =>
                trackProductEvent(UPGRADE_PROMPT_CLICKED, { source: 'ai_key_settings' })
              }
            >
              <Link href={route('billing.index')}>Upgrade to Pro</Link>
            </Button>
          </div>
        )}

        {/* INTAI-301 (AI-P3-5): Active mode + budget block — shows which budget the
            next draft spends so users understand the cost/savings tradeoff at a glance. */}
        <div className="max-w-2xl mt-6 rounded-lg border bg-card p-4">
          <div className="flex items-center justify-between gap-3">
            <div className="flex items-center gap-2">
              <Info className="size-4 text-muted-foreground shrink-0" />
              <span className="text-sm font-medium">
                {isByok ? 'Next draft charges your API key' : 'Next draft uses RankWizAI credits'}
              </span>
            </div>
            <Badge variant={isByok ? 'default' : 'secondary'} className="shrink-0">
              {isByok ? 'BYOK' : 'Bundled'}
            </Badge>
          </div>
          {!isByok && bundled_quota && (
            <p className="text-xs text-muted-foreground mt-2">
              {bundled_quota.used} of {bundled_quota.limit} bundled drafts used this month
              {bundled_quota.is_first_month && ' (pro-rated first month)'}.
              Resets {bundled_quota.reset_date}.
              Add your own OpenAI or Anthropic key to remove the monthly cap.
            </p>
          )}
          {isByok && (
            <p className="text-xs text-muted-foreground mt-2">
              {/* AI-BYOK-R14-02 / INTUX-R15-01: show model-accurate per-draft cost when the
                  resolver returned a model + estimate; fall back to the flat-band copy otherwise
                  (bundled user, no validated key, or resolver threw).
                  perDraftLabel is the single-source const (computed once above) — consumed
                  here AND in the Cost-Transparency education block so both locations never
                  disagree on rounding. */}
              {perDraftLabel != null ? (
                <>
                  Next draft uses{' '}
                  <span className="font-medium text-foreground">{active_model}</span>{' '}
                  — about{' '}
                  <span className="font-medium text-foreground">{perDraftLabel}</span>{' '}
                  each, charged directly to your API provider. No RankWizAI monthly cap
                  applies.
                </>
              ) : (
                <>
                  Typical cost is $0.01–$0.05 per draft, charged directly to your API
                  provider. No RankWizAI monthly cap applies.
                </>
              )}
            </p>
          )}
        </div>

        {/* Educational Section - Collapsible for existing keys */}
        <div className="max-w-2xl mt-4 rounded-lg border bg-card p-6">
          <button
            aria-expanded={educationExpanded}
            onClick={() => setEducationExpanded(!educationExpanded)}
            className="w-full flex items-center justify-between hover:opacity-80 transition-opacity focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
          >
            <h3 className="text-lg font-medium">{BYOK_SETTINGS_SECTION_HEADING}</h3>
            <ChevronDown
              className={`size-5 text-muted-foreground transition-transform duration-200 ${
                educationExpanded ? 'rotate-180' : ''
              }`}
            />
          </button>

          {educationExpanded && (
            <div className="mt-4 space-y-4">
              <div>
                <h4 className="font-medium text-foreground mb-2">Your AI Drafts Are Already Active</h4>
                <p className="text-sm text-muted-foreground">
                  {BYOK_SETTINGS_EXPLAINER}
                </p>
              </div>

              <div>
                <h4 className="font-medium text-foreground mb-2">
                  How to Get Your Key (5 minutes)
                </h4>
                <ol className="text-sm text-muted-foreground space-y-2 list-decimal list-inside">
                  <li>
                    Go to{' '}
                    <a
                      href="https://platform.openai.com/api-keys"
                      target="_blank"
                      rel="noopener noreferrer"
                      className="text-primary hover:underline inline-flex items-center gap-1"
                    >
                      platform.openai.com/api-keys
                      <ExternalLink className="size-3" />
                    </a>
                  </li>
                  <li>Sign up or log into your OpenAI account</li>
                  <li>Go to Billing (left sidebar) and add a payment method</li>
                  <li>Create a new API key in the API Keys section</li>
                  <li>Copy the key and paste it below</li>
                </ol>
              </div>

              <div>
                <h4 className="font-medium text-foreground mb-2">Cost Transparency</h4>
                {/* INTUX-R15-01: gate on perDraftLabel (null when no model/estimate) so this
                    block and the BYOK banner above always agree on the cost figure. */}
                {perDraftLabel != null ? (
                  <p className="text-sm text-muted-foreground">
                    Your next draft uses <strong>{active_model}</strong> — about{' '}
                    <strong>{perDraftLabel}</strong> each. You pay your API provider
                    directly for what you use. Check your{' '}
                    <a
                      href="https://platform.openai.com/account/billing/overview"
                      target="_blank"
                      rel="noopener noreferrer"
                      className="text-primary hover:underline"
                    >
                      OpenAI billing dashboard
                    </a>{' '}
                    anytime to see usage.
                  </p>
                ) : (
                  <p className="text-sm text-muted-foreground">
                    Typical cost is <strong>$0.01 - $0.05 per AI draft</strong>. You only pay OpenAI
                    for what you use. Check your{' '}
                    <a
                      href="https://platform.openai.com/account/billing/overview"
                      target="_blank"
                      rel="noopener noreferrer"
                      className="text-primary hover:underline"
                    >
                      OpenAI billing dashboard
                    </a>{' '}
                    anytime to see usage.
                  </p>
                )}
              </div>

              <Alert className="bg-background border-primary/20">
                <Lock className="size-4 text-primary" />
                <AlertDescription className="text-sm">
                  Your API key is encrypted at rest and never logged or shared. Only RankWizAI uses it
                  to process your content.
                </AlertDescription>
              </Alert>
            </div>
          )}
        </div>

        {/* MED-006: Prominent validation banner — surfaces failed AI key state
            on page load so users notice immediately that AI generation is
            broken. The Badge + inline message inside the card below stays as
            the secondary surface (status detail + Last validated timestamp). */}
        {showValidationBanner && bannerSeverity && (
          <Alert
            variant={bannerSeverity}
            role="alert"
            data-testid="ai-key-validation-banner"
            className="max-w-2xl mt-6"
          >
            {bannerSeverity === 'destructive' ? (
              <XCircle className="size-4" />
            ) : (
              <AlertTriangle className="size-4" />
            )}
            <AlertTitle>{bannerTitle}</AlertTitle>
            <AlertDescription>{bannerMessage}</AlertDescription>
          </Alert>
        )}

        {/* R8PROD-004: Multi-key list — one card per stored provider */}
        <div className="max-w-2xl mt-6 space-y-3">
          <h3 className="text-lg font-medium">Your AI Keys</h3>

          {stored_keys.length === 0 && (
            <p className="text-sm text-muted-foreground">
              No keys saved yet. Add a key below to enable AI draft generation.
            </p>
          )}

          {stored_keys.map((storedKey) => {
            const valState = storedKey.validation.status ?? (storedKey.is_validated ? 'valid' : null);
            const providerLabel = formatProviderName(storedKey.provider);
            const isUnvalidated = !storedKey.is_validated && !storedKey.validation.status;
            return (
              <div key={storedKey.provider} className="rounded-lg border bg-card p-4 space-y-2">
                <div className="flex items-center justify-between gap-2">
                  <span className="font-medium text-sm">{providerLabel}</span>
                  {valState ? (
                    <Badge variant={validationBadgeVariant[valState] ?? 'default'}>
                      {statusLabelMap[valState] ?? valState}
                    </Badge>
                  ) : (
                    <Badge variant="secondary">unvalidated</Badge>
                  )}
                </div>
                <div className="flex items-center gap-2">
                  <span className="text-xs font-mono text-muted-foreground">{storedKey.masked_key}</span>
                </div>
                {/* INTAI-301 (AI-P3-2): hint on unvalidated keys so users understand
                    the key will not be used until they click Test Key */}
                {isUnvalidated && (
                  <p className="text-xs text-warning-strong">
                    This key won&apos;t be used for drafts until you test it.
                  </p>
                )}
                {valState && (() => {
                  const info = getValidationInfo(valState, storedKey.provider);
                  return info ? (
                    <div className="space-y-1">
                      <p className="text-sm text-muted-foreground">
                        {info.message}
                      </p>
                      {info.actionUrl && (
                        <a
                          href={info.actionUrl}
                          target="_blank"
                          rel="noopener noreferrer"
                          className="text-sm text-primary hover:underline inline-flex items-center gap-1"
                        >
                          {info.action}
                          <ExternalLink className="size-3" />
                        </a>
                      )}
                    </div>
                  ) : null;
                })()}
                {storedKey.validation.at && (
                  <p className="text-xs text-muted-foreground">
                    Last check: {formatDateTime(storedKey.validation.at)}
                  </p>
                )}
                <div className="flex gap-2 pt-1">
                  {/* INTAI-301 (AI-P3-2): Test Key rendered for EVERY stored provider
                      (openai, anthropic). The validate endpoint now accepts a provider param. */}
                  <LoadingButton
                    loading={validateProcessing && validatingProvider === storedKey.provider}
                    onClick={() => handleValidate(storedKey.provider)}
                    size="sm"
                    loadingText="Validating…"
                  >
                    Test Key
                  </LoadingButton>
                  <Button
                    variant="destructive"
                    size="sm"
                    onClick={() => setShowDeleteFor(storedKey.provider)}
                  >
                    Remove
                  </Button>
                  <ConfirmDialog
                    open={showDeleteFor === storedKey.provider}
                    onOpenChange={(open) => setShowDeleteFor(open ? storedKey.provider : null)}
                    title={`Remove ${providerLabel} key?`}
                    description={(() => {
                      // F4UXA-012: surface in-flight AI job count when key is removed mid-batch
                      const activeAiJobs = (active_jobs ?? []).filter(
                        (j) => j.type === 'ai_draft' || j.type === 'batch_ai',
                      );
                      const base =
                        'Removing this key will immediately disable AI draft generation for this provider. Your existing drafts are preserved.';
                      if (activeAiJobs.length > 0) {
                        return `${base} ${activeAiJobs.length} AI draft job${activeAiJobs.length > 1 ? 's are' : ' is'} currently running and will be cancelled.`;
                      }
                      return base;
                    })()}
                    confirmLabel="Remove Key"
                    variant="destructive"
                    onConfirm={() =>
                      // router.delete is fire-and-forget (gotcha #11). Inertia sends
                      // the `data` option as request body fields (not query params)
                      // for DELETE — Laravel reads them via $request->input('provider').
                      router.delete(route('settings.ai.destroy-key'), {
                        data: { provider: storedKey.provider },
                        preserveScroll: true,
                        onError: () => toast.error('Could not remove the key. It is still active — please try again.'),
                      })
                    }
                  />
                </div>
              </div>
            );
          })}
        </div>

        {/* Add / Replace Key Section */}
        <div className="max-w-md mt-6 rounded-lg border bg-card p-6">
          <h3 className="text-lg font-medium">
            {has_key ? 'Add or Replace a Key' : 'Add Your First Key'}
          </h3>
          {!has_key && (
            <p className="text-sm text-muted-foreground mt-2 mb-4">
              Add an API key from OpenAI or Anthropic to enable AI draft generation.
            </p>
          )}

          {replaceKeyExpanded && (
            <form id="replace-key-form" onSubmit={submitKey} className="space-y-3 mt-4">
              {/* R8PROD-004: provider select */}
              <div>
                <label htmlFor="provider-select" className="text-sm font-medium">
                  Provider
                </label>
                <select
                  id="provider-select"
                  value={data.provider}
                  onChange={(e) => {
                    setData('provider', e.target.value);
                  }}
                  className="mt-1 block w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                >
                  {Object.entries(PROVIDER_CONFIG).map(([key, cfg]) => (
                    <option key={key} value={key}>
                      {cfg.label}
                    </option>
                  ))}
                </select>
              </div>
              <div>
                <label htmlFor="api-key" className="text-sm font-medium">
                  API Key
                </label>
                {PROVIDER_CONFIG[data.provider] && (
                  <p className="text-xs text-muted-foreground mt-0.5">
                    Get your key at{' '}
                    <a
                      href={PROVIDER_CONFIG[data.provider].helpUrl}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="text-primary hover:underline inline-flex items-center gap-1"
                    >
                      {PROVIDER_CONFIG[data.provider].helpLabel}
                      <ExternalLink className="size-3" />
                    </a>
                  </p>
                )}
                {/* F4UXA-002: reveal toggle — mirrors Login.tsx pattern */}
                <div className="relative flex items-center mt-1">
                  <Input
                    id="api-key"
                    type={showKey ? 'text' : 'password'}
                    value={data.api_key}
                    onChange={(e) => setData('api_key', e.target.value)}
                    placeholder={PROVIDER_CONFIG[data.provider]?.placeholder ?? 'sk-…'}
                    className="pr-10"
                    autoComplete="off"
                    aria-invalid={!!errors.api_key}
                    aria-describedby={errors.api_key ? 'api-key-error' : undefined}
                  />
                  <button
                    type="button"
                    onClick={() => setShowKey(!showKey)}
                    className="absolute right-3 text-muted-foreground hover:text-foreground transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm"
                    aria-label={showKey ? 'Hide API key' : 'Show API key'}
                    aria-pressed={showKey}
                  >
                    {showKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
                  </button>
                </div>
                <InputError id="api-key-error" message={errors.api_key} className="mt-1" />
              </div>
              <LoadingButton loading={processing} loadingText="Saving key...">
                Save Key
              </LoadingButton>
            </form>
          )}

          {!replaceKeyExpanded && (
            <div className="mt-3">
              <Button
                type="button"
                variant="outline"
                size="sm"
                onClick={() => setReplaceKeyExpanded(true)}
              >
                Add Key
              </Button>
            </div>
          )}
        </div>
      </div>
    </>
  );
}

AiKeySettings.layout = (page: React.ReactNode) => <DashboardLayout>{page}</DashboardLayout>;
