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 } from '@/app/hooks';
import {
  _linkPropToEntityValue,
  isEvaluatedComponentModel,
} from '@/features/layout/layoutModelSlice';
import useInputUIData from '@/hooks/useInputUIData';

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 inputUIData = useInputUIData();
  const { model, selectedComponent: selectedComponentId } = inputUIData;
  const dispatch = useAppDispatch();
  const selectedModel: ComponentModel | EvaluatedComponentModel | boolean =
    model?.[selectedComponentId] || false;
  if (!selectedModel) {
    return null;
  }
  const [linkerOpen, setLinkerOpen] = useState(false);
  const handleFieldClick = (value: LinkSuggestion) => {
    dispatch(
      _linkPropToEntityValue({
        componentToUpdateId: selectedComponentId,
        propName,
        newSource: value.source,
        inputUIData,
      }),
    );
  };

  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,
    childOf,
  }: {
    item: LinkSuggestion;
    isActive: boolean;
    childOf?: string;
  }) => {
    return (
      <DropdownMenu.Item
        onClick={() => handleFieldClick(item)}
        disabled={isActive}
        className={clsx({
          [styles.disabled]: isActive,
        })}
        data-link-suggestion-option={item.label}
        data-child-of={childOf}
      >
        {isActive && <CheckIcon />}
        {item.label}
      </DropdownMenu.Item>
    );
  };

  const NestedDropdownItem = ({
    item,
    parent,
  }: {
    item: LinkSuggestion;
    parent?: string;
  }) => {
    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,
            })}
            data-link-suggestion-option={item.label}
            data-child-of={parent}
          >
            {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}
                  childOf="myself"
                />
                <Separator orientation="horizontal" size="4" my="2" />
              </>
            )}

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

    return (
      <DropdownMenuItem
        key={item.id}
        item={item}
        isActive={isActive}
        childOf={parent}
      />
    );
  };

  // Flatten the suggestions to a list of linkable items with their full path as
  // the label for testing purposes.
  const flatSuggestions = suggestions
    .reduce((acc: LinkSuggestion[], suggestion) => {
      const addSuggestionWithPath = (
        item: LinkSuggestion,
        path: string = '',
      ): void => {
        const currentPath = path ? `${path} > ${item.label}` : item.label;
        // Only add items that have a 'source' property (linkable items)
        if (item.source) {
          // If this item has both a source AND children, it appears ONLY in its own submenu
          // as "myself" (see line 135), NOT as a standalone item.
          if (item.items && item.items.length > 0) {
            const myselfPath = `${currentPath} > ${item.label}`;
            acc.push({ ...item, label: myselfPath });
          } else {
            // No children, add normally as a standalone item
            acc.push({ ...item, label: currentPath });
          }
        }

        // Recursively process sub-items
        if (item.items && item.items.length > 0) {
          item.items.forEach((subItem) =>
            addSuggestionWithPath(subItem, currentPath),
          );
        }
      };
      addSuggestionWithPath(suggestion);
      return acc;
    }, [])
    .map((item) => item.label);

  return (
    <DropdownMenu.Root onOpenChange={(open) => setLinkerOpen(open)}>
      <DropdownMenu.Trigger>
        <button
          className={clsx(styles.linker, {
            [styles.linkerOpen]: linkerOpen,
          })}
          aria-label={`Link ${propName} to an other field`}
          data-canvas-link-suggestions={JSON.stringify(flatSuggestions)}
        >
          {linked && <Link1Icon className={styles.default} />}
          {!linked && <LinkNone1Icon className={styles.default} />}
        </button>
      </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}
                parent={'root'}
              />
            );
          }
          const isActive =
            JSON.stringify(suggestion.source) === selectedSourceAsString;
          return (
            <DropdownMenuItem
              key={suggestion.id}
              item={suggestion}
              isActive={isActive}
              childOf="root"
            />
          );
        })}
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  );
};

export default PropLinker;
