/*
  Os serviços opcionais — a biblioteca da empresa.

  Gêmeo do arquivo de mesmo nome na loja (apps/public). Os dois apps não
  compartilham código, e este cadastro precisa das mesmas regras dos dois
  lados: o admin grava, a loja lê. Quando um mudar, o outro muda junto.

  "Criação de arte", "Revisão de arquivo", "Montagem no local": o que a
  gráfica faz além de entregar a peça. Cadastrado UMA VEZ na empresa, e cada
  produto só decide se oferece. Cadastrar produto a produto era o desenho
  anterior e estava errado: o mesmo serviço em mil produtos vira mil preços
  diferentes na primeira vez que alguém corrige um deles.

  Por dentro, cada serviço escolhido é uma **opção com preço** — a mesma coisa
  que uma variação. É isso que o faz viajar sozinho para o pedido, o orçamento,
  o PDV, a ficha de produção e o portal do cliente, sem um único caminho novo.
  O `attribute_name` da opção é o **nome do grupo**, que o lojista escolhe: no
  cupom sai "Acabamento: Verniz", não "Serviços: Verniz".

  Duas regras que este arquivo existe para não deixar ninguém esquecer:

  1. Serviço quase sempre é cobrado **uma vez**, não por peça. Quem pede 500
     canetas paga uma arte, não quinhentas.
  2. O grupo manda em quantos podem ser marcados. "Escolher um" é o que
     impede o cliente de somar duas entregas ou dois prazos.
*/

export type ModoDeEscolha = "varias" | "uma";

export type IconeDoServico =
  | "arte"
  | "revisao"
  | "montagem"
  | "entrega"
  | "urgencia"
  | "embalagem"
  | "acabamento"
  | "impressao"
  | "corte"
  | "projeto"
  | "foto"
  | "garantia"
  | "servico";

export const ICONES_DE_SERVICO: IconeDoServico[] = [
  "arte",
  "revisao",
  "montagem",
  "entrega",
  "urgencia",
  "embalagem",
  "acabamento",
  "impressao",
  "corte",
  "projeto",
  "foto",
  "garantia",
  "servico",
];

/* De onde vem o custo do serviço — o que separa isto de um checkbox bonito. */
export type TipoDeCusto = "nenhum" | "insumo" | "mao_de_obra" | "maquina";

export type GrupoDeServicos = {
  id: string;
  nome: string;
  modo: ModoDeEscolha;
  /* Só faz sentido com modo "uma": obriga a escolher antes de comprar. */
  obrigatorio: boolean;
  posicao: number;
  ativo: boolean;
};

export type ServicoOpcional = {
  id: string;
  grupoId: string | null;
  titulo: string;
  descricao: string | null;
  /* O texto do (i). Só existe quando o lojista escreveu — sem ele, nada de
     ícone de ajuda pendurado na tela sem função. */
  detalhes: string | null;
  icone: IconeDoServico;
  preco: number;
  /* Falso (o padrão) = cobrado uma vez no pedido. */
  porPeca: boolean;
  valeParaTodos: boolean;
  posicao: number;
  ativo: boolean;
};

export type CustoDoServico = {
  tipo: TipoDeCusto;
  materialId: string | null;
  materialQtd: number;
  roleId: string | null;
  machineId: string | null;
  minutos: number;
  setupMinutos: number;
};

/* O grupo que recebe quem não tem grupo. Existe para a loja nunca desenhar
   uma seção sem título. */
export const GRUPO_PADRAO = "Serviços";

export type GrupoNaVitrine = {
  grupo: GrupoDeServicos;
  servicos: ServicoOpcional[];
};

function texto(valor: unknown) {
  return String(valor ?? "").trim();
}

function numero(valor: unknown, padrao = 0) {
  const n = Number(valor);
  return Number.isFinite(n) && n >= 0 ? n : padrao;
}

function ehUuid(valor: string) {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(valor);
}

export function novoIdDeServico() {
  if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
  /* Sem `randomUUID` (navegador antigo, contexto inseguro) ainda precisa sair
     um UUID de verdade: é ele que amarra o serviço ao custo e ao histórico. */
  const aleatorio = () => Math.floor(Math.random() * 16).toString(16);
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) =>
    c === "x" ? aleatorio() : (Math.floor(Math.random() * 4) + 8).toString(16)
  );
}

/** Um grupo vindo do banco, na forma que a tela usa. */
export function lerGrupo(bruto: unknown): GrupoDeServicos | null {
  if (!bruto || typeof bruto !== "object" || Array.isArray(bruto)) return null;
  const linha = bruto as Record<string, unknown>;
  const id = texto(linha.id);
  const nome = texto(linha.name ?? linha.nome);
  if (!ehUuid(id) || !nome) return null;
  return {
    id: id.toLowerCase(),
    nome,
    modo: (linha.selection_mode ?? linha.modo) === "uma" ? "uma" : "varias",
    obrigatorio: (linha.required ?? linha.obrigatorio) === true,
    posicao: Number.isFinite(Number(linha.position ?? linha.posicao)) ? Number(linha.position ?? linha.posicao) : 0,
    ativo: (linha.active ?? linha.ativo) !== false,
  };
}

export function lerGrupos(bruto: unknown): GrupoDeServicos[] {
  if (!Array.isArray(bruto)) return [];
  return bruto
    .map(lerGrupo)
    .filter((g): g is GrupoDeServicos => Boolean(g))
    .sort((a, b) => a.posicao - b.posicao || a.nome.localeCompare(b.nome, "pt-BR"));
}

/** Um serviço vindo do banco, na forma que a tela usa. */
export function lerServico(bruto: unknown): ServicoOpcional | null {
  if (!bruto || typeof bruto !== "object" || Array.isArray(bruto)) return null;
  const linha = bruto as Record<string, unknown>;
  const titulo = texto(linha.title ?? linha.titulo);
  if (!titulo) return null;
  const id = texto(linha.id);
  const grupoId = texto(linha.group_id ?? linha.grupoId);
  const icone = texto(linha.icon ?? linha.icone) as IconeDoServico;
  return {
    id: ehUuid(id) ? id.toLowerCase() : novoIdDeServico(),
    grupoId: ehUuid(grupoId) ? grupoId.toLowerCase() : null,
    titulo,
    descricao: texto(linha.description ?? linha.descricao) || null,
    detalhes: texto(linha.details ?? linha.detalhes) || null,
    icone: ICONES_DE_SERVICO.includes(icone) ? icone : "servico",
    preco: Math.round(numero(linha.price ?? linha.preco) * 100) / 100,
    /* O padrão é cobrar uma vez: é o certo para arte, revisão e projeto. */
    porPeca: (linha.charge_per_unit ?? linha.porPeca) === true,
    valeParaTodos: (linha.apply_to_all ?? linha.valeParaTodos) === true,
    posicao: Number.isFinite(Number(linha.position ?? linha.posicao)) ? Number(linha.position ?? linha.posicao) : 0,
    ativo: (linha.active ?? linha.ativo) !== false,
  };
}

export function lerServicos(bruto: unknown): ServicoOpcional[] {
  if (!Array.isArray(bruto)) return [];
  return bruto
    .map(lerServico)
    .filter((s): s is ServicoOpcional => Boolean(s))
    .sort((a, b) => a.posicao - b.posicao || a.titulo.localeCompare(b.titulo, "pt-BR"));
}

export function lerCusto(bruto: unknown): CustoDoServico {
  const vazio: CustoDoServico = {
    tipo: "nenhum",
    materialId: null,
    materialQtd: 0,
    roleId: null,
    machineId: null,
    minutos: 0,
    setupMinutos: 0,
  };
  if (!bruto || typeof bruto !== "object" || Array.isArray(bruto)) return vazio;
  const linha = bruto as Record<string, unknown>;
  const tipo = texto(linha.cost_kind ?? linha.tipo) as TipoDeCusto;
  const materialId = texto(linha.material_id ?? linha.materialId);
  const roleId = texto(linha.role_id ?? linha.roleId);
  const machineId = texto(linha.machine_id ?? linha.machineId);
  return {
    tipo: (["nenhum", "insumo", "mao_de_obra", "maquina"] as TipoDeCusto[]).includes(tipo) ? tipo : "nenhum",
    materialId: ehUuid(materialId) ? materialId : null,
    materialQtd: numero(linha.material_qty ?? linha.materialQtd),
    roleId: ehUuid(roleId) ? roleId : null,
    machineId: ehUuid(machineId) ? machineId : null,
    minutos: numero(linha.minutes ?? linha.minutos),
    setupMinutos: numero(linha.setup_minutes ?? linha.setupMinutos),
  };
}

/**
 * Quais serviços um produto oferece.
 *
 * A regra existe para o lojista poder dizer "isto vale para todos" e ainda
 * assim desligar num produto: **o vínculo do produto sempre vence**; sem
 * vínculo, decide o `valeParaTodos` do serviço.
 */
export function servicosDoProduto(
  servicos: ServicoOpcional[],
  vinculos: Array<{ service_id?: unknown; enabled?: unknown }> | null | undefined
): ServicoOpcional[] {
  const decisao = new Map<string, boolean>();
  for (const vinculo of vinculos || []) {
    const id = texto(vinculo?.service_id).toLowerCase();
    if (!ehUuid(id)) continue;
    decisao.set(id, vinculo?.enabled !== false);
  }
  return servicos.filter((servico) => {
    if (!servico.ativo) return false;
    const escolhido = decisao.get(servico.id);
    return escolhido === undefined ? servico.valeParaTodos : escolhido;
  });
}

/**
 * Os serviços prontos para desenhar: agrupados, na ordem, sem grupo vazio.
 *
 * Quem não tem grupo cai num grupo padrão de múltipla escolha, para a loja
 * nunca desenhar uma seção sem título nem inventar regra.
 */
export function vitrineDeServicos(
  grupos: GrupoDeServicos[],
  servicos: ServicoOpcional[]
): GrupoNaVitrine[] {
  const porId = new Map(grupos.filter((g) => g.ativo).map((g) => [g.id, g]));
  const avulso: GrupoDeServicos = {
    id: "",
    nome: GRUPO_PADRAO,
    modo: "varias",
    obrigatorio: false,
    posicao: Number.MAX_SAFE_INTEGER,
    ativo: true,
  };

  const caixas = new Map<string, GrupoNaVitrine>();
  for (const servico of servicos) {
    const grupo = (servico.grupoId && porId.get(servico.grupoId)) || avulso;
    const caixa = caixas.get(grupo.id);
    if (caixa) caixa.servicos.push(servico);
    else caixas.set(grupo.id, { grupo, servicos: [servico] });
  }

  return [...caixas.values()]
    .map((caixa) => ({
      grupo: caixa.grupo,
      servicos: [...caixa.servicos].sort((a, b) => a.posicao - b.posicao),
    }))
    .sort((a, b) => a.grupo.posicao - b.grupo.posicao || a.grupo.nome.localeCompare(b.grupo.nome, "pt-BR"));
}

/**
 * O que fica marcado depois de clicar num serviço.
 *
 * Em grupo de escolha única, clicar num serviço **desmarca os irmãos** — é o
 * que impede duas entregas no mesmo pedido. Clicar no que já estava marcado
 * desmarca, a não ser que o grupo seja obrigatório: aí a escolha continua,
 * porque tirar levaria o cliente a um estado que a loja não aceita.
 */
export function alternarEscolha(
  marcados: string[],
  servico: ServicoOpcional,
  caixa: GrupoNaVitrine
): string[] {
  const jaEstava = marcados.includes(servico.id);
  if (caixa.grupo.modo === "uma") {
    const irmaos = new Set(caixa.servicos.map((s) => s.id));
    const semIrmaos = marcados.filter((id) => !irmaos.has(id));
    if (jaEstava && !caixa.grupo.obrigatorio) return semIrmaos;
    return [...semIrmaos, servico.id];
  }
  return jaEstava ? marcados.filter((id) => id !== servico.id) : [...marcados, servico.id];
}

/**
 * O grupo obrigatório que ainda não foi respondido, se houver.
 *
 * A loja usa para travar o botão de comprar com um recado que diz o que
 * falta, em vez de um botão morto sem explicação.
 */
export function grupoPendente(vitrine: GrupoNaVitrine[], marcados: string[]): GrupoNaVitrine | null {
  for (const caixa of vitrine) {
    if (!caixa.grupo.obrigatorio) continue;
    const respondeu = caixa.servicos.some((s) => marcados.includes(s.id));
    if (!respondeu) return caixa;
  }
  return null;
}

/**
 * O serviço escolhido, na forma de opção — o pacote que o pedido entende.
 *
 * `affects_stock` é sempre falso: serviço não baixa estoque de peça. Quando
 * ele consome um insumo, quem cuida disso é a receita do serviço, no banco.
 */
export function servicoComoOpcao(servico: ServicoOpcional, nomeDoGrupo?: string | null) {
  return {
    option_id: servico.id,
    attribute_name: texto(nomeDoGrupo) || GRUPO_PADRAO,
    label: servico.titulo,
    price: servico.preco,
    price_applies_per_quantity: servico.porPeca,
    /* Serviço "por unidade" conta por peça também no produto por m²:
       ninguém cobra revisão de arte por metro. */
    price_unit: servico.porPeca ? "piece" : "order",
    affects_stock: false,
  };
}

/** O que os serviços marcados somam: por peça e uma vez só. */
export function somaDosServicos(vitrine: GrupoNaVitrine[], marcados: string[]) {
  let porQuantidade = 0;
  let umaVez = 0;
  for (const caixa of vitrine) {
    for (const servico of caixa.servicos) {
      if (!marcados.includes(servico.id)) continue;
      if (servico.porPeca) porQuantidade += servico.preco;
      else umaVez += servico.preco;
    }
  }
  return { porQuantidade, umaVez };
}

/** Os escolhidos, com o nome do grupo de cada um, na ordem da vitrine. */
export function escolhidosComGrupo(vitrine: GrupoNaVitrine[], marcados: string[]) {
  const lista: Array<{ servico: ServicoOpcional; grupo: GrupoDeServicos }> = [];
  for (const caixa of vitrine) {
    for (const servico of caixa.servicos) {
      if (marcados.includes(servico.id)) lista.push({ servico, grupo: caixa.grupo });
    }
  }
  return lista;
}
