"use client";

import { useState, useCallback, useEffect, useRef } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/components/ui/card";
import { PACKAGE_CATALOG, type PackageDef } from "@/lib/packages";
import { isPersonalOrgName, displayOrgNameFor } from "@/lib/personal-org";
import {
  configureStepSchema,
  billingStepSchema,
  onboardingSchema,
  fieldErrors,
  SUPPORTED_COUNTRIES,
  type SupportedCountry,
} from "@/lib/validation";

type Step = "welcome" | "configure" | "billing" | "confirm";

const STEPS: Step[] = ["welcome", "configure", "billing", "confirm"];

// Inline fallbacks — only used if the API call to /api/workspace-defaults fails
const FALLBACK_SOUL = `# AI Assistant

You are a helpful AI assistant for {company}. You are professional, concise, and friendly.

## Guidelines
- Answer questions accurately and helpfully
- If you don't know something, say so
- Keep responses clear and to the point
- Respect privacy and confidentiality
`;

const FALLBACK_AGENTS = `# Agents

On session start, read the following workspace files in order:
1. SOUL.md — your personality and behavioural guidelines
2. TOOLS.md — available tools and how to use them
3. USER.md — information about the current user (if present)

Follow the instructions in SOUL.md for every interaction.
`;

const FALLBACK_TOOLS = `# Tools

The following tools are available to you as an AI assistant.

## LLM
You have access to a large language model for text generation, summarisation,
translation, and general question answering.
`;

const CATEGORIES = [
  { key: "channel" as const, labelKey: "categories.channels" },
  { key: "skill" as const, labelKey: "categories.skills" },
] as const;

interface WizardProps {
  orgName: string;
  /**
   * The user's display name. Used as the visible label for personal
   * accounts (where `orgName` is an opaque ID like "personal-3f2a8b1c"
   * or a synthetic legacy "{name} (Personal)" string). Ignored for
   * company accounts.
   */
  userName?: string;
  userEmail?: string;
  /**
   * Bug 6: when present, the wizard renders in "edit" mode — fields
   * are pre-populated from the request, the SOUL.md auto-fetch is
   * skipped (we trust the existing values), and the submit button
   * PATCHes /api/onboarding/[id] instead of POSTing /api/onboarding.
   *
   * Per-package secrets are deliberately NOT pre-filled, even if the
   * customer originally supplied them — server-side decryption to
   * the client would be a security regression. The user re-enters
   * any secrets they want to change; if they leave them blank, the
   * existing encrypted blob in the DB is preserved by the PATCH
   * endpoint.
   */
  editingRequest?: {
    id: string;
    instanceName: string;
    agentName: string;
    soulMd: string;
    agentsMd: string;
    packages: string[];
    billingAddress: {
      company?: string;
      street?: string;
      city?: string;
      postalCode?: string;
      country?: string;
    };
    billingNotes: string;
  };
  onComplete: () => void;
}

export function OnboardingWizard({
  orgName,
  userName,
  userEmail,
  editingRequest,
  onComplete,
}: WizardProps) {
  const t = useTranslations("onboarding");
  const tPkg = useTranslations("packages");
  const tCommon = useTranslations("common");
  const tCountries = useTranslations("countries");

  // Personal accounts have an org name that is either the legacy
  // "{givenName} {familyName} (Personal)" or the current opaque
  // "personal-{8hex}" form. Either way, the customer-facing display
  // should be the user's own name — never the org name. SOUL.md
  // interpolation and the billing form follow the same rule so
  // invoices and prompts don't leak "(Personal)" or "personal-3f2a..".
  const isPersonal = isPersonalOrgName(orgName);
  const displayOrgName = displayOrgNameFor({
    name: userName,
    email: userEmail,
    orgName,
    isPersonal,
  });
  const isEditing = Boolean(editingRequest);

  // Edit mode jumps straight to the configure step — the welcome step
  // is a first-time onboarding affordance and only adds friction when
  // the customer is fixing a typo.
  const [step, setStep] = useState<Step>(isEditing ? "configure" : "welcome");
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState("");
  const [advancedOpen, setAdvancedOpen] = useState(false);
  // In edit mode we already have soulMd/agentsMd from the request;
  // skip the workspace-defaults round trip that would overwrite them.
  const [defaultsLoaded, setDefaultsLoaded] = useState(isEditing);

  const [config, setConfig] = useState(() => {
    if (editingRequest) {
      return {
        instanceName: editingRequest.instanceName,
        agentName: editingRequest.agentName,
        soulMd: editingRequest.soulMd,
        agentsMd: editingRequest.agentsMd,
        packages: editingRequest.packages,
        billingAddress: {
          company: editingRequest.billingAddress.company ?? "",
          street: editingRequest.billingAddress.street ?? "",
          city: editingRequest.billingAddress.city ?? "",
          postalCode: editingRequest.billingAddress.postalCode ?? "",
          country: editingRequest.billingAddress.country ?? "CH",
        },
        billingNotes: editingRequest.billingNotes,
      };
    }
    return {
      instanceName: "",
      agentName: "Assistant",
      soulMd: FALLBACK_SOUL.replace("{company}", displayOrgName),
      agentsMd: FALLBACK_AGENTS,
      packages: [] as string[],
      billingAddress: {
        // For personal accounts, leave the company field empty — it'll
        // appear on invoices. The user can still type something if they
        // want to.
        company: isPersonal ? "" : displayOrgName,
        street: "",
        city: "",
        postalCode: "",
        country: "CH",
      },
      billingNotes: "",
    };
  });

  // TOOLS.md preview — readonly, auto-generated
  const [toolsMdPreview, setToolsMdPreview] = useState(FALLBACK_TOOLS);

  // Per-package collected secrets: { "telegram": { "bot-token": "123:ABC" }, ... }
  const [packageSecrets, setPackageSecrets] = useState<
    Record<string, Record<string, string>>
  >({});
  // Per-package disclaimer acceptance
  const [disclaimerAccepted, setDisclaimerAccepted] = useState<
    Record<string, boolean>
  >({});

  // Fetch DB-stored defaults on mount
  useEffect(() => {
    fetch("/api/workspace-defaults")
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => {
        if (data) {
          setConfig((prev) => ({
            ...prev,
            soulMd: data.soulMd ?? prev.soulMd,
            agentsMd: data.agentsMd ?? prev.agentsMd,
          }));
          setToolsMdPreview(data.toolsMd ?? FALLBACK_TOOLS);
          setDefaultsLoaded(true);
        }
      })
      .catch(() => {
        /* use inline fallbacks */
      });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Re-fetch TOOLS.md preview when packages change
  const packagesKey = config.packages.sort().join(",");
  const prevPackagesKey = useRef(packagesKey);
  useEffect(() => {
    if (prevPackagesKey.current === packagesKey && defaultsLoaded) return;
    prevPackagesKey.current = packagesKey;
    fetch(
      `/api/workspace-defaults?packages=${encodeURIComponent(packagesKey)}`
    )
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => {
        if (data?.toolsMd) setToolsMdPreview(data.toolsMd);
      })
      .catch(() => {});
  }, [packagesKey, defaultsLoaded]);

  const stepIndex = STEPS.indexOf(step);

  // Bug 12 — per-step validation. `errors` holds field-path → message
  // for the inline labels under each input. We only populate it on
  // attempted advancement; touching a field clears its own error so
  // valid input doesn't keep showing stale messages.
  const [errors, setErrors] = useState<Record<string, string>>({});
  const clearError = useCallback((path: string) => {
    setErrors((prev) => {
      if (!prev[path]) return prev;
      const next = { ...prev };
      delete next[path];
      return next;
    });
  }, []);

  /**
   * Validate the current step against its schema. On success: clear
   * errors and return true. On failure: populate errors and return
   * false so the caller can refuse to advance.
   *
   * Welcome and configure-step have no schema interaction with billing
   * fields — keeping the schemas narrow means we don't surface a
   * billing error when the user is still typing on the configure step.
   */
  const validateStep = (s: Step): boolean => {
    if (s === "welcome") return true;
    if (s === "configure") {
      const r = configureStepSchema.safeParse({ agentName: config.agentName });
      if (r.success) {
        setErrors({});
        return true;
      }
      setErrors(fieldErrors(r.error));
      return false;
    }
    if (s === "billing") {
      const r = billingStepSchema.safeParse({
        billingAddress: config.billingAddress,
      });
      if (r.success) {
        setErrors({});
        return true;
      }
      setErrors(fieldErrors(r.error));
      return false;
    }
    // confirm: validate the union (defence in depth — submit handler
    // also runs onboardingSchema before POST).
    const r = onboardingSchema.safeParse(config);
    if (r.success) {
      setErrors({});
      return true;
    }
    setErrors(fieldErrors(r.error));
    return false;
  };

  const goNext = () => {
    if (!validateStep(step)) return;
    if (stepIndex < STEPS.length - 1) setStep(STEPS[stepIndex + 1]);
  };

  const goBack = () => {
    // Going back never re-validates; the user's existing errors stay
    // pinned to fields so they can fix them after navigating back.
    if (stepIndex > 0) setStep(STEPS[stepIndex - 1]);
  };

  const togglePackage = useCallback((pkgId: string) => {
    setConfig((prev) => {
      const removing = prev.packages.includes(pkgId);
      if (removing) {
        setPackageSecrets((s) => {
          const next = { ...s };
          delete next[pkgId];
          return next;
        });
        setDisclaimerAccepted((d) => {
          const next = { ...d };
          delete next[pkgId];
          return next;
        });
      }
      return {
        ...prev,
        packages: removing
          ? prev.packages.filter((p) => p !== pkgId)
          : [...prev.packages, pkgId],
      };
    });
  }, []);

  const updateSecret = useCallback(
    (pkgId: string, key: string, value: string) => {
      setPackageSecrets((prev) => ({
        ...prev,
        [pkgId]: { ...(prev[pkgId] || {}), [key]: value },
      }));
    },
    []
  );

  // Validate that all secret-requiring enabled packages have complete credentials
  const packageCredentialsValid = (): boolean => {
    for (const pkgId of config.packages) {
      const def = PACKAGE_CATALOG.find((p) => p.id === pkgId);
      if (!def?.requiresSecrets) continue;
      const secrets = packageSecrets[pkgId] || {};
      for (const field of def.secrets || []) {
        if (!secrets[field.key]?.trim()) return false;
      }
      if (def.disclaimerKey && !disclaimerAccepted[pkgId]) return false;
    }
    return true;
  };

  const handleSubmit = async () => {
    // Defence in depth: re-run the full schema before sending. The
    // server schema is the authoritative gate but we save a round trip
    // by catching any client-side gaps here. In practice this should
    // never fail at this point — the per-step validators have already
    // caught everything — but a future regression in the per-step
    // schemas would otherwise let the bad payload through.
    if (!validateStep("confirm")) {
      setError(t("validationError"));
      return;
    }

    setSubmitting(true);
    setError("");

    try {
      // Build secrets payload — only for packages that require them
      const secretsPayload: Record<string, Record<string, string>> = {};
      for (const pkgId of config.packages) {
        const def = PACKAGE_CATALOG.find((p) => p.id === pkgId);
        if (def?.requiresSecrets && packageSecrets[pkgId]) {
          secretsPayload[pkgId] = packageSecrets[pkgId];
        }
      }

      // Bug 6: edit mode targets the per-row endpoint with PATCH;
      // create mode targets the collection endpoint with POST. Body
      // shape is the same — both routes parse it through
      // onboardingSchema.
      const url = editingRequest
        ? `/api/onboarding/${encodeURIComponent(editingRequest.id)}`
        : "/api/onboarding";
      const method = editingRequest ? "PATCH" : "POST";

      const res = await fetch(url, {
        method,
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          ...config,
          packageSecrets:
            Object.keys(secretsPayload).length > 0
              ? secretsPayload
              : undefined,
        }),
      });

      if (!res.ok) {
        const data = await res.json();
        throw new Error(data.error || "Submission failed");
      }

      onComplete();
    } catch (err: any) {
      setError(err.message);
    } finally {
      setSubmitting(false);
    }
  };

  // Step indicator
  const StepIndicator = () => (
    <div className="flex items-center justify-center gap-2 mb-8">
      {STEPS.map((s, i) => (
        <div key={s} className="flex items-center gap-2">
          <div
            className={`h-2 w-2 rounded-full transition-colors ${
              i <= stepIndex ? "bg-accent" : "bg-border"
            }`}
          />
          {i < STEPS.length - 1 && (
            <div
              className={`h-px w-8 transition-colors ${
                i < stepIndex ? "bg-accent" : "bg-border"
              }`}
            />
          )}
        </div>
      ))}
    </div>
  );

  return (
    <div className="max-w-xl mx-auto">
      <StepIndicator />

      {/* Step: Welcome */}
      {step === "welcome" && (
        <Card className="animate-in">
          <div className="text-center py-4">
            <div className="h-14 w-14 rounded-xl bg-accent/15 flex items-center justify-center mx-auto mb-4">
              <svg
                className="h-7 w-7 text-accent"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
                strokeWidth={1.5}
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
                />
              </svg>
            </div>
            <h2 className="font-display text-xl font-semibold text-text-primary mb-2">
              {t("welcomeTitle")}
            </h2>
            <p className="text-sm text-text-secondary max-w-sm mx-auto mb-6">
              {t("welcomeDescription")}
            </p>
            <div className="space-y-2 text-left max-w-sm mx-auto mb-6">
              {["swissHosted", "privacy", "customizable"].map((key) => (
                <div key={key} className="flex items-start gap-2">
                  <span className="text-accent mt-0.5 text-sm">✓</span>
                  <span className="text-sm text-text-secondary">
                    {t(`welcomeFeature_${key}`)}
                  </span>
                </div>
              ))}
            </div>
          </div>
          <div className="flex justify-end">
            <button
              onClick={goNext}
              className="py-2 px-6 bg-accent text-white text-sm font-medium rounded-lg hover:bg-accent-dim transition-colors"
            >
              {t("getStarted")}
            </button>
          </div>
        </Card>
      )}

      {/* Step: Configure */}
      {step === "configure" && (
        <Card className="animate-in">
          <h2 className="font-display text-lg font-semibold text-text-primary mb-1">
            {t("configureTitle")}
          </h2>
          <p className="text-sm text-text-secondary mb-6">
            {t("configureDescription")}
          </p>

          <div className="space-y-4">
            <div>
              <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                {t("instanceName")}
              </label>
              <input
                type="text"
                value={config.instanceName}
                onChange={(e) =>
                  setConfig((prev) => ({ ...prev, instanceName: e.target.value }))
                }
                placeholder={t("instanceNamePlaceholder")}
                className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors"
              />
              <p className="text-xs text-text-muted mt-1">
                {t("instanceNameHint")}
              </p>
            </div>

            <FieldWithError error={errors.agentName}>
              <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                {t("agentName")} <RequiredMark />
              </label>
              <input
                type="text"
                required
                value={config.agentName}
                onChange={(e) => {
                  clearError("agentName");
                  setConfig((prev) => ({ ...prev, agentName: e.target.value }));
                }}
                className={inputClass(errors.agentName)}
              />
            </FieldWithError>

            <div>
              <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                {t("soulMd")}
              </label>
              <textarea
                value={config.soulMd}
                onChange={(e) =>
                  setConfig((prev) => ({ ...prev, soulMd: e.target.value }))
                }
                rows={8}
                className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary font-mono text-xs focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors resize-y"
              />
              <p className="text-xs text-text-muted mt-1">
                {t("soulMdHint")}
              </p>
            </div>

            {/* Advanced: AGENTS.md + TOOLS.md preview */}
            <div className="border border-border rounded-lg overflow-hidden">
              <button
                type="button"
                onClick={() => setAdvancedOpen((o) => !o)}
                className="w-full flex items-center justify-between px-3 py-2.5 text-left hover:bg-surface-3/30 transition-colors"
              >
                <span className="text-xs font-semibold uppercase tracking-wider text-text-muted">
                  {t("advancedConfig")}
                </span>
                <svg
                  className={`h-4 w-4 text-text-muted transition-transform ${
                    advancedOpen ? "rotate-180" : ""
                  }`}
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke="currentColor"
                  strokeWidth={2}
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    d="M19 9l-7 7-7-7"
                  />
                </svg>
              </button>
              {advancedOpen && (
                <div className="border-t border-border px-3 py-4 space-y-4 bg-surface-1/30">
                  {/* AGENTS.md */}
                  <div>
                    <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                      {t("agentsMd")}
                    </label>
                    <textarea
                      value={config.agentsMd}
                      onChange={(e) =>
                        setConfig((prev) => ({
                          ...prev,
                          agentsMd: e.target.value,
                        }))
                      }
                      rows={6}
                      className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary font-mono text-xs focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors resize-y"
                    />
                    <p className="text-xs text-text-muted mt-1">
                      {t("agentsMdHint")}
                    </p>
                  </div>

                  {/* TOOLS.md — readonly preview */}
                  <div>
                    <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                      {t("toolsMd")}
                    </label>
                    <textarea
                      value={toolsMdPreview}
                      readOnly
                      rows={6}
                      className="w-full px-3 py-2 bg-surface-3/50 border border-border rounded-lg text-sm text-text-secondary font-mono text-xs cursor-not-allowed resize-y"
                    />
                    <p className="text-xs text-text-muted mt-1">
                      {t("toolsMdHint")}
                    </p>
                  </div>
                </div>
              )}
            </div>

            {/* Packages — grouped by category */}
            <div>
              <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-2">
                {t("packages")}
              </label>

              {CATEGORIES.map(({ key, labelKey }) => {
                const packages = PACKAGE_CATALOG.filter(
                  (p) => p.category === key
                );
                if (packages.length === 0) return null;

                return (
                  <div key={key} className="mb-4">
                    <h4 className="text-[10px] font-semibold uppercase tracking-wider text-text-muted/70 mb-1.5">
                      {tPkg(labelKey)}
                    </h4>
                    <div className="space-y-2">
                      {packages.map((pkg) => {
                        const isSelected = config.packages.includes(pkg.id);
                        const secrets = packageSecrets[pkg.id] || {};

                        return (
                          <div
                            key={pkg.id}
                            className={`border rounded-lg overflow-hidden transition-colors ${
                              isSelected
                                ? "border-accent bg-accent/5"
                                : "border-border bg-surface-2"
                            }`}
                          >
                            {/* Toggle row */}
                            <button
                              type="button"
                              onClick={() => togglePackage(pkg.id)}
                              className="w-full flex items-center justify-between px-3 py-2.5 cursor-pointer hover:bg-surface-3/30 transition-colors"
                            >
                              <div className="text-left">
                                <span
                                  className={`text-sm font-medium ${
                                    isSelected
                                      ? "text-accent"
                                      : "text-text-secondary"
                                  }`}
                                >
                                  {pkg.name}
                                </span>
                                {pkg.requiresSecrets && (
                                  <span className="ml-1.5 text-[10px] text-text-muted">
                                    ({tPkg("requiresApiKey")})
                                  </span>
                                )}
                              </div>
                              <div
                                className={`shrink-0 ml-3 h-5 w-9 rounded-full transition-colors ${
                                  isSelected ? "bg-accent" : "bg-surface-3"
                                }`}
                              >
                                <div
                                  className={`h-4 w-4 rounded-full bg-white shadow-sm mt-0.5 transition-transform ${
                                    isSelected
                                      ? "translate-x-4"
                                      : "translate-x-0.5"
                                  }`}
                                />
                              </div>
                            </button>

                            {/* Inline credential inputs — expand when selected + requires secrets */}
                            {isSelected && pkg.requiresSecrets && (
                              <div className="border-t border-border px-3 py-3 space-y-3 bg-surface-1/50">
                                {pkg.instructionsKey && (
                                  <div className="bg-surface-2 border border-border rounded-lg p-3 text-xs text-text-secondary leading-relaxed whitespace-pre-line">
                                    {tPkg(
                                      pkg.instructionsKey.replace(
                                        "packages.",
                                        ""
                                      )
                                    )}
                                  </div>
                                )}

                                {(pkg.secrets || []).map((field) => (
                                  <label key={field.key} className="block">
                                    <span className="text-xs text-text-secondary mb-1 block">
                                      {tPkg(
                                        field.labelKey.replace("packages.", "")
                                      )}
                                    </span>
                                    <input
                                      type="password"
                                      placeholder={tPkg(
                                        field.placeholderKey.replace(
                                          "packages.",
                                          ""
                                        )
                                      )}
                                      value={secrets[field.key] || ""}
                                      onChange={(e) =>
                                        updateSecret(
                                          pkg.id,
                                          field.key,
                                          e.target.value
                                        )
                                      }
                                      className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors"
                                    />
                                  </label>
                                ))}

                                {pkg.disclaimerKey && (
                                  <label className="flex items-start gap-2 text-xs text-text-secondary">
                                    <input
                                      type="checkbox"
                                      checked={
                                        disclaimerAccepted[pkg.id] || false
                                      }
                                      onChange={(e) =>
                                        setDisclaimerAccepted((prev) => ({
                                          ...prev,
                                          [pkg.id]: e.target.checked,
                                        }))
                                      }
                                      className="mt-0.5 accent-accent"
                                    />
                                    <span>
                                      {tPkg(
                                        pkg.disclaimerKey.replace(
                                          "packages.",
                                          ""
                                        )
                                      )}
                                    </span>
                                  </label>
                                )}
                              </div>
                            )}
                          </div>
                        );
                      })}
                    </div>
                  </div>
                );
              })}

              <p className="text-xs text-text-muted mt-1">
                {t("packagesHint")}
              </p>
            </div>
          </div>

          <div className="flex justify-between mt-6">
            <button
              onClick={goBack}
              className="py-2 px-4 text-sm text-text-secondary hover:text-text-primary transition-colors"
            >
              {t("back")}
            </button>
            <button
              onClick={goNext}
              disabled={!packageCredentialsValid()}
              className="py-2 px-6 bg-accent text-white text-sm font-medium rounded-lg hover:bg-accent-dim transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
            >
              {t("next")}
            </button>
          </div>
        </Card>
      )}

      {/* Step: Billing */}
      {step === "billing" && (
        <Card className="animate-in">
          <h2 className="font-display text-lg font-semibold text-text-primary mb-1">
            {t("billingTitle")}
          </h2>
          <p className="text-sm text-text-secondary mb-6">
            {t("billingDescription")}
          </p>

          <div className="space-y-4">
            {/* Bug 2: company line is meaningless for personal accounts.
                Hide entirely rather than render an empty disabled field
                — the latter would just suggest the customer should
                fill it in. */}
            {!isPersonal && (
              <div>
                <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                  {t("billingCompany")}
                </label>
                <input
                  type="text"
                  value={config.billingAddress.company}
                  onChange={(e) => {
                    clearError("billingAddress.company");
                    setConfig((prev) => ({
                      ...prev,
                      billingAddress: {
                        ...prev.billingAddress,
                        company: e.target.value,
                      },
                    }));
                  }}
                  className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors"
                />
              </div>
            )}

            <FieldWithError error={errors["billingAddress.street"]}>
              <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                {t("billingStreet")} <RequiredMark />
              </label>
              <input
                type="text"
                required
                value={config.billingAddress.street}
                onChange={(e) => {
                  clearError("billingAddress.street");
                  setConfig((prev) => ({
                    ...prev,
                    billingAddress: {
                      ...prev.billingAddress,
                      street: e.target.value,
                    },
                  }));
                }}
                className={inputClass(errors["billingAddress.street"])}
              />
            </FieldWithError>

            <div className="grid grid-cols-3 gap-3">
              <FieldWithError error={errors["billingAddress.postalCode"]}>
                <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                  {t("billingPostalCode")} <RequiredMark />
                </label>
                <input
                  type="text"
                  required
                  value={config.billingAddress.postalCode}
                  onChange={(e) => {
                    clearError("billingAddress.postalCode");
                    setConfig((prev) => ({
                      ...prev,
                      billingAddress: {
                        ...prev.billingAddress,
                        postalCode: e.target.value,
                      },
                    }));
                  }}
                  className={inputClass(errors["billingAddress.postalCode"])}
                />
              </FieldWithError>
              <div className="col-span-2">
                <FieldWithError error={errors["billingAddress.city"]}>
                  <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                    {t("billingCity")} <RequiredMark />
                  </label>
                  <input
                    type="text"
                    required
                    value={config.billingAddress.city}
                    onChange={(e) => {
                      clearError("billingAddress.city");
                      setConfig((prev) => ({
                        ...prev,
                        billingAddress: {
                          ...prev.billingAddress,
                          city: e.target.value,
                        },
                      }));
                    }}
                    className={inputClass(errors["billingAddress.city"])}
                  />
                </FieldWithError>
              </div>
            </div>

            {/* Bug 3: country was a free-text field — typos broke
                invoicing. Now a fixed list of DACH+ neighbours. Add
                more codes to SUPPORTED_COUNTRIES in lib/validation.ts
                when expanding markets. */}
            <FieldWithError error={errors["billingAddress.country"]}>
              <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                {t("billingCountry")} <RequiredMark />
              </label>
              <select
                value={config.billingAddress.country}
                onChange={(e) => {
                  clearError("billingAddress.country");
                  setConfig((prev) => ({
                    ...prev,
                    billingAddress: {
                      ...prev.billingAddress,
                      country: e.target.value as SupportedCountry,
                    },
                  }));
                }}
                className={inputClass(errors["billingAddress.country"])}
              >
                {SUPPORTED_COUNTRIES.map((code) => (
                  <option key={code} value={code}>
                    {tCountries(code)}
                  </option>
                ))}
              </select>
            </FieldWithError>

            <div>
              <label className="block text-xs font-semibold uppercase tracking-wider text-text-muted mb-1.5">
                {t("billingNotes")}
              </label>
              <textarea
                value={config.billingNotes}
                onChange={(e) =>
                  setConfig((prev) => ({
                    ...prev,
                    billingNotes: e.target.value,
                  }))
                }
                rows={3}
                placeholder={t("billingNotesPlaceholder")}
                className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors resize-y"
              />
            </div>
          </div>

          <div className="flex justify-between mt-6">
            <button
              onClick={goBack}
              className="py-2 px-4 text-sm text-text-secondary hover:text-text-primary transition-colors"
            >
              {t("back")}
            </button>
            <button
              onClick={goNext}
              className="py-2 px-6 bg-accent text-white text-sm font-medium rounded-lg hover:bg-accent-dim transition-colors"
            >
              {t("next")}
            </button>
          </div>
        </Card>
      )}

      {/* Step: Confirm */}
      {step === "confirm" && (
        <Card className="animate-in">
          <h2 className="font-display text-lg font-semibold text-text-primary mb-1">
            {t("confirmTitle")}
          </h2>
          <p className="text-sm text-text-secondary mb-6">
            {t("confirmDescription")}
          </p>

          {/* Bug 4 redesign: previously this step only showed agentName
              and city — useless for actually reviewing what's about to
              be submitted. Now it shows the real config: instance
              name, agent name, packages, billing one-liner, contact
              email, and notes. Each row uses two columns rather than
              flex-justify-between so long values wrap underneath the
              label rather than being squashed onto one line. */}
          <div className="space-y-4">
            <div className="bg-surface-2 border border-border rounded-lg p-4 divide-y divide-border">
              <ReviewRow
                label={t("instanceName")}
                value={
                  config.instanceName.trim() || (
                    <span className="text-text-muted italic">
                      {t("reviewInstanceDefault")}
                    </span>
                  )
                }
                mono
              />
              <ReviewRow
                label={t("agentName")}
                value={config.agentName}
                mono
              />
              <ReviewRow
                label={t("packages")}
                value={
                  config.packages.length === 0 ? (
                    <span className="text-text-muted italic">
                      {t("reviewNoPackages")}
                    </span>
                  ) : (
                    <div className="flex flex-wrap gap-1 justify-end">
                      {config.packages.map((pkg) => (
                        <span
                          key={pkg}
                          className="text-xs font-mono bg-accent/10 text-accent border border-accent/20 rounded-full px-2 py-0.5"
                        >
                          {pkg}
                        </span>
                      ))}
                    </div>
                  )
                }
              />
              <ReviewRow
                label={t("reviewBillingTo")}
                value={
                  <div className="text-text-primary text-right">
                    {/* For personal: skip the company line so the
                        invoice rendering matches what the user actually
                        entered. For company: include it as the first
                        line. */}
                    {!isPersonal &&
                      config.billingAddress.company &&
                      config.billingAddress.company.trim().length > 0 && (
                        <div>{config.billingAddress.company}</div>
                      )}
                    <div>{config.billingAddress.street}</div>
                    <div>
                      {config.billingAddress.postalCode}{" "}
                      {config.billingAddress.city}
                    </div>
                    <div className="text-text-muted">
                      {tCountries(
                        config.billingAddress.country as SupportedCountry
                      )}
                    </div>
                  </div>
                }
              />
              <ReviewRow
                label={t("reviewContactEmail")}
                value={userEmail || ""}
                mono
              />
              {config.billingNotes.trim().length > 0 && (
                <ReviewRow
                  label={t("billingNotes")}
                  value={
                    <span className="text-text-primary whitespace-pre-wrap text-right">
                      {config.billingNotes}
                    </span>
                  }
                />
              )}
            </div>

            <p className="text-xs text-text-muted">{t("confirmNote")}</p>
          </div>

          {error && (
            <div className="text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded-lg px-3 py-2 mt-4">
              {error}
            </div>
          )}

          {/* Aggregate validation errors — if any per-step schema check
              missed something (it shouldn't, but defence in depth),
              the user sees a consolidated list here rather than a
              silent submit failure. */}
          {Object.keys(errors).length > 0 && (
            <div className="text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded-lg px-3 py-2 mt-4">
              <div className="font-semibold mb-1">
                {t("validationErrorsTitle")}
              </div>
              <ul className="list-disc list-inside space-y-0.5">
                {Object.entries(errors).map(([path, msg]) => (
                  <li key={path}>
                    <span className="font-mono">{path}</span>: {msg}
                  </li>
                ))}
              </ul>
            </div>
          )}

          <div className="flex justify-between mt-6">
            <button
              onClick={goBack}
              className="py-2 px-4 text-sm text-text-secondary hover:text-text-primary transition-colors"
            >
              {t("back")}
            </button>
            <button
              onClick={handleSubmit}
              disabled={submitting}
              className="py-2.5 px-6 bg-accent text-white text-sm font-medium rounded-lg hover:bg-accent-dim transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
            >
              {submitting
                ? tCommon("loading")
                : isEditing
                ? t("saveChanges")
                : t("submitRequest")}
            </button>
          </div>
        </Card>
      )}
    </div>
  );
}

/**
 * Two-column review row used by the confirm step. Right-aligned value
 * with the label as a muted prefix on the left.
 */
function ReviewRow({
  label,
  value,
  mono,
}: {
  label: string;
  value: React.ReactNode;
  mono?: boolean;
}) {
  return (
    <div className="flex justify-between gap-4 text-sm py-2 first:pt-0 last:pb-0">
      <span className="text-text-muted shrink-0">{label}</span>
      <span
        className={`text-text-primary text-right min-w-0 break-words ${
          mono ? "font-mono" : ""
        }`}
      >
        {value}
      </span>
    </div>
  );
}

/**
 * Renders children + an inline error message if present. Children
 * supply the label and input; this wrapper just appends the message.
 */
function FieldWithError({
  error,
  children,
}: {
  error?: string;
  children: React.ReactNode;
}) {
  return (
    <div>
      {children}
      {error && (
        <p className="text-xs text-red-400 mt-1" role="alert">
          {error}
        </p>
      )}
    </div>
  );
}

function RequiredMark() {
  return (
    <span aria-hidden="true" className="text-accent">
      *
    </span>
  );
}

/**
 * Tailwind class for input/select with optional error-state ring.
 * Centralised here to keep the wizard's many fields visually
 * consistent without repeating the long class string.
 */
function inputClass(error?: string): string {
  return `w-full px-3 py-2 bg-surface-2 border rounded-lg text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 transition-colors ${
    error
      ? "border-red-400/60 focus:ring-red-400 focus:border-red-400"
      : "border-border focus:ring-accent focus:border-accent"
  }`;
}
