"use client";

import { useMemo } from "react";
import {
  chaveDoDia,
  doTexto,
  estiloSituacao,
  horarioDoEvento,
  inicioDaSemana,
  somaDias,
  type EventoAgenda,
} from "@/app/dashboard/agenda/tipos";
import { Barras, Bloco, FaixaDeNumeros, Matriz, PALETA, Rosca, Vazio } from "./graficos";

/**
 * Corpo do relatório da agenda.
 *
 * Mora fora das rotas porque duas paginas o desenham: a interna, de quem esta
 * logado, e a publica, de quem recebeu o link. Se cada uma tivesse a propria
 * copia, os numeros comecariam a divergir na primeira correcao feita so de um
 * lado — e um relatorio que muda conforme quem abre nao vale nada.
 */

const HORA_ABRE = 8;
const HORA_FECHA = 18;
const DIAS_SEMANA = ["Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"];
const CONCLUIDOS = new Set(["concluido", "delivered", "finished", "paid", "received", "approved"]);

export const ROTULO_TIPO: Record<string, string> = {
  compromisso: "Compromissos",
  pedido_entrega: "Entregas de pedido",
  conta_pagar: "Contas a pagar",
  conta_receber: "Contas a receber",
  link_pagamento: "Links de pagamento",
  comissao: "Comissões",
  compra_fornecedor: "Compras de fornecedor",
  cupom: "Cupons",
};

/* Cada dimensao sabe extrair a propria chave de um evento, entao a matriz
   funciona com qualquer combinacao sem um `if` por par possivel. */
export const DIMENSOES = {
  categoria: { rotulo: "Categoria", de: (e: EventoAgenda) => e.categoria_nome || "Sem categoria" },
  diaSemana: {
    rotulo: "Dia da semana",
    de: (e: EventoAgenda) => DIAS_SEMANA[(doTexto(e.data).getDay() + 6) % 7],
  },
  hora: {
    rotulo: "Faixa de horário",
    de: (e: EventoAgenda) => {
      const { inicioMin } = horarioDoEvento(e);
      if (inicioMin === null) return "Sem hora";
      return `${String(Math.floor(inicioMin / 60)).padStart(2, "0")}h`;
    },
  },
  situacao: {
    rotulo: "Situação",
    de: (e: EventoAgenda) => estiloSituacao(e.situacao).texto || "Sem status",
  },
  tipo: { rotulo: "Origem", de: (e: EventoAgenda) => ROTULO_TIPO[e.tipo] ?? e.tipo },
} as const;

export type ChaveDim = keyof typeof DIMENSOES;

export function CorpoRelatorio({
  eventos,
  de,
  ate,
  linha,
  coluna,
  aoTrocarLinha,
  aoTrocarColuna,
  comDetalhes = true,
}: {
  eventos: EventoAgenda[];
  de: string;
  ate: string;
  linha: ChaveDim;
  coluna: ChaveDim;
  aoTrocarLinha?: (d: ChaveDim) => void;
  aoTrocarColuna?: (d: ChaveDim) => void;
  /* A lista nominal so aparece quando quem compartilhou permitiu. Nos links
     sem detalhes os nomes nem chegam do banco, entao a tabela nao teria o que
     mostrar alem de linhas genericas. */
  comDetalhes?: boolean;
}) {
  const dados = useMemo(() => calcular(eventos, de, ate), [eventos, de, ate]);

  return (
    <>
      <FaixaDeNumeros
        itens={[
          { rotulo: "Registros", valor: String(dados.total), detalhe: `${dados.dias} dias` },
          {
            rotulo: "Ocupação",
            valor: `${dados.ocupacao}%`,
            detalhe: `${dados.horas.toFixed(0)}h de ${dados.horasUteis}h`,
            cor: "#6C3CF0",
          },
          {
            rotulo: "Concluídos",
            valor: `${dados.pctConcluidos}%`,
            detalhe: `${dados.concluidos} de ${dados.total}`,
            cor: "#10B981",
          },
          {
            rotulo: "Em atraso",
            valor: String(dados.atrasados),
            detalhe: dados.atrasados ? "Precisa de ação" : "Nada pendente",
            cor: dados.atrasados ? "#F59E0B" : undefined,
          },
          {
            rotulo: "Média/dia útil",
            valor: dados.mediaDiaUtil.toFixed(1),
            detalhe: "compromissos",
            cor: "#3B82F6",
          },
          {
            rotulo: "Dia mais cheio",
            valor: dados.picoDia ? String(dados.picoDia.n) : "0",
            detalhe: dados.picoDia ? doTexto(dados.picoDia.dia).toLocaleDateString("pt-BR") : "—",
            cor: "#EC4899",
          },
        ]}
      />

      <div className="grid gap-3 lg:grid-cols-2">
        <Bloco titulo="Volume por dia" descricao="Onde a carga se acumula ao longo do período." largo>
          <Barras dados={dados.porDia} altura={160} />
        </Bloco>

        <Bloco titulo="Por categoria" descricao="O que ocupa a agenda.">
          <Rosca dados={dados.porCategoria} centro={String(dados.total)} legendaCentro="registros" />
        </Bloco>

        <Bloco titulo="Por situação" descricao="Quanto já foi fechado e quanto está em aberto.">
          <Rosca dados={dados.porSituacao} centro={`${dados.pctConcluidos}%`} legendaCentro="concluído" />
        </Bloco>

        <Bloco titulo="Por dia da semana" descricao="Qual dia concentra mais trabalho — útil para redistribuir.">
          <Barras dados={dados.porDiaSemana} altura={140} />
        </Bloco>

        <Bloco titulo="Por horário" descricao="Os vales são onde ainda cabe encaixar serviço.">
          <Barras dados={dados.porHora} altura={140} />
        </Bloco>

        <Bloco titulo="Por origem" descricao="O que foi marcado e o que o sistema espelhou.">
          {dados.porTipo.length ? (
            <Rosca dados={dados.porTipo} centro={String(dados.proprios)} legendaCentro="próprios" />
          ) : (
            <Vazio />
          )}
        </Bloco>

        {comDetalhes && (
          <Bloco titulo="Quem mais aparece" descricao="Clientes e contrapartes mais frequentes.">
            {dados.topClientes.length ? (
              <ul className="space-y-1.5">
                {dados.topClientes.map((c, i) => (
                  <li key={c.nome} className="flex items-center gap-2 text-[11.5px]">
                    <span
                      aria-hidden
                      className="h-[9px] w-[9px] shrink-0 rounded-full"
                      style={{ background: PALETA[i % PALETA.length] }}
                    />
                    <span className="min-w-0 flex-1 truncate font-medium text-[var(--ag-text-2)]">{c.nome}</span>
                    <span className="shrink-0 font-semibold tabular-nums text-[var(--ag-text)]">{c.n}</span>
                  </li>
                ))}
              </ul>
            ) : (
              <Vazio texto="Nenhum registro com cliente vinculado." />
            )}
          </Bloco>
        )}
      </div>

      <Bloco titulo="Cruzamento" descricao="Duas dimensões lado a lado. A cor mostra onde a carga se concentra.">
        {aoTrocarLinha && aoTrocarColuna ? (
          <div className="mb-3.5 flex flex-wrap items-end gap-3 print:hidden">
            <SeletorDim rotulo="Linhas" valor={linha} aoTrocar={aoTrocarLinha} />
            <SeletorDim rotulo="Colunas" valor={coluna} aoTrocar={aoTrocarColuna} />
            <p className="mb-2 text-[11.5px] text-[var(--ag-text-5)]">
              {DIMENSOES[linha].rotulo} × {DIMENSOES[coluna].rotulo}
            </p>
          </div>
        ) : (
          <p className="mb-3 text-[11.5px] font-medium text-[var(--ag-text-3)]">
            {DIMENSOES[linha].rotulo} × {DIMENSOES[coluna].rotulo}
          </p>
        )}
        <Cruzamento eventos={eventos} linha={linha} coluna={coluna} />
      </Bloco>

      {comDetalhes && (
        <Bloco titulo="Registros" descricao={`${dados.total} no período, do mais recente ao mais antigo.`}>
          <div className="overflow-x-auto">
            <table className="w-full text-[11.5px]">
              <thead>
                <tr className="border-b border-[var(--ag-line-h)] text-left text-[var(--ag-text-3)]">
                  <th className="py-2 pr-3 font-semibold">Data</th>
                  <th className="py-2 pr-3 font-semibold">Hora</th>
                  <th className="py-2 pr-3 font-semibold">Compromisso</th>
                  <th className="py-2 pr-3 font-semibold">Categoria</th>
                  <th className="py-2 pr-3 font-semibold">Origem</th>
                  <th className="py-2 font-semibold">Situação</th>
                </tr>
              </thead>
              <tbody>
                {dados.listados.map((e, i) => {
                  const { inicioMin } = horarioDoEvento(e);
                  const s = estiloSituacao(e.situacao);
                  return (
                    <tr key={`${e.tipo}-${e.origem_id}-${i}`} className="border-b border-[var(--ag-line-h)]">
                      <td className="py-2 pr-3 tabular-nums text-[var(--ag-text-2)]">
                        {doTexto(e.data).toLocaleDateString("pt-BR")}
                      </td>
                      <td className="py-2 pr-3 tabular-nums text-[var(--ag-text-3)]">
                        {inicioMin === null
                          ? "—"
                          : `${String(Math.floor(inicioMin / 60)).padStart(2, "0")}:${String(inicioMin % 60).padStart(2, "0")}`}
                      </td>
                      <td className="max-w-[320px] truncate py-2 pr-3 font-medium text-[var(--ag-text)]">{e.titulo}</td>
                      <td className="py-2 pr-3">
                        <span
                          className="rounded-full px-2 py-[2px] text-[10.5px] font-medium"
                          style={{ background: `${e.categoria_cor}17`, color: e.categoria_cor }}
                        >
                          {e.categoria_nome}
                        </span>
                      </td>
                      <td className="py-2 pr-3 text-[var(--ag-text-3)]">{ROTULO_TIPO[e.tipo] ?? e.tipo}</td>
                      <td className="py-2">
                        {s.texto && (
                          <span
                            className="rounded-full px-2 py-[2px] text-[10.5px] font-medium"
                            style={{ background: s.fundo, color: s.cor }}
                          >
                            {s.texto}
                          </span>
                        )}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            {dados.total > dados.listados.length && (
              <p className="pt-2.5 text-[11px] text-[var(--ag-text-5)]">
                Mostrando os {dados.listados.length} primeiros de {dados.total}. Estreite o período para ver todos.
              </p>
            )}
          </div>
        </Bloco>
      )}
    </>
  );
}

function SeletorDim({
  rotulo,
  valor,
  aoTrocar,
}: {
  rotulo: string;
  valor: ChaveDim;
  aoTrocar: (d: ChaveDim) => void;
}) {
  return (
    <label className="flex flex-col gap-1">
      <span className="text-[10.5px] font-semibold uppercase tracking-[0.04em] text-[var(--ag-text-4)]">{rotulo}</span>
      <select
        value={valor}
        onChange={(e) => aoTrocar(e.target.value as ChaveDim)}
        className="h-9 rounded-[9px] border border-[var(--ag-border-strong)] bg-[var(--ag-surface)] px-2.5 text-[12.5px] text-[var(--ag-text)]"
      >
        {Object.entries(DIMENSOES).map(([k, d]) => (
          <option key={k} value={k}>
            {d.rotulo}
          </option>
        ))}
      </select>
    </label>
  );
}

function Cruzamento({
  eventos,
  linha,
  coluna,
}: {
  eventos: EventoAgenda[];
  linha: ChaveDim;
  coluna: ChaveDim;
}) {
  const { linhas, colunas, mapa } = useMemo(() => {
    const m = new Map<string, number>();
    const totalPorChave = new Map<string, number>();
    const ls = new Set<string>();
    const cs = new Set<string>();

    eventos.forEach((e) => {
      const l = DIMENSOES[linha].de(e);
      const c = DIMENSOES[coluna].de(e);
      ls.add(l);
      cs.add(c);
      m.set(`${l}|${c}`, (m.get(`${l}|${c}`) ?? 0) + 1);
      totalPorChave.set(`L:${l}`, (totalPorChave.get(`L:${l}`) ?? 0) + 1);
      totalPorChave.set(`C:${c}`, (totalPorChave.get(`C:${c}`) ?? 0) + 1);
    });

    /* Dia da semana e hora tem ordem natural; o resto ordena por volume, que
       poe o mais relevante no topo. */
    const ordenar = (chave: ChaveDim, valores: string[], prefixo: "L" | "C") => {
      if (chave === "diaSemana") return DIAS_SEMANA.filter((d) => valores.includes(d));
      if (chave === "hora") return [...valores].sort();
      return [...valores].sort(
        (a, b) => (totalPorChave.get(`${prefixo}:${b}`) ?? 0) - (totalPorChave.get(`${prefixo}:${a}`) ?? 0)
      );
    };

    return {
      linhas: ordenar(linha, [...ls], "L").slice(0, 14),
      colunas: ordenar(coluna, [...cs], "C").slice(0, 14),
      mapa: m,
    };
  }, [eventos, linha, coluna]);

  return <Matriz linhas={linhas} colunas={colunas} valor={(l, c) => mapa.get(`${l}|${c}`) ?? 0} />;
}

/* ---------------- Cálculo ---------------- */

export function calcular(eventos: EventoAgenda[], de: string, ate: string) {
  const inicio = doTexto(de);
  const fim = doTexto(ate);
  const dias: Date[] = [];
  for (let d = inicio; d <= fim; d = somaDias(d, 1)) dias.push(d);

  const uteis = dias.filter((d) => ![0, 6].includes(d.getDay()));
  const baseDias = uteis.length || dias.length;
  const total = eventos.length;

  let minutos = 0;
  eventos.forEach((e) => {
    const { inicioMin, fimMin } = horarioDoEvento(e);
    if (inicioMin === null) return;
    const f = fimMin && fimMin > inicioMin ? fimMin : inicioMin + 60;
    const a = Math.max(inicioMin, HORA_ABRE * 60);
    const b = Math.min(f, HORA_FECHA * 60);
    if (b > a) minutos += b - a;
  });
  const horasUteis = baseDias * (HORA_FECHA - HORA_ABRE);
  const ocupacao = Math.min(100, Math.round((minutos / (horasUteis * 60)) * 100));

  const concluidos = eventos.filter((e) => CONCLUIDOS.has(String(e.situacao || "").toLowerCase())).length;
  const hoje = chaveDoDia(new Date());
  const atrasados = eventos.filter((e) => {
    const s = String(e.situacao || "").toLowerCase();
    if (s === "overdue") return true;
    return e.data < hoje && !CONCLUIDOS.has(s) && s !== "cancelado";
  }).length;

  const contagemDia = new Map<string, number>();
  eventos.forEach((e) => contagemDia.set(e.data, (contagemDia.get(e.data) ?? 0) + 1));

  /* Em periodos longos a barra por dia vira poeira: acima de 45 dias agrupa
     por semana, que continua respondendo "quando encheu". */
  const porDia =
    dias.length > 45
      ? agruparSemanas(dias, contagemDia)
      : dias.map((d) => ({
          rotulo: String(d.getDate()),
          valor: contagemDia.get(chaveDoDia(d)) ?? 0,
          cor: [0, 6].includes(d.getDay()) ? "#C4B5FD" : "#6C3CF0",
        }));

  const picoEntrada = [...contagemDia.entries()].sort((a, b) => b[1] - a[1])[0];
  const picoDia = picoEntrada ? { dia: picoEntrada[0], n: picoEntrada[1] } : null;

  const cat = new Map<string, { n: number; cor: string }>();
  eventos.forEach((e) => {
    const k = e.categoria_nome || "Sem categoria";
    const atual = cat.get(k);
    if (atual) atual.n += 1;
    else cat.set(k, { n: 1, cor: e.categoria_cor });
  });
  const porCategoria = [...cat.entries()]
    .map(([rotulo, v]) => ({ rotulo, valor: v.n, cor: v.cor }))
    .sort((a, b) => b.valor - a.valor)
    .slice(0, 8);

  const sit = new Map<string, { n: number; cor: string }>();
  eventos.forEach((e) => {
    const s = estiloSituacao(e.situacao);
    const k = s.texto || "Sem status";
    const atual = sit.get(k);
    if (atual) atual.n += 1;
    else sit.set(k, { n: 1, cor: s.cor });
  });
  const porSituacao = [...sit.entries()]
    .map(([rotulo, v]) => ({ rotulo, valor: v.n, cor: v.cor }))
    .sort((a, b) => b.valor - a.valor);

  const semana = new Array(7).fill(0);
  eventos.forEach((e) => {
    semana[(doTexto(e.data).getDay() + 6) % 7] += 1;
  });
  const maxSemana = Math.max(...semana);
  const porDiaSemana = DIAS_SEMANA.map((rotulo, i) => ({
    rotulo,
    valor: semana[i],
    cor: semana[i] === maxSemana && maxSemana > 0 ? "#6C3CF0" : "#A78BFA",
  }));

  const horas = new Map<number, number>();
  eventos.forEach((e) => {
    const { inicioMin } = horarioDoEvento(e);
    if (inicioMin === null) return;
    horas.set(Math.floor(inicioMin / 60), (horas.get(Math.floor(inicioMin / 60)) ?? 0) + 1);
  });
  const faixa = [...horas.keys()];
  const hIni = faixa.length ? Math.min(...faixa, HORA_ABRE) : HORA_ABRE;
  const hFim = faixa.length ? Math.max(...faixa, HORA_FECHA) : HORA_FECHA;
  const maxHora = Math.max(...horas.values(), 0);
  const porHora = Array.from({ length: hFim - hIni + 1 }, (_, i) => {
    const h = hIni + i;
    const v = horas.get(h) ?? 0;
    return {
      rotulo: `${String(h).padStart(2, "0")}h`,
      valor: v,
      cor: v === maxHora && maxHora > 0 ? "#0EA5E9" : "#7DD3FC",
    };
  });

  const tipos = new Map<string, number>();
  eventos.forEach((e) => tipos.set(e.tipo, (tipos.get(e.tipo) ?? 0) + 1));
  const porTipo = [...tipos.entries()]
    .map(([t, n], i) => ({ rotulo: ROTULO_TIPO[t] ?? t, valor: n, cor: PALETA[i % PALETA.length] }))
    .sort((a, b) => b.valor - a.valor);
  const proprios = tipos.get("compromisso") ?? 0;

  /* O nome do cliente vive no subtítulo, depois do horário. Em link sem
     detalhes ele nem vem do banco, e a lista sai vazia por consequencia. */
  const clientes = new Map<string, number>();
  eventos.forEach((e) => {
    const nome = String(e.subtitulo || "")
      .replace(/^\d{2}:\d{2}(–\d{2}:\d{2})?\s*(·\s*)?/, "")
      .trim();
    if (!nome || /^sem /i.test(nome)) return;
    clientes.set(nome, (clientes.get(nome) ?? 0) + 1);
  });
  const topClientes = [...clientes.entries()]
    .map(([nome, n]) => ({ nome, n }))
    .sort((a, b) => b.n - a.n)
    .slice(0, 8);

  const listados = [...eventos].sort((a, b) => b.data.localeCompare(a.data)).slice(0, 120);

  return {
    total,
    dias: dias.length,
    horas: minutos / 60,
    horasUteis,
    ocupacao,
    concluidos,
    pctConcluidos: total ? Math.round((concluidos / total) * 100) : 0,
    atrasados,
    mediaDiaUtil: baseDias ? total / baseDias : 0,
    picoDia,
    porDia,
    porCategoria,
    porSituacao,
    porDiaSemana,
    porHora,
    porTipo,
    proprios,
    topClientes,
    listados,
  };
}

function agruparSemanas(dias: Date[], contagem: Map<string, number>) {
  const baldes = new Map<string, { rotulo: string; valor: number }>();
  dias.forEach((d) => {
    const seg = inicioDaSemana(d);
    const k = chaveDoDia(seg);
    const atual = baldes.get(k);
    const n = contagem.get(chaveDoDia(d)) ?? 0;
    if (atual) atual.valor += n;
    else
      baldes.set(k, {
        rotulo: `${String(seg.getDate()).padStart(2, "0")}/${String(seg.getMonth() + 1).padStart(2, "0")}`,
        valor: n,
      });
  });
  return [...baldes.values()].map((b) => ({ ...b, cor: "#6C3CF0" }));
}
