"use client";

import { useCallback, useEffect, useRef, useState } from "react";

/* Ponteiro unificado: o mesmo codigo atende dedo, caneta e mouse. Escrever
   uma via para toque e outra para mouse e como as duas divergem com o tempo. */

function vibrar(ms: number) {
  /* Resposta fisica so onde existe e so para quem nao pediu menos movimento. */
  if (typeof navigator === "undefined" || !("vibrate" in navigator)) return;
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
  navigator.vibrate(ms);
}

/**
 * Deslizar para trocar de periodo.
 *
 * So dispara quando o movimento horizontal domina o vertical: sem isso, rolar
 * a agenda com o dedo trocaria de semana sem querer, que e o jeito mais rapido
 * de a pessoa desistir do gesto.
 */
export function useDeslizar({
  aoAnterior,
  aoProximo,
  bloqueado,
}: {
  aoAnterior: () => void;
  aoProximo: () => void;
  bloqueado?: boolean;
}) {
  const inicio = useRef<{ x: number; y: number; t: number } | null>(null);

  const onPointerDown = useCallback((e: React.PointerEvent) => {
    if (e.pointerType === "mouse") return;
    inicio.current = { x: e.clientX, y: e.clientY, t: Date.now() };
  }, []);

  const onPointerUp = useCallback(
    (e: React.PointerEvent) => {
      const i = inicio.current;
      inicio.current = null;
      if (!i || bloqueado) return;

      const dx = e.clientX - i.x;
      const dy = e.clientY - i.y;
      const tempo = Date.now() - i.t;

      /* 60px de curso, dominancia horizontal de 1.5x e meio segundo de teto:
         abaixo disso e toque tremido, acima e rolagem lenta. */
      if (Math.abs(dx) < 60 || Math.abs(dx) < Math.abs(dy) * 1.5 || tempo > 500) return;

      vibrar(8);
      if (dx > 0) aoAnterior();
      else aoProximo();
    },
    [aoAnterior, aoProximo, bloqueado]
  );

  return { onPointerDown, onPointerUp };
}

export type EventoArrastavel = {
  tipo: string;
  origem_id: string;
  titulo: string;
  data: string;
  /* Hora atual, quando existe. Serve para o solto saber se o horario mudou
     de verdade ou se a pessoa devolveu o cartao no mesmo lugar. */
  hora?: number | null;
};

/* Só o que tem onde gravar a nova data. Vencimento de conta e fim de cupom
   nao se remarcam arrastando: mudar aquilo e decisao financeira, nao de
   agenda. */
export function podeArrastar(tipo: string) {
  return tipo === "compromisso" || tipo === "pedido_entrega";
}

/**
 * Arrastar para remarcar.
 *
 * No mouse comeca ao mover alguns pixels. No dedo exige segurar meio segundo,
 * senao qualquer rolagem da lista viraria um arrasto — e remarcar entrega de
 * cliente por engano e caro.
 */
export function useArrastarEvento({
  aoSoltar,
}: {
  aoSoltar: (evento: EventoArrastavel, novaData: string, novaHora: number | null) => void;
}) {
  const [arrastando, setArrastando] = useState<EventoArrastavel | null>(null);
  const [posicao, setPosicao] = useState<{ x: number; y: number } | null>(null);
  const [diaAlvo, setDiaAlvo] = useState<string | null>(null);
  const [horaAlvo, setHoraAlvo] = useState<number | null>(null);

  const pendente = useRef<{ evento: EventoArrastavel; x: number; y: number; timer: number | null } | null>(null);
  const arrastandoRef = useRef<EventoArrastavel | null>(null);

  const limpar = useCallback(() => {
    if (pendente.current?.timer) window.clearTimeout(pendente.current.timer);
    pendente.current = null;
    arrastandoRef.current = null;
    setArrastando(null);
    setPosicao(null);
    setDiaAlvo(null);
    setHoraAlvo(null);
  }, []);

  const comecar = useCallback((evento: EventoArrastavel, x: number, y: number) => {
    arrastandoRef.current = evento;
    setArrastando(evento);
    setPosicao({ x, y });
    vibrar(12);
  }, []);

  const iniciar = useCallback(
    (e: React.PointerEvent, bruto: EventoArrastavel & { subtitulo?: string | null }) => {
      if (!podeArrastar(bruto.tipo)) return;

      /* A hora atual sai do subtitulo ("HH:MM · Cliente") quando nao veio
         pronta: sem ela, soltar na mesma celula da grade gravaria de novo. */
      const achado = String(bruto.subtitulo || "").match(/^(\d{2}):/);
      const evento: EventoArrastavel = {
        ...bruto,
        hora: bruto.hora ?? (achado ? Number(achado[1]) : null),
      };

      if (e.pointerType === "mouse" && e.button !== 0) return;

      if (e.pointerType === "mouse") {
        pendente.current = { evento, x: e.clientX, y: e.clientY, timer: null };
        return;
      }

      const x = e.clientX;
      const y = e.clientY;
      const timer = window.setTimeout(() => comecar(evento, x, y), 500);
      pendente.current = { evento, x, y, timer };
    },
    [comecar]
  );

  useEffect(() => {
    const mover = (e: PointerEvent) => {
      const p = pendente.current;

      /* Mouse: alguns pixels de folga separam clique de arrasto. */
      if (p && !arrastandoRef.current && e.pointerType === "mouse") {
        if (Math.hypot(e.clientX - p.x, e.clientY - p.y) > 6) comecar(p.evento, e.clientX, e.clientY);
        return;
      }

      /* Dedo: se saiu do lugar antes de completar a espera, era rolagem. */
      if (p && !arrastandoRef.current && p.timer) {
        if (Math.hypot(e.clientX - p.x, e.clientY - p.y) > 12) {
          window.clearTimeout(p.timer);
          pendente.current = null;
        }
        return;
      }

      if (!arrastandoRef.current) return;
      e.preventDefault();
      setPosicao({ x: e.clientX, y: e.clientY });

      const alvo = document.elementFromPoint(e.clientX, e.clientY);
      const celula = alvo?.closest<HTMLElement>("[data-dia]");
      setDiaAlvo(celula?.dataset.dia ?? null);
      /* A hora so existe na grade do mes; nas outras visoes a celula e o dia
         inteiro e o horario original e preservado. */
      const hora = celula?.dataset.hora;
      setHoraAlvo(hora === undefined ? null : Number(hora));
    };

    const soltar = (e: PointerEvent) => {
      const evento = arrastandoRef.current;
      if (!evento) {
        limpar();
        return;
      }
      const alvo = document.elementFromPoint(e.clientX, e.clientY);
      const celula = alvo?.closest<HTMLElement>("[data-dia]");
      const novaData = celula?.dataset.dia;
      const novaHora = celula?.dataset.hora;
      const mudouDia = Boolean(novaData) && novaData !== evento.data;
      const mudouHora = novaHora !== undefined && `${novaHora}` !== `${evento.hora ?? ""}`;
      if (novaData && (mudouDia || mudouHora)) {
        vibrar(18);
        aoSoltar(evento, novaData, novaHora === undefined ? null : Number(novaHora));
      }
      limpar();
    };

    const cancelar = () => limpar();

    window.addEventListener("pointermove", mover, { passive: false });
    window.addEventListener("pointerup", soltar);
    window.addEventListener("pointercancel", cancelar);
    return () => {
      window.removeEventListener("pointermove", mover);
      window.removeEventListener("pointerup", soltar);
      window.removeEventListener("pointercancel", cancelar);
    };
  }, [aoSoltar, comecar, limpar]);

  return { iniciar, arrastando, posicao, diaAlvo, horaAlvo };
}

/**
 * Rolagem horizontal por arrasto, com sombra na borda.
 *
 * Duas coisas que andam juntas: a sombra avisa que ha mais coluna para o lado
 * — sem ela a ultima coluna cortada parece que o conteudo acabou ali — e o
 * arrasto e o proprio jeito de chegar la, sem procurar barra de rolagem.
 *
 * O cursor e `ew-resize` (a seta dupla horizontal), e nao a maozinha: a
 * maozinha ja significa "pegue este compromisso e remarque" na mesma tela, e
 * dois gestos diferentes com o mesmo icone confundem qual dos dois vai
 * acontecer.
 *
 * So no mouse. No toque o proprio deslize do dedo ja rola, e capturar o gesto
 * aqui brigaria com o deslizar-para-trocar-de-semana.
 */
export function useRolagemHorizontal<T extends HTMLElement>(dependencia?: unknown) {
  const ref = useRef<T>(null);
  const [temMaisPraDireita, setTemMaisPraDireita] = useState(false);
  const [podeRolar, setPodeRolar] = useState(false);
  const [arrastandoTela, setArrastandoTela] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    const checar = () => {
      const sobra = el.scrollWidth - el.clientWidth;
      setPodeRolar(sobra > 4);
      setTemMaisPraDireita(sobra - el.scrollLeft > 4);
    };
    checar();
    el.addEventListener("scroll", checar, { passive: true });
    const ro = new ResizeObserver(checar);
    ro.observe(el);

    /* Estado do arrasto fora do React: isto roda a cada pixel de movimento, e
       um setState por quadro so para guardar a origem seria desperdicio. */
    let ativo = false;
    let moveu = false;
    let xInicial = 0;
    let scrollInicial = 0;

    const down = (e: PointerEvent) => {
      if (e.pointerType !== "mouse" || e.button !== 0) return;
      if (el.scrollWidth - el.clientWidth <= 4) return;
      /* Cartao, link ou campo: ali o arrasto tem outro dono (remarcar) ou o
         clique precisa chegar inteiro. */
      if ((e.target as HTMLElement).closest("button, a, input, select, textarea, label")) return;
      ativo = true;
      moveu = false;
      xInicial = e.clientX;
      scrollInicial = el.scrollLeft;
    };

    const move = (e: PointerEvent) => {
      if (!ativo) return;
      const dx = e.clientX - xInicial;
      if (!moveu) {
        if (Math.abs(dx) < 4) return;
        moveu = true;
        setArrastandoTela(true);
      }
      e.preventDefault();
      el.scrollLeft = scrollInicial - dx;
    };

    const up = () => {
      if (!ativo) return;
      ativo = false;
      if (!moveu) return;
      /* Segura o clique que o navegador dispara no fim do arrasto: sem isto,
         parar de rolar em cima de uma celula vazia abriria o formulario de
         novo compromisso sem ninguem ter pedido. */
      const engolir = (ev: MouseEvent) => {
        ev.stopPropagation();
        ev.preventDefault();
      };
      el.addEventListener("click", engolir, { capture: true, once: true });
      window.setTimeout(() => el.removeEventListener("click", engolir, { capture: true }), 0);
      moveu = false;
      setArrastandoTela(false);
    };

    el.addEventListener("pointerdown", down);
    window.addEventListener("pointermove", move, { passive: false });
    window.addEventListener("pointerup", up);
    window.addEventListener("pointercancel", up);

    return () => {
      el.removeEventListener("scroll", checar);
      ro.disconnect();
      el.removeEventListener("pointerdown", down);
      window.removeEventListener("pointermove", move);
      window.removeEventListener("pointerup", up);
      window.removeEventListener("pointercancel", up);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [dependencia]);

  return { ref, temMaisPraDireita, podeRolar, arrastandoTela };
}
