/**
 * O resumo do que aconteceu na produção de um pedido.
 *
 * A tabela `order_production_events` guarda três coisas no mesmo formato:
 * passagens por etapa do kanban, sessões do cronômetro e apontamentos por
 * item. Este módulo transforma a lista bruta no que as telas mostram: quanto
 * tempo o pedido ficou em cada etapa, quanto o cronômetro correu, quanto
 * disso foi dentro do expediente, e quem fez cada item em quanto tempo.
 *
 * Tudo puro: recebe eventos, expediente e "agora", devolve números. O teste
 * ao lado cobre as pontas.
 */

import type { CorteNaBobina, NaturezaDoInsumo, PlanoDeRodagemDoItem, Rodagem } from "./ficha-de-producao.ts";
import { segundosUteis, type Expediente } from "./expediente.ts";

export type TipoDeEvento = "etapa" | "cronometro" | "item";

export type EventoDeProducao = {
  id: string;
  kind: TipoDeEvento;
  status: string | null;
  stage_id: string | null;
  stage_label: string | null;
  order_item_id: string | null;
  item_title: string | null;
  started_at: string;
  ended_at: string | null;
  started_by: string | null;
  ended_by: string | null;
  source?: string | null;
};

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

const TERMINAIS = new Set(["finished", "delivered", "canceled"]);

export type Etapa = {
  rotulo: string;
  status: string | null;
  stageId: string | null;
  inicio: string;
  fim: string | null;
  minutos: number;
  minutosUteis: number;
  aberta: boolean;
  /* Etapa final (finalizado, entregue, cancelado) ainda aberta: o pedido
     parou ali, e o relógio dela não diz nada. */
  terminal: boolean;
  aproximada: boolean;
};

export type Sessao = {
  inicio: string;
  fim: string | null;
  minutos: number;
  minutosUteis: number;
  aberta: boolean;
  por: string | null;
  porNome: string | null;
  aproximada: boolean;
};

export type Apontamento = {
  id: string;
  inicio: string;
  fim: string | null;
  minutos: number;
  aberto: boolean;
  por: string | null;
  porNome: string | null;
};

export type ResumoDoItem = {
  orderItemId: string;
  titulo: string | null;
  aberto: Apontamento | null;
  feitos: Apontamento[];
  minutosReais: number;
};

export type ResumoDaProducao = {
  etapas: Etapa[];
  sessoes: Sessao[];
  tempoCorridoMin: number;
  tempoUtilMin: number;
  itens: ResumoDoItem[];
};

/* Perdas e retrabalho: o que se estragou na bancada, registrado. */
export type MotivoDePerda = "impressao" | "corte" | "acabamento" | "defeito_insumo" | "cliente" | "retrabalho" | "outro";

export const ROTULO_DO_MOTIVO: Record<MotivoDePerda, string> = {
  impressao: "Erro de impressão",
  corte: "Erro de corte",
  acabamento: "Erro de acabamento",
  defeito_insumo: "Defeito do insumo",
  cliente: "Alteração do cliente",
  retrabalho: "Retrabalho",
  outro: "Outro",
};

export const MOTIVOS_DE_PERDA = Object.keys(ROTULO_DO_MOTIVO) as MotivoDePerda[];

export type StatusDaPerda = "aplicada" | "pendente" | "recusada" | "estornada";

export type PerdaRegistrada = {
  id: string;
  orderItemId: string | null;
  itemTitle: string | null;
  /* Nulo quando a perda é da peça pronta. */
  materialId: string | null;
  materialNome: string;
  unidade: string;
  quantidade: number;
  motivo: MotivoDePerda;
  notas: string | null;
  refeito: boolean;
  status: StatusDaPerda;
  registradoPor: string | null;
  registradoPorNome: string | null;
  registradoEm: string;
  revisadoPorNome: string | null;
  revisadoEm: string | null;
  /* Verdadeiro quando a perda baixou estoque de fato. */
  comMovimento: boolean;
};

/* Um insumo do item, do jeito que o formulário de perda e o plano de
   rodagem precisam. */
export type InsumoDoItemParaPerda = {
  materialId: string | null;
  nome: string;
  unidade: string;
  quantidadePrevista: number;
  /* A conta crua (fração de folha, área) antes da regra de peça inteira. */
  quantidadeLiquida: number;
  natureza: NaturezaDoInsumo;
  /* A chave desta linha no plano de rodagem do item. */
  chave: string;
  ajustavel: "rendimento" | "bobina" | null;
  rendimento: number | null;
  rendimentoDoCadastro: number | null;
  rendimentoEstimado: boolean;
  sobra: number;
  rodagem: Rodagem | null;
  corte: CorteNaBobina | null;
  larguraBobinaM: number | null;
};

/* O que a rota `/api/orders/[id]/producao` devolve, e as telas leem. */
export type PainelDeProducao = {
  ok: true;
  expediente: Expediente;
  order: {
    id: string;
    number: number;
    status: string;
    production_started_at: string | null;
    production_accumulated_seconds: number;
    production_last_stopped_at: string | null;
    production_first_started_at: string | null;
    production_finished_at: string | null;
    minutos_previstos: number;
  };
  resumo: ResumoDaProducao;
  itens: {
    id: string;
    title: string;
    minutos_previstos: number;
    aberto: Apontamento | null;
    feitos: Apontamento[];
    minutos_reais: number;
    insumos: InsumoDoItemParaPerda[];
    perdas: PerdaRegistrada[];
    /* O plano de rodagem gravado no item (o que a produção decidiu). */
    plano: PlanoDeRodagemDoItem;
  }[];
  perdas: PerdaRegistrada[];
  /* Quem está vendo pode aprovar, recusar e estornar perdas. */
  podeAprovarPerdas: boolean;
  /* Nulo: toda perda baixa direto. Zero: toda perda espera aprovação. */
  perdasExigemAprovacaoAcimaPct: number | null;
};

const duasCasas = (n: number) => Math.round(n * 100) / 100;

function minutosEntre(inicio: string, fim: string | null, agoraMs: number) {
  const a = Date.parse(inicio);
  const b = fim ? Date.parse(fim) : agoraMs;
  if (!Number.isFinite(a) || !Number.isFinite(b) || b <= a) return 0;
  return duasCasas((b - a) / 60000);
}

function minutosUteisEntre(inicio: string, fim: string | null, agoraMs: number, expediente: Expediente) {
  const a = Date.parse(inicio);
  const b = fim ? Date.parse(fim) : agoraMs;
  return duasCasas(segundosUteis(a, b, expediente) / 60);
}

export function rotuloDaEtapa(evento: Pick<EventoDeProducao, "status" | "stage_label">) {
  const label = (evento.stage_label || "").trim();
  if (label) return label;
  return ROTULO_DO_STATUS[evento.status || ""] || evento.status || "Etapa";
}

export function resumirProducao(
  eventos: EventoDeProducao[],
  expediente: Expediente,
  agoraMs: number,
  nomeDe: (userId: string) => string | null = () => null
): ResumoDaProducao {
  const ordenados = [...eventos].sort((a, b) => a.started_at.localeCompare(b.started_at));

  const etapas: Etapa[] = ordenados
    .filter((e) => e.kind === "etapa")
    .map((e) => ({
      rotulo: rotuloDaEtapa(e),
      status: e.status,
      stageId: e.stage_id,
      inicio: e.started_at,
      fim: e.ended_at,
      minutos: minutosEntre(e.started_at, e.ended_at, agoraMs),
      minutosUteis: minutosUteisEntre(e.started_at, e.ended_at, agoraMs, expediente),
      aberta: !e.ended_at,
      terminal: !e.ended_at && TERMINAIS.has(e.status || ""),
      aproximada: e.source === "historico",
    }));

  const sessoes: Sessao[] = ordenados
    .filter((e) => e.kind === "cronometro")
    .map((e) => ({
      inicio: e.started_at,
      fim: e.ended_at,
      minutos: minutosEntre(e.started_at, e.ended_at, agoraMs),
      minutosUteis: minutosUteisEntre(e.started_at, e.ended_at, agoraMs, expediente),
      aberta: !e.ended_at,
      por: e.started_by,
      porNome: e.started_by ? nomeDe(e.started_by) : null,
      aproximada: e.source === "historico",
    }));

  const porItem = new Map<string, ResumoDoItem>();
  for (const e of ordenados) {
    if (e.kind !== "item" || !e.order_item_id) continue;
    const atual =
      porItem.get(e.order_item_id) ??
      { orderItemId: e.order_item_id, titulo: e.item_title, aberto: null, feitos: [], minutosReais: 0 };
    const apontamento: Apontamento = {
      id: e.id,
      inicio: e.started_at,
      fim: e.ended_at,
      minutos: minutosEntre(e.started_at, e.ended_at, agoraMs),
      aberto: !e.ended_at,
      por: e.started_by,
      porNome: e.started_by ? nomeDe(e.started_by) : null,
    };
    if (apontamento.aberto) {
      /* Dois abertos no mesmo item é erro de dado; o mais recente vale. */
      atual.aberto = apontamento;
    } else {
      atual.feitos.push(apontamento);
      atual.minutosReais = duasCasas(atual.minutosReais + apontamento.minutos);
    }
    if (!atual.titulo && e.item_title) atual.titulo = e.item_title;
    porItem.set(e.order_item_id, atual);
  }

  return {
    etapas,
    sessoes,
    tempoCorridoMin: duasCasas(sessoes.reduce((acc, s) => acc + s.minutos, 0)),
    tempoUtilMin: duasCasas(sessoes.reduce((acc, s) => acc + s.minutosUteis, 0)),
    itens: [...porItem.values()],
  };
}
