/**
 * As contas dos painéis, sem banco: recebem as linhas já lidas e devolvem o
 * total e os grupos. Ficam aqui, separadas, para serem testadas.
 */

import { casaDoDia, casasDoTempo, granularidadeDe, rotuloDaCasa, type Intervalo } from "./periodos.ts";

export const DIMENSOES = ["tempo", "vendedor", "canal", "produto", "cliente", "etapa", "situacao", "origem", "fornecedor", "tipo"] as const;
export type Dimensao = (typeof DIMENSOES)[number];

export const ROTULO_DA_DIMENSAO: Record<Dimensao, string> = {
  tempo: "Ao longo do tempo",
  vendedor: "Por vendedor",
  canal: "Por canal de venda",
  produto: "Por produto",
  cliente: "Por cliente",
  etapa: "Por etapa da produção",
  situacao: "Por situação",
  origem: "Por origem",
  fornecedor: "Por fornecedor",
  tipo: "Entradas e saídas",
};

export type Valor = { chave: string; rotulo: string };

/**
 * Uma linha lida do banco, já traduzida: o dia em que conta, o valor de cada
 * agrupamento possível e os números. `num` é o que se soma; `den`, quando a
 * métrica é uma razão (ticket médio, taxa de conversão), é o que divide.
 */
export type Linha = {
  dia: string | null;
  dims: Partial<Record<Dimensao, Valor>>;
  num: number;
  den?: number;
  extra?: Record<string, number>;
};

export type Metodo = "soma" | "razao" | "percentual";

export type Grupo = { chave: string; rotulo: string; valor: number; extra?: Record<string, number> };

export type Agregado = { valor: number; grupos: Grupo[] | null };

type Acumulado = { rotulo: string; num: number; den: number; extra: Record<string, number> };

function arredondar(valor: number) {
  return Math.round(valor * 100) / 100;
}

function valorDe(metodo: Metodo, num: number, den: number) {
  if (metodo === "soma") return arredondar(num);
  if (!den) return 0;
  return arredondar(metodo === "percentual" ? (num / den) * 100 : num / den);
}

function somarExtra(destino: Record<string, number>, origem: Record<string, number> | undefined) {
  if (!origem) return;
  for (const [chave, valor] of Object.entries(origem)) destino[chave] = (destino[chave] ?? 0) + valor;
}

function arredondarExtra(extra: Record<string, number>) {
  const limpo: Record<string, number> = {};
  for (const [chave, valor] of Object.entries(extra)) limpo[chave] = arredondar(valor);
  return Object.keys(limpo).length ? limpo : undefined;
}

export const ROTULO_OUTROS = "Outros";

/**
 * Soma as linhas. Sem agrupamento, devolve só o total. Agrupado pelo tempo,
 * devolve todas as casas do período na ordem, inclusive as vazias. Agrupado
 * por outra coisa, devolve do maior para o menor, com o que passa do limite
 * juntado em "Outros" (assim a soma das fatias continua dando o total).
 */
export function agregar(
  linhas: Linha[],
  metodo: Metodo,
  dimensao: Dimensao | null,
  opcoes: { intervalo?: Intervalo; limite?: number | null } = {}
): Agregado {
  let num = 0;
  let den = 0;
  for (const linha of linhas) {
    num += linha.num;
    den += linha.den ?? 0;
  }
  const valor = valorDe(metodo, num, den);
  if (!dimensao) return { valor, grupos: null };

  const mapa = new Map<string, Acumulado>();
  const granularidade = dimensao === "tempo" && opcoes.intervalo ? granularidadeDe(opcoes.intervalo) : "dia";

  if (dimensao === "tempo" && opcoes.intervalo) {
    for (const casa of casasDoTempo(opcoes.intervalo, granularidade)) {
      mapa.set(casa, { rotulo: rotuloDaCasa(casa, granularidade), num: 0, den: 0, extra: {} });
    }
  }

  for (const linha of linhas) {
    let chave: string;
    let rotulo: string;
    if (dimensao === "tempo") {
      if (!linha.dia) continue;
      chave = casaDoDia(linha.dia, granularidade);
      rotulo = rotuloDaCasa(chave, granularidade);
      /* Linha fora do período (não deveria vir): não abre casa nova. */
      if (opcoes.intervalo && !mapa.has(chave)) continue;
    } else {
      const dim = linha.dims[dimensao];
      if (!dim) continue;
      chave = dim.chave;
      rotulo = dim.rotulo;
    }
    const atual = mapa.get(chave) ?? { rotulo, num: 0, den: 0, extra: {} };
    atual.num += linha.num;
    atual.den += linha.den ?? 0;
    somarExtra(atual.extra, linha.extra);
    mapa.set(chave, atual);
  }

  const todos = [...mapa.entries()].map(([chave, acc]) => ({ chave, acc }));

  if (dimensao === "tempo") {
    todos.sort((a, b) => a.chave.localeCompare(b.chave));
    return { valor, grupos: todos.map(({ chave, acc }) => ({ chave, rotulo: acc.rotulo, valor: valorDe(metodo, acc.num, acc.den), extra: arredondarExtra(acc.extra) })) };
  }

  todos.sort((a, b) => valorDe(metodo, b.acc.num, b.acc.den) - valorDe(metodo, a.acc.num, a.acc.den) || a.acc.rotulo.localeCompare(b.acc.rotulo, "pt-BR"));

  const limite = opcoes.limite && opcoes.limite > 0 ? Math.floor(opcoes.limite) : null;
  const visiveis = limite ? todos.slice(0, limite) : todos;
  const resto = limite ? todos.slice(limite) : [];
  const grupos: Grupo[] = visiveis.map(({ chave, acc }) => ({ chave, rotulo: acc.rotulo, valor: valorDe(metodo, acc.num, acc.den), extra: arredondarExtra(acc.extra) }));

  if (resto.length) {
    const outros: Acumulado = { rotulo: ROTULO_OUTROS, num: 0, den: 0, extra: {} };
    for (const { acc } of resto) {
      outros.num += acc.num;
      outros.den += acc.den;
      somarExtra(outros.extra, acc.extra);
    }
    grupos.push({ chave: "__outros__", rotulo: `${ROTULO_OUTROS} (${resto.length})`, valor: valorDe(metodo, outros.num, outros.den), extra: arredondarExtra(outros.extra) });
  }

  return { valor, grupos };
}

/** Quanto o número mudou em relação ao período anterior, em %. `null` quando não há base. */
export function variacao(atual: number, anterior: number | null | undefined): number | null {
  if (anterior === null || anterior === undefined || !Number.isFinite(anterior)) return null;
  if (anterior === 0) return atual === 0 ? 0 : null;
  return Math.round(((atual - anterior) / Math.abs(anterior)) * 1000) / 10;
}

/* ------------------------------------------------------------------ canais */

export const CANAIS = {
  pdv: "Balcão (PDV)",
  loja: "Loja online",
  chat: "Chat",
  painel: "Painel (equipe)",
} as const;
export type Canal = keyof typeof CANAIS;

/**
 * Por onde a venda entrou. O sistema não guarda o canal numa coluna: ele fica
 * marcado nas observações internas ("origem:pdv", "origem:catalogo",
 * "origem:chat") e no título do orçamento do PDV ("Venda PDV ..."), que é o
 * mesmo critério do relatório financeiro. O resto foi lançado pela equipe no
 * painel (balcão, WhatsApp, telefone): o sistema não separa esses.
 */
export function canalDaVenda(sinais: { tituloDoOrcamento?: string | null; notasDoOrcamento?: string | null; notasDoPedido?: string | null }): Canal {
  const titulo = String(sinais.tituloDoOrcamento || "").toLowerCase();
  const notas = `${sinais.notasDoOrcamento || ""}\n${sinais.notasDoPedido || ""}`.toLowerCase();
  if (notas.includes("origem:pdv") || titulo.startsWith("venda pdv") || notas.includes("venda rápida pdv") || notas.includes("venda rapida pdv")) return "pdv";
  if (notas.includes("origem:catalogo") || titulo.startsWith("pedido do catálogo") || titulo.startsWith("pedido do catalogo")) return "loja";
  if (notas.includes("origem:chat")) return "chat";
  return "painel";
}

export function valorDoCanal(canal: Canal): Valor {
  return { chave: canal, rotulo: CANAIS[canal] };
}

/* ------------------------------------------------------------ pedidos */

export const SITUACAO_DO_PEDIDO: Record<string, string> = {
  pending_production: "Aguardando produção",
  in_production: "Em produção",
  finished: "Pronto",
  delivered: "Entregue",
  canceled: "Cancelado",
};

/* As mesmas regras do relatório comercial (lib/reports/service.ts): pedido
   cancelado ou rascunho não é venda. */
const PEDIDO_QUE_NAO_E_VENDA = new Set(["canceled", "cancelled", "draft"]);

export function pedidoConta(status: string | null | undefined) {
  return !PEDIDO_QUE_NAO_E_VENDA.has(String(status || "pending_production").trim().toLowerCase());
}

/** Pedido ainda andando: não foi entregue nem cancelado. */
export function pedidoEmAberto(status: string | null | undefined) {
  const s = String(status || "pending_production").trim().toLowerCase();
  return pedidoConta(s) && s !== "delivered";
}

export type ColunaDoQuadro = { id: string; label: string; status?: string };

/**
 * A etapa da produção em que o pedido está: a coluna do quadro da empresa
 * (profiles.production_kanban_columns) quando o pedido tem uma; senão a
 * situação dele.
 */
export function etapaDoPedido(pedido: { status?: string | null; production_stage_id?: string | null }, colunas: ColunaDoQuadro[]): Valor {
  const etapa = pedido.production_stage_id;
  if (etapa && etapa !== "__kanban_archived__") {
    const coluna = colunas.find((c) => c.id === etapa);
    if (coluna) return { chave: coluna.id, rotulo: coluna.label || "Etapa" };
  }
  const status = String(pedido.status || "pending_production");
  return { chave: status, rotulo: SITUACAO_DO_PEDIDO[status] ?? status };
}

export function lerColunasDoQuadro(bruto: unknown): ColunaDoQuadro[] {
  if (!Array.isArray(bruto)) return [];
  return bruto
    .filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object")
    .filter((item) => typeof item.id === "string")
    .map((item, indice) => ({
      id: String(item.id),
      label: typeof item.label === "string" && item.label.trim() ? item.label.trim() : `Etapa ${indice + 1}`,
      status: typeof item.status === "string" ? item.status : undefined,
    }));
}

/** O nome que aparece para um cliente, ou para quem não tem cliente. */
export function rotuloDoCliente(cliente: { name?: string | null; company_name?: string | null } | null | undefined) {
  return (cliente?.company_name || cliente?.name || "").trim() || "Cliente sem nome";
}

/** Vencida, vence nos próximos 7 dias, ou mais adiante. */
export function situacaoDoVencimento(vencimento: string | null | undefined, hoje: string): Valor {
  if (!vencimento) return { chave: "sem_data", rotulo: "Sem vencimento" };
  if (vencimento < hoje) return { chave: "vencida", rotulo: "Vencida" };
  const daqui7 = new Date(Date.parse(`${hoje}T00:00:00Z`) + 7 * 86_400_000).toISOString().slice(0, 10);
  if (vencimento <= daqui7) return { chave: "semana", rotulo: "Vence em até 7 dias" };
  return { chave: "depois", rotulo: "Vence depois" };
}
