"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { useSearchParams } from "next/navigation";
import {
  FaArrowLeft,
  FaUsers,
  FaHourglass,
  FaCheck,
  FaXmark,
  FaCircleQuestion,
  FaPause,
} from "react-icons/fa6";
import Link from "next/link";
import { Pagination } from "@nextui-org/react";
import toast from "react-hot-toast";

import {
  Participant,
  ParticipantStatus,
  ProjectOverview,
  FieldConfig,
} from "@/types/participant-list/provisional-types";
import ParticipantCard from "@/components/pages/client/participant-list/participants/partials/participant-card";
import ParticipantTable from "@/components/pages/client/participant-list/participants/partials/participant-table";
import ParticipantFilters, {
  FilterOption,
  ViewMode,
} from "@/components/pages/client/participant-list/participants/partials/participant-filters";
import QuotaIndicator from "@/components/pages/client/participant-list/participants/partials/quota-indicator";
import { useClientPortalAuth } from "@/components/pages/client/participant-list/common/client-portal-auth-context";
import { getParticipantsForPortal } from "@/services/client/participant-list/portal-actions";

const PAGE_SIZE_OPTIONS: Record<ViewMode, number[]> = {
  grid: [8, 12, 24, 48],
  table: [10, 25, 50],
};

const DEFAULT_PAGE_SIZE: Record<ViewMode, number> = {
  grid: 12,
  table: 10,
};

const EMPTY_PARTICIPANTS: Participant[] = [];

// Maps the kebab-case `?status` token used in notification-email deep links to
// the in-component filter key. BE is frozen on `to-review`; unknown tokens fall
// back to "all" so a stale or malformed link still lands on a usable view.
const STATUS_PARAM_TO_FILTER: Record<string, ParticipantStatus> = {
  "to-review": "toReview",
  accepted: "accepted",
  rejected: "rejected",
  queried: "queried",
  "on-hold": "onHold",
};

function resolveStatusParam(value: string | null): ParticipantStatus | "all" {
  if (!value) return "all";
  return STATUS_PARAM_TO_FILTER[value] ?? "all";
}

type ParticipantListProps = {
  participants: Participant[];
  project: ProjectOverview;
  visibilityControls: FieldConfig[];
  researchers: string[];
};

type ServerFilters = {
  researcher: string;
  subCriterionDescriptionIds: number[];
};

function ParticipantList({
  participants,
  project,
  visibilityControls,
  researchers,
}: ParticipantListProps) {
  const { auth, handlePortalStatus, routes } = useClientPortalAuth();
  // When a researcher is selected we show a server-filtered override; otherwise
  // render the `participants` prop directly so the default view stays in sync
  // with the server instead of freezing a snapshot copied into state.
  const [serverFilteredParticipants, setServerFilteredParticipants] = useState<
    Participant[] | null
  >(null);
  const searchParams = useSearchParams();
  const [search, setSearch] = useState("");
  // Seed from `?status=` so notification-email deep links land pre-filtered.
  const [activeFilter, setActiveFilter] = useState<ParticipantStatus | "all">(
    () => resolveStatusParam(searchParams.get("status")),
  );
  const [researcher, setResearcher] = useState("");
  const [selectedDescriptionIds, setSelectedDescriptionIds] = useState<
    number[]
  >([]);
  const [isServerFilterLoading, setIsServerFilterLoading] = useState(false);
  const [serverFilterError, setServerFilterError] =
    useState<ServerFilters | null>(null);
  const requestSequence = useRef(0);
  const filterDebounceTimer = useRef<ReturnType<typeof setTimeout> | null>(
    null,
  );
  const [viewMode, setViewMode] = useState<ViewMode>("grid");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE.grid);
  const participantList = serverFilterError
    ? EMPTY_PARTICIPANTS
    : (serverFilteredParticipants ?? participants);

  const handleFilterChange = (filter: ParticipantStatus | "all") => {
    setActiveFilter(filter);
    setPage(1);
  };

  const handleSearchChange = (value: string) => {
    setSearch(value);
    setPage(1);
  };

  const clearScheduledServerFilter = () => {
    if (filterDebounceTimer.current) {
      clearTimeout(filterDebounceTimer.current);
      filterDebounceTimer.current = null;
    }
  };

  const applyServerFilters = (
    nextResearcher: string,
    nextDescriptionIds: number[],
  ) => {
    const requestId = ++requestSequence.current;
    const nextFilters = {
      researcher: nextResearcher,
      subCriterionDescriptionIds: nextDescriptionIds,
    };
    setServerFilterError(null);
    if (!nextResearcher && nextDescriptionIds.length === 0) {
      setServerFilteredParticipants(null);
      setIsServerFilterLoading(false);
      return;
    }
    if (!auth) {
      setIsServerFilterLoading(false);
      return;
    }
    setIsServerFilterLoading(true);
    getParticipantsForPortal(auth, {
      researcher: nextResearcher,
      subCriterionDescriptionIds: nextDescriptionIds,
    })
      .then((result) => {
        if (requestId !== requestSequence.current) return;
        if (result.ok) {
          setServerFilteredParticipants(result.data.participants);
          setServerFilterError(null);
        } else {
          setServerFilterError(nextFilters);
          if (!handlePortalStatus(result.status)) {
            toast.error("Couldn't apply the participant filters.");
          }
        }
      })
      .catch(() => {
        if (requestId !== requestSequence.current) return;
        setServerFilterError(nextFilters);
        if (!handlePortalStatus(0)) {
          toast.error("Couldn't apply the participant filters.");
        }
      })
      .finally(() => {
        if (requestId === requestSequence.current)
          setIsServerFilterLoading(false);
      });
  };

  const scheduleServerFilters = (
    nextResearcher: string,
    nextDescriptionIds: number[],
  ) => {
    clearScheduledServerFilter();
    requestSequence.current += 1;
    setServerFilterError(null);
    setIsServerFilterLoading(true);
    filterDebounceTimer.current = setTimeout(() => {
      filterDebounceTimer.current = null;
      applyServerFilters(nextResearcher, nextDescriptionIds);
    }, 250);
  };

  useEffect(
    () => () => {
      if (filterDebounceTimer.current) {
        clearTimeout(filterDebounceTimer.current);
      }
    },
    [],
  );

  const handleResearcherChange = (value: string) => {
    clearScheduledServerFilter();
    setResearcher(value);
    setPage(1);
    applyServerFilters(value, selectedDescriptionIds);
  };

  const handleCriterionDescriptionChange = (
    id: number,
    isSelected: boolean,
  ) => {
    if (isSelected && selectedDescriptionIds.length >= 50) {
      toast.error("You can select up to 50 criterion values.");
      return;
    }
    const nextDescriptionIds = isSelected
      ? [...selectedDescriptionIds, id]
      : selectedDescriptionIds.filter((selectedId) => selectedId !== id);
    setSelectedDescriptionIds(nextDescriptionIds);
    setPage(1);
    scheduleServerFilters(researcher, nextDescriptionIds);
  };

  const handleViewModeChange = (mode: ViewMode) => {
    setViewMode(mode);
    setPageSize(DEFAULT_PAGE_SIZE[mode]);
    setPage(1);
  };

  const handlePageSizeChange = (size: number) => {
    setPageSize(size);
    setPage(1);
  };

  const handleClearFilters = () => {
    clearScheduledServerFilter();
    setSearch("");
    setActiveFilter("all");
    setResearcher("");
    setSelectedDescriptionIds([]);
    setPage(1);
    applyServerFilters("", []);
  };

  const retryServerFilters = () => {
    if (!serverFilterError) return;
    applyServerFilters(
      serverFilterError.researcher,
      serverFilterError.subCriterionDescriptionIds,
    );
  };

  const counts = useMemo(() => {
    const map: Record<ParticipantStatus | "all", number> = {
      all: participantList.length,
      toReview: 0,
      accepted: 0,
      rejected: 0,
      queried: 0,
      onHold: 0,
    };

    for (const p of participantList) {
      map[p.status]++;
    }

    return map;
  }, [participantList]);

  const iconClass = "size-3 shrink-0";

  const filters: FilterOption[] = [
    {
      key: "all",
      label: "All",
      count: counts.all,
      icon: <FaUsers className={iconClass} />,
    },
    {
      key: "toReview",
      label: "To review",
      count: counts.toReview,
      icon: <FaHourglass className={iconClass} />,
    },
    {
      key: "accepted",
      label: "Accepted",
      count: counts.accepted,
      icon: <FaCheck className={iconClass} />,
    },
    {
      key: "rejected",
      label: "Rejected",
      count: counts.rejected,
      icon: <FaXmark className={iconClass} />,
    },
    {
      key: "queried",
      label: "Queried",
      count: counts.queried,
      icon: <FaCircleQuestion className={iconClass} />,
    },
    {
      key: "onHold",
      label: "On hold",
      count: counts.onHold,
      icon: <FaPause className={iconClass} />,
    },
  ];

  const filtered = useMemo(() => {
    let result = participantList;

    if (activeFilter !== "all") {
      result = result.filter((p) => p.status === activeFilter);
    }

    if (search.trim()) {
      const q = search.toLowerCase().trim();
      result = result.filter(
        (p) =>
          p.firstName.toLowerCase().includes(q) ||
          p.lastName.toLowerCase().includes(q),
      );
    }

    return result;
  }, [participantList, activeFilter, search]);

  const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
  const pageStart = (page - 1) * pageSize;
  const paginated = filtered.slice(pageStart, pageStart + pageSize);

  const hasActiveFilters =
    Boolean(search) ||
    activeFilter !== "all" ||
    Boolean(researcher) ||
    selectedDescriptionIds.length > 0;

  return (
    <div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8 font-noto">
      <header className="space-y-1">
        <Link
          className="inline-flex items-center gap-2 text-xs text-gray-400 dark:text-gray-300 hover:text-gray-600 dark:hover:text-neutral-300 transition-colors mb-2"
          href={routes.home}
        >
          <FaArrowLeft className="size-2.5" />
          Back to project
        </Link>

        <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
          <div>
            <h1 className="font-inter text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
              Participants
            </h1>
            <p className="text-sm text-gray-500 dark:text-neutral-400 mt-0.5">
              {project.title}
            </p>
          </div>

          <QuotaIndicator
            filled={project.stats.filledQuota}
            total={project.stats.quota}
          />
        </div>
      </header>

      <div className="mt-6">
        <ParticipantFilters
          activeFilter={activeFilter}
          criteria={project.criteria}
          filters={filters}
          isResearcherLoading={isServerFilterLoading}
          researcher={researcher}
          researchers={researchers}
          search={search}
          selectedDescriptionIds={selectedDescriptionIds}
          viewMode={viewMode}
          onCriterionDescriptionChange={handleCriterionDescriptionChange}
          onFilterChange={handleFilterChange}
          onResearcherChange={handleResearcherChange}
          onSearchChange={handleSearchChange}
          onViewModeChange={handleViewModeChange}
        />
      </div>

      {filtered.length > 0 && (
        <div className="mt-4 flex items-center justify-between gap-3">
          <div className="flex items-center gap-2">
            <label
              className="text-xs text-gray-400 dark:text-gray-300 whitespace-nowrap"
              htmlFor="page-size-select"
            >
              Per page
            </label>
            <select
              className="h-7 rounded-md border border-gray-200 dark:border-neutral-700 bg-white dark:bg-neutral-800 text-xs text-gray-700 dark:text-neutral-300 px-2 focus:outline-none focus:ring-1 focus:ring-midnightBlue"
              id="page-size-select"
              value={pageSize}
              onChange={(e) => handlePageSizeChange(Number(e.target.value))}
            >
              {PAGE_SIZE_OPTIONS[viewMode].map((n) => (
                <option key={n} value={n}>
                  {n}
                </option>
              ))}
            </select>
          </div>
          <p className="text-xs text-gray-400 dark:text-gray-300">
            Showing {pageStart + 1}–
            {Math.min(pageStart + pageSize, filtered.length)} of{" "}
            {filtered.length} participants
          </p>
        </div>
      )}

      <div
        aria-busy={isServerFilterLoading}
        className={`mt-4 transition-opacity ${
          isServerFilterLoading ? "pointer-events-none opacity-50" : ""
        }`}
      >
        {serverFilterError ? (
          <div
            className="flex flex-col items-center justify-center py-16 text-center"
            role="alert"
          >
            <p className="text-sm text-red-600 dark:text-red-400">
              Couldn&apos;t apply the participant filters.
            </p>
            <button
              className="mt-2 text-xs font-medium text-gray-500 underline transition-colors hover:text-gray-900 dark:text-neutral-400 dark:hover:text-white"
              type="button"
              onClick={retryServerFilters}
            >
              Try again
            </button>
          </div>
        ) : filtered.length === 0 ? (
          <div className="flex flex-col items-center justify-center py-16 text-center">
            <p className="text-sm text-gray-400 dark:text-gray-300">
              {activeFilter !== "all" && !search
                ? "No participants with this status yet."
                : "No participants found."}
            </p>
            {hasActiveFilters && (
              <button
                className="mt-2 text-xs font-medium text-gray-500 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white underline transition-colors"
                type="button"
                onClick={handleClearFilters}
              >
                Clear filters
              </button>
            )}
          </div>
        ) : viewMode === "grid" ? (
          <div className="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-4 gap-2">
            {paginated.map((participant) => (
              <ParticipantCard
                key={participant.reference}
                participant={participant}
                visibilityControls={visibilityControls}
              />
            ))}
          </div>
        ) : (
          <ParticipantTable
            page={page}
            pageSize={pageSize}
            participants={filtered}
            visibilityControls={visibilityControls}
          />
        )}
      </div>

      {totalPages > 1 && (
        <div className="mt-6 flex justify-center">
          <Pagination
            classNames={{
              cursor: "bg-midnightBlue text-white",
            }}
            page={page}
            total={totalPages}
            onChange={setPage}
          />
        </div>
      )}
    </div>
  );
}

export default ParticipantList;
