"use client";

import { useEffect, useRef, useState, type ReactNode } from "react";
import type { Locale } from "date-fns";

import { cn } from "@/lib/utils";
import { Typography } from "@/components/ui/typography";

import { EventBlock } from "./event-block";
import { EventChip } from "./event-chip";
import { DEFAULT_SCROLL_HOUR, HOUR_HEIGHT } from "./constants";
import {
  formatColumnHeader,
  getHourRows,
  isSameDay,
  isToday,
  setHours,
  startOfDay,
} from "./lib/date-utils";
import {
  getDayEvents,
  getNowOffset,
  layoutDayEvents,
} from "./lib/event-layout";
import type { CalendarEvent } from "./types";

interface TimeGridProps<TMeta> {
  /** 1 day (day view) or 7 days (week view). */
  days: Date[];
  events: CalendarEvent<TMeta>[];
  locale: Locale;
  startHour: number;
  endHour: number;
  allDayLabel: string;
  onEventClick: (event: CalendarEvent<TMeta>) => void;
  onSelectSlot?: (date: Date) => void;
  renderEvent?: (event: CalendarEvent<TMeta>) => ReactNode;
}

/**
 * Shared hour-by-hour scaffold for the week (7 columns) and day (1 column)
 * views. The component IS the single scroll surface: the day-header + all-day
 * strip stay pinned via `sticky top-0` while the hour rows scroll under them,
 * and the same container scrolls horizontally when the week is too wide. It
 * fills its parent's height (`flex-1`), so the page never gets a second
 * scrollbar — the calendar sizes to the content area and only this grid scrolls.
 */
export function TimeGrid<TMeta>({
  days,
  events,
  locale,
  startHour,
  endHour,
  allDayLabel,
  onEventClick,
  onSelectSlot,
  renderEvent,
}: TimeGridProps<TMeta>) {
  const scrollRef = useRef<HTMLDivElement>(null);
  const [now, setNow] = useState<Date | null>(null);
  const hourRows = getHourRows(locale, startHour, endHour);
  const gridHeight = (endHour - startHour) * HOUR_HEIGHT;
  const isWeek = days.length > 1;

  const perDay = days.map((day) => {
    const { allDay, timed } = getDayEvents(events, day);
    return {
      day,
      allDay,
      positioned: layoutDayEvents(timed, startHour, endHour),
      nowOffset:
        now && isSameDay(day, now)
          ? getNowOffset(now, startHour, endHour)
          : null,
    };
  });
  const hasAllDay = perDay.some((d) => d.allDay.length > 0);

  // Resolve "now" on the client only (after paint), so the indicator never
  // triggers a hydration mismatch and the time stays current on remount.
  useEffect(() => {
    const id = requestAnimationFrame(() => setNow(new Date()));
    return () => cancelAnimationFrame(id);
  }, []);

  // Bring the working morning into view on mount instead of midnight.
  useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTop = Math.max(
        0,
        (DEFAULT_SCROLL_HOUR - startHour) * HOUR_HEIGHT,
      );
    }
  }, [startHour]);

  return (
    <div
      ref={scrollRef}
      className="min-h-88 flex-1 overflow-auto overscroll-contain"
    >
      <div
        className={cn("flex flex-col", isWeek && "min-w-[720px] xl:min-w-0")}
      >
        {/* Pinned header: weekday/date row + all-day strip */}
        <div className="sticky top-0 z-40 bg-white">
          <div className="flex border-b border-border">
            {/* top-start corner: stays pinned on both axes */}
            <div className="sticky start-0 z-50 w-14 shrink-0 bg-white" />
            {perDay.map(({ day }) => {
              const header = formatColumnHeader(day, locale);
              const today = isToday(day);
              return (
                <div
                  key={day.toISOString()}
                  className="flex flex-1 flex-col items-center gap-0.5 border-s border-border py-2"
                >
                  <Typography as="span" variant="caption" color="secondary">
                    {header.weekday}
                  </Typography>
                  <span
                    className={cn(
                      "flex size-8 items-center justify-center rounded-full",
                      today && "bg-primary",
                    )}
                  >
                    <Typography
                      as="span"
                      variant="number"
                      color="inherit"
                      className={
                        today ? "text-primary-foreground" : "text-main-text"
                      }
                    >
                      {header.day}
                    </Typography>
                  </span>
                </div>
              );
            })}
          </div>

          {hasAllDay && (
            <div className="flex border-b border-border">
              <div className="sticky start-0 z-50 flex w-14 shrink-0 items-center justify-center bg-white px-1 py-1">
                <Typography
                  as="span"
                  variant="caption"
                  color="secondary"
                  className="text-center"
                >
                  {allDayLabel}
                </Typography>
              </div>
              {perDay.map(({ day, allDay }) => (
                <div
                  key={day.toISOString()}
                  className="flex min-w-0 flex-1 flex-col gap-0.5 border-s border-border p-1"
                >
                  {allDay.map((event) => (
                    <EventChip
                      key={event.id}
                      event={event}
                      locale={locale}
                      onClick={onEventClick}
                      renderEvent={renderEvent}
                    />
                  ))}
                </div>
              ))}
            </div>
          )}
        </div>

        {/* Time area */}
        <div className="relative flex" style={{ height: gridHeight }}>
          {/* Hour gutter — pinned to the inline-start during horizontal scroll */}
          <div className="sticky start-0 z-30 w-14 shrink-0 bg-white">
            {hourRows.map((row) => (
              <div
                key={row.hour}
                style={{ height: HOUR_HEIGHT }}
                className="relative"
              >
                <Typography
                  as="span"
                  variant="caption"
                  color="secondary"
                  className="absolute end-2 -top-2"
                >
                  {row.label}
                </Typography>
              </div>
            ))}
          </div>

          {/* Day columns */}
          {perDay.map(({ day, positioned, nowOffset }) => (
            <div
              key={day.toISOString()}
              className="relative flex-1 border-s border-border"
            >
              {/* Hour slots — border on top so the line aligns with its label */}
              {hourRows.map((row) => (
                <div
                  key={row.hour}
                  style={{ height: HOUR_HEIGHT }}
                  className="border-t border-border/50 hover:bg-muted/30"
                  onClick={() =>
                    onSelectSlot?.(setHours(startOfDay(day), row.hour))
                  }
                />
              ))}

              {/* Positioned events */}
              {positioned.map(
                ({ event, topPct, heightPct, leftPct, widthPct }) => (
                  <EventBlock
                    key={event.id}
                    event={event}
                    locale={locale}
                    compact={heightPct < 6}
                    onClick={onEventClick}
                    renderEvent={renderEvent}
                    style={{
                      top: `${topPct}%`,
                      height: `${heightPct}%`,
                      insetInlineStart: `calc(${leftPct}% + 2px)`,
                      width: `calc(${widthPct}% - 4px)`,
                    }}
                  />
                ),
              )}

              {/* Now indicator */}
              {nowOffset !== null && (
                <div
                  className="pointer-events-none absolute inset-x-0 z-20 flex items-center"
                  style={{ top: `${nowOffset * 100}%` }}
                >
                  <span className="-ms-1 size-2 rounded-full bg-mistake" />
                  <span className="h-px flex-1 bg-mistake" />
                </div>
              )}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
