/**
 * Web pairing screen — ticket #13 lot F2, extended by #75 and #77 lot F1.
 *
 * Shown by {@link WebApp} when the client is not paired. The user types the code
 * generated from an already-paired device (or printed by `--new-code`) plus a
 * name for *this* device; on submit we `POST /api/pair {code, name}` via the
 * {@link WebSession}. On success the server sets the HttpOnly session cookie and
 * we advance to the workspace.
 *
 * The device name is typed here, on the new device, because this is the only
 * moment the person is holding it — the generating device cannot know what to
 * call it. It is prefilled from a readable derivation (never a raw User-Agent).
 *
 * Transport-neutral at the component seam: it talks to the injected
 * {@link WebSession}, never to `@tauri-apps/api` (the CI guard `no-direct-invoke`
 * stays green). Pure web-only UI — desktop never mounts it.
 */

import { useState, type FormEvent } from "react";

import { normalizePairingCode, type GatewayError } from "@/domain";
import { Button, Field, Input, Panel } from "@/shared";
import type { WebSession } from "@/adapters/http";
import { clampDeviceName, currentDeviceName, DEVICE_NAME_MAX } from "./deviceName";

interface PairingScreenProps {
  /** The shared web session performing the `POST /api/pair` handshake. */
  session: WebSession;
  /** Called once pairing succeeds (cookie set) so the app can advance. */
  onPaired: () => void;
}

/** The failure as the form needs it: a message plus which field to blame. */
interface PairingFailure {
  code: string;
  message: string;
}

function describe(e: unknown): PairingFailure {
  if (e && typeof e === "object" && "message" in e) {
    const err = e as GatewayError;
    return { code: String(err.code ?? "ERROR"), message: String(err.message) };
  }
  return { code: "ERROR", message: String(e) };
}

export function PairingScreen({ session, onPaired }: PairingScreenProps) {
  const [code, setCode] = useState("");
  const [name, setName] = useState(() => clampDeviceName(currentDeviceName()));
  const [error, setError] = useState<PairingFailure | null>(null);
  const [busy, setBusy] = useState(false);

  const normalizedCode = normalizePairingCode(code);
  const trimmedName = name.trim();
  const canSubmit = normalizedCode.length > 0 && trimmedName.length > 0;

  // A rejected name is the one failure the user fixes in the form rather than by
  // fetching a new code (the server validates it before consuming the code, so
  // the code is still live). Point at the field instead of the whole form.
  const nameError = error?.code === "invalidName" ? error.message : null;
  const formError = error && !nameError ? error.message : null;

  async function submit(e: FormEvent): Promise<void> {
    e.preventDefault();
    if (!canSubmit || busy) return;
    setBusy(true);
    setError(null);
    try {
      await session.pair(normalizedCode, clampDeviceName(trimmedName));
      onPaired();
    } catch (err) {
      setError(describe(err));
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="flex h-full items-center justify-center overflow-y-auto bg-canvas p-4 sm:p-6">
      <Panel className="w-full max-w-sm">
        <form onSubmit={submit} className="flex flex-col gap-4">
          <div className="flex flex-col gap-1">
            <h1 className="text-lg font-semibold tracking-tight">Appairer cet appareil</h1>
            <p className="text-sm text-muted">
              Saisissez le code affiché par le serveur IdeA pour connecter ce
              navigateur.
            </p>
          </div>

          <Field label="Code d'appairage" hint="8 caractères : chiffres et lettres A-F">
            {({ id, describedBy }) => (
              <Input
                id={id}
                aria-describedby={describedBy}
                value={code}
                onChange={(e) => setCode(e.target.value)}
                placeholder="p. ex. AB12CD34"
                autoFocus
                autoComplete="one-time-code"
                // #75 — the code mixes digits and letters, so phones need the
                // text keyboard; uppercase it on the way in, never autocorrect.
                inputMode="text"
                autoCapitalize="characters"
                autoCorrect="off"
                spellCheck={false}
                disabled={busy}
                invalid={!!formError}
              />
            )}
          </Field>

          <Field
            label="Nom de cet appareil"
            hint="Pour le reconnaître dans la liste des appareils."
            error={nameError ?? undefined}
          >
            {({ id, describedBy }) => (
              <Input
                id={id}
                aria-describedby={describedBy}
                value={name}
                onChange={(e) => setName(e.target.value)}
                maxLength={DEVICE_NAME_MAX}
                autoComplete="off"
                autoCorrect="off"
                spellCheck={false}
                disabled={busy}
                invalid={!!nameError}
              />
            )}
          </Field>

          {formError && (
            <p role="alert" data-testid="pairing-error" className="text-sm text-danger">
              {formError}
            </p>
          )}

          <Button type="submit" disabled={busy || !canSubmit}>
            {busy ? "Appairage…" : "Appairer"}
          </Button>
        </form>
      </Panel>
    </div>
  );
}
