"use client";

import { createPortal } from "react-dom";
import { useEffect, useMemo, useRef, useState, type ReactNode, type UIEvent } from "react";
import { trpc } from "~/lib/trpc/client.js";
import type { WidgetProps } from "~/components/dashboard/widget-registry.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { AnimatedNumber } from "~/components/ui/AnimatedNumber.js";
import { WidgetFilterBar, type WidgetFilter } from "~/components/dashboard/WidgetFilterBar.js";
import { useWidgetFilterOptions } from "~/hooks/useWidgetFilterOptions.js";
import { useReferenceData } from "~/hooks/useReferenceData.js";
import { useAnchoredOverlay } from "~/hooks/useAnchoredOverlay.js";

function UtilizationBar({ percent }: { percent: number }) {
  const barColor =
    percent >= 80 ? "bg-green-500" : percent >= 50 ? "bg-amber-500" : "bg-red-500";
  return (
    <div className="h-1 w-full rounded-full bg-gray-100 dark:bg-gray-800 mt-0.5">
      <div
        className={`h-full rounded-full transition-all duration-500 ${barColor}`}
        style={{ width: `${Math.min(percent, 100)}%` }}
      />
    </div>
  );
}

type TopSortKey = "name" | "actual" | "expected";
type WatchSortKey = "name" | "actual" | "target";

type ChargeabilityRow = {
  id: string;
  displayName: string;
  chapter: string | null;
  chargeabilityTarget: number;
  actualChargeability: number;
  expectedChargeability: number;
  countryCode?: string | null;
  countryName?: string | null;
  federalState?: string | null;
  metroCityName?: string | null;
  derivation?: {
    weeklyAvailabilityHours: number;
    baseWorkingDays: number;
    effectiveWorkingDayEquivalent: number;
    baseAvailableHours: number;
    effectiveAvailableHours: number;
    publicHolidayCount: number;
    publicHolidayWorkdayCount: number;
    publicHolidayHoursDeduction: number;
    absenceDayEquivalent: number;
    absenceHoursDeduction: number;
    actualBookedHours: number;
    expectedBookedHours: number;
    targetBookedHours: number;
    unassignedHours: number;
  };
};

function formatHours(value: number | undefined): string {
  if (value === undefined) return "—";
  return `${Number.isInteger(value) ? value.toFixed(0) : value.toFixed(1)}h`;
}

function formatDayEquivalent(value: number | undefined): string {
  if (value === undefined) return "—";
  return Number.isInteger(value) ? `${value}` : value.toFixed(1);
}

function MetricPill({ label, value }: { label: string; value: string }) {
  return (
    <span className="inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-50 px-2 py-0.5 text-[10px] font-medium text-gray-600 dark:border-gray-700 dark:bg-gray-900/70 dark:text-gray-300">
      <span className="text-gray-400 dark:text-gray-500">{label}</span>
      <span className="text-gray-700 dark:text-gray-200">{value}</span>
    </span>
  );
}

function formatLocation(row: ChargeabilityRow): string {
  const parts = [row.countryCode ?? row.countryName ?? null, row.federalState ?? null, row.metroCityName ?? null]
    .filter((part): part is string => Boolean(part));
  return parts.length > 0 ? parts.join(" / ") : "No calendar context";
}

function ChargeabilityContextLine({ row }: { row: ChargeabilityRow }) {
  const derivation = row.derivation;

  if (!derivation) {
    return null;
  }

  return (
    <div className="mt-1.5 space-y-1.5 text-[10px] leading-4 text-gray-500 dark:text-gray-400">
      <div className="flex flex-wrap gap-1">
        <MetricPill label="Loc" value={formatLocation(row)} />
        <MetricPill label="Week" value={formatHours(derivation.weeklyAvailabilityHours)} />
        <MetricPill label="Target" value={formatHours(derivation.targetBookedHours)} />
      </div>
      <div className="grid gap-x-3 gap-y-0.5 sm:grid-cols-2">
        <div>
          Days {formatDayEquivalent(derivation.baseWorkingDays)} {"->"} {formatDayEquivalent(derivation.effectiveWorkingDayEquivalent)}
        </div>
        <div>
          Holidays {derivation.publicHolidayWorkdayCount}/{derivation.publicHolidayCount} ({formatHours(derivation.publicHolidayHoursDeduction)})
        </div>
        <div>
          Base {formatHours(derivation.baseAvailableHours)} {"->"} Effective {formatHours(derivation.effectiveAvailableHours)}
        </div>
        <div>
          Absence {formatDayEquivalent(derivation.absenceDayEquivalent)} ({formatHours(derivation.absenceHoursDeduction)})
        </div>
        <div>
          Actual {formatHours(derivation.actualBookedHours)} · Expected {formatHours(derivation.expectedBookedHours)}
        </div>
        <div>
          Free {formatHours(derivation.unassignedHours)}
        </div>
      </div>
    </div>
  );
}

function FilterDropdown({ label, children }: { label: string; children: ReactNode }) {
  const [isOpen, setIsOpen] = useState(false);
  const triggerRef = useRef<HTMLButtonElement | null>(null);
  const { panelRef, position } = useAnchoredOverlay<HTMLButtonElement>({
    open: isOpen,
    onClose: () => setIsOpen(false),
    align: "end",
    triggerRef,
  });

  return (
    <div className="relative">
      <button
        ref={triggerRef}
        type="button"
        onClick={() => setIsOpen((current) => !current)}
        className="inline-flex min-w-44 items-center justify-between gap-3 rounded-xl border border-gray-300 bg-white px-3 py-2 text-xs text-gray-700 shadow-sm transition hover:border-gray-400 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200"
      >
        <span className="truncate">{label}</span>
        <span className="text-[10px] text-gray-400">{isOpen ? "▲" : "▼"}</span>
      </button>
      {isOpen && typeof document !== "undefined"
        ? createPortal(
            <div
              ref={panelRef}
              className="fixed z-[9998] w-72 rounded-2xl border border-gray-200 bg-white p-3 shadow-lg dark:border-gray-700 dark:bg-gray-900"
              style={{
                top: position.top,
                left: position.left,
              }}
            >
              {children}
            </div>,
            document.body,
          )
        : null}
    </div>
  );
}

export function ChargeabilityWidget({ config: _config, onConfigChange }: WidgetProps) {
  const config = _config as {
    topN?: number;
    watchlistThreshold?: number;
    chapter?: string;
    includeProposed?: boolean;
    showDetails?: boolean;
  };
  const { chapters } = useWidgetFilterOptions({ chapters: true });
  const { countries } = useReferenceData({ countries: true });

  const widgetFilters = useMemo<WidgetFilter[]>(
    () => [
      { type: "select", key: "chapter", label: "Chapter", options: chapters },
      { type: "toggle", key: "includeProposed", label: "Include Proposed" },
    ],
    [chapters],
  );

  const includeProposed = !!config.includeProposed;
  const showDetails = !!config.showDetails;
  const chapterFilter = (config.chapter as string) ?? "";
  const [showDeparted, setShowDeparted] = useState(false);
  const [selectedCountryIds, setSelectedCountryIds] = useState<string[]>([]);
  const [topSort, setTopSort] = useState<TopSortKey>("actual");
  const [topDir, setTopDir] = useState<"asc" | "desc">("desc");
  const [watchSort, setWatchSort] = useState<WatchSortKey>("actual");
  const [watchDir, setWatchDir] = useState<"asc" | "desc">("asc");
  const batchSize = Math.max(config.topN ?? 10, 10);
  const [topVisibleCount, setTopVisibleCount] = useState(batchSize);
  const [watchVisibleCount, setWatchVisibleCount] = useState(batchSize);

  const selectedCountryLabel = useMemo(() => {
    if (selectedCountryIds.length === 0) return "Countries: All";
    if (selectedCountryIds.length === 1) {
      return `Country: ${countries.find((country) => country.id === selectedCountryIds[0])?.name ?? "1 selected"}`;
    }
    return `Countries: ${selectedCountryIds.length} selected`;
  }, [countries, selectedCountryIds]);

  function toggleTop(key: TopSortKey) {
    if (topSort === key) setTopDir((d) => (d === "asc" ? "desc" : "asc"));
    else {
      setTopSort(key);
      setTopDir(key === "name" ? "asc" : "desc");
    }
  }
  function toggleWatch(key: WatchSortKey) {
    if (watchSort === key) setWatchDir((d) => (d === "asc" ? "desc" : "asc"));
    else {
      setWatchSort(key);
      setWatchDir(key === "name" ? "asc" : "asc");
    }
  }

  const { data, isLoading } = trpc.dashboard.getChargeabilityOverview.useQuery(
    {
      includeProposed,
      topN: config.topN ?? 10,
      watchlistThreshold: config.watchlistThreshold ?? 15,
      ...(selectedCountryIds.length > 0 ? { countryIds: selectedCountryIds } : {}),
      ...(!showDeparted ? { departed: false } : {}),
    },
    { staleTime: 60_000, placeholderData: (prev) => prev },
  );

  useEffect(() => {
    setTopVisibleCount(batchSize);
    setWatchVisibleCount(batchSize);
  }, [batchSize, includeProposed, selectedCountryIds, showDeparted]);

  // These useMemo hooks MUST be before any early return to respect Rules of Hooks
  const rawTop = data?.top ?? [];
  const rawWatch = data?.watchlist ?? [];
  const month = (data?.month as string) ?? "";

  const filteredTop = useMemo(() => {
    const arr = rawTop as ChargeabilityRow[];
    if (!chapterFilter) return arr;
    return arr.filter((r) => r.chapter === chapterFilter);
  }, [rawTop, chapterFilter]);

  const filteredWatch = useMemo(() => {
    const arr = rawWatch as ChargeabilityRow[];
    if (!chapterFilter) return arr;
    return arr.filter((r) => r.chapter === chapterFilter);
  }, [rawWatch, chapterFilter]);

  if (isLoading) {
    return (
      <div className="flex flex-col gap-3 pt-1">
        <div className="h-2 w-32 shimmer-skeleton rounded" />
        {[...Array(4)].map((_, i) => (
          <div key={i} className="flex gap-3 px-2 py-1">
            <div className="h-3 w-4 shimmer-skeleton rounded" />
            <div className="h-3 flex-1 shimmer-skeleton rounded" />
            <div className="h-3 w-10 shimmer-skeleton rounded" />
            <div className="h-3 w-10 shimmer-skeleton rounded" />
          </div>
        ))}
        <div className="border-t border-gray-100 dark:border-gray-800 mt-1 pt-2">
          <div className="h-2 w-20 shimmer-skeleton rounded mb-2" />
          {[...Array(3)].map((_, i) => (
            <div key={i} className="flex gap-3 px-2 py-1">
              <div className="h-3 flex-1 shimmer-skeleton rounded" />
              <div className="h-3 w-10 bg-gray-100 dark:bg-gray-800 rounded" />
              <div className="h-3 w-10 bg-gray-100 dark:bg-gray-800 rounded" />
            </div>
          ))}
        </div>
      </div>
    );
  }

  const top = ([...filteredTop]).sort((a, b) => {
    const mult = topDir === "asc" ? 1 : -1;
    switch (topSort) {
      case "name":
        return mult * a.displayName.localeCompare(b.displayName);
      case "actual":
        return mult * (a.actualChargeability - b.actualChargeability);
      case "expected":
        return mult * (a.expectedChargeability - b.expectedChargeability);
      default:
        return 0;
    }
  });

  const watchlist = ([...filteredWatch]).sort((a, b) => {
    const mult = watchDir === "asc" ? 1 : -1;
    switch (watchSort) {
      case "name":
        return mult * a.displayName.localeCompare(b.displayName);
      case "actual":
        return mult * (a.actualChargeability - b.actualChargeability);
      case "target":
        return mult * (a.chargeabilityTarget - b.chargeabilityTarget);
      default:
        return 0;
    }
  });

  function TopInd({ k }: { k: TopSortKey }) {
    return topSort === k ? (
      <span className="text-[10px] ml-0.5">{topDir === "asc" ? "▲" : "▼"}</span>
    ) : (
      <span className="text-[10px] ml-0.5 text-gray-300">⇅</span>
    );
  }
  function WatchInd({ k }: { k: WatchSortKey }) {
    return watchSort === k ? (
      <span className="text-[10px] ml-0.5">{watchDir === "asc" ? "▲" : "▼"}</span>
    ) : (
      <span className="text-[10px] ml-0.5 text-gray-300">⇅</span>
    );
  }

  const visibleTop = top.slice(0, topVisibleCount);
  const visibleWatchlist = watchlist.slice(0, watchVisibleCount);

  function handleSectionScroll(
    event: UIEvent<HTMLElement>,
    visibleCount: number,
    totalCount: number,
    setVisibleCount: (value: number | ((current: number) => number)) => void,
  ) {
    const element = event.currentTarget;
    const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
    if (distanceFromBottom > 48 || visibleCount >= totalCount) {
      return;
    }
    setVisibleCount((current) => Math.min(current + batchSize, totalCount));
  }

  function toggleCountry(countryId: string, checked: boolean) {
    setSelectedCountryIds((current) => {
      if (checked) {
        return current.includes(countryId) ? current : [...current, countryId];
      }
      return current.filter((id) => id !== countryId);
    });
  }

  return (
    <div className="h-full flex flex-col gap-2 overflow-hidden">
      <div className="px-1 flex-shrink-0 flex flex-col gap-2">
        <WidgetFilterBar filters={widgetFilters} values={_config} onChange={onConfigChange ?? (() => {})} />
        <div className="flex items-center justify-between gap-3">
          {month && (
            <p className="text-xs text-gray-400 flex items-center gap-1">
              Period: {month}
              <InfoTooltip
                content="Chargeability is calculated for the current calendar month. Available hours are derived from each person's weekly schedule and reduced by regional public holidays plus approved absences. Watchlist threshold: 15 percentage points below target."
                width="w-72"
              />
            </p>
          )}
        </div>
        <div className="flex flex-wrap items-center gap-2">
          <label className="flex items-center gap-2 rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs text-gray-600 dark:border-gray-700 dark:bg-gray-900/70 dark:text-gray-300">
            <input
              type="checkbox"
              checked={showDeparted}
              onChange={(event) => setShowDeparted(event.target.checked)}
              className="rounded border-gray-300"
            />
            Show departed
            <InfoTooltip content="When enabled, resources who have left the company are included in the lists." />
          </label>
          <FilterDropdown label={selectedCountryLabel}>
            <div className="space-y-2">
              <div className="flex items-center justify-between">
                <p className="text-xs font-medium text-gray-600">Countries</p>
                {selectedCountryIds.length > 0 ? (
                  <button
                    type="button"
                    onClick={() => setSelectedCountryIds([])}
                    className="text-[11px] text-brand-600 hover:text-brand-700"
                  >
                    Clear
                  </button>
                ) : null}
              </div>
              <p className="text-[11px] text-gray-400">
                Empty selection means all countries are included.
              </p>
              <div className="max-h-48 space-y-1 overflow-y-auto pr-1">
                {countries.map((country) => (
                  <label
                    key={country.id}
                    className="flex items-center gap-2 text-xs text-gray-700"
                  >
                    <input
                      type="checkbox"
                      checked={selectedCountryIds.includes(country.id)}
                      onChange={(event) => toggleCountry(country.id, event.target.checked)}
                      className="rounded border-gray-300"
                    />
                    <span>{country.name}</span>
                  </label>
                ))}
              </div>
            </div>
          </FilterDropdown>
        </div>
      </div>

      {/* Top list */}
      <section
        className="flex-1 min-h-0 overflow-auto"
        onScroll={(event) =>
          handleSectionScroll(event, topVisibleCount, top.length, setTopVisibleCount)
        }
      >
        <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider px-1 mb-1 sticky top-0 bg-white flex items-center">
          Top Chargeability
          <InfoTooltip content="Resources ranked by highest actual chargeability this month. Chargeability = chargeable booked hours divided by holiday- and absence-adjusted available hours." />
          <span className="ml-1 font-normal normal-case text-gray-400">
            {visibleTop.length}/{top.length}
          </span>
        </h3>
        {top.length === 0 ? (
          <p className="text-xs text-gray-400 px-1">No data available.</p>
        ) : (
          <table className="w-full text-xs">
            <thead>
              <tr className="text-gray-400 border-b border-gray-100">
                <th className="px-2 py-1 text-left font-medium w-6">#</th>
                <th className="px-2 py-1 text-left font-medium">
                  <button
                    type="button"
                    onClick={() => toggleTop("name")}
                    className="inline-flex items-center hover:text-gray-600 cursor-pointer"
                  >
                    Name
                    <TopInd k="name" />
                  </button>
                </th>
                <th className="px-2 py-1 text-right font-medium">
                  <span className="inline-flex items-center justify-end">
                    <button
                      type="button"
                      onClick={() => toggleTop("actual")}
                      className="inline-flex items-center hover:text-gray-600 cursor-pointer"
                    >
                      Actual
                      <TopInd k="actual" />
                    </button>
                    <InfoTooltip
                      content="Actual uses CONFIRMED and ACTIVE bookings on active projects. Turn on 'Include proposed' to also count proposed work and imported TBD planning."
                      width="w-72"
                    />
                  </span>
                </th>
                <th className="px-2 py-1 text-right font-medium">
                  <span className="inline-flex items-center justify-end">
                    <button
                      type="button"
                      onClick={() => toggleTop("expected")}
                      className="inline-flex items-center hover:text-gray-600 cursor-pointer"
                    >
                      Expected
                      <TopInd k="expected" />
                    </button>
                    <InfoTooltip
                      content="Expected includes all non-CANCELLED bookings this month, including draft projects and proposed planning rows."
                      width="w-72"
                    />
                  </span>
                </th>
              </tr>
            </thead>
            <tbody className="divide-y divide-gray-50">
              {visibleTop.map((r, i) => (
                <tr key={r.id} className="hover:bg-gray-50 dark:hover:bg-gray-800/40">
                  <td className="px-2 py-1 text-gray-400">{i + 1}</td>
                  <td className="px-2 py-1 text-gray-800 dark:text-gray-200 max-w-[240px] align-top">
                    <div className="truncate">
                      <span title={r.displayName}>{r.displayName}</span>
                      {r.chapter && <span className="text-gray-400 ml-1">· {r.chapter}</span>}
                    </div>
                    {showDetails ? <ChargeabilityContextLine row={r} /> : null}
                    <UtilizationBar percent={r.actualChargeability} />
                  </td>
                  <td className="px-2 py-1 text-right font-semibold text-green-700 dark:text-green-400 align-top">
                    <div>
                      <AnimatedNumber value={r.actualChargeability} suffix="%" />
                    </div>
                    {showDetails ? (
                      <div className="mt-1 text-[10px] font-normal leading-4 text-gray-500 dark:text-gray-400">
                        {formatHours(r.derivation?.actualBookedHours)} / {formatHours(r.derivation?.effectiveAvailableHours)}
                      </div>
                    ) : null}
                  </td>
                  <td className="px-2 py-1 text-right text-gray-400 align-top">
                    <div>
                      <AnimatedNumber value={r.expectedChargeability} suffix="%" />
                    </div>
                    {showDetails ? (
                      <div className="mt-1 text-[10px] leading-4 text-gray-500 dark:text-gray-400">
                        {formatHours(r.derivation?.expectedBookedHours)} / {formatHours(r.derivation?.effectiveAvailableHours)}
                      </div>
                    ) : null}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </section>

      <div className="border-t border-gray-100 flex-shrink-0" />

      {/* Watchlist */}
      <section
        className="flex-1 min-h-0 overflow-auto"
        onScroll={(event) =>
          handleSectionScroll(event, watchVisibleCount, watchlist.length, setWatchVisibleCount)
        }
      >
        <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider px-1 mb-1 sticky top-0 bg-white flex items-center">
          Watchlist <span className="font-normal text-gray-400">(below target)</span>
          <InfoTooltip content="Resources whose actual chargeability is more than 15 percentage points below their individual target. These may need more project assignments." />
          <span className="ml-1 font-normal normal-case text-gray-400">
            {visibleWatchlist.length}/{watchlist.length}
          </span>
        </h3>
        {watchlist.length === 0 ? (
          <p className="text-xs text-gray-400 px-1">All resources at or near target.</p>
        ) : (
          <table className="w-full text-xs">
            <thead>
              <tr className="text-gray-400 border-b border-gray-100">
                <th className="px-2 py-1 text-left font-medium">
                  <button
                    type="button"
                    onClick={() => toggleWatch("name")}
                    className="inline-flex items-center hover:text-gray-600 cursor-pointer"
                  >
                    Name
                    <WatchInd k="name" />
                  </button>
                </th>
                <th className="px-2 py-1 text-right font-medium">
                  <span className="inline-flex items-center justify-end">
                    <button
                      type="button"
                      onClick={() => toggleWatch("actual")}
                      className="inline-flex items-center hover:text-gray-600 cursor-pointer"
                    >
                      Actual
                      <WatchInd k="actual" />
                    </button>
                    <InfoTooltip content="Actual chargeability this month. By default this counts CONFIRMED and ACTIVE bookings on ACTIVE projects; the toggle can also include PROPOSED work." />
                  </span>
                </th>
                <th className="px-2 py-1 text-right font-medium">
                  <span className="inline-flex items-center justify-end">
                    <button
                      type="button"
                      onClick={() => toggleWatch("target")}
                      className="inline-flex items-center hover:text-gray-600 cursor-pointer"
                    >
                      Target
                      <WatchInd k="target" />
                    </button>
                    <InfoTooltip content="Chargeability target set by management. Watchlist shows resources more than 15 percentage points below their target." />
                  </span>
                </th>
              </tr>
            </thead>
            <tbody className="divide-y divide-gray-50">
              {visibleWatchlist.map((r) => (
                <tr key={r.id} className="hover:bg-gray-50 dark:hover:bg-gray-800/40">
                  <td className="px-2 py-1 text-gray-800 dark:text-gray-200 max-w-[240px] align-top">
                    <div className="truncate">
                      <span title={r.displayName}>{r.displayName}</span>
                      {r.chapter && <span className="text-gray-400 ml-1">· {r.chapter}</span>}
                    </div>
                    {showDetails ? <ChargeabilityContextLine row={r} /> : null}
                    <UtilizationBar percent={r.actualChargeability} />
                  </td>
                  <td className="px-2 py-1 text-right font-semibold text-red-600 dark:text-red-400 align-top">
                    <div>
                      <AnimatedNumber value={r.actualChargeability} suffix="%" />
                    </div>
                    {showDetails ? (
                      <div className="mt-1 text-[10px] font-normal leading-4 text-gray-500 dark:text-gray-400">
                        {formatHours(r.derivation?.actualBookedHours)} / {formatHours(r.derivation?.effectiveAvailableHours)}
                      </div>
                    ) : null}
                  </td>
                  <td className="px-2 py-1 text-right text-gray-400 align-top">
                    <div>
                      <AnimatedNumber value={r.chargeabilityTarget} suffix="%" />
                    </div>
                    {showDetails ? (
                      <div className="mt-1 text-[10px] leading-4 text-gray-500 dark:text-gray-400">
                        Target {formatHours(r.derivation?.targetBookedHours)} · Free {formatHours(r.derivation?.unassignedHours)}
                      </div>
                    ) : null}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </section>
    </div>
  );
}
