"use client"

import * as React from "react"
import { useVirtualizer } from "@tanstack/react-virtual"
import { ChevronDown, X, Loader2, Check } from "lucide-react"

import {
  useGetBulkAssignPickerUsersQuery,
  useGetUsersQuery,
} from "@/api/rtk"
import { useAuthToken } from "@/hooks/use-auth-token"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover"
import type { ApiUser } from "@/api/users/types"
import type { Role } from "@/api/permissions/types"
import { getApiEntityId } from "@/api/permissions/types"

const PAGE_LIMIT = 50
const SEARCH_DEBOUNCE_MS = 350
const ROW_PX = 56

export type ContributorUserPickerProps = {
  value: string[]
  onChange: (ids: string[]) => void
  disabled?: boolean
  placeholder?: string
  className?: string
  /** Resolve chip labels for ids (e.g. from `useGetAllUsersQuery` or deal `contributors`). */
  labelById?: Record<string, string | undefined>
  /** When set, `/users` is filtered by role (e.g. AE / BDR role id from org settings). */
  roleId?: string
  /** When set (e.g. team selected), search results are limited to these user ids. */
  restrictToUserIds?: readonly string[]
  /** Override empty list copy after search + filters. */
  emptyListMessage?: string
  /**
   * Client-only list (search filters locally). Use when `roleId` is unknown but you still
   * have a bounded set (e.g. AEs on the selected team from `useGetAllUsersQuery`).
   */
  staticUserPool?: Pick<ApiUser, "id" | "name" | "email">[]
  /**
   * Use `/users/bulk-assign-picker` (UPDATE + BULK_ASSIGN_LEAD) instead of `/users`
   * (READ user). Set on bulk-assign owner/AE/BDR/contributor pickers.
   */
  forBulkAssign?: boolean
}

/** Resolve a role id from `/roles` by exact name match (normalized). */
export function pickRoleIdFromOrgRoles(
  roles: Role[] | undefined,
  candidateNames: string[],
): string | undefined {
  if (!roles?.length) return undefined
  const norm = (s: string) => s.trim().toLowerCase().replace(/\s+/g, " ")
  for (const candidate of candidateNames) {
    const c = norm(candidate)
    const hit = roles.find((r) => norm(r.name || "") === c)
    const id = hit ? getApiEntityId(hit) : undefined
    if (id) return id
  }
  for (const r of roles) {
    const n = norm(r.name || "")
    if (
      n.includes("account manager") ||
      n.includes("account mgr") ||
      n.includes("acct mgr")
    ) {
      const id = getApiEntityId(r)
      if (id) return id
    }
  }
  for (const r of roles) {
    const n = norm(r.name || "")
    if (n === "am" || n.startsWith("am ") || n.endsWith(" am") || n.includes(" am ")) {
      const id = getApiEntityId(r)
      if (id) return id
    }
  }
  return undefined
}

function userRowLabel(u: Pick<ApiUser, "id" | "name" | "email">): string {
  const n = u.name?.trim()
  if (n) return n
  return u.email?.trim() || u.id
}

export function ContributorUserPicker({
  value,
  onChange,
  disabled = false,
  placeholder = "Search users…",
  className,
  labelById = {},
  roleId,
  restrictToUserIds,
  emptyListMessage,
  staticUserPool,
  forBulkAssign = false,
}: ContributorUserPickerProps) {
  const { token: accessToken } = useAuthToken()
  const [open, setOpen] = React.useState(false)
  const [search, setSearch] = React.useState("")
  const [debouncedSearch, setDebouncedSearch] = React.useState("")

  React.useEffect(() => {
    const t = window.setTimeout(
      () => setDebouncedSearch(search.trim()),
      SEARCH_DEBOUNCE_MS,
    )
    return () => window.clearTimeout(t)
  }, [search])

  const pickedMetaRef = React.useRef<Map<string, string>>(new Map())
  const [, setListRenderTick] = React.useState(0)

  const bumpList = React.useCallback(() => {
    setListRenderTick((t) => t + 1)
  }, [])

  React.useEffect(() => {
    const m = pickedMetaRef.current
    let changed = false
    for (const id of value) {
      const fromProp = labelById[id]
      if (fromProp && m.get(id) !== fromProp) {
        m.set(id, fromProp)
        changed = true
      } else if (!m.has(id) && fromProp) {
        m.set(id, fromProp)
        changed = true
      }
    }
    for (const id of [...m.keys()]) {
      if (!value.includes(id)) {
        m.delete(id)
        changed = true
      }
    }
    if (changed) bumpList()
  }, [value, labelById, bumpList])

  /** When `staticUserPool` is provided (including `[]`), skip `/users` and search locally. */
  const useStaticPool = staticUserPool != null

  const restrictSet = React.useMemo(() => {
    if (!restrictToUserIds?.length) return null
    return new Set(restrictToUserIds)
  }, [restrictToUserIds])

  const pickerQueryArgs = {
    page: 1,
    limit: PAGE_LIMIT,
    search: debouncedSearch || undefined,
    sortBy: "name" as const,
    sortOrder: "asc" as const,
    roleId: roleId?.trim() || undefined,
  };
  const pickerSkip = !open || !accessToken || useStaticPool;

  const bulkAssignUsers = useGetBulkAssignPickerUsersQuery(pickerQueryArgs, {
    skip: pickerSkip || !forBulkAssign,
  });
  const catalogUsers = useGetUsersQuery(pickerQueryArgs, {
    skip: pickerSkip || forBulkAssign,
  });
  const { data, isFetching, isError } = forBulkAssign
    ? bulkAssignUsers
    : catalogUsers;

  const staticRows = React.useMemo(() => {
    if (!useStaticPool || !staticUserPool) return []
    const q = debouncedSearch.trim().toLowerCase()
    let list = staticUserPool.filter((u) => {
      if (!q) return true
      const name = (u.name || "").toLowerCase()
      const email = (u.email || "").toLowerCase()
      return name.includes(q) || email.includes(q)
    })
    if (restrictSet) {
      list = list.filter((u) => restrictSet.has(u.id))
    }
    return list
  }, [useStaticPool, staticUserPool, debouncedSearch, restrictSet])

  const rows = React.useMemo(() => {
    if (useStaticPool) {
      return staticRows.map((u) => ({ ...u, role: undefined } as ApiUser))
    }
    let list = (data?.data ?? []).filter((u) => !u.isDeleted)
    if (restrictSet) {
      list = list.filter((u) => restrictSet.has(u.id))
    }
    return list
  }, [useStaticPool, staticRows, data?.data, restrictSet])
  const total = useStaticPool ? staticRows.length : (data?.total ?? 0)
  const showTotalHint =
    !useStaticPool && !restrictSet && total > PAGE_LIMIT
  const listRef = React.useRef<HTMLDivElement>(null)

  // TanStack Virtual returns unstable function refs; React Compiler skips memoization (same as MultiSelect).
  // eslint-disable-next-line react-hooks/incompatible-library -- useVirtualizer
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => listRef.current,
    estimateSize: () => ROW_PX,
    overscan: 8,
  })

  React.useEffect(() => {
    listRef.current?.scrollTo({ top: 0 })
  }, [debouncedSearch])

  const toggle = (u: ApiUser) => {
    const id = u.id
    const label = userRowLabel(u)
    const next = value.includes(id)
      ? value.filter((x) => x !== id)
      : [...value, id]
    if (next.includes(id)) pickedMetaRef.current.set(id, label)
    else pickedMetaRef.current.delete(id)
    bumpList()
    onChange(next)
  }

  const removeId = (id: string) => {
    pickedMetaRef.current.delete(id)
    bumpList()
    onChange(value.filter((x) => x !== id))
  }

  const chipLabel = (id: string) =>
    pickedMetaRef.current.get(id) ?? labelById[id] ?? id

  return (
    <Popover
      modal={false}
      open={open}
      onOpenChange={(next) => {
        setOpen(next)
        if (!next) setSearch("")
      }}
    >
      <PopoverTrigger asChild>
        <Button
          type="button"
          variant="outline"
          role="combobox"
          aria-expanded={open}
          disabled={disabled}
          className={cn(
            "h-auto min-h-10 w-full justify-between rounded-xl border-gray-200 bg-gray-50 px-3 py-2 text-left font-normal hover:bg-gray-50",
            className,
          )}
        >
          <div className="flex min-w-0 flex-1 flex-wrap items-center gap-1.5">
            {value.length === 0 ? (
              <span className="text-muted-foreground text-[13px]">
                {placeholder}
              </span>
            ) : (
              value.map((id) => (
                <Badge
                  key={id}
                  variant="secondary"
                  className="max-w-[200px] shrink-0 gap-1 rounded-md px-2 py-0.5 text-[12px] font-medium"
                  onClick={(e) => e.stopPropagation()}
                >
                  <span className="truncate">{chipLabel(id)}</span>
                  <span
                    role="button"
                    tabIndex={0}
                    className="ring-offset-background rounded-sm opacity-70 hover:opacity-100"
                    onKeyDown={(e) => {
                      if (e.key === "Enter" || e.key === " ") {
                        e.preventDefault()
                        removeId(id)
                      }
                    }}
                    onMouseDown={(e) => {
                      e.preventDefault()
                      e.stopPropagation()
                    }}
                    onClick={(e) => {
                      e.preventDefault()
                      e.stopPropagation()
                      removeId(id)
                    }}
                  >
                    <X className="size-3.5" />
                  </span>
                </Badge>
              ))
            )}
          </div>
          <ChevronDown className="ml-1 size-4 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent
        className="z-[60] w-[var(--radix-popover-trigger-width)] min-w-[280px] max-w-[min(100vw-2rem,420px)] p-0"
        align="start"
        onCloseAutoFocus={(e) => e.preventDefault()}
      >
        <div className="flex flex-col gap-0 border-b border-border/60 p-2">
          <Input
            type="search"
            placeholder="Search by name or email…"
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            className="h-9 rounded-lg border-gray-200 bg-white text-[13px]"
            autoFocus
          />
          {showTotalHint ? (
            <p className="text-muted-foreground px-1 pt-1 text-[11px] leading-snug">
              Showing first {PAGE_LIMIT} of {total}. Type more to narrow results.
            </p>
          ) : null}
        </div>
        <div
          ref={listRef}
          className="max-h-[min(320px,50vh)] overflow-y-auto overscroll-contain p-1"
        >
          {!useStaticPool && isFetching && rows.length === 0 ? (
            <div className="text-muted-foreground flex items-center justify-center gap-2 py-10 text-sm">
              <Loader2 className="size-4 animate-spin" />
              Loading…
            </div>
          ) : !useStaticPool && isError ? (
            <div className="text-destructive px-3 py-8 text-center text-sm">
              Could not load users. Try again.
            </div>
          ) : rows.length === 0 ? (
            <div className="text-muted-foreground px-3 py-8 text-center text-sm">
              {emptyListMessage ??
                (restrictSet
                  ? "No users on this team match your search for this role."
                  : "No users match your search.")}
            </div>
          ) : (
            <div
              className="relative w-full"
              style={{ height: `${virtualizer.getTotalSize()}px` }}
            >
              {virtualizer.getVirtualItems().map((vi) => {
                const u = rows[vi.index]
                if (!u) return null
                const selected = value.includes(u.id)
                return (
                  <button
                    key={u.id}
                    type="button"
                    className={cn(
                      "absolute left-0 flex w-full items-center gap-2 rounded-lg px-2.5 text-left text-[13px] transition-colors",
                      selected
                        ? "bg-primary/10 text-primary"
                        : "hover:bg-muted/80 text-foreground",
                    )}
                    style={{
                      height: `${vi.size}px`,
                      transform: `translateY(${vi.start}px)`,
                    }}
                    onClick={() => toggle(u)}
                  >
                    <span className="flex min-w-0 flex-1 flex-col gap-0.5 py-1">
                      <span className="truncate font-medium">
                        {userRowLabel(u)}
                      </span>
                      {u.email ? (
                        <span className="text-muted-foreground truncate text-[11px]">
                          {u.email}
                        </span>
                      ) : null}
                    </span>
                    {selected ? (
                      <Check className="text-primary size-4 shrink-0" />
                    ) : null}
                  </button>
                )
              })}
            </div>
          )}
        </div>
      </PopoverContent>
    </Popover>
  )
}
