import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { Box } from '@wix/design-system';
import {
  CellMultiReferenceInput,
  type CellMultiReferenceInputHandle,
  type CellMultiReferenceItem,
} from '@wix/patterns-fields';
import { observer } from 'mobx-react-lite';
import { useCollection } from '@wix/bex-core/react';
import type { CellEditProps } from '../../types';
import { useCloseOnAncestorScroll } from '../../../../hooks/useCloseOnAncestorScroll';
import { useFilterCollectionStates } from '../../../../hooks/useFilterCollectionStates';
import { FilterTranslationProvider } from '../../../../providers';
import { getImageFieldId } from '../reference/getImageFieldId';
import { useOpenReferencedCollection } from '../reference/openCollectionContext';
import type {
  MultiReferenceValue,
  ReferenceItem,
  ReferenceTypeConfig,
} from './types';

function _MultiReferenceEditContent({
  value,
  onChange,
  onCommit,
  typeConfig,
  editTrigger,
  inputRef,
}: CellEditProps<MultiReferenceValue>) {
  const config = typeConfig as ReferenceTypeConfig | undefined;
  const refSchema = config?.refSchema;
  const acHandle = useRef<CellMultiReferenceInputHandle | null>(null);
  const wrapperRef = useRef<HTMLDivElement | null>(null);
  // Opener provided by ReferenceEditWrapper (a stable ancestor) so the
  // navigation + return-to-foreground subscription survive this editor
  // unmounting when the overlay opens.
  const openReferencedCollection = useOpenReferencedCollection();

  const idField = refSchema?.idField ?? '_id';
  const displayField = refSchema?.displayField ?? 'title';
  const imageField = getImageFieldId(refSchema?.fields);

  const collection = useCollection<ReferenceItem>({
    queryName: `multi-reference-cell-edit-${refSchema?.id ?? 'unknown'}`,
    itemKey: (item) => item.id,
    itemName: (item) => item.name,
    fetchData: async (query) => {
      if (!config?.fetchReferenceItems) {
        return { items: [], total: 0 };
      }
      return config.fetchReferenceItems(query);
    },
  });

  const initialSearchTextRef = useRef(
    editTrigger &&
      typeof editTrigger === 'object' &&
      editTrigger.kind === 'type'
      ? editTrigger.key
      : undefined,
  );

  useEffect(() => {
    const dispose = collection.init();
    if (initialSearchTextRef.current !== undefined) {
      collection.search(initialSearchTextRef.current, { clearResult: true });
    }
    // Seed editingValue with the current value so a no-op edit commits the same
    // list back instead of `undefined` (which would clear the cell).
    onChange(value ?? []);
    return dispose;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [collection]);

  const selectedRefs = value ?? [];

  const selectedItems = useMemo<CellMultiReferenceItem[]>(
    () =>
      selectedRefs.map((ref, i) => ({
        id: String(ref[idField] ?? i),
        name: String(ref[displayField] ?? ''),
        image: imageField ? (ref[imageField] as string | undefined) : undefined,
      })),
    [selectedRefs, idField, displayField, imageField],
  );

  const selectedIds = useMemo(
    () => new Set(selectedItems.map((item) => item.id)),
    [selectedItems],
  );

  const keyedItems = collection.result.keyedItems;
  const fetchedLastPage = collection.fetchedLastPage;
  const searchValue = collection.query.search.value;

  // Exclude already-selected references from the dropdown options.
  const items = useMemo<CellMultiReferenceItem[]>(
    () =>
      keyedItems
        .map((ki) => ({
          id: String(ki.key),
          name: ki.item.name,
          image: ki.item.image,
        }))
        .filter((item) => !selectedIds.has(item.id)),
    [keyedItems, selectedIds],
  );

  const handleSelect = useCallback(
    (item: CellMultiReferenceItem) => {
      const next = [
        ...selectedRefs,
        {
          [idField]: item.id,
          [displayField]: item.name,
          ...(imageField && item.image ? { [imageField]: item.image } : {}),
        },
      ];
      onChange(next);
      collection.clearSearch();
    },
    [selectedRefs, idField, displayField, imageField, onChange, collection],
  );

  const handleRemoveTag = useCallback(
    (id: string) => {
      const next = selectedRefs.filter(
        (ref) => String(ref[idField] ?? '') !== id,
      );
      onChange(next.length ? next : null);
    },
    [selectedRefs, idField, onChange],
  );

  const handleSearchChange = useCallback(
    (search: string) => {
      if (search) {
        collection.search(search, { clearResult: true });
      } else {
        collection.clearSearch();
      }
    },
    [collection],
  );

  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      // Production should commit (not revert) on Escape — close the editor and
      // keep the current selection. Stop propagation so the framework's default
      // cancel-on-Escape doesn't also fire.
      if (e.key === 'Escape') {
        e.preventDefault();
        e.stopPropagation();
        onCommit();
      }
    },
    [onCommit],
  );

  const closeDropdown = useCallback(() => acHandle.current?.hideOptions(), []);
  useCloseOnAncestorScroll(wrapperRef, closeDropdown, true);

  // Commit on blur, unless focus moved into the portaled dropdown / popover.
  const handleBlur = useCallback(() => {
    requestAnimationFrame(() => {
      const active = document.activeElement as HTMLElement | null;
      if (!active) {
        onCommit();
        return;
      }
      if (wrapperRef.current?.contains(active)) {
        return;
      }
      if (
        active.closest(
          '[role="listbox"], [role="dialog"], [data-hook*="popover"]',
        )
      ) {
        return;
      }
      onCommit();
    });
  }, [onCommit]);

  // Click-outside fallback. Empty cells often never get a focused descendant,
  // so onBlur on the wrapper never fires.
  useEffect(() => {
    const handleDocMouseDown = (e: MouseEvent) => {
      const target = e.target as HTMLElement | null;
      if (!target) {
        return;
      }
      if (wrapperRef.current?.contains(target)) {
        return;
      }
      if (
        target.closest?.(
          '[role="listbox"], [role="dialog"], [data-hook*="popover"]',
        )
      ) {
        return;
      }
      onCommit();
    };
    document.addEventListener('mousedown', handleDocMouseDown);
    return () => document.removeEventListener('mousedown', handleDocMouseDown);
  }, [onCommit]);

  const { showInitialLoader, fixedFooter } = useFilterCollectionStates({
    collection,
    fixedFooter: config?.renderFooter?.(),
  });

  // Refresh this editor's dropdown options when the overlay closes — force
  // invalidates the shared query cache (keyed by queryName), so even after this
  // editor has unmounted the next open refetches fresh.
  const handleOpenCollection = useMemo(
    () =>
      openReferencedCollection
        ? () =>
            openReferencedCollection(() =>
              collection.clearResultAndMoveToStart({ force: true }),
            )
        : undefined,
    [openReferencedCollection, collection],
  );

  if (!refSchema) {
    return null;
  }

  return (
    <Box ref={wrapperRef} width="100%" height="100%">
      <CellMultiReferenceInput
        ref={acHandle}
        items={items}
        selectedItems={selectedItems}
        value={searchValue}
        onChange={handleSearchChange}
        onSelect={handleSelect}
        onRemoveTag={handleRemoveTag}
        onKeyDown={handleKeyDown}
        onBlur={handleBlur}
        infiniteScroll
        hasMore={!showInitialLoader && !fetchedLastPage}
        loadMore={collection.fetchNextPageIfNeeded}
        fixedFooter={fixedFooter}
        onOpenCollection={handleOpenCollection}
        autoOpen={!!editTrigger}
        inputRef={inputRef}
        dataHook="multi-reference-edit"
      />
    </Box>
  );
}

const MultiReferenceEditContent = observer(_MultiReferenceEditContent);

export const MultiReferenceEdit = (
  props: CellEditProps<MultiReferenceValue>,
) => (
  <FilterTranslationProvider>
    <MultiReferenceEditContent {...props} />
  </FilterTranslationProvider>
);
