"use client";

import type { AccessScope } from "@/lib/features/types";
import type { AdminUser } from "@/lib/features/users/types";
import {
  ActionIcon,
  Avatar,
  Badge,
  Button,
  Card,
  Group,
  Pagination,
  Select,
  Stack,
  Text,
  TextInput,
  Title,
  Tooltip,
} from "@mantine/core";
import { useDebouncedValue, useDisclosure } from "@mantine/hooks";
import {
  IconPlus,
  IconSearch,
  IconShieldCheck,
  IconX,
} from "@tabler/icons-react";
import moment from "moment";
import { useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router";
import {
  MemberDataTable,
  type MemberColumn,
} from "@/routes/members/_components/MemberDataTable";
import {
  ActiveUserFilterChips,
  countActiveUserFilters,
  EMPTY_USER_FILTERS,
  UserFilterDrawer,
  UserFilterTrigger,
  type UserFilterValues,
} from "./_components/UserFilters";

const UsersClientPage: React.FC<{
  tableProps: { data: AdminUser[]; totalRows: number };
  accessScope: AccessScope;
  roles: { value: string; label: string }[];
  filters: UserFilterValues;
}> = ({ tableProps, accessScope, roles, filters }) => {
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const [drawerOpened, drawer] = useDisclosure(false);
  const [values, setValues] = useState<UserFilterValues>(filters);
  /*
   * Table-level search, applied as you type.
   *
   * Debounced by 350ms rather than fired per keystroke: each apply rewrites the
   * URL and re-runs the server-side loader, so an undebounced field would issue
   * a request per character.
   */
  const [searchDraft, setSearchDraft] = useState(filters.searchTerm ?? "");
  const [debouncedSearch] = useDebouncedValue(searchDraft, 350);

  useEffect(() => {
    const next = debouncedSearch.trim();
    // Guard against re-applying what the loader already returned, which would
    // otherwise loop: apply → loader → new filters → apply.
    if (next === (values.searchTerm ?? "")) return;
    apply({ ...values, searchTerm: next });
    // `apply` and `values` are recreated every render; keying the effect on the
    // debounced text alone is what makes it fire once per settled edit.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedSearch]);

  const page = Number(searchParams.get("page") ?? "1") || 1;
  const pageSize = Number(searchParams.get("pageSize") ?? "50") || 50;
  const totalPages = Math.max(
    1,
    Math.ceil((tableProps.totalRows ?? 0) / pageSize),
  );
  const rangeStart = (page - 1) * pageSize + 1;
  const rangeEnd = Math.min(page * pageSize, tableProps.totalRows ?? 0);

  // Filters go through the URL so the loader applies them server-side.
  const apply = (next: UserFilterValues) => {
    setValues(next);
    const params = new URLSearchParams(searchParams);
    const set = (key: string, value: string) =>
      value ? params.set(key, value) : params.delete(key);

    set("search", next.searchTerm);
    set("roleIds", next.roleIds.join(","));
    // "all" is explicit: an absent param means the default (active only).
    set(
      "isActive",
      next.inactiveOnly ? "false" : next.includeInactive ? "all" : "true",
    );
    set("superAdmin", next.superAdminsOnly ? "true" : "");
    params.set("page", "1");
    setSearchParams(params);
  };

  const reset = () => {
    setSearchDraft("");
    apply(EMPTY_USER_FILTERS);
  };

  const goToPage = (next: number) => {
    const params = new URLSearchParams(searchParams);
    params.set("page", String(next));
    params.set("pageSize", String(pageSize));
    setSearchParams(params);
  };

  return (
    <Stack gap="lg">
      <Group justify="space-between" align="center">
        <Group gap={8} align="baseline">
          <Title order={6}>All Users</Title>
          <Text fz={12} c="dimmed">
            {(tableProps.totalRows ?? 0).toLocaleString()} user(s)
          </Text>
        </Group>
        <Group gap="sm">
          <UserFilterTrigger
            activeCount={countActiveUserFilters(values)}
            onClick={drawer.open}
          />
          {accessScope?.create || accessScope?.superAdmin ? (
            <Button
              leftSection={<IconPlus size={16} />}
              onClick={() => navigate("new")}
            >
              New user
            </Button>
          ) : null}
        </Group>
      </Group>

      <ActiveUserFilterChips
        values={values}
        roles={roles}
        onChange={apply}
        onReset={reset}
      />

      {/* <Card withBorder radius="lg" p="md"> */}
      <Group justify="space-between" align="center" mb="sm">
        <TextInput
          size="sm"
          w={280}
          placeholder="Search name, email or username"
          leftSection={<IconSearch size={14} />}
          value={searchDraft}
          onChange={(event) => setSearchDraft(event.currentTarget.value)}
          onKeyDown={(event) => {
            if (event.key === "Enter") {
              apply({ ...values, searchTerm: searchDraft.trim() });
            }
          }}
          rightSection={
            searchDraft ? (
              <ActionIcon
                variant="subtle"
                size="sm"
                aria-label="Clear search"
                onClick={() => {
                  setSearchDraft("");
                  apply({ ...values, searchTerm: "" });
                }}
              >
                <IconX size={13} />
              </ActionIcon>
            ) : (
              <ActionIcon
                variant="subtle"
                size="sm"
                aria-label="Search"
                onClick={() =>
                  apply({ ...values, searchTerm: searchDraft.trim() })
                }
              >
                {/* <IconArrowRight size={13} /> */}
              </ActionIcon>
            )
          }
        />
      </Group>
      <MemberDataTable
        rows={tableProps.data}
        columns={columns}
        minWidth={1100}
        rowKey={(row) => String(row.id)}
        onRowClick={(row) => navigate(String(row.id))}
        emptyMessage="No users match"
      />
      {/* </Card> */}

      {/* Range + page size alongside the pager: "page 3 of 40" alone doesn't say
          how many rows are in hand, and the size was previously fixed at 50. */}
      <Group justify="space-between" align="center">
        <Text fz={12} c="dimmed">
          {tableProps.totalRows
            ? `Showing ${rangeStart.toLocaleString()}–${rangeEnd.toLocaleString()} of ${tableProps.totalRows.toLocaleString()}`
            : "No users to show"}
        </Text>
        <Group gap="sm">
          <Select
            size="xs"
            w={110}
            aria-label="Rows per page"
            data={["25", "50", "100", "150"]}
            value={String(pageSize)}
            onChange={(value) => {
              if (!value) return;
              const params = new URLSearchParams(searchParams);
              params.set("pageSize", value);
              // Back to the first page: page 7 of a 50-row paging is out of
              // range once the page size grows.
              params.set("page", "1");
              setSearchParams(params);
            }}
          />
          {totalPages > 1 ? (
            <Pagination
              size="sm"
              value={page}
              total={totalPages}
              onChange={goToPage}
              withEdges
            />
          ) : null}
        </Group>
      </Group>

      <UserFilterDrawer
        opened={drawerOpened}
        onClose={drawer.close}
        values={values}
        roles={roles}
        onChange={apply}
        onReset={reset}
      />
    </Stack>
  );
};

export default UsersClientPage;

const columns: MemberColumn<AdminUser>[] = [
  {
    key: "user",
    header: "User",
    width: 300,
    render: (row) => {
      const name = [row.first_name, row.last_name].filter(Boolean).join(" ");
      return (
        <Group gap={8} wrap="nowrap">
          <Avatar src={row.avatar_url ?? undefined} radius="xl" size={30}>
            {(name || row.email || "?").slice(0, 2).toUpperCase()}
          </Avatar>
          <Stack gap={0}>
            <Group gap={6} wrap="nowrap">
              <Text fz="sm" fw={500}>
                {name || "—"}
              </Text>
              {row.is_super_admin ? (
                <Tooltip label="Super admin — full access to every module">
                  <Badge
                    variant="light"
                    size="xs"
                    radius={4}
                    leftSection={<IconShieldCheck size={10} />}
                  >
                    super
                  </Badge>
                </Tooltip>
              ) : null}
            </Group>
            <Text fz={11} c="dimmed">
              {row.email}
            </Text>
          </Stack>
        </Group>
      );
    },
  },
  {
    key: "job_title",
    header: "Designation",
    width: 170,
    render: (row) =>
      row.job_title || (
        <Text fz="sm" c="dimmed">
          —
        </Text>
      ),
  },
  {
    key: "roles",
    header: "Roles",
    width: 320,
    render: (row) => {
      if (row.is_super_admin) {
        return (
          <Text fz="sm" c="dimmed">
            all modules
          </Text>
        );
      }
      if (!row.roles?.length) {
        return (
          <Text fz="sm" c="dimmed">
            None
          </Text>
        );
      }
      // Wrapped and clamped: a user with eight roles used to stretch the row
      // and push Status and Last Login off the visible width.
      const shown = row.roles.slice(0, 3);
      const rest = row.roles.length - shown.length;
      return (
        <Group gap={4} wrap="wrap" style={{ maxWidth: 300 }}>
          {shown.map((role) => (
            <Badge
              key={role.id}
              variant="light"
              size="sm"
              radius={4}
              maw={140}
              styles={{
                label: {
                  overflow: "hidden",
                  textOverflow: "ellipsis",
                },
              }}
              title={role.name}
            >
              {role.name}
            </Badge>
          ))}
          {rest > 0 ? (
            <Tooltip
              label={row.roles
                .slice(3)
                .map((role) => role.name)
                .join(", ")}
            >
              <Badge variant="light" color="gray" size="sm" radius={4}>
                +{rest}
              </Badge>
            </Tooltip>
          ) : null}
        </Group>
      );
    },
  },
  {
    key: "status",
    header: "Status",
    width: 110,
    render: (row) => (
      <Badge
        variant="light"
        color={row.is_active ? undefined : "gray"}
        radius={4}
      >
        {row.is_active ? "Active" : "Inactive"}
      </Badge>
    ),
  },
  {
    key: "last_login_at",
    header: "Last Login",
    width: 140,
    render: (row) =>
      row.last_login_at ? (
        <Tooltip label={moment(row.last_login_at).format("LLL")}>
          <Text fz="sm">{moment(row.last_login_at).fromNow()}</Text>
        </Tooltip>
      ) : (
        <Text fz="sm" c="dimmed">
          Never
        </Text>
      ),
  },
  {
    key: "created_at",
    header: "Created At",
    width: 130,
    render: (row) => moment(row.created_at).format("LL"),
  },
];
