import { useState } from 'react';
import clsx from 'clsx';
import { CheckIcon, Link1Icon, LinkNone1Icon } from '@radix-ui/react-icons';
import { DropdownMenu, Separator, Text } 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 {
  ComponentModel,
  DynamicPropSource,
  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[];
}
const ICON_SLOT_WIDTH = 12;

const ICON_SLOT_STYLE: React.CSSProperties = {
  width: `${ICON_SLOT_WIDTH}px`,
  display: 'inline-flex',
  flexShrink: 0,
};

/**
 * 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\FieldForComponentSuggester,
 *   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 currentExpression = undefined;
  if (
    isEvaluatedComponentModel(selectedModel) &&
    selectedModel?.source?.[propName] &&
    Object.prototype.hasOwnProperty.call(
      Object(selectedModel.source[propName] || {}),
      'expression',
    )
  ) {
    const fieldSource = selectedModel.source[propName] as DynamicPropSource;
    currentExpression = fieldSource.expression;
  }

  // Recursively check for active descendant in nested items.
  const checkForActiveDescendant = (
    items: LinkSuggestion[],
    currentExpression: string,
  ): boolean =>
    items.some((item) =>
      item.source?.expression === currentExpression
        ? true
        : item.items?.length
          ? checkForActiveDescendant(item.items, currentExpression)
          : false,
    );

  const IconSlot = ({ children }: { children?: React.ReactNode }) => (
    <span style={ICON_SLOT_STYLE}>{children ?? null}</span>
  );

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

  const NestedDropdownItem = ({
    item,
  }: {
    item: LinkSuggestion;
  }): React.ReactNode => {
    const isActive = item.source?.expression === currentExpression;

    if (item.items && item.items.length > 0) {
      const hasActiveChild = currentExpression
        ? checkForActiveDescendant(item.items, currentExpression)
        : false;
      return (
        <DropdownMenu.Sub>
          <DropdownMenu.SubTrigger
            className={clsx(styles.subTrigger, styles.dropdownMenuItem, {
              [styles.disabled]: isActive && item.source,
            })}
          >
            <IconSlot />
            <Text
              className={clsx({
                [styles.hasActiveChild]: hasActiveChild,
              })}
            >
              {item.label}
            </Text>
          </DropdownMenu.SubTrigger>

          <DropdownMenu.SubContent>
            {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={index} item={suggestion} />;
          }
          // @todo: Temporarily hard code this until we have a better way to filter suggestions.
          if (suggestion.label === 'Revision log message') return null;
          const isActive = suggestion.source?.expression === currentExpression;
          return (
            <DropdownMenuItem
              key={suggestion.id}
              item={suggestion}
              isActive={isActive}
            />
          );
        })}
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  );
};

export default PropLinker;
