/**
 * Para onde a pessoa volta depois de clicar em "Permitir".
 *
 * É o ponto mais sensível do login por botão. O programa que se apresenta
 * escolhe o próprio nome, e nada impede alguém de registrar um programa
 * chamado "Claude" que manda a pessoa de volta para um site dele: ela clica em
 * "Permitir" achando que é o Claude, e quem recebe o acesso é o golpista.
 *
 * Por isso a volta só pode ser para lugares de uma lista fechada:
 *
 * - os endereços de retorno dos assistentes conhecidos (Claude, ChatGPT e os
 *   editores que usam um endereço fixo na internet);
 * - o próprio computador da pessoa (`localhost`), que é para onde voltam os
 *   programas instalados, como o Claude Code e o Codex. Ninguém de fora recebe
 *   o que é entregue em `localhost`;
 * - os aplicativos de computador que voltam por um endereço próprio do
 *   aplicativo (`cursor://`, `claude://`).
 *
 * Um programa que não está na lista continua podendo se ligar pela chave de
 * acesso. Programa novo na lista entra por decisão nossa, aqui.
 *
 * Só regra, sem banco, para o teste ao lado rodar sozinho.
 */

export type DestinoDaVolta = {
  /* O endereço normalizado, como fica guardado. */
  endereco: string;
  tipo: "site" | "computador" | "aplicativo";
  /* Como a tela de permissão descreve o destino para a pessoa. */
  rotulo: string;
  /* O nome do assistente, quando o destino é de um que conhecemos. */
  assistente: string | null;
};

type Conhecido = { host: string; caminho: RegExp; assistente: string };

/* Levantados na documentação de cada programa em outubro de 2026. O caminho
   entra na regra para o retorno não poder ser trocado por outra página do
   mesmo site. */
const SITES: Conhecido[] = [
  { host: "claude.ai", caminho: /^\/api\/mcp\/auth_callback$/, assistente: "Claude" },
  { host: "claude.com", caminho: /^\/api\/mcp\/auth_callback$/, assistente: "Claude" },
  { host: "chatgpt.com", caminho: /^\/connector_platform_oauth_redirect$/, assistente: "ChatGPT" },
  { host: "chatgpt.com", caminho: /^\/connector\/oauth\/[A-Za-z0-9_-]{1,120}$/, assistente: "ChatGPT" },
  { host: "www.cursor.com", caminho: /^\/agents\/mcp\/oauth\/callback$/, assistente: "Cursor" },
  { host: "cursor.com", caminho: /^\/agents\/mcp\/oauth\/callback$/, assistente: "Cursor" },
  { host: "vscode.dev", caminho: /^\/redirect$/, assistente: "VS Code" },
  { host: "insiders.vscode.dev", caminho: /^\/redirect$/, assistente: "VS Code" },
];

/* Endereços próprios de aplicativos instalados: quem recebe é o aplicativo, no
   computador da pessoa. */
const APLICATIVOS: Array<Conhecido & { esquema: string }> = [
  { esquema: "cursor:", host: "anysphere.cursor-mcp", caminho: /^\/oauth\/(?:[A-Za-z0-9_-]{1,80}\/)?callback$/, assistente: "Cursor" },
  { esquema: "claude:", host: "claude.ai", caminho: /^\/mcp-auth-callback\/[A-Za-z0-9_-]{1,60}$/, assistente: "Claude" },
];

const COMPUTADOR = new Set(["localhost", "127.0.0.1", "[::1]"]);
const TAMANHO_MAXIMO = 400;

export function lerDestinoDaVolta(bruto: unknown): DestinoDaVolta | null {
  if (typeof bruto !== "string" || !bruto || bruto.length > TAMANHO_MAXIMO || bruto !== bruto.trim()) return null;

  let url: URL;
  try {
    url = new URL(bruto);
  } catch {
    return null;
  }
  /* Usuário e senha no endereço e pedaço depois do `#` nunca fazem parte de um
     retorno legítimo, e os dois já foram usados para enganar conferências. */
  if (url.username || url.password || url.hash) return null;

  if (url.protocol === "http:" && COMPUTADOR.has(url.hostname)) {
    return { endereco: url.toString(), tipo: "computador", rotulo: "um programa neste computador", assistente: null };
  }

  /* Fora do próprio computador, o retorno é um endereço fixo: sem porta e sem
     nada depois do `?`, para não sobrar espaço para um desvio. */
  if (url.port || url.search) return null;

  if (url.protocol === "https:") {
    const site = SITES.find((conhecido) => conhecido.host === url.hostname && conhecido.caminho.test(url.pathname));
    if (!site) return null;
    return { endereco: url.toString(), tipo: "site", rotulo: url.hostname, assistente: site.assistente };
  }

  const aplicativo = APLICATIVOS.find(
    (conhecido) => conhecido.esquema === url.protocol && conhecido.host === url.hostname && conhecido.caminho.test(url.pathname)
  );
  if (aplicativo) {
    return { endereco: url.toString(), tipo: "aplicativo", rotulo: `o aplicativo ${aplicativo.assistente} neste computador`, assistente: aplicativo.assistente };
  }

  return null;
}

/**
 * O endereço pedido agora é um dos que o programa registrou?
 *
 * A comparação é exata, com uma exceção: programa que volta para o próprio
 * computador abre uma porta livre a cada vez, então ali a porta não conta
 * (é o que a norma dos aplicativos instalados manda). `localhost` e
 * `127.0.0.1` continuam sendo endereços diferentes.
 */
export function voltaRegistrada(registrados: readonly string[], pedido: string): boolean {
  const destino = lerDestinoDaVolta(pedido);
  if (!destino) return false;

  for (const registrado of registrados) {
    if (registrado === pedido || registrado === destino.endereco) return true;
    if (destino.tipo !== "computador") continue;
    const outro = lerDestinoDaVolta(registrado);
    if (!outro || outro.tipo !== "computador") continue;
    const a = new URL(destino.endereco);
    const b = new URL(outro.endereco);
    if (a.hostname === b.hostname && a.pathname === b.pathname && a.search === b.search) return true;
  }
  return false;
}
