/**
 * Regras de um compromisso da agenda.
 *
 * Mora fora das rotas porque as duas — criar e editar — precisam das mesmas
 * regras, e um arquivo de rota do Next so pode exportar os handlers.
 */

const SO_DATA = /^\d{4}-\d{2}-\d{2}$/;
const SO_HORA = /^([01]\d|2[0-3]):[0-5]\d$/;
const SO_HEX = /^#[0-9A-Fa-f]{6}$/;
const SO_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const REPETICOES = new Set(["none", "daily", "weekly", "monthly"]);
const SITUACOES = new Set(["agendado", "concluido", "cancelado"]);

export type CorpoCompromisso = {
  title?: unknown;
  description?: unknown;
  event_date?: unknown;
  start_time?: unknown;
  end_time?: unknown;
  client_id?: unknown;
  responsible_user_id?: unknown;
  status?: unknown;
  recurrence?: unknown;
  recurrence_until?: unknown;
  location_address?: unknown;
  location_lat?: unknown;
  location_lng?: unknown;
  category_id?: unknown;
  color?: unknown;
  lembrete_minutos?: unknown;
};

/* As opcoes que a tela oferece. Ficam aqui, e nao so no formulario, porque a
   API precisa recusar o que nao veio dela. */
export const LEMBRETES_ACEITOS = new Set([5, 10, 15, 30, 60, 120, 240, 1440, 2880]);

/* Coordenada fora da faixa nao e erro do usuario, e sim dado ruim de uma
   fonte externa: descarta em silencio para o compromisso ainda ser salvo. */
function coordenada(v: unknown, min: number, max: number): number | null {
  const n = Number(v);
  if (!Number.isFinite(n) || n < min || n > max || n === 0) return null;
  return n;
}

/* Mesma logica de doTexto/chaveDoDia em tipos.ts, duplicada aqui de proposito:
   este arquivo roda tanto no servidor (rota) quanto no navegador (formulario,
   para o aviso aparecer antes de gastar uma ida a rede), e nao deveria
   importar de dentro de app/dashboard so por isso. */
function paraData(iso: string): Date {
  const [a, m, d] = iso.split("-").map(Number);
  return new Date(a, (m || 1) - 1, d || 1);
}
function paraYMD(d: Date): string {
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const dia = String(d.getDate()).padStart(2, "0");
  return `${d.getFullYear()}-${m}-${dia}`;
}

export function validar(corpo: CorpoCompromisso | null) {
  const titulo = String(corpo?.title ?? "").trim();
  if (!titulo) return { ok: false as const, erro: "Dê um nome ao compromisso." };
  if (titulo.length > 200) return { ok: false as const, erro: "Nome muito longo (máximo 200)." };

  const dia = String(corpo?.event_date ?? "");
  if (!SO_DATA.test(dia)) return { ok: false as const, erro: "Informe o dia no formato AAAA-MM-DD." };

  const situacao = String(corpo?.status ?? "agendado");
  if (!SITUACOES.has(situacao)) return { ok: false as const, erro: "Situação inválida." };

  /* "Agendado" e o passado nao convivem: uma coisa ou ja aconteceu (entao e
     concluido ou cancelado) ou ainda vai acontecer (entao e no futuro).
     Editar um registro antigo para concluido/cancelado continua livre — e
     como se registra o que ja passou. */
  if (situacao === "agendado" && dia < paraYMD(new Date())) {
    return {
      ok: false as const,
      erro: "Esse dia já passou. Se o compromisso já aconteceu, marque como concluído; senão, escolha uma data futura.",
    };
  }

  const hora = (v: unknown) => {
    const t = String(v ?? "").trim();
    if (!t) return null;
    /* input type=time devolve HH:MM, mas alguns navegadores mandam HH:MM:SS. */
    const curto = t.slice(0, 5);
    return SO_HORA.test(curto) ? curto : undefined;
  };

  const inicio = hora(corpo?.start_time);
  const fim = hora(corpo?.end_time);
  if (inicio === undefined || fim === undefined) {
    return { ok: false as const, erro: "Horário inválido." };
  }
  if (inicio && fim && fim <= inicio) {
    return { ok: false as const, erro: "O fim precisa ser depois do início." };
  }

  const repeticao = String(corpo?.recurrence ?? "none");
  if (!REPETICOES.has(repeticao)) return { ok: false as const, erro: "Repetição inválida." };

  const ate = String(corpo?.recurrence_until ?? "").trim() || null;
  if (repeticao !== "none") {
    if (!ate || !SO_DATA.test(ate)) {
      return { ok: false as const, erro: "Repetição precisa de uma data final." };
    }
    if (ate < dia) return { ok: false as const, erro: "A data final é antes do começo." };

    /* Teto de proposito: sem ele, um ano digitado errado (2260 em vez de
       2026) cria uma serie que a expansao em generate_series vai gerar para
       sempre, silenciosamente, sem nenhum aviso de que algo esta estranho. */
    const teto = paraData(dia);
    teto.setFullYear(teto.getFullYear() + 3);
    if (paraData(ate) > teto) {
      return {
        ok: false as const,
        erro: "A repetição não pode passar de 3 anos a partir do início. Confira o ano digitado.",
      };
    }
  }

  const descricao = String(corpo?.description ?? "").trim();
  if (descricao.length > 2000) return { ok: false as const, erro: "Observação muito longa." };

  const endereco2 = String(corpo?.location_address ?? "").trim();
  if (endereco2.length > 400) return { ok: false as const, erro: "Endereço muito longo." };

  const categoriaId = String(corpo?.category_id ?? "").trim();
  if (!SO_UUID.test(categoriaId)) return { ok: false as const, erro: "Escolha uma categoria." };

  const corBruta = corpo?.color;
  const cor =
    corBruta === null || corBruta === undefined || corBruta === ""
      ? null
      : SO_HEX.test(String(corBruta))
        ? String(corBruta)
        : undefined;
  if (cor === undefined) return { ok: false as const, erro: "Cor inválida." };

  /* Antecedencia do lembrete, em minutos. Aceita so a lista que a tela
     oferece: numero livre vindo do cliente viraria lembrete de tres anos
     antes, e o disparador varreria uma janela inutil. */
  const lembreteBruto = corpo?.lembrete_minutos;
  const lembrete =
    lembreteBruto === null || lembreteBruto === undefined || lembreteBruto === ""
      ? null
      : LEMBRETES_ACEITOS.has(Number(lembreteBruto))
        ? Number(lembreteBruto)
        : undefined;
  if (lembrete === undefined) return { ok: false as const, erro: "Antecedência inválida." };

  return {
    ok: true as const,
    dados: {
      title: titulo,
      description: descricao || null,
      event_date: dia,
      start_time: inicio,
      end_time: fim,
      client_id: typeof corpo?.client_id === "string" && corpo.client_id ? corpo.client_id : null,
      responsible_user_id:
        typeof corpo?.responsible_user_id === "string" && corpo.responsible_user_id
          ? corpo.responsible_user_id
          : null,
      status: situacao,
      recurrence: repeticao,
      recurrence_until: repeticao === "none" ? null : ate,
      location_address: endereco2 || null,
      location_lat: coordenada(corpo?.location_lat, -90, 90),
      location_lng: coordenada(corpo?.location_lng, -180, 180),
      category_id: categoriaId,
      color: cor,
      lembrete_minutos: lembrete,
    },
  };
}
