"use client";

import { useEffect, useState } from "react";
import { Clock, Plus } from "lucide-react";
import { useRolagemHorizontal } from "./gestos";
import { DicaEvento, useDica } from "./dica";
import { chaveDoDia, estiloSituacao, horarioDoEvento, type EventoAgenda } from "./tipos";

/**
 * Semana (ou dia) como grade de horario, com o bloco do compromisso na
 * altura da propria duracao — a mesma linguagem visual de qualquer agenda
 * premium: quem olha ve de relance onde o dia esta cheio, nao so que existe
 * "algo" as 14h.
 *
 * As celulas de fundo (uma por hora, por dia) carregam data-dia/data-hora e
 * ficam abaixo dos blocos. Durante o arrasto os blocos ganham
 * pointer-events-none, entao o ponteiro sempre acerta a celula por baixo —
 * sem isso, soltar em cima de outro compromisso perderia o alvo.
 */

const ROW_H = 72;
const EIXO_W = 56;
/* Calibrado para os sete dias caberem sem rolagem num monitor comum: acima
   disso a semana ficava cortada em telas de 1600px e a pessoa tinha de
   arrastar para ver sexta e sabado. Em tela maior o flex-1 estica as colunas
   bem alem deste minimo. */
const COL_MIN_W = 172;
const MAX_SEM_HORA = 3;

/* Linhas discretas de proposito: a grade e referencia, nao conteudo. Quando
   ela tem o mesmo peso do cartao, a tela vira tabela. */
const LINHA_H = "var(--ag-line-h)";
const LINHA_V = "var(--ag-line-v)";

/* Faixas de altura que decidem quanto cabe dentro do cartao. Sem isso todo
   compromisso vira pill com reticencias, mesmo os de duas horas que tem
   espaco de sobra.

   Os numeros saem da soma real do conteudo, nao de chute: 20px de padding
   vertical, 16px por linha de titulo, 6px de respiro e 20px do rodape. Chutar
   para baixo fazia o titulo de duas linhas passar por cima do horario. */
const ALTURA_2_LINHAS = 78;
const ALTURA_STATUS = 62;
const ALTURA_HORA = 58;

function horaFmt(min: number) {
  const h = Math.floor(min / 60);
  const m = min % 60;
  return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}

type Posicionado = {
  evento: EventoAgenda;
  inicioMin: number;
  fimMin: number;
  lane: number;
  lanes: number;
};

/* Faixas por sobreposicao: varre em ordem de inicio e usa a primeira faixa
   livre (fim <= novo inicio), como qualquer agenda faz para nao empilhar
   compromissos do mesmo horario um em cima do outro.

   O numero de faixas e contado por bloco de sobreposicao, e nao pelo dia
   inteiro: com a conta global, duas reunioes empilhadas as 9h espremiam a
   largura de todos os outros compromissos do dia — inclusive os que estavam
   sozinhos as 15h e tinham a coluna toda disponivel. E dai vinha o
   "Reuniao de alinhament..." cortado sem motivo. */
function posicionarDia(eventos: EventoAgenda[]): Posicionado[] {
  const comHorario = eventos
    .map((e) => {
      const { inicioMin, fimMin } = horarioDoEvento(e);
      if (inicioMin === null) return null;
      return { evento: e, inicioMin, fimMin: fimMin && fimMin > inicioMin ? fimMin : inicioMin + 60 };
    })
    .filter((x): x is { evento: EventoAgenda; inicioMin: number; fimMin: number } => x !== null)
    .sort((a, b) => a.inicioMin - b.inicioMin || a.fimMin - b.fimMin);

  const resultado: Posicionado[] = [];
  let bloco: Posicionado[] = [];
  let fimPorFaixa: number[] = [];
  let fimDoBloco = -1;

  const fechar = () => {
    const lanes = Math.max(1, fimPorFaixa.length);
    for (const item of bloco) resultado.push({ ...item, lanes });
    bloco = [];
    fimPorFaixa = [];
  };

  for (const item of comHorario) {
    /* Comecou depois do fim de tudo que veio antes: bloco novo, largura cheia. */
    if (bloco.length && item.inicioMin >= fimDoBloco) fechar();

    let lane = fimPorFaixa.findIndex((fim) => fim <= item.inicioMin);
    if (lane === -1) {
      lane = fimPorFaixa.length;
      fimPorFaixa.push(item.fimMin);
    } else {
      fimPorFaixa[lane] = item.fimMin;
    }

    bloco.push({ ...item, lane, lanes: 0 });
    fimDoBloco = Math.max(fimDoBloco, item.fimMin);
  }
  if (bloco.length) fechar();

  return resultado;
}

export default function GradeHoraria({
  dias,
  porDia,
  hojeChave,
  aoAbrir,
  aoArrastar,
  aoCriar,
  diaAlvo,
  horaAlvo,
  arrastandoId,
}: {
  dias: Date[];
  porDia: Map<string, EventoAgenda[]>;
  hojeChave: string;
  aoAbrir: (evento: EventoAgenda) => void;
  aoArrastar: (e: React.PointerEvent, evento: EventoAgenda) => void;
  aoCriar?: (dia: string, hora: number | null) => void;
  diaAlvo: string | null;
  horaAlvo: number | null;
  arrastandoId?: string | null;
}) {
  const [agora, setAgora] = useState(() => new Date());
  useEffect(() => {
    const id = window.setInterval(() => setAgora(new Date()), 60000);
    return () => window.clearInterval(id);
  }, []);

  const { ref: scrollRef, temMaisPraDireita, podeRolar, arrastandoTela } =
    useRolagemHorizontal<HTMLDivElement>(dias.length);
  const { dicaProps, camada, fecharDica } = useDica();

  const todosComHora = dias.flatMap((d) => porDia.get(chaveDoDia(d)) ?? []).map((e) => horarioDoEvento(e).inicioMin);
  const horasUsadas = todosComHora.filter((h): h is number => h !== null).map((min) => Math.floor(min / 60));
  const horaInicio = horasUsadas.length ? Math.max(0, Math.min(...horasUsadas) - 1) : 8;
  const horaFim = horasUsadas.length ? Math.min(23, Math.max(...horasUsadas) + 1) : 19;
  const horas = Array.from({ length: horaFim - horaInicio + 1 }, (_, i) => horaInicio + i);
  const alturaTotal = horas.length * ROW_H;

  const emArrasto = Boolean(arrastandoId);

  /* A linha do agora atravessa a grade inteira — do corredor de horas ate o
     ultimo dia — e nao so a coluna de hoje. Por isso e calculada uma vez
     aqui em cima, e nao dentro de cada coluna. */
  const minutoInicio = horaInicio * 60;
  const minutoAgora = agora.getHours() * 60 + agora.getMinutes();
  const hojeNaSemana = dias.some((d) => chaveDoDia(d) === hojeChave);
  const mostrarAgora = hojeNaSemana && minutoAgora >= minutoInicio && minutoAgora <= horaFim * 60;
  const topoAgora = ((minutoAgora - minutoInicio) / 60) * ROW_H;

  return (
    <div className="relative overflow-hidden rounded-[14px] border border-[var(--ag-border)] bg-[var(--ag-surface)] shadow-[var(--ag-sombra-1)]">
      <div
        ref={scrollRef}
        className={`overflow-x-auto [&::-webkit-scrollbar]:h-[6px] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-[var(--ag-border-strong)] [&::-webkit-scrollbar-thumb]:hover:bg-[var(--ag-text-5)] [&::-webkit-scrollbar-track]:bg-transparent ${
          podeRolar ? (arrastandoTela ? "cursor-[ew-resize] select-none" : "cursor-[ew-resize]") : ""
        }`}
      >
        <div style={{ minWidth: dias.length > 1 ? EIXO_W + dias.length * COL_MIN_W : undefined }}>
          {/* Cabecalho: dia da semana + numero, e a faixa "sem horario" logo abaixo. */}
          <div className="sticky top-0 z-20 flex border-b border-[var(--ag-line-v)] bg-[var(--ag-surface)]">
            {/* Canto: fica parado nos dois eixos, senao o cabecalho do dia
                passaria por baixo dele ao rolar para o lado. */}
            <span className="sticky left-0 z-10 shrink-0 bg-[var(--ag-surface)]" style={{ width: EIXO_W }} />
            {dias.map((dia) => {
              const chave = chaveDoDia(dia);
              const ehHoje = chave === hojeChave;
              return (
                <div
                  key={chave}
                  className="flex flex-1 flex-col items-center justify-center gap-[7px] border-l text-center"
                  style={{
                    height: 72,
                    borderColor: LINHA_V,
                    background: ehHoje ? "rgba(108,60,240,0.06)" : undefined,
                  }}
                >
                  <p
                    className="text-[11px] font-semibold uppercase leading-none tracking-[0.04em]"
                    style={{ color: ehHoje ? "#6C3CF0" : "var(--ag-text-3)" }}
                  >
                    {dia.toLocaleDateString("pt-BR", { weekday: "short" }).replace(".", "")}
                  </p>
                  <p
                    className="text-[19px] leading-none tabular-nums"
                    style={{ color: ehHoje ? "#6C3CF0" : "var(--ag-text)", fontWeight: ehHoje ? 700 : 650 }}
                  >
                    {dia.getDate()}
                  </p>
                </div>
              );
            })}
          </div>

          {/* Sem horario: prazos e compromissos sem hora marcada, um chip por
              evento, sem competir por espaco com a grade proporcional embaixo. */}
          {dias.some((d) => (porDia.get(chaveDoDia(d)) ?? []).some((e) => horarioDoEvento(e).inicioMin === null)) && (
            <div className="flex border-b border-[var(--ag-line-v)]">
              <span
                className="sticky left-0 z-10 shrink-0 bg-[var(--ag-surface)] px-[6px] py-2 text-right text-[10px] font-medium text-[var(--ag-text-4)]"
                style={{ width: EIXO_W }}
              >
                Sem hora
              </span>
              {dias.map((dia) => {
                const chave = chaveDoDia(dia);
                const semHora = (porDia.get(chave) ?? []).filter((e) => horarioDoEvento(e).inicioMin === null);
                const visiveis = semHora.slice(0, MAX_SEM_HORA);
                const ocultas = semHora.length - visiveis.length;
                return (
                  <div
                    key={chave}
                    data-dia={chave}
                    onClick={
                      aoCriar
                        ? (ev) => {
                            /* So o vazio da faixa cria: o clique que veio de um
                               mini-cartao ja abriu aquele compromisso. */
                            if ((ev.target as HTMLElement).closest("button")) return;
                            aoCriar(chave, null);
                          }
                        : undefined
                    }
                    className={`flex min-w-0 flex-1 flex-col content-start gap-[4px] border-l p-[6px] ${
                      aoCriar ? "cursor-pointer hover:bg-[rgba(108,60,240,0.03)]" : ""
                    } ${diaAlvo === chave && horaAlvo === null ? "bg-[rgba(108,60,240,0.06)]" : ""}`}
                    style={{ borderColor: LINHA_V }}
                  >
                    {visiveis.map((e) => (
                      <button
                        key={`${e.tipo}-${e.origem_id}`}
                        type="button"
                        onClick={() => aoAbrir(e)}
                        {...dicaProps(<DicaEvento evento={e} />)}
                        onPointerDown={(ev) => {
                          fecharDica();
                          aoArrastar(ev, e);
                        }}
                        className={`flex h-[30px] w-full min-w-0 items-center gap-[7px] rounded-[8px] border border-[var(--ag-border)] bg-[var(--ag-surface)] px-[9px] text-left shadow-[var(--ag-sombra-2)] transition-[border-color,box-shadow] hover:border-[rgba(108,60,240,0.28)] hover:shadow-[var(--ag-sombra-3)] ${
                          arrastandoId === e.origem_id ? "opacity-40" : ""
                        } ${emArrasto ? "pointer-events-none" : ""}`}
                      >
                        <span
                          aria-hidden
                          className="h-[14px] w-[3px] shrink-0 rounded-full"
                          style={{ background: e.categoria_cor, opacity: 0.75 }}
                        />
                        <span className="min-w-0 flex-1 truncate text-[11px] font-medium text-[var(--ag-text-2)]">
                          {e.titulo}
                        </span>
                      </button>
                    ))}
                    {ocultas > 0 && (
                      <span className="px-[9px] py-[2px] text-[10px] font-medium text-[var(--ag-text-4)]">
                        +{ocultas}
                      </span>
                    )}
                  </div>
                );
              })}
            </div>
          )}

          {/* Grade proporcional. */}
          <div className="relative flex">
            {/* Corredor das horas parado na esquerda: ao arrastar a semana
                para o lado, sem isto some a unica referencia de que horas e
                cada linha — e a grade vira um monte de retangulos soltos. */}
            <div
              className="sticky left-0 z-[15] shrink-0 bg-[var(--ag-surface)]"
              style={{ width: EIXO_W, boxShadow: temMaisPraDireita ? "1px 0 0 var(--ag-line-h)" : undefined }}
            >
              {horas.map((h) => (
                <div
                  key={h}
                  className="relative border-t first:border-t-0"
                  style={{ height: ROW_H, borderColor: LINHA_H }}
                >
                  <span className="absolute right-2 top-0 -translate-y-1/2 bg-[var(--ag-surface)] pl-1 text-[11px] font-medium tabular-nums text-[var(--ag-text-4)]">
                    {String(h).padStart(2, "0")}:00
                  </span>
                </div>
              ))}
            </div>

            {dias.map((dia) => {
              const chave = chaveDoDia(dia);
              const ehHoje = chave === hojeChave;
              const doDia = porDia.get(chave) ?? [];
              const posicionados = posicionarDia(doDia);

              return (
                <div
                  key={chave}
                  className="relative flex-1 border-l"
                  style={{
                    height: alturaTotal,
                    minWidth: dias.length > 1 ? COL_MIN_W : undefined,
                    borderColor: LINHA_V,
                    background: ehHoje ? "rgba(108,60,240,0.035)" : undefined,
                  }}
                >
                  {/* A celula vazia e o atalho de criar: ate aqui so o botao
                      do topo abria o formulario, e clicar no horario que a
                      pessoa ja estava olhando nao fazia nada. Fica como div, e
                      nao button, porque o arrasto-para-rolar solta o gesto
                      quando o alvo e um botao — e a grade toda deixaria de
                      rolar por arrasto. */}
                  {horas.map((h) => (
                    <div
                      key={h}
                      data-dia={chave}
                      data-hora={h}
                      onClick={aoCriar ? () => aoCriar(chave, h) : undefined}
                      role={aoCriar ? "button" : undefined}
                      tabIndex={aoCriar ? -1 : undefined}
                      aria-label={
                        aoCriar ? `Criar compromisso às ${String(h).padStart(2, "0")}:00` : undefined
                      }
                      className={`group/celula relative border-t first:border-t-0 ${
                        aoCriar ? "cursor-pointer" : ""
                      }`}
                      style={{
                        height: ROW_H,
                        borderColor: LINHA_H,
                        background:
                          diaAlvo === chave && horaAlvo === h ? "rgba(108,60,240,0.08)" : undefined,
                      }}
                    >
                      {aoCriar && !emArrasto && (
                        <span
                          aria-hidden
                          className="pointer-events-none absolute inset-x-[3px] inset-y-[2px] flex items-center justify-center rounded-[8px] opacity-0 transition-opacity duration-100 group-hover/celula:opacity-100"
                          style={{ background: "rgba(108,60,240,0.055)" }}
                        >
                          <Plus className="h-[15px] w-[15px]" style={{ color: "#9077F2" }} />
                        </span>
                      )}
                    </div>
                  ))}

                  {posicionados.map(({ evento, inicioMin, fimMin, lane, lanes }) => {
                    const top = ((inicioMin - minutoInicio) / 60) * ROW_H;
                    /* 28px e o piso do mini-cartao: abaixo disso o texto vazava
                       nos compromissos de 15 minutos, e o bloco passava a ler
                       como etiqueta solta em vez de cartao. */
                    const altura = Math.max(((fimMin - inicioMin) / 60) * ROW_H - 4, 28);
                    const largura = 100 / lanes;
                    const situacao = estiloSituacao(evento.situacao);

                    /* Formatos conforme a altura real do bloco. O compacto so
                       vale para compromisso curto de verdade — nao e o padrao
                       para todo mundo. */
                    const duasLinhas = altura >= ALTURA_2_LINHAS;
                    const comStatus = altura >= ALTURA_STATUS;
                    const compacto = altura < ALTURA_HORA;

                    return (
                      <button
                        key={`${evento.tipo}-${evento.origem_id}`}
                        type="button"
                        onClick={() => aoAbrir(evento)}
                        /* O spread vem antes: ele traz o proprio
                           onPointerDown (fechar a dica), e depois dele o
                           arrasto reassume o evento sem se perder. */
                        {...dicaProps(<DicaEvento evento={evento} />)}
                        onPointerDown={(ev) => {
                          fecharDica();
                          aoArrastar(ev, evento);
                        }}
                        className={`group absolute flex flex-col overflow-hidden border bg-[var(--ag-surface)] text-left transition-[transform,box-shadow,border-color] duration-150 ease-out hover:z-30 hover:-translate-y-px hover:border-[rgba(108,60,240,0.28)] hover:shadow-[var(--ag-sombra-4)] ${
                          compacto
                            ? "justify-center rounded-[8px] border-[var(--ag-border)] py-[3px] pl-[13px] pr-[8px] shadow-[var(--ag-sombra-2)]"
                            : "rounded-[10px] border-[var(--ag-border)] p-[10px] pl-[14px] shadow-[var(--ag-sombra-3)]"
                        } ${arrastandoId === evento.origem_id ? "opacity-40" : ""} ${
                          podeArrastarNaGrade(evento.tipo) ? "touch-none cursor-grab active:cursor-grabbing" : ""
                        } ${emArrasto ? "pointer-events-none" : ""}`}
                        style={{
                          top,
                          height: altura,
                          left: `calc(${lane * largura}% + 3px)`,
                          width: `calc(${largura}% - 6px)`,
                        }}
                      >
                        <span
                          aria-hidden
                          className={`absolute left-[5px] w-[3px] rounded-full ${
                            compacto ? "bottom-[5px] top-[5px]" : "bottom-[8px] top-[8px]"
                          }`}
                          style={{ background: evento.categoria_cor, opacity: 0.75 }}
                        />

                        {compacto ? (
                          /* Curto demais para duas alturas de texto: hora e
                             titulo dividem a unica linha que cabe. */
                          <span className="flex min-w-0 items-baseline gap-[5px]">
                            <span
                              className="shrink-0 text-[11px] font-semibold tabular-nums"
                              style={{ color: evento.categoria_cor }}
                            >
                              {horaFmt(inicioMin)}
                            </span>
                            <span className="truncate text-[12px] font-semibold leading-[1.3] text-[var(--ag-text)]">
                              {evento.titulo}
                            </span>
                          </span>
                        ) : (
                          <>
                            {/* Sem `block` junto de line-clamp — a utility traz o
                                proprio display:-webkit-box, e `block` vencia, o
                                que fazia o titulo crescer sem limite. */}
                            <span
                              className={`text-[12px] font-semibold leading-[1.3] text-[var(--ag-text)] ${
                                duasLinhas ? "line-clamp-2" : "block truncate"
                              }`}
                            >
                              {evento.titulo}
                            </span>
                            {/* Rodape logo abaixo do titulo, e nao empurrado
                                para o fim: num bloco de tres horas o mt-auto
                                abria um vao vazio no meio do cartao. A duracao
                                ja e lida pela altura. */}
                            <span className="mt-[6px] flex items-center gap-[5px]">
                              <Clock className="h-[11px] w-[11px] shrink-0" style={{ color: "#9C8AEF" }} />
                              <span className="truncate text-[11px] font-medium tabular-nums text-[var(--ag-text-3)]">
                                {horaFmt(inicioMin)}
                              </span>
                              {comStatus && situacao.texto && (
                                <span
                                  className="ml-auto flex h-[20px] shrink-0 items-center rounded-full px-[7px] text-[10px] font-medium"
                                  style={{ background: situacao.fundo, color: situacao.cor }}
                                >
                                  {situacao.texto}
                                </span>
                              )}
                            </span>
                          </>
                        )}
                      </button>
                    );
                  })}
                </div>
              );
            })}

            {/* Linha do agora: uma so, atravessando a grade inteira. */}
            {mostrarAgora && (
              <div
                aria-hidden
                className="pointer-events-none absolute inset-x-0 z-10 flex items-center"
                style={{ top: topoAgora, transform: "translateY(-50%)" }}
              >
                <div className="sticky left-0 z-10 flex shrink-0 justify-end pr-1.5" style={{ width: EIXO_W }}>
                  <span className="rounded-full bg-[#6C3CF0] px-[9px] py-[4px] text-[11px] font-semibold text-white shadow-sm">
                    {horaFmt(minutoAgora)}
                  </span>
                </div>
                <div className="h-0 flex-1 border-t border-dashed" style={{ borderColor: "rgba(108,60,240,0.55)" }} />
              </div>
            )}
          </div>
        </div>
      </div>

      {/* Sinaliza que ha mais dias pra ver rolando pra direita — sem isso, o
          ultimo dia visivel parece cortado sem motivo, nao um convite pra rolar. */}
      {temMaisPraDireita && (
        <div
          aria-hidden
          className="pointer-events-none absolute bottom-0 right-0 top-0 z-30 w-10 bg-gradient-to-l from-[var(--ag-surface)] to-transparent"
        />
      )}

      {camada}
    </div>
  );
}

function podeArrastarNaGrade(tipo: string) {
  return tipo === "compromisso" || tipo === "pedido_entrega";
}
