

import { useEffect, useRef, useState } from "react";
import type { WeeklyActivity } from "../../../hooks/useGitHubStats";
import { useHoverPopover } from "../../../hooks/useHoverPopover";
import HoverPopover from "./HoverPopover";
import Icon from "../Icon";

interface ActivityHeatmapProps {
  /** 52 weeks of commit activity data. */
  weeks: WeeklyActivity[];
  /** Whether data is still loading. */
  loading?: boolean;
}

const DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

/** Pixel gap between cells. */
const CELL_GAP = 3;
/** Width of the day-label column including right padding. */
const LABEL_WIDTH = 32;
/** Minimum cell size to prevent cells from becoming invisible. */
const MIN_CELL_SIZE = 8;
/** Fixed cell size for mobile (scroll mode). */
const MOBILE_CELL_SIZE = 10;
/** Container width threshold below which we switch to scroll mode. */
const SCROLL_MODE_THRESHOLD = 600;
/** A full year of weekly data. */
const WEEKS_PER_YEAR = 52;
/** Seconds in one week. */
const SECONDS_PER_WEEK = 7 * 86400;

/**
 * Formats a Unix timestamp (seconds) + day offset into a readable date.
 */
function formatDate(weekTimestamp: number, dayIndex: number): string {
  const date = new Date((weekTimestamp + dayIndex * 86400) * 1000);
  return date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", year: "numeric" });
}

/**
 * Builds month labels with pixel offsets relative to the cell grid.
 */
function buildMonthLabels(weeks: WeeklyActivity[], cellSize: number): { label: string; offset: number }[] {
  const labels: { label: string; offset: number }[] = [];
  let lastMonth = -1;

  for (let idx = 0; idx < weeks.length; idx++) {
    const date = new Date(weeks[idx].week * 1000);
    const month = date.getMonth();
    if (month !== lastMonth) {
      labels.push({
        label: MONTH_NAMES[month],
        offset: idx * (cellSize + CELL_GAP),
      });
      lastMonth = month;
    }
  }

  return labels;
}

/** Data displayed in the hover popover. */
interface HeatmapHover {
  date: string;
  count: number;
}

/** Maps intensity level (0–4) to a Tailwind opacity class. */
const OPACITY_MAP: Record<number, string> = {
  0: "opacity-[0.06]",
  1: "opacity-[0.25]",
  2: "opacity-[0.45]",
  3: "opacity-[0.7]",
  4: "opacity-100",
};

/** Returns an intensity level (0–4) for a commit count relative to the maximum. */
function intensityLevel(count: number, maxCommits: number): number {
  if (count === 0) return 0;
  const ratio = count / maxCommits;
  if (ratio <= 0.25) return 1;
  if (ratio <= 0.5) return 2;
  if (ratio <= 0.75) return 3;
  return 4;
}

/**
 * Pads the weeks array to a full year (52 weeks) by prepending empty weeks.
 * If the array already has 52+ entries it is returned unchanged.
 */
function padToFullYear(weeks: WeeklyActivity[]): WeeklyActivity[] {
  if (weeks.length >= WEEKS_PER_YEAR) return weeks;

  const emptyDays = [0, 0, 0, 0, 0, 0, 0];
  const missing = WEEKS_PER_YEAR - weeks.length;
  const earliestTimestamp = weeks.length > 0 ? weeks[0].week : Math.floor(Date.now() / 1000);

  const padding: WeeklyActivity[] = Array.from({ length: missing }, (_, idx) => ({
    week: earliestTimestamp - (missing - idx) * SECONDS_PER_WEEK,
    total: 0,
    days: emptyDays,
  }));

  return [...padding, ...weeks];
}

/**
 * Calculates the cell size that fills the available container width.
 * Formula: floor((availableWidth - LABEL_WIDTH - (colCount - 1) * CELL_GAP) / colCount)
 */
function computeCellSize(containerWidth: number, colCount: number): number {
  const availableForCells = containerWidth - LABEL_WIDTH - (colCount - 1) * CELL_GAP;
  return Math.max(MIN_CELL_SIZE, Math.floor(availableForCells / colCount));
}

/**
 * A GitHub-style commit activity heatmap showing 52 weeks of daily commit counts.
 *
 * Cell size is dynamically calculated to fill the available container width.
 * Layout: day labels on the left, a column-flow grid of square cells on the right.
 * Month labels are positioned above the grid using pixel offsets.
 */
export default function ActivityHeatmap({ weeks, loading = false }: ActivityHeatmapProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [cellSize, setCellSize] = useState(0);
  const [scrollMode, setScrollMode] = useState(false);
  const { hover, popover, show: showPopover, hide: hidePopover, cancelHide } = useHoverPopover<HeatmapHover>();

  const fullYear = padToFullYear(weeks);
  const colCount = fullYear.length;

  useEffect(() => {
    const container = containerRef.current;
    if (!container || colCount === 0) return;

    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const width = entry.contentRect.width;
        if (width < SCROLL_MODE_THRESHOLD) {
          // Mobile: fixed cell size, horizontal scroll
          setScrollMode(true);
          setCellSize(MOBILE_CELL_SIZE);
        } else {
          // Desktop: compute cell size to fill available space
          setScrollMode(false);
          setCellSize(computeCellSize(width, colCount));
        }
      }
    });

    observer.observe(container);
    // Initial measurement
    const width = container.clientWidth;
    if (width < SCROLL_MODE_THRESHOLD) {
      setScrollMode(true);
      setCellSize(MOBILE_CELL_SIZE);
    } else {
      setScrollMode(false);
      setCellSize(computeCellSize(width, colCount));
    }

    return () => observer.disconnect();
  }, [colCount, loading]);

  if (loading) {
    return (
      <div className="rounded-xl border border-border bg-frosted-glass p-6 backdrop-blur-xl">
        <h3 className="mb-4 flex items-center gap-3 text-xl font-semibold text-foreground">
          <Icon name="calendar" size={24} className="text-accent" />
          Commit Activity
        </h3>
        <div className="h-32 w-full rounded-md bg-accent/10 animate-skeleton" />
      </div>
    );
  }


  const maxCommits = Math.max(1, ...fullYear.flatMap((week) => week.days));
  const monthLabels = buildMonthLabels(fullYear, cellSize);

  function handleMouseEnter(event: React.MouseEvent<HTMLDivElement>, weekTimestamp: number, dayIdx: number, count: number) {
    const cell = event.currentTarget;
    const wrapper = cell.closest("[data-heatmap-grid]");
    if (!wrapper) return;

    const wrapperRect = wrapper.getBoundingClientRect();
    const cellRect = cell.getBoundingClientRect();

    showPopover(
      { date: formatDate(weekTimestamp, dayIdx), count },
      cellRect.left - wrapperRect.left + cellRect.width / 2,
      cellRect.top - wrapperRect.top,
    );
  }

  const gridHeight = 7 * cellSize + 6 * CELL_GAP;

  return (
    <div ref={containerRef} className="rounded-xl border border-border bg-frosted-glass p-6 backdrop-blur-xl">
      <h3 className="mb-4 flex items-center gap-3 text-xl font-semibold text-foreground">
        <Icon name="calendar" size={24} className="text-accent" />
        Commit Activity
      </h3>

      {cellSize > 0 && (
        <div className="relative" data-heatmap-grid onMouseLeave={hidePopover}>

          {/* Scrollable wrapper for mobile */}
          <div className={scrollMode ? "overflow-x-auto pb-2" : ""}>
            {/* Month labels: absolutely positioned above the cell grid */}
            <div className="relative h-5" style={{ marginLeft: LABEL_WIDTH, minWidth: scrollMode ? colCount * (cellSize + CELL_GAP) : undefined }}>
              {monthLabels.map(({ label, offset }) => (
                <span
                  key={`${label}-${offset}`}
                  className="absolute bottom-0 text-xs text-muted"
                  style={{ left: offset }}
                >
                  {label}
                </span>
              ))}
            </div>

            {/* Day labels (left) + Cell grid (right) */}
            <div className="flex" style={{ minWidth: scrollMode ? LABEL_WIDTH + colCount * (cellSize + CELL_GAP) : undefined }}>
              {/* Day labels */}
              <div
                className={`flex shrink-0 flex-col ${scrollMode ? "sticky left-0 z-10 bg-frosted-glass" : ""}`}
                style={{
                  width: LABEL_WIDTH,
                  height: gridHeight,
                  justifyContent: "space-around",
                }}
              >
                {DAY_LABELS.map((day) => (
                  <span
                    key={day}
                    className="text-right text-xs leading-none text-muted"
                    style={{ height: cellSize, lineHeight: `${cellSize}px`, paddingRight: 6 }}
                  >
                    {day}
                  </span>
                ))}
              </div>

              {/* Cell grid: column-flow: 7 rows, columns auto-created per week */}
              <div
                className="grid"
                style={{
                  gridTemplateRows: `repeat(7, ${cellSize}px)`,
                  gridAutoFlow: "column",
                  gridAutoColumns: `${cellSize}px`,
                  gap: CELL_GAP,
                }}
              >
                {fullYear.map((week) =>
                  week.days.map((count, dayIdx) => {
                    const level = intensityLevel(count, maxCommits);
                    return (
                      <div
                        key={`${week.week}-${dayIdx}`}
                        className={`rounded-sm bg-accent ${OPACITY_MAP[level]} transition-opacity hover:ring-1 hover:ring-accent/60`}
                        style={{ width: cellSize, height: cellSize }}
                        onMouseEnter={(event) => count > 0 && handleMouseEnter(event, week.week, dayIdx, count)}
                        onMouseLeave={hidePopover}
                      />
                    );
                  })
                )}
              </div>
            </div>
          </div>

          {/* Popover */}
          <HoverPopover
            visible={!!hover}
            x={popover?.x ?? 0}
            y={popover?.y ?? 0}
            offsetY={-11}
            minWidth="10rem"
            onMouseEnter={cancelHide}
            onMouseLeave={hidePopover}
          >
            <p className="whitespace-nowrap text-center text-sm font-medium text-foreground">{popover?.data.date}</p>
            <p className="whitespace-nowrap text-center text-sm text-muted">
              <span className="font-bold text-accent">{popover?.data.count ?? 0}</span> commit{popover?.data.count !== 1 ? "s" : ""}
            </p>
          </HoverPopover>
        </div>
      )}

      {/* Legend */}
      <div className="mt-6 flex items-center justify-center gap-1.5 text-xs text-muted">
        <span>Less</span>
        {[0, 1, 2, 3, 4].map((level) => (
          <div
            key={level}
            className={`rounded-sm bg-accent ${OPACITY_MAP[level]}`}
            style={{ width: 11, height: 11 }}
          />
        ))}
        <span>More</span>
      </div>
    </div>
  );
}
