/**
 * Readable device-name derivation for the pairing screen (ticket #77, lot F1).
 *
 * The name is only a **prefill**: the user sees it, can rewrite it, and it is
 * what the device list will show forever after. So it must read like something a
 * human would type — `iPhone`, `Chrome sur Windows` — never a raw User-Agent.
 * The UA string is an implementation detail of this derivation and must not
 * reach any surface: no tooltip, no placeholder, no fallback text.
 *
 * Pure and UA-string-driven (no `navigator` access) so the mapping is testable
 * without a browser; {@link currentDeviceName} is the thin impure wrapper.
 */

/** Bounds enforced by the pairing form and the rename action. */
export const DEVICE_NAME_MAX = 40;
export const DEVICE_NAME_MIN = 1;

/** Last-resort label when nothing recognisable is in the UA. */
const FALLBACK = "Cet appareil";

/** Browsers, most specific first: Edge/Opera also claim "Chrome"/"Safari". */
const BROWSERS: ReadonlyArray<[RegExp, string]> = [
  [/\bEdg(?:e|A|iOS)?\//, "Edge"],
  [/\bOPR\/|\bOpera\//, "Opera"],
  [/\bFirefox\/|\bFxiOS\//, "Firefox"],
  [/\bChrome\/|\bCriOS\//, "Chrome"],
  [/\bSafari\//, "Safari"],
];

/** Desktop OSes only — phones/tablets are named by the device, not the OS. */
const DESKTOP_OS: ReadonlyArray<[RegExp, string]> = [
  [/\bWindows\b/, "Windows"],
  [/\bMac OS X\b|\bMacintosh\b/, "Mac"],
  [/\bCrOS\b/, "ChromeOS"],
  [/\bLinux\b|\bX11\b/, "Linux"],
];

/**
 * Derives a human label from a User-Agent string.
 *
 * Phones and tablets are named by the device alone (`iPhone`, `Android`) because
 * that is how people refer to them; on desktop the browser is the distinguishing
 * fact when several browsers share one machine, hence `Chrome sur Windows`.
 */
export function deriveDeviceName(userAgent: string): string {
  const ua = userAgent ?? "";

  if (/\biPhone\b/.test(ua)) return "iPhone";
  if (/\biPad\b/.test(ua)) return "iPad";
  // iPadOS 13+ masquerades as a Mac; the touch points give it away, but that is
  // a `navigator` fact, not a UA one — a plain "Mac" here is an honest miss.
  if (/\bAndroid\b/.test(ua)) return "Android";

  const browser = BROWSERS.find(([re]) => re.test(ua))?.[1];
  const os = DESKTOP_OS.find(([re]) => re.test(ua))?.[1];

  if (browser && os) return `${browser} sur ${os}`;
  return browser ?? os ?? FALLBACK;
}

/**
 * Clamps a derived or typed name to the contract's 1-40 characters.
 *
 * Counts **code points**, not UTF-16 units, to match the server's
 * `DeviceName::new` (`chars().count()`). `slice(0, 40)` would both disagree with
 * that bound on astral characters and be able to cut a surrogate pair in half,
 * putting a lone surrogate on the wire.
 */
export function clampDeviceName(name: string): string {
  return Array.from(name.trim()).slice(0, DEVICE_NAME_MAX).join("");
}

/** Derives the current browser's device name; `Cet appareil` outside a browser. */
export function currentDeviceName(): string {
  if (typeof navigator === "undefined" || !navigator.userAgent) return FALLBACK;
  return deriveDeviceName(navigator.userAgent);
}
