import {
  Anchor,
  Badge,
  Card,
  Center,
  Checkbox,
  Group,
  Loader,
  Stack,
  Table,
  Text,
  TextInput,
  Tooltip,
  UnstyledButton,
} from "@mantine/core";
import {
  IconArrowDown,
  IconArrowUp,
  IconFilterSearch,
  IconSearch,
  IconX,
} from "@tabler/icons-react";
import { Link } from "react-router";
import TablePaginationFooter from "@/components/TablePaginationFooter";
import styles from "@/routes/promo-code-campaigns/_components/promo.module.css";
import { formatRevenue } from "@/lib/export/eventAnalyticsSheets";
import {
  eventTypeLabel,
  memberDisplayName,
  type MemberAnalyticsResponse,
} from "@/lib/features/event-analytics/types";

export type MemberSort = {
  sortBy: "events" | "revenue" | "points" | "lastSeen" | "member";
  sortDir: "asc" | "desc";
};

// Kept for the commented-out First seen / Last seen columns below.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const fmtDate = (iso: string | null) => (iso ? iso.slice(0, 10) : "—");

function SortHeader({
  label,
  field,
  sort,
  onSort,
  numeric,
}: {
  label: string;
  field: MemberSort["sortBy"];
  sort: MemberSort;
  onSort: (next: MemberSort) => void;
  numeric?: boolean;
}) {
  const active = sort.sortBy === field;
  return (
    <UnstyledButton
      onClick={() =>
        onSort({
          sortBy: field,
          // Re-clicking the active column flips direction; a new column starts
          // descending, which is what people want from "most events" columns.
          sortDir: active && sort.sortDir === "desc" ? "asc" : "desc",
        })
      }
    >
      <Group gap={4} justify={numeric ? "flex-end" : "flex-start"} wrap="nowrap">
        <Text size="sm" fw={600} c={active ? undefined : "dimmed"}>
          {label}
        </Text>
        {active &&
          (sort.sortDir === "desc" ? (
            <IconArrowDown size={13} />
          ) : (
            <IconArrowUp size={13} />
          ))}
      </Group>
    </UnstyledButton>
  );
}

type Props = {
  data: MemberAnalyticsResponse | null;
  loading: boolean;
  sort: MemberSort;
  onSort: (next: MemberSort) => void;
  page: number;
  pageSize: number;
  onPageChange: (page: number) => void;
  onPageSizeChange: (pageSize: number) => void;
  /** Clicking a member narrows every panel on the page to that member. */
  /**
   * Narrows every panel on the page to one member without leaving it.
   *
   * Kept alongside the link to the member's own page: the two answer different
   * questions — "show me this member's slice of the current view" versus "take
   * me to everything about this member".
   */
  onSelectMember: (memberId: string) => void;
  /** Event types in view, one column each, in funnel order. */
  eventTypeColumns: string[];
  /** Member-number search, applied server-side across every member. */
  search: string;
  onSearchChange: (value: string) => void;
  /** Members ticked for a per-member export sheet. */
  selected: string[];
  onSelectedChange: (next: string[]) => void;
};

export default function MemberAnalytics({
  data,
  loading,
  sort,
  onSort,
  page,
  pageSize,
  onPageChange,
  onPageSizeChange,
  onSelectMember,
  eventTypeColumns,
  search,
  onSearchChange,
  selected,
  onSelectedChange,
}: Props) {
  const rows = data?.rows ?? [];
  const selectedSet = new Set(selected);
  const pageIds = rows.map((r) => r.memberId);
  const allOnPageSelected =
    pageIds.length > 0 && pageIds.every((id) => selectedSet.has(id));
  const someOnPageSelected = pageIds.some((id) => selectedSet.has(id));

  const toggleRow = (memberId: string) => {
    onSelectedChange(
      selectedSet.has(memberId)
        ? selected.filter((id) => id !== memberId)
        : [...selected, memberId],
    );
  };

  /*
   * The header box acts on this page only.
   *
   * Selecting every member across every page would silently commit the user to
   * an export of unknown size; selecting what they can see is predictable, and
   * the count next to it says exactly how many are held.
   */
  const togglePage = () => {
    onSelectedChange(
      allOnPageSelected
        ? selected.filter((id) => !pageIds.includes(id))
        : [...new Set([...selected, ...pageIds])],
    );
  };

  /** Search and selection stay put even when the table has nothing to show. */
  const toolbar = (
    <Group justify="space-between" align="center" wrap="wrap" gap="sm">
      <TextInput
        placeholder="Search by member number"
        leftSection={<IconSearch size={16} />}
        w={280}
        value={search}
        onChange={(e) => onSearchChange(e.currentTarget.value)}
      />
      {selected.length > 0 && (
        <Group gap="xs">
          <Text size="sm" c="dimmed">
            {selected.length} selected for export
          </Text>
          <UnstyledButton onClick={() => onSelectedChange([])} aria-label="Clear selection">
            <Group gap={2}>
              <IconX size={13} />
              <Text size="sm">Clear</Text>
            </Group>
          </UnstyledButton>
        </Group>
      )}
    </Group>
  );

  if (loading && !data) {
    return (
      <Center py="xl">
        <Loader size="sm" />
      </Center>
    );
  }

  if (!data || rows.length === 0) {
    return (
      <Stack gap="sm">
        {toolbar}
        <Card className={styles.sectionCard} p="xl">
          <Text c="dimmed" ta="center">
            {search
              ? `No member number matches "${search}".`
              : "No member activity for these filters."}
          </Text>
        </Card>
      </Stack>
    );
  }

  return (
    <Stack gap="sm">
      {toolbar}
      <Card className={styles.tableCard} p={0}>
        <Table.ScrollContainer minWidth={940}>
        <Table highlightOnHover verticalSpacing="sm" horizontalSpacing="md">
          <Table.Thead className={styles.tableHead}>
            <Table.Tr>
              <Table.Th w={40}>
                <Checkbox
                  size="xs"
                  aria-label="Select every member on this page"
                  checked={allOnPageSelected}
                  indeterminate={someOnPageSelected && !allOnPageSelected}
                  onChange={togglePage}
                />
              </Table.Th>
              <Table.Th>
                <SortHeader label="Member" field="member" sort={sort} onSort={onSort} />
              </Table.Th>
              <Table.Th>Account</Table.Th>
              {/* A count, not the list. Five property names per row pushed every
                  number off-screen; the names are on the member's own page. */}
              <Table.Th ta="right">Properties</Table.Th>
              <Table.Th ta="right">
                <SortHeader label="Events" field="events" sort={sort} onSort={onSort} numeric />
              </Table.Th>
              {/* One column per event type, in funnel order: the row reads
                  left to right as that member's progress through the funnel. */}
              {eventTypeColumns.map((eventType) => (
                <Table.Th key={eventType} ta="right">
                  <Text size="sm" fw={600} c="dimmed">
                    {eventTypeLabel(eventType)}
                  </Text>
                </Table.Th>
              ))}
              <Table.Th ta="right">
                <SortHeader label="Points" field="points" sort={sort} onSort={onSort} numeric />
              </Table.Th>
              <Table.Th ta="right">
                <SortHeader label="Revenue" field="revenue" sort={sort} onSort={onSort} numeric />
              </Table.Th>
              {/* First seen / Last seen are hidden until the event timestamps
                  are confirmed — they were rendering "—" for every member, which
                  reads as "this member has no dates" rather than "we could not
                  read the date". The values are still returned by the API and
                  the member detail page still shows them under "Seen"; only
                  these two columns are out. Restore once `occurredAt` is
                  verified against live data. */}
              {/* <Table.Th>First seen</Table.Th>
              <Table.Th>
                <SortHeader label="Last seen" field="lastSeen" sort={sort} onSort={onSort} />
              </Table.Th> */}
              <Table.Th w={40} aria-label="Row actions" />
            </Table.Tr>
          </Table.Thead>
          <Table.Tbody>
            {rows.map((row) => (
              <Table.Tr
                key={row.memberId}
                bg={selectedSet.has(row.memberId) ? "var(--mantine-primary-color-light)" : undefined}
              >
                <Table.Td>
                  <Checkbox
                    size="xs"
                    aria-label={`Select member ${row.memberId}`}
                    checked={selectedSet.has(row.memberId)}
                    onChange={() => toggleRow(row.memberId)}
                  />
                </Table.Td>
                <Table.Td>
                  {/* Name over number: the number is the join key and still has
                      to be visible, but it is not what anyone reads a row by. */}
                  <Stack gap={0}>
                    <Anchor
                      component={Link}
                      to={`/admin/event-analytics/members/${encodeURIComponent(row.memberId)}`}
                      size="sm"
                      fw={600}
                      lineClamp={1}
                    >
                      {memberDisplayName(row)}
                    </Anchor>
                    <Group gap={6} wrap="nowrap">
                      <Text size="xs" c="dimmed">
                        {row.profile?.fullName ? row.memberId : "No member record"}
                      </Text>
                      {row.profile?.fullName && (
                        // Straight into the members module, the same handoff the
                        // promo-code tables make.
                        <Anchor
                          component={Link}
                          to={`/admin/members/${encodeURIComponent(row.memberId)}?from=/admin/event-analytics&fromLabel=Event%20Analytics`}
                          size="xs"
                          c="dimmed"
                          title="Open the full member record"
                        >
                          record →
                        </Anchor>
                      )}
                    </Group>
                  </Stack>
                </Table.Td>
                <Table.Td>
                  <Stack gap={2}>
                    {row.profile?.accountType && (
                      <Badge size="sm" variant="light" w="fit-content">
                        {row.profile.accountType}
                      </Badge>
                    )}
                    <Text size="xs" c="dimmed" lineClamp={1}>
                      {[row.profile?.country, row.profile?.email]
                        .filter(Boolean)
                        .join(" · ") || "—"}
                    </Text>
                  </Stack>
                </Table.Td>
                <Table.Td ta="right">
                  {/* Hover reveals which properties, so the count is not a dead
                      end when someone needs the detail mid-scan. */}
                  <Tooltip
                    label={row.properties.join(", ")}
                    disabled={!row.properties.length}
                    multiline
                    w={260}
                    withArrow
                  >
                    <Text
                      size="sm"
                      className={styles.num}
                      c={row.properties.length ? undefined : "dimmed"}
                      style={{ cursor: row.properties.length ? "help" : undefined }}
                    >
                      {row.properties.length || "—"}
                    </Text>
                  </Tooltip>
                </Table.Td>
                <Table.Td ta="right" className={styles.num}>
                  {row.events.toLocaleString("en-US")}
                </Table.Td>
                {eventTypeColumns.map((eventType) => {
                  const count = row.eventCounts[eventType] ?? 0;
                  return (
                    <Table.Td key={eventType} ta="right" className={styles.num}>
                      {/* Dim a zero so the steps a member reached stand out
                          from the ones they never did. */}
                      <Text size="sm" c={count ? undefined : "dimmed"}>
                        {count}
                      </Text>
                    </Table.Td>
                  );
                })}
                <Table.Td ta="right" className={styles.num}>
                  {row.points.toLocaleString("en-US")}
                </Table.Td>
                <Table.Td ta="right">
                  <Text size="sm">{formatRevenue(row.revenueByCurrency)}</Text>
                </Table.Td>
                {/* Hidden with their headers — see the note in the head row. */}
                {/* <Table.Td>{fmtDate(row.firstSeen)}</Table.Td>
                <Table.Td>{fmtDate(row.lastSeen)}</Table.Td> */}
                <Table.Td>
                  {/* Filters the page down to this member, as distinct from the
                      name link, which navigates away to their own page. */}
                  <Tooltip label="Filter this page to this member" withArrow>
                    <UnstyledButton
                      onClick={() => onSelectMember(row.memberId)}
                      aria-label={`Filter to member ${row.memberId}`}
                    >
                      <IconFilterSearch size={15} opacity={0.55} />
                    </UnstyledButton>
                  </Tooltip>
                </Table.Td>
              </Table.Tr>
            ))}
          </Table.Tbody>
        </Table>
        </Table.ScrollContainer>
      </Card>

      <TablePaginationFooter
        total={data.total}
        page={page}
        totalPages={data.totalPages}
        pageSize={pageSize}
        onPageChange={onPageChange}
        onPageSizeChange={onPageSizeChange}
      />
    </Stack>
  );
}
