import { ATTR_LABEL, BRANDS, PRODUCTS, SCHEMA, cap, fmtAttr } from "./catalog";
import type { Product, SearchState, SortKey } from "./types";
import { CATEGORIES } from "./types";

export const norm = (x: string) => x.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
export const blank = (): SearchState => ({ q: "", c: "", t: [], brand: [], pmin: null, pmax: null, free: false, offer: false, ver: false, rating: 0, attr: {}, sort: "rel", view: "grid", n: 12 });
const clone = (s: SearchState): SearchState => JSON.parse(JSON.stringify(s));

const NOUN: Record<string, string> = { smartphone: "Celulares", celular: "Celulares", telefone: "Celulares", notebook: "Informática", laptop: "Informática", teclado: "Informática", fone: "Áudio", smartwatch: "Áudio", relogio: "Áudio", caixa: "Áudio", tenis: "Moda", camiseta: "Moda", mochila: "Moda", cafeteira: "Casa", luminaria: "Casa", garrafa: "Casa" };
const SYN: Record<string, string> = { celular: "smartphone", telefone: "smartphone", laptop: "notebook", relogio: "smartwatch", headphone: "fone" };
const STOP = new Set(["de", "da", "do", "com", "para", "e", "a", "o"]);

/** Entende uma busca livre: “celular vento 256gb” → categoria, marca, armazenamento. */
export function fromQuery(q: string): SearchState {
  const s = blank(); s.q = q;
  for (let w of norm(q).split(/[^a-z0-9.]+/).filter(Boolean)) {
    if (w.length > 4 && w.endsWith("s")) w = w.slice(0, -1);
    if (BRANDS.includes(w)) { s.brand.push(w); continue; }
    const m = w.match(/^(\d{2,4})(gb|tb)$/);
    if (m) { (s.attr.storage ??= []).push(String(m[2] === "tb" ? Number(m[1]) * 1024 : m[1])); continue; }
    if (w === "5g") { s.attr.g5 = ["true"]; continue; }
    if (STOP.has(w)) continue;
    if (NOUN[w] && !s.c) s.c = NOUN[w]!;
    s.t.push(SYN[w] ?? w);
  }
  return s;
}

export function serialize(s: SearchState): string {
  const P = new URLSearchParams(); P.set("x", "1");
  if (s.q) P.set("q", s.q); if (s.c) P.set("c", s.c); if (s.t.length) P.set("t", s.t.join(","));
  if (s.brand.length) P.set("marca", s.brand.join(","));
  if (s.pmin != null) P.set("min", String(s.pmin)); if (s.pmax != null) P.set("max", String(s.pmax));
  if (s.free) P.set("frete", "1"); if (s.offer) P.set("oferta", "1"); if (s.ver) P.set("verif", "1");
  if (s.rating) P.set("nota", String(s.rating));
  for (const k in s.attr) if (s.attr[k]!.length) P.set("a_" + k, s.attr[k]!.join(","));
  if (s.sort !== "rel") P.set("ordem", s.sort); if (s.view !== "grid") P.set("vis", s.view);
  return P.toString();
}
export function deserialize(P: URLSearchParams): SearchState {
  const L = (k: string) => (P.get(k) ?? "").split(",").filter(Boolean);
  const s = !P.has("x") && P.get("q") ? fromQuery(P.get("q")!) : blank();
  s.q = P.get("q") ?? "";
  if (P.has("c")) s.c = P.get("c")!;
  if (P.has("x")) { s.t = L("t"); s.brand = L("marca"); }
  if (P.has("min")) s.pmin = Number(P.get("min")); if (P.has("max")) s.pmax = Number(P.get("max"));
  s.free = P.get("frete") === "1" || s.free; s.offer = P.get("oferta") === "1"; s.ver = P.get("verif") === "1";
  s.rating = Number(P.get("nota")) || 0; s.sort = (P.get("ordem") as SortKey) || "rel"; s.view = P.get("vis") === "list" ? "list" : "grid";
  P.forEach((v, k) => { if (k.startsWith("a_")) s.attr[k.slice(2)] = v.split(",").filter(Boolean); });
  return s;
}

export const inCat = (p: Product, c: string) =>
  c === "Esportes" ? ["p-shoe", "p-bot", "p-bag", "p-watch"].includes(p.art) : c === "Ofertas" ? !!p.originalPriceCents : p.cat === c;

export function match(p: Product, s: SearchState, skip = ""): boolean {
  if (s.c && !inCat(p, s.c)) return false;
  if (s.t.length) { const T = norm(p.title); if (!s.t.every((t) => T.includes(t))) return false; }
  if (skip !== "brand" && s.brand.length && !s.brand.includes(p.brand)) return false;
  if (skip !== "price") { const v = p.priceCents / 100; if (s.pmin != null && v < s.pmin) return false; if (s.pmax != null && v > s.pmax) return false; }
  if (s.free && !p.freeShipping) return false; if (s.offer && !p.originalPriceCents) return false;
  if (s.ver && !p.verified) return false; if (s.rating && p.rating < s.rating) return false;
  for (const k in s.attr) if (skip !== "a:" + k && s.attr[k]!.length && !s.attr[k]!.includes(String(p.attrs[k]))) return false;
  return true;
}
export const run = (s: SearchState, skip = "", list: Product[] = PRODUCTS) => list.filter((p) => match(p, s, skip));

const score = (p: Product) => p.rating * Math.log10(p.reviews + 10) + (p.verified ? 0.5 : 0) + (p.freeShipping ? 0.3 : 0);
export const SORTERS: Record<SortKey, (a: Product, b: Product) => number> = {
  rel: (a, b) => score(b) - score(a), pa: (a, b) => a.priceCents - b.priceCents, pd: (a, b) => b.priceCents - a.priceCents,
  vend: (a, b) => b.reviews - a.reviews, nota: (a, b) => b.rating - a.rating || b.reviews - a.reviews, novo: (a, b) => b.id - a.id,
};
export const SORT_LABEL: Record<SortKey, string> = { rel: "Mais relevantes", pa: "Menor preço", pd: "Maior preço", vend: "Mais vendidos", nota: "Melhor avaliados", novo: "Mais recentes" };
export const search = (s: SearchState, list: Product[] = PRODUCTS) => run(s, "", list).sort(SORTERS[s.sort]);

function facet(s: SearchState, skip: string, get: (p: Product) => unknown, list: Product[] = PRODUCTS): [string, number][] {
  const m = new Map<string, number>();
  run(s, skip, list).forEach((p) => { const v = get(p); if (v !== undefined && v !== "") m.set(String(v), (m.get(String(v)) ?? 0) + 1); });
  const a = [...m];
  return a.every((x) => !isNaN(Number(x[0]))) ? a.sort((x, y) => Number(x[0]) - Number(y[0])) : a.sort((x, y) => y[1] - x[1]);
}

/* ---------- modelo dos filtros (a UI só desenha isto) ---------- */
export interface FilterOption { field: string; value: string; label: string; count?: number; checked: boolean }
export type FilterGroup =
  | { id: string; title: string; type: "check" | "radio"; options: FilterOption[] }
  | { id: "price"; title: string; type: "price"; pmin: number | null; pmax: number | null; ranges: { label: string; value: string; on: boolean }[] };
/** Fonte das contagens por faceta: vem de uma lista de produtos (cliente/testes) ou de agregações do OpenSearch (backend). */
export interface FacetSource { cat(): [string, number][]; brand(): [string, number][]; attr(k: string): [string, number][] }
export function listFacets(s: SearchState, list: Product[] = PRODUCTS): FacetSource {
  return {
    cat: () => CATEGORIES.map((c) => [c as string, run(s, "", list).filter((p) => p.cat === c).length] as [string, number]).filter((x) => x[1]),
    brand: () => facet(s, "brand", (p) => p.brand, list),
    attr: (k) => facet(s, "a:" + k, (p) => p.attrs[k], list),
  };
}
export function filterModel(s: SearchState, list: Product[] = PRODUCTS): FilterGroup[] { return filterModelFrom(s, listFacets(s, list)); }
export function filterModelFrom(s: SearchState, src: FacetSource): FilterGroup[] {
  const g: FilterGroup[] = [];
  if (!s.c) g.push({ id: "cat", title: "Categoria", type: "radio", options: src.cat().map(([c, n]) => ({ field: "cat", value: c, label: c, count: n, checked: false })) });
  g.push({ id: "ship", title: "Frete e ofertas", type: "check", options: [
    { field: "free", value: "1", label: "Frete grátis", checked: s.free }, { field: "offer", value: "1", label: "Em oferta", checked: s.offer }, { field: "ver", value: "1", label: "Vendedor verificado", checked: s.ver }] });
  g.push({ id: "brand", title: "Marca", type: "check", options: src.brand().map(([v, n]) => ({ field: "brand", value: v, label: cap(v), count: n, checked: s.brand.includes(v) })) });
  (SCHEMA[s.c] ?? []).forEach(([k, l, u]) => {
    const f = src.attr(k);
    if (f.length) g.push({ id: "a" + k, title: l, type: "check", options: f.map(([v, n]) => ({ field: "a_" + k, value: v, label: fmtAttr(v, u), count: n, checked: (s.attr[k] ?? []).includes(v) })) });
  });
  const ranges: [number, number | null][] = [[0, 100], [100, 300], [300, 1000], [1000, null]];
  g.push({ id: "price", title: "Preço", type: "price", pmin: s.pmin, pmax: s.pmax, ranges: ranges.map(([a, b]) => ({ label: b ? `R$ ${a} a ${b}` : `Acima de R$ ${a}`, value: `${a},${b ?? ""}`, on: s.pmin === a && s.pmax === b })) });
  g.push({ id: "rating", title: "Avaliação", type: "radio", options: [[4, "4 estrelas ou mais"], [3, "3 estrelas ou mais"], [0, "Todas"]].map(([v, l]) => ({ field: "rating", value: String(v), label: String(l), checked: s.rating === v })) });
  return g;
}

/** Aplica uma mudança de filtro e devolve um novo estado (imutável). */
export function applyFilter(s0: SearchState, field: string, value: string, checked = true): SearchState {
  const s = clone(s0); const tg = (a: string[], v: string, on: boolean) => { const i = a.indexOf(v); if (on && i < 0) a.push(v); if (!on && i >= 0) a.splice(i, 1); };
  if (field === "cat") { s.c = value; s.attr = {}; }
  else if (field === "brand") tg(s.brand, value, checked);
  else if (field.startsWith("a_")) { const k = field.slice(2); s.attr[k] ??= []; tg(s.attr[k]!, value, checked); }
  else if (field === "free" || field === "offer" || field === "ver") s[field] = checked;
  else if (field === "rating") s.rating = Number(value);
  else if (field === "pmin") s.pmin = value === "" ? null : Number(value);
  else if (field === "pmax") s.pmax = value === "" ? null : Number(value);
  else if (field === "price") { const [a = "", b = ""] = value.split(","); s.pmin = Number(a); s.pmax = b === "" ? null : Number(b); }
  s.n = 12; return s;
}
export function clearFilters(s0: SearchState): SearchState { const s = clone(s0); Object.assign(s, { t: [], brand: [], pmin: null, pmax: null, free: false, offer: false, ver: false, rating: 0, attr: {}, n: 12 }); return s; }

export interface Chip { label: string; value: string; key: string }
export function chips(s: SearchState): Chip[] {
  const c: Chip[] = [];
  s.t.forEach((t) => c.push({ label: "", value: `“${t}”`, key: "t|" + t }));
  if (s.c) c.push({ label: "Categoria", value: s.c, key: "c|" });
  s.brand.forEach((b) => c.push({ label: "Marca", value: cap(b), key: "brand|" + b }));
  for (const k in s.attr) s.attr[k]!.forEach((v) => { const [l, u] = ATTR_LABEL[k] ?? [k, ""]; c.push({ label: l, value: fmtAttr(v, u), key: `a|${k}|${v}` }); });
  if (s.pmin != null || s.pmax != null) c.push({ label: "Preço", value: `R$ ${s.pmin ?? 0} a ${s.pmax ?? "∞"}`, key: "price|" });
  if (s.free) c.push({ label: "", value: "Frete grátis", key: "free|" });
  if (s.offer) c.push({ label: "", value: "Em oferta", key: "offer|" });
  if (s.ver) c.push({ label: "", value: "Vendedor verificado", key: "ver|" });
  if (s.rating) c.push({ label: "Avaliação", value: s.rating + "+", key: "rating|" });
  return c;
}
export function removeChip(s0: SearchState, key: string): SearchState {
  const s = clone(s0); const [a, b = "", c = ""] = key.split("|");
  if (a === "t") s.t = s.t.filter((x) => x !== b); else if (a === "c") s.c = "";
  else if (a === "brand") s.brand = s.brand.filter((x) => x !== b);
  else if (a === "a") s.attr[b] = (s.attr[b] ?? []).filter((x) => x !== c);
  else if (a === "price") { s.pmin = null; s.pmax = null; }
  else if (a === "free" || a === "offer" || a === "ver") s[a] = false; else if (a === "rating") s.rating = 0;
  s.n = 12; return s;
}
/** Para o painel de sugestões: o que o sistema entendeu da busca. */
export function understood(q: string): string[] {
  const s = fromQuery(q); const out: string[] = [];
  if (s.c) out.push(s.c); s.brand.forEach((b) => out.push(cap(b)));
  for (const k in s.attr) s.attr[k]!.forEach((v) => { const [, u] = ATTR_LABEL[k] ?? [k, ""]; out.push(k === "g5" ? "5G" : fmtAttr(v, u)); });
  return out;
}
