/*
  O aparelho de quem abriu o link, pelo que o próprio navegador diz.

  Nada aqui é adivinhado: o tipo, o sistema, o navegador e o aplicativo saem
  do "user agent" (a identificação que todo navegador manda) e, quando o
  navegador oferece, das "dicas" que ele dá a mais (o Chrome esconde o modelo
  do Android no user agent desde 2023 e só conta por essa outra via). O que
  não dá para saber fica vazio — o iPhone, por exemplo, nunca diz o modelo.

  Robôs (buscadores, pré-visualização de links, testes automáticos) não
  contam como leitura.
*/

export type TipoDeAparelho = "celular" | "tablet" | "computador";

export type Aparelho = {
  tipo: TipoDeAparelho;
  sistema: string | null;
  versao: string | null;
  navegador: string | null;
  modelo: string | null;
  app: string | null;
  robo: boolean;
};

export type DicasDoNavegador = {
  /* navigator.userAgentData: modelo e versão do sistema, quando existem. */
  modelo?: string | null;
  versao?: string | null;
  /* navigator.maxTouchPoints: o iPad se apresenta como Mac, mas tem toque. */
  toques?: number | null;
};

/* Pelo nome dos robôs conhecidos, e não por "bot" solto: há celular com "bot"
   no nome do modelo. */
const ROBO =
  /(?:google|bing|yandex|duckduck|apple|ahrefs|semrush|mj12|dot|petal|slack|twitter|linkedin|discord|telegram|pinterest|baidu|sogou|seznam|amazon|claude|gpt|perplexity|cc|facebook|meta|yeti|naver|qwant|coccoc)bot|\bbot\b|\bbot\/|bytespider|crawl|spider|slurp|facebookexternalhit|facebookcatalog|embedly|headlesschrome|lighthouse|pagespeed|pingdom|uptimerobot|python-requests|curl\/|wget\/|go-http-client|node-fetch|axios\/|java\/|okhttp|phantomjs/i;

/* Aplicativos que abrem o link dentro deles mesmos. */
const APPS: Array<[RegExp, string]> = [
  [/Instagram/i, "Instagram"],
  [/FBAN|FBAV|FB_IAB|FBIOS|FB4A/i, "Facebook"],
  [/WhatsApp/i, "WhatsApp"],
  [/LinkedInApp/i, "LinkedIn"],
  [/musical_ly|TikTok|BytedanceWebview/i, "TikTok"],
  [/Pinterest/i, "Pinterest"],
  [/Snapchat/i, "Snapchat"],
  [/Telegram/i, "Telegram"],
  [/\bLine\//i, "LINE"],
  [/\bGSA\//i, "Google"],
];

/* Prefixo do código do modelo → fabricante. Só os comuns no Brasil. */
const FABRICANTES: Array<[RegExp, string]> = [
  [/^(SM-|GT-|SAMSUNG)/i, "Samsung"],
  [/^(moto|XT\d)/i, "Motorola"],
  [/^(Redmi|POCO|Mi\s|M\d{4}[A-Z]|2\d{3}[A-Z0-9]{4,}|22\d{6}[A-Z]|23\d{6}[A-Z])/i, "Xiaomi"],
  [/^(LM-|LG-)/i, "LG"],
  [/^RMX/i, "Realme"],
  [/^CPH/i, "OPPO"],
  [/^(ASUS|ASUS_)/i, "Asus"],
  [/^Pixel/i, "Google"],
  [/^Nokia/i, "Nokia"],
  [/^(HUAWEI|[A-Z]{3}-(L|LX)\d)/i, "Huawei"],
  [/^(Infinix)/i, "Infinix"],
  [/^(TECNO)/i, "Tecno"],
  [/^(ONEPLUS|KB\d{4}|IN\d{4}|LE\d{4})/i, "OnePlus"],
];

const texto = (valor: unknown) => (typeof valor === "string" ? valor.trim() : "");

/* "17_5_1" → "17.5". Só o que importa para gente: a versão e a primeira casa. */
function versaoCurta(bruta: string | null | undefined) {
  const partes = texto(bruta).replace(/_/g, ".").split(".").filter(Boolean);
  if (!partes.length || !/^\d+$/.test(partes[0])) return null;
  const segunda = partes[1] && /^\d+$/.test(partes[1]) && partes[1] !== "0" ? `.${partes[1]}` : "";
  return `${partes[0]}${segunda}`;
}

export function fabricanteDoModelo(modelo: string | null | undefined) {
  const limpo = texto(modelo);
  if (!limpo) return null;
  return FABRICANTES.find(([padrao]) => padrao.test(limpo))?.[1] ?? null;
}

/* O modelo como a pessoa reconhece: "Samsung SM-A536E", "Motorola moto g(60)". */
export function nomeDoModelo(modelo: string | null | undefined) {
  const limpo = texto(modelo).replace(/\s+/g, " ");
  /* "K" é o que o Chrome põe no lugar do modelo quando esconde. */
  if (!limpo || limpo === "K" || /^Android/i.test(limpo) || limpo.length > 40) return null;
  const marca = fabricanteDoModelo(limpo);
  if (!marca) return limpo;
  /* "SAMSUNG SM-S918B" e "SM-S918B" viram o mesmo "Samsung SM-S918B". */
  const semMarca = limpo.replace(new RegExp(`^${marca}[\\s_-]+`, "i"), "");
  return semMarca.toLowerCase().startsWith(marca.toLowerCase()) ? semMarca : `${marca} ${semMarca}`;
}

/* O trecho entre parênteses do Android, pedaço por pedaço: "Android 13; moto
   g(60) Build/T1RD33; wv". O modelo pode ter parênteses no nome, então não dá
   para cortar no primeiro ")". */
function modeloDoAndroid(agente: string) {
  const inicio = agente.search(/Android/i);
  if (inicio < 0) return null;
  const fim = agente.indexOf(") AppleWebKit", inicio);
  const trecho = (fim > 0 ? agente.slice(inicio, fim) : agente.slice(inicio).replace(/\)[^()]*$/, "")).trim();
  for (const parte of trecho.split(";").slice(1)) {
    const semBuild = parte.replace(/\s*Build\/.*$/i, "").trim();
    if (!semBuild || /^(wv|Mobile|Tablet|U|Linux|[a-z]{2}(?:[-_][a-z]{2})?)$/i.test(semBuild) || /^rv:/i.test(semBuild)) continue;
    return semBuild;
  }
  return null;
}

function navegadorDoAgente(agente: string) {
  if (/EdgA\/|EdgiOS\/|Edg\/|Edge\//.test(agente)) return "Edge";
  if (/OPR\/|Opera|OPiOS/.test(agente)) return "Opera";
  if (/SamsungBrowser/.test(agente)) return "Samsung Internet";
  if (/YaBrowser/.test(agente)) return "Yandex";
  if (/Firefox\/|FxiOS/.test(agente)) return "Firefox";
  if (/CriOS/.test(agente)) return "Chrome";
  if (/;\s*wv\)/.test(agente)) return null;
  if (/Chrome\/|Chromium\//.test(agente)) return "Chrome";
  if (/Version\/[\d.]+.*Safari/.test(agente)) return "Safari";
  return null;
}

export function lerAparelho(agenteBruto: string | null | undefined, dicas: DicasDoNavegador = {}): Aparelho {
  const agente = texto(agenteBruto).slice(0, 600);
  const robo = !agente || ROBO.test(agente);
  const toques = Number(dicas.toques) || 0;
  const app = APPS.find(([padrao]) => padrao.test(agente))?.[1] ?? null;

  let tipo: TipoDeAparelho = "computador";
  let sistema: string | null = null;
  let versao: string | null = null;
  let modelo: string | null = nomeDoModelo(dicas.modelo);

  if (/iPad/.test(agente) || (/Macintosh/.test(agente) && toques > 1)) {
    tipo = "tablet";
    sistema = "iPadOS";
    versao = versaoCurta(agente.match(/OS (\d+[_.]\d+(?:[_.]\d+)?)/)?.[1] ?? agente.match(/Version\/(\d+(?:\.\d+)?)/)?.[1]);
    modelo = "iPad";
  } else if (/iPhone|iPod/.test(agente)) {
    tipo = "celular";
    sistema = "iOS";
    versao = versaoCurta(agente.match(/OS (\d+[_.]\d+(?:[_.]\d+)?)/)?.[1]);
    modelo = "iPhone";
  } else if (/Android/i.test(agente)) {
    tipo = /Mobile/i.test(agente) ? "celular" : "tablet";
    sistema = "Android";
    versao = versaoCurta(dicas.versao) ?? versaoCurta(agente.match(/Android\s*([\d.]+)/i)?.[1]);
    modelo = modelo ?? nomeDoModelo(modeloDoAndroid(agente));
  } else if (/Windows/.test(agente)) {
    sistema = "Windows";
    const nt = agente.match(/Windows NT (\d+\.\d+)/)?.[1];
    /* O Windows 11 também diz "NT 10.0"; só a dica do navegador separa os dois. */
    const plataforma = Number(texto(dicas.versao).split(".")[0]);
    versao =
      nt === "10.0"
        ? Number.isFinite(plataforma) && plataforma >= 13
          ? "11"
          : Number.isFinite(plataforma) && plataforma > 0
            ? "10"
            : null
        : ({ "6.3": "8.1", "6.2": "8", "6.1": "7" } as Record<string, string>)[nt ?? ""] ?? null;
    modelo = null;
  } else if (/CrOS/.test(agente)) {
    sistema = "ChromeOS";
    modelo = null;
  } else if (/Macintosh|Mac OS X/.test(agente)) {
    sistema = "macOS";
    modelo = null;
  } else if (/Linux/.test(agente)) {
    sistema = "Linux";
    modelo = null;
  }

  /* Dentro de um app, o "navegador" é o do app; o Safari/Chrome por trás não
     diz nada de útil. */
  const navegador = app ? null : navegadorDoAgente(agente) ?? (sistema === "iOS" || sistema === "iPadOS" ? "Safari" : null);

  return { tipo, sistema, versao, navegador, modelo, app, robo };
}

/* De onde o link foi aberto, quando o navegador conta: um app do Android
   (android-app://com.whatsapp) ou um site. */
export function origemDoLink(bruta: string | null | undefined, hostDaPagina?: string | null) {
  const valor = texto(bruta);
  if (!valor) return null;
  const doApp = valor.match(/^android-app:\/\/([^/]+)/i)?.[1]?.toLowerCase();
  if (doApp) {
    if (doApp.includes("whatsapp")) return "WhatsApp";
    if (doApp.includes("instagram")) return "Instagram";
    if (doApp.includes("facebook")) return "Facebook";
    if (doApp.includes("google.android.gm")) return "Gmail";
    if (doApp.includes("telegram")) return "Telegram";
    if (doApp.includes("googlequicksearchbox")) return "Google";
    return "Aplicativo";
  }
  try {
    const host = new URL(valor).hostname.replace(/^www\./, "").toLowerCase();
    if (!host || (hostDaPagina && host === hostDaPagina.replace(/^www\./, "").toLowerCase())) return null;
    if (host.includes("whatsapp") || host === "wa.me") return "WhatsApp";
    if (host.includes("instagram")) return "Instagram";
    if (host.includes("facebook") || host === "fb.com" || host === "m.me") return "Facebook";
    if (host.includes("mail.google") || host.includes("gmail")) return "Gmail";
    if (host.includes("outlook") || host.includes("live.com")) return "Outlook";
    if (host.includes("google")) return "Google";
    return host.slice(0, 60);
  } catch {
    return null;
  }
}
