/**
 * Loja WooCommerce (WordPress): a API pública de loja,
 * `/wp-json/wc/store/v1/products`. Os preços vêm em centavos, e o produto com
 * variação traz só a faixa de preço: o preço de cada variação vem numa
 * segunda chamada (`?type=variation&parent=<id>`), juntada aqui por id.
 */

import { absoluto, dentroDoTeto, linha, menorPreco, precoEmCentavos, produtoVazio, semRepetir, TETOS, type ProdutoLido, type VarianteLida } from "../modelo.ts";
import { lista, objeto, primeiroTexto, texto, type Objeto } from "./dado.ts";

/* Quantas combinações uma variação "qualquer valor" pode virar, no máximo. */
const EXPANSAO_MAXIMA = 500;

export type VariacaoDoWoo = { id: string; preco: number | null; precoAntigo: number | null; foto: string | null; codigo: string | null };

function precos(dado: Objeto) {
  const valores = objeto(dado.prices) ?? {};
  const casas = valores.currency_minor_unit;
  const faixa = objeto(valores.price_range);
  const preco = precoEmCentavos(valores.price, casas) ?? precoEmCentavos(faixa?.min_amount, casas);
  const cheio = precoEmCentavos(valores.regular_price, casas);
  return {
    preco: preco && preco > 0 ? preco : null,
    precoAntigo: cheio && preco && cheio > preco ? cheio : null,
    moeda: primeiroTexto(valores.currency_code) || null,
  };
}

/** Os dados de cada variação, lidos da segunda chamada. */
export function lerVariacoesDoWoo(dado: unknown, origem: string): VariacaoDoWoo[] {
  return lista(dado)
    .slice(0, TETOS.variantes * 5)
    .map((bruto) => objeto(bruto))
    .filter((variacao): variacao is Objeto => Boolean(variacao))
    .map((variacao) => {
      const valores = precos(variacao);
      const foto = objeto(lista(variacao.images)[0]);
      return {
        id: texto(variacao.id),
        preco: valores.preco,
        precoAntigo: valores.precoAntigo,
        foto: absoluto(foto?.src, origem),
        codigo: primeiroTexto(variacao.sku) || null,
      };
    })
    .filter((variacao) => variacao.id);
}

function produto(bruto: unknown, variacoesLidas: Map<string, VariacaoDoWoo>, origem: string): ProdutoLido | null {
  const dado = objeto(bruto);
  if (!dado) return null;
  const nome = linha(dado.name, 300);
  const referencia = absoluto(dado.permalink, origem);
  if (!nome || !referencia) return null;

  const lido = produtoVazio(referencia, nome);
  lido.descricao = primeiroTexto(dado.description, dado.short_description);
  const valores = precos(dado);
  lido.preco = valores.preco;
  lido.precoAntigo = valores.precoAntigo;
  lido.moeda = valores.moeda;
  lido.codigo = primeiroTexto(dado.sku) || null;
  lido.fotos = semRepetir(
    lista(dado.images)
      .slice(0, TETOS.fotos)
      .map((foto) => absoluto(objeto(foto)?.src, referencia))
      .filter((foto): foto is string => Boolean(foto))
  );
  const categoria = linha(objeto(lista(dado.categories)[0])?.name, 120);
  if (categoria) lido.categorias = [categoria];

  /* Atributos que viram escolha: os marcados como "usado em variação". O
     valor na variação é o apelido do termo ("azul-marinho"); o nome que a
     pessoa lê ("Azul marinho") vem da lista de termos do atributo. */
  const atributos = lista(dado.attributes)
    .slice(0, TETOS.atributos)
    .map((bruto2) => objeto(bruto2))
    .filter((atributo): atributo is Objeto => Boolean(atributo) && atributo?.has_variations === true)
    .map((atributo) => {
      const termos = lista(atributo.terms)
        .slice(0, TETOS.valores)
        .map((termo) => objeto(termo))
        .filter((termo): termo is Objeto => Boolean(termo))
        .map((termo) => ({ nome: linha(termo.name, 80), apelido: texto(termo.slug).toLowerCase() }))
        .filter((termo) => termo.nome);
      return { nome: linha(atributo.name, 60), termos };
    })
    .filter((atributo) => atributo.nome && atributo.termos.length);

  const nomeDoValor = (nomeDoAtributo: string, valor: string) => {
    const atributo = atributos.find((candidato) => candidato.nome.toLowerCase() === nomeDoAtributo.toLowerCase());
    const alvo = valor.toLowerCase();
    return atributo?.termos.find((termo) => termo.apelido === alvo || termo.nome.toLowerCase() === alvo)?.nome ?? linha(valor, 80);
  };

  const variantes: VarianteLida[] = [];
  for (const bruta of lista(dado.variations).slice(0, TETOS.variantes)) {
    if (variantes.length >= TETOS.variantes) break;
    const variacao = objeto(bruta);
    if (!variacao) continue;
    const extra = variacoesLidas.get(texto(variacao.id));
    /* Valor vazio é "qualquer um" no WooCommerce: a mesma variação vale para
       todos os valores daquele atributo. As combinações se multiplicam (20
       valores em 6 atributos dariam 64 milhões), então a conta é feita antes
       de expandir: passou do teto, esta variação fica de fora. */
    let combinacoes: Array<Record<string, string>> = [{}];
    for (const par of lista(variacao.attributes).slice(0, TETOS.atributos)) {
      const atributo = objeto(par);
      const nomeDoAtributo = linha(atributo?.name, 60);
      if (!nomeDoAtributo) continue;
      const valor = texto(atributo?.value).trim();
      const valores = valor
        ? [nomeDoValor(nomeDoAtributo, valor)]
        : (atributos.find((candidato) => candidato.nome.toLowerCase() === nomeDoAtributo.toLowerCase())?.termos.map((termo) => termo.nome) ?? []);
      if (combinacoes.length * Math.max(1, valores.length) > EXPANSAO_MAXIMA) {
        combinacoes = [];
        break;
      }
      combinacoes = combinacoes.flatMap((combinacao) => valores.map((escolhido) => ({ ...combinacao, [nomeDoAtributo]: escolhido })));
    }
    for (const opcoes of combinacoes) {
      if (!Object.keys(opcoes).length) continue;
      if (variantes.length >= TETOS.variantes) break;
      variantes.push({
        opcoes,
        preco: extra?.preco ?? null,
        precoAntigo: extra?.precoAntigo ?? null,
        foto: extra?.foto ?? null,
        codigo: extra?.codigo ?? null,
      });
    }
  }

  if (variantes.length > 1) {
    lido.variantes = variantes;
    lido.atributos = atributos.map((atributo) => ({ nome: atributo.nome, valores: atributo.termos.map((termo) => termo.nome) }));
    const menor = menorPreco(variantes.map((variante) => variante.preco));
    if (menor !== null) lido.preco = menor;
  }
  return dentroDoTeto(lido);
}

/** A lista da API de loja; `variacoes` traz os preços das variações já buscados. */
export function lerWooCommerce(dado: unknown, origem: string, variacoes: VariacaoDoWoo[] = []): ProdutoLido[] {
  const porId = new Map(variacoes.map((variacao) => [variacao.id, variacao]));
  return lista(dado)
    .map((bruto) => produto(bruto, porId, origem))
    .filter((lido): lido is ProdutoLido => Boolean(lido));
}

/** Os ids dos produtos com variação, para buscar o preço de cada variação. */
export function produtosComVariacaoNoWoo(dado: unknown): string[] {
  return lista(dado)
    .map((bruto) => objeto(bruto))
    .filter((produto2): produto2 is Objeto => Boolean(produto2) && lista(produto2?.variations).length > 0)
    .map((produto2) => texto(produto2.id))
    .filter(Boolean);
}
