import { useState } from 'react';
import clsx from 'clsx';
import { CheckIcon, Link1Icon, LinkNone1Icon } from '@radix-ui/react-icons';
import { DropdownMenu, Separator } from '@radix-ui/themes';

import { useAppDispatch, useAppSelector } from '@/app/hooks';
import {
  _updateExistingComponentValuesForLinking,
  isEvaluatedComponentModel,
  selectModel,
} from '@/features/layout/layoutModelSlice';
import { selectSelectedComponentUuid } from '@/features/ui/uiSlice';

import type {
  BasePropSource,
  ComponentModel,
  EvaluatedComponentModel,
} from '@/features/layout/layoutModelSlice';

import styles from './PropLinker.module.css';

export interface LinkSuggestion {
  id?: string;
  label: string;
  source?: any;
  items?: LinkSuggestion[];
}

export interface PropLinkerProps {
  propName: string;
  linked: boolean;
  suggestions: LinkSuggestion[];
}

/**
 * Appears as a link icon. When clicked, a dropdown appears with
 * suggestions for linking this field to other fields.
 * @param {string} propName
 *   The machine name of the prop being linked.
 * @param {boolean} linked
 *   Whether the field is currently linked.
 * @param {object} suggestions
 *   An object of suggested fields to link to, keyed by a unique identifier.
 *   This list is generated by \Drupal\canvas\ShapeMatcher\PropSourceSuggester.
 *   Each suggestion contains:
 *   - label: The human-readable name of the field.
 *   - source: An object containing the dynamic expression that results in the
 *     prop resolving to the linked field's value.
 */
const PropLinker = ({ propName, linked, suggestions }: PropLinkerProps) => {
  const model = useAppSelector(selectModel);
  const selectedComponent = useAppSelector(selectSelectedComponentUuid);
  const dispatch = useAppDispatch();
  const selectedComponentId: string = selectedComponent || 'noop';
  const selectedModel: ComponentModel | EvaluatedComponentModel =
    model[selectedComponentId] || {};
  const [linkerOpen, setLinkerOpen] = useState(false);
  const handleFieldClick = (value: LinkSuggestion) => {
    dispatch(
      _updateExistingComponentValuesForLinking({
        componentToUpdateId: selectedComponentId,
        values: {
          [propName]: 'noop',
        },
        sources: {
          [propName]: value.source,
        },
      }),
    );
  };

  let selectedSourceAsString = '';
  if (
    isEvaluatedComponentModel(selectedModel) &&
    selectedModel?.source?.[propName] &&
    Object.prototype.hasOwnProperty.call(
      Object(selectedModel.source[propName] || {}),
      'expression',
    )
  ) {
    // The selected source must be treated as an opaque object, so compare the entire object by using stringify.
    selectedSourceAsString = JSON.stringify(
      selectedModel.source[propName] as BasePropSource,
    );
  }

  const DropdownMenuItem = ({
    item,
    isActive,
  }: {
    item: LinkSuggestion;
    isActive: boolean;
  }) => {
    return (
      <DropdownMenu.Item
        onClick={() => handleFieldClick(item)}
        disabled={isActive}
        className={clsx({
          [styles.disabled]: isActive,
        })}
      >
        {isActive && <CheckIcon />}
        {item.label}
      </DropdownMenu.Item>
    );
  };

  const NestedDropdownItem = ({ item }: { item: LinkSuggestion }) => {
    const isActive = JSON.stringify(item.source) === selectedSourceAsString;

    if (item.items && item.items.length > 0) {
      return (
        <DropdownMenu.Sub>
          <DropdownMenu.SubTrigger
            className={clsx(styles.subTrigger, {
              [styles.disabled]: isActive && item.source,
            })}
          >
            {item.label}
          </DropdownMenu.SubTrigger>

          <DropdownMenu.SubContent>
            {/* Show the parent item in its own menu if it has a source itself to link to. */}
            {item.source && (
              <>
                <DropdownMenuItem
                  key={item.id}
                  item={item}
                  isActive={isActive}
                />
                <Separator orientation="horizontal" size="4" my="2" />
              </>
            )}

            {item.items.map((sub, index) => (
              <NestedDropdownItem key={sub.id || index} item={sub} />
            ))}
          </DropdownMenu.SubContent>
        </DropdownMenu.Sub>
      );
    }

    return <DropdownMenuItem key={item.id} item={item} isActive={isActive} />;
  };

  return (
    <DropdownMenu.Root onOpenChange={(open) => setLinkerOpen(open)}>
      <DropdownMenu.Trigger>
        <span
          className={clsx(styles.linker, {
            [styles.linkerOpen]: linkerOpen,
          })}
        >
          {linked && <Link1Icon className={styles.default} />}
          {!linked && <LinkNone1Icon className={styles.default} />}
        </span>
      </DropdownMenu.Trigger>
      <DropdownMenu.Content align="end" side="bottom">
        {suggestions.map((suggestion, index) => {
          if (suggestion.items && suggestion.items.length > 0) {
            return (
              <NestedDropdownItem
                key={suggestion.id || index}
                item={suggestion}
              />
            );
          }
          const isActive =
            JSON.stringify(suggestion.source) === selectedSourceAsString;
          return (
            <DropdownMenuItem
              key={suggestion.id}
              item={suggestion}
              isActive={isActive}
            />
          );
        })}
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  );
};

export default PropLinker;
