'use client'
import type { ComboboxChangeEventDetails } from '@langgenius/dify-ui/combobox'
import type {
  AccessControlAccount,
  AccessControlGroup,
  Subject,
  SubjectAccount,
  SubjectGroup,
} from '@/models/access-control'
import { Avatar } from '@langgenius/dify-ui/avatar'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxInputGroup,
  ComboboxItem,
  ComboboxItemText,
  ComboboxList,
  ComboboxStatus,
  ComboboxTrigger,
} from '@langgenius/dify-ui/combobox'
import { RiArrowRightSLine, RiOrganizationChart } from '@remixicon/react'
import { useDebounce } from 'ahooks'
import { useAtomValue } from 'jotai'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { userProfileAtom } from '@/context/account-state'
import { SubjectType } from '@/models/access-control'
import { useSearchForWhiteListCandidates } from '@/service/access-control'
import useAccessControlStore from '../../../../context/access-control-store'
import Loading from '../../base/loading'

export default function AddMemberOrGroupDialog() {
  const { t } = useTranslation()
  const [open, setOpen] = useState(false)
  const [keyword, setKeyword] = useState('')
  const scrollRootRef = useRef<HTMLDivElement>(null)
  const anchorRef = useRef<HTMLDivElement>(null)
  const specificGroups = useAccessControlStore((s) => s.specificGroups)
  const setSpecificGroups = useAccessControlStore((s) => s.setSpecificGroups)
  const specificMembers = useAccessControlStore((s) => s.specificMembers)
  const setSpecificMembers = useAccessControlStore((s) => s.setSpecificMembers)
  const selectedGroupsForBreadcrumb = useAccessControlStore((s) => s.selectedGroupsForBreadcrumb)
  const debouncedKeyword = useDebounce(keyword, { wait: 500 })

  const lastAvailableGroup = selectedGroupsForBreadcrumb[selectedGroupsForBreadcrumb.length - 1]
  const { isLoading, isFetchingNextPage, fetchNextPage, data } = useSearchForWhiteListCandidates(
    { keyword: debouncedKeyword, groupId: lastAvailableGroup?.id, resultsPerPage: 10 },
    open,
  )
  const pages = data?.pages ?? []
  const subjects = pages.flatMap((page) => page.subjects ?? [])
  const selectedSubjects = [
    ...specificGroups.map(groupToSubject),
    ...specificMembers.map(memberToSubject),
  ]
  const hasResults = pages.length > 0 && subjects.length > 0
  const shouldShowBreadcrumb = hasResults || selectedGroupsForBreadcrumb.length > 0
  const hasMore = pages[pages.length - 1]?.hasMore ?? false

  useEffect(() => {
    let observer: IntersectionObserver | undefined
    if (anchorRef.current) {
      observer = new IntersectionObserver(
        (entries) => {
          if (entries[0]!.isIntersecting && !isLoading && hasMore) fetchNextPage()
        },
        { root: scrollRootRef.current, rootMargin: '20px' },
      )
      observer.observe(anchorRef.current)
    }
    return () => observer?.disconnect()
  }, [isLoading, fetchNextPage, hasMore])

  const handleOpenChange = (nextOpen: boolean) => {
    if (!nextOpen) setKeyword('')

    setOpen(nextOpen)
  }

  const handleInputValueChange = (inputValue: string, details: ComboboxChangeEventDetails) => {
    if (details.reason !== 'item-press') setKeyword(inputValue)
  }

  const handleValueChange = (nextSubjects: Subject[]) => {
    const nextGroups: AccessControlGroup[] = []
    const nextMembers: AccessControlAccount[] = []

    for (const subject of nextSubjects) {
      if (subject.subjectType === SubjectType.GROUP)
        nextGroups.push((subject as SubjectGroup).groupData)
      else nextMembers.push((subject as SubjectAccount).accountData)
    }

    setSpecificGroups(nextGroups)
    setSpecificMembers(nextMembers)
  }

  return (
    <Combobox<Subject, true>
      multiple
      open={open}
      value={selectedSubjects}
      inputValue={keyword}
      items={subjects}
      itemToStringLabel={getSubjectLabel}
      itemToStringValue={getSubjectValue}
      isItemEqualToValue={isSameSubject}
      filter={null}
      onOpenChange={handleOpenChange}
      onInputValueChange={handleInputValueChange}
      onValueChange={handleValueChange}
    >
      <ComboboxTrigger
        aria-label={t(($) => $['operation.add'], { ns: 'common' })}
        icon={false}
        size="small"
        className="h-6 w-auto min-w-[52px] shrink-0 rounded-md border-0 bg-transparent px-2 py-0 text-xs font-medium text-components-button-secondary-accent-text hover:bg-state-accent-hover focus-visible:bg-state-accent-hover data-popup-open:bg-state-accent-hover"
      >
        <span className="inline-flex min-w-0 items-center justify-center gap-x-0.5 whitespace-nowrap">
          <span className="i-ri-add-circle-fill size-4 shrink-0" aria-hidden="true" />
          <span className="shrink-0">{t(($) => $['operation.add'], { ns: 'common' })}</span>
        </span>
      </ComboboxTrigger>
      <ComboboxContent
        placement="bottom-end"
        alignOffset={300}
        popupClassName="relative flex max-h-[400px] w-[400px] flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-0 shadow-lg backdrop-blur-[5px]"
      >
        <div ref={scrollRootRef} className="min-h-0 overflow-y-auto">
          <div className="sticky top-0 z-10 bg-components-panel-bg-blur p-2 pb-0.5 backdrop-blur-[5px]">
            <ComboboxInputGroup className="h-8 min-h-8 px-2">
              <span
                className="mr-0.5 i-ri-search-line size-4 shrink-0 text-text-tertiary"
                aria-hidden="true"
              />
              <ComboboxInput
                aria-label={t(
                  ($) => $['accessControlDialog.operateGroupAndMember.searchPlaceholder'],
                  { ns: 'app' },
                )}
                placeholder={t(
                  ($) => $['accessControlDialog.operateGroupAndMember.searchPlaceholder'],
                  { ns: 'app' },
                )}
                className="block h-4.5 grow px-1 py-0 text-[13px] text-text-primary"
              />
            </ComboboxInputGroup>
          </div>
          {isLoading ? (
            <ComboboxStatus className="p-1">
              <Loading />
            </ComboboxStatus>
          ) : (
            <>
              {shouldShowBreadcrumb && (
                <div className="flex h-7 items-center px-2 py-0.5">
                  <SelectedGroupsBreadCrumb />
                </div>
              )}
              {hasResults ? (
                <>
                  <ComboboxList className="max-h-none p-1">
                    {(subject: Subject) => (
                      <SubjectItem key={getSubjectValue(subject)} subject={subject} />
                    )}
                  </ComboboxList>
                  {isFetchingNextPage && <Loading />}
                  <div ref={anchorRef} className="h-0" />
                </>
              ) : (
                <ComboboxEmpty className="flex h-7 items-center justify-center px-2 py-0.5">
                  {t(($) => $['accessControlDialog.operateGroupAndMember.noResult'], { ns: 'app' })}
                </ComboboxEmpty>
              )}
            </>
          )}
        </div>
      </ComboboxContent>
    </Combobox>
  )
}

function groupToSubject(group: AccessControlGroup): SubjectGroup {
  return {
    subjectId: group.id,
    subjectType: SubjectType.GROUP,
    groupData: group,
  }
}

function memberToSubject(member: AccessControlAccount): SubjectAccount {
  return {
    subjectId: member.id,
    subjectType: SubjectType.ACCOUNT,
    accountData: member,
  }
}

function getSubjectLabel(subject: Subject) {
  if (subject.subjectType === SubjectType.GROUP) return (subject as SubjectGroup).groupData.name

  return (subject as SubjectAccount).accountData.name
}

function getSubjectValue(subject: Subject) {
  return `${subject.subjectType}:${subject.subjectId}`
}

function isSameSubject(item: Subject, value: Subject) {
  return item.subjectId === value.subjectId && item.subjectType === value.subjectType
}

function SubjectItem({ subject }: { subject: Subject }) {
  if (subject.subjectType === SubjectType.GROUP)
    return <GroupItem group={(subject as SubjectGroup).groupData} subject={subject} />

  return <MemberItem member={(subject as SubjectAccount).accountData} subject={subject} />
}

function SelectedGroupsBreadCrumb() {
  const selectedGroupsForBreadcrumb = useAccessControlStore((s) => s.selectedGroupsForBreadcrumb)
  const setSelectedGroupsForBreadcrumb = useAccessControlStore(
    (s) => s.setSelectedGroupsForBreadcrumb,
  )
  const { t } = useTranslation()

  const handleBreadCrumbClick = (index: number) => {
    const newGroups = selectedGroupsForBreadcrumb.slice(0, index + 1)
    setSelectedGroupsForBreadcrumb(newGroups)
  }
  const handleReset = () => {
    setSelectedGroupsForBreadcrumb([])
  }
  const hasBreadcrumb = selectedGroupsForBreadcrumb.length > 0

  return (
    <div className="flex h-7 items-center gap-x-0.5 px-2 py-0.5">
      {hasBreadcrumb ? (
        <button
          type="button"
          className="cursor-pointer border-none bg-transparent p-0 text-left system-xs-regular text-text-accent focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:outline-hidden"
          onClick={handleReset}
        >
          {t(($) => $['accessControlDialog.operateGroupAndMember.allMembers'], { ns: 'app' })}
        </button>
      ) : (
        <span className="system-xs-regular text-text-tertiary">
          {t(($) => $['accessControlDialog.operateGroupAndMember.allMembers'], { ns: 'app' })}
        </span>
      )}
      {selectedGroupsForBreadcrumb.map((group, index) => {
        const isLastGroup = index === selectedGroupsForBreadcrumb.length - 1

        return (
          <div
            key={index}
            className="flex items-center gap-x-0.5 system-xs-regular text-text-tertiary"
          >
            <span>/</span>
            {isLastGroup ? (
              <span>{group.name}</span>
            ) : (
              <button
                type="button"
                className="cursor-pointer border-none bg-transparent p-0 text-left system-xs-regular text-text-accent focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:outline-hidden"
                onClick={() => handleBreadCrumbClick(index)}
              >
                {group.name}
              </button>
            )}
          </div>
        )
      })}
    </div>
  )
}

type GroupItemProps = {
  group: AccessControlGroup
  subject: Subject
}
function GroupItem({ group, subject }: GroupItemProps) {
  const { t } = useTranslation()
  const specificGroups = useAccessControlStore((s) => s.specificGroups)
  const selectedGroupsForBreadcrumb = useAccessControlStore((s) => s.selectedGroupsForBreadcrumb)
  const setSelectedGroupsForBreadcrumb = useAccessControlStore(
    (s) => s.setSelectedGroupsForBreadcrumb,
  )
  const isChecked = specificGroups.some((g) => g.id === group.id)

  const handleExpandClick = () => {
    setSelectedGroupsForBreadcrumb([...selectedGroupsForBreadcrumb, group])
  }

  return (
    <div className="flex items-center gap-2 rounded-lg hover:bg-state-base-hover">
      <BaseItem subject={subject}>
        <SelectionBox checked={isChecked} />
        <ComboboxItemText className="flex grow items-center px-0">
          <div className="mr-2 size-5 overflow-hidden rounded-full bg-components-icon-bg-blue-solid">
            <div className="bg-access-app-icon-mask-bg flex size-full items-center justify-center">
              <RiOrganizationChart
                className="h-[14px] w-[14px] text-components-avatar-shape-fill-stop-0"
                aria-hidden="true"
              />
            </div>
          </div>
          <span className="mr-1 system-sm-medium text-text-secondary">{group.name}</span>
          <span className="system-xs-regular text-text-tertiary">{group.groupSize}</span>
        </ComboboxItemText>
      </BaseItem>
      <Button
        size="small"
        disabled={isChecked}
        variant="ghost-accent"
        className="mr-1 flex shrink-0 items-center justify-between px-1.5 py-1"
        onPointerDown={(event) => event.preventDefault()}
        onClick={handleExpandClick}
      >
        <span className="px-[3px]">
          {t(($) => $['accessControlDialog.operateGroupAndMember.expand'], { ns: 'app' })}
        </span>
        <RiArrowRightSLine className="size-4" aria-hidden="true" />
      </Button>
    </div>
  )
}

type MemberItemProps = {
  member: AccessControlAccount
  subject: Subject
}
function MemberItem({ member, subject }: MemberItemProps) {
  const currentUser = useAtomValue(userProfileAtom)
  const { t } = useTranslation()
  const specificMembers = useAccessControlStore((s) => s.specificMembers)
  const isChecked = specificMembers.some((m) => m.id === member.id)
  return (
    <BaseItem subject={subject} className="pr-3">
      <SelectionBox checked={isChecked} />
      <ComboboxItemText className="flex grow items-center px-0">
        <div className="mr-2 size-5 overflow-hidden rounded-full bg-components-icon-bg-blue-solid">
          <div className="bg-access-app-icon-mask-bg flex size-full items-center justify-center">
            <Avatar size="xxs" avatar={null} name={member.name} />
          </div>
        </div>
        <span className="mr-1 system-sm-medium text-text-secondary">{member.name}</span>
        {currentUser.email === member.email && (
          <span className="system-xs-regular text-text-tertiary">
            ({t(($) => $.you, { ns: 'common' })})
          </span>
        )}
      </ComboboxItemText>
      <span className="system-xs-regular text-text-quaternary">{member.email}</span>
    </BaseItem>
  )
}

type BaseItemProps = {
  className?: string
  subject: Subject
  children: React.ReactNode
}
function BaseItem({ children, className, subject }: BaseItemProps) {
  return (
    <ComboboxItem
      value={subject}
      className={cn(
        'mx-0 flex min-h-8 grow grid-cols-none items-center gap-2 rounded-lg p-1 pl-2',
        className,
      )}
    >
      {children}
    </ComboboxItem>
  )
}

function SelectionBox({ checked }: { checked: boolean }) {
  return (
    <span
      aria-hidden="true"
      className={cn(
        'flex size-4 shrink-0 items-center justify-center rounded-sm shadow-xs shadow-shadow-shadow-3',
        checked
          ? 'bg-components-checkbox-bg text-components-checkbox-icon'
          : 'border border-components-checkbox-border bg-components-checkbox-bg-unchecked',
      )}
    >
      {checked && <span className="i-ri-check-line size-3" />}
    </span>
  )
}
