import {
  useContext,
  useId,
  useMemo,
  useState,
  type PropsWithChildren,
  type ReactNode,
} from 'react';
import { Sentiment, Typography } from '../common';
import Body from '../body';
import { AdditionalInfo } from './AdditionalInfo';
import { IconButton, type ListItemIconButtonProps } from './IconButton';
import { Checkbox, type ListItemCheckboxProps } from './Checkbox';
import { Navigation, type ListItemNavigationProps } from './Navigation';
import { clsx } from 'clsx';
import { Button, type ListItemButtonProps } from './Button';
import { Radio, type ListItemRadioProps } from './Radio';
import { Switch, type ListItemSwitchProps } from './Switch';
import { AvatarLayout } from './AvatarLayout';
import { AvatarView } from './AvatarView';
import { Image } from './Image';
import { Prompt } from './Prompt';
import { PrimitiveAnchor, type PrimitiveAnchorProps } from '../primitives';
import {
  ListItemContext,
  type ListItemContextData,
  type ListItemMediaSize,
} from './ListItemContext';

export type ListItemTypes =
  | 'non-interactive'
  | 'navigation'
  | 'radio'
  | 'checkbox'
  | 'switch'
  | 'button'
  | 'icon-button';

export type ListItemControlProps =
  | ListItemNavigationProps
  | ListItemCheckboxProps
  | ListItemButtonProps
  | ListItemIconButtonProps
  | ListItemRadioProps
  | ListItemSwitchProps;

export type ListItemProps = {
  /** @default 'li' */
  as?: 'li' | 'div';
  /**
   * Swaps vertical hierarchy of title and subtitle and their corresponding right values.
   */
  inverted?: boolean;
  /**
   * Disables the control and renders the ListItem in greyscale and with slightly decreased opacity.
   */
  disabled?: boolean;
  /**
   * If set, it'll extend the `disabled` state, overriding existing or injecting uniquely styled prompt with the message provided via this prop. <br />
   * **NB:** This message cannot house more than **1** link or inline button.<br />
   * **NB:** It must be used together with `disabled` prop and will be disregarded otherwise.
   */
  disabledPromptMessage?: ReactNode;
  /**
   * Highlights the list item as an action to be taken or already taken. <br />
   */
  spotlight?: 'active' | 'inactive';
  title: ReactNode;
  subtitle?: ReactNode;
  /**
   * Requires `<ListItem.AdditionalInfo />` component as a sole child. <br />
   * Can be only rendered if `subtitle` is also provided.
   */
  additionalInfo?: ReactNode;
  valueTitle?: ReactNode;
  valueSubtitle?: ReactNode;
  /**
   * Requires one of the following as a sole child: <br />
   * `<ListItem.AvatarView />`,
   * `<ListItem.AvatarLayout />` or
   * `<ListItem.Image />`
   */
  media?: ReactNode;
  /**
   * Requires one of the following as a sole child: <br/>
   * `<ListItem.Button />`, <br/>
   * `<ListItem.Checkbox />`, <br/>
   * `<ListItem.IconButton />`, <br/>
   * `<ListItem.Navigation />`, <br/>
   * `<ListItem.Radio />`, or
   * `<ListItem.Switch />`
   * @default null
   */
  control?: ReactNode;
  /**
   * Requires `<ListItem.Prompt />` component as a sole child.
   */
  prompt?: ReactNode;
  className?: string;
  /**
   * A number between `0–100` which resolves to a `fr` value of a `grid-template-columns` declaration. E.g. `valueColumnWidth={25}` will result in a `75fr 25fr`. <br />
   * Controls the width ratio of left side content (title and subtitle) to the right side content.
   */
  valueColumnWidth?: number;
  id?: string;
};

/**
 * @see [Design documentation](https://docs.wise.design/components/list-item)
 * @see [Storybook documentation](https://storybook.wise.design/?path=/docs/content-listitem--docs)
 */
export const ListItem = ({
  as: ListItemElement = 'li',
  title,
  subtitle,
  additionalInfo,
  prompt,
  inverted,
  media,
  spotlight,
  valueTitle,
  valueSubtitle,
  control = null,
  disabled,
  disabledPromptMessage,
  className,
  valueColumnWidth,
  id,
}: ListItemProps) => {
  const idPrefix = useId();
  const [controlProps, setControlProps] = useState<ListItemControlProps>({});
  const [controlType, setControlType] = useState<ListItemTypes>('non-interactive');
  const [mediaSize, setMediaSize] = useState<ListItemMediaSize | undefined>();

  const ids: ListItemContextData['ids'] = {
    title: `${idPrefix}_title`,
    ...(subtitle ? { subtitle: `${idPrefix}_subtitle` } : {}),
    ...(valueTitle ? { valueTitle: `${idPrefix}_value-title` } : {}),
    ...(valueSubtitle ? { valueSubtitle: `${idPrefix}_value-subtitle` } : {}),
    control: `${idPrefix}_control`,
    ...(prompt || (disabled && disabledPromptMessage) ? { prompt: `${idPrefix}_prompt` } : {}),
    ...(additionalInfo ? { additionalInfo: `${idPrefix}_additional-info` } : {}),
  };

  const isPartiallyInteractive = Boolean(
    (controlType === 'button' || controlType === 'icon-button') &&
    (controlProps as ListItemButtonProps | ListItemIconButtonProps)?.partiallyInteractive,
  );
  const isFullyInteractive = controlType !== 'non-interactive' && !isPartiallyInteractive;
  const isButtonAsLink =
    (controlType === 'button' || controlType === 'icon-button') &&
    Boolean((controlProps as ListItemButtonProps | ListItemIconButtonProps)?.href);

  const titlesAndValues = [
    inverted ? ids.subtitle : ids.title,
    inverted ? ids.title : ids.subtitle,
    inverted ? ids.valueSubtitle : ids.valueTitle,
    inverted ? ids.valueTitle : ids.valueSubtitle,
  ].join(' ');
  const additionalInfoPrompt = [ids.additionalInfo, ids.prompt].filter(Boolean).join(' ');

  const describedByIds = useMemo(() => {
    return isFullyInteractive && !isButtonAsLink
      ? additionalInfoPrompt
      : `${titlesAndValues} ${additionalInfoPrompt}`;
  }, [additionalInfoPrompt, isButtonAsLink, isFullyInteractive, titlesAndValues]);
  const listItemContext = useMemo(
    () => ({
      setControlType,
      setControlProps,
      setMediaSize,
      ids,
      props: { disabled, inverted, disabledPromptMessage },
      mediaSize,
      isPartiallyInteractive,
      describedByIds,
    }),
    [describedByIds, mediaSize, disabled, inverted, disabledPromptMessage, isPartiallyInteractive],
  );
  const gridColumnsStyle = {
    '--wds-list-item-body-left': valueColumnWidth ? `${100 - valueColumnWidth}fr` : '50fr',
    '--wds-list-item-body-right': valueColumnWidth ? `${valueColumnWidth}fr` : '50fr',
  } as React.CSSProperties;

  const getFeatureClassName = () => {
    const partials = [];
    const hasMedia = Boolean(media);
    const hasControl = Boolean(control);
    const hasInfo = Boolean(additionalInfo);
    const hasPrompt = Boolean(prompt) || (disabled && Boolean(disabledPromptMessage));

    /* eslint-disable functional/immutable-data */
    if (hasMedia && hasControl) {
      partials.push('wds-list-item-hasMedia-hasControl');
    }

    if (hasMedia && !hasControl) {
      partials.push('wds-list-item-hasMedia-noControl');
    }

    if (!hasMedia && hasControl) {
      partials.push('wds-list-item-noMedia-hasControl');
    }

    if (!hasMedia && !hasControl) {
      partials.push('wds-list-item-noMedia-noControl');
    }

    if (hasInfo && hasPrompt) {
      partials.push('wds-list-item-hasInfo-hasPrompt');
    }
    if (hasInfo && !hasPrompt) {
      partials.push('wds-list-item-hasInfo-noPrompt');
    }
    if (!hasInfo && hasPrompt) {
      partials.push('wds-list-item-noInfo-hasPrompt');
    }
    if (!hasInfo && !hasPrompt) {
      partials.push('wds-list-item-noInfo-noPrompt');
    }
    /* eslint-enable functional/immutable-data */

    return partials.join(' ');
  };

  return (
    <ListItemContext.Provider value={listItemContext}>
      <ListItemElement
        className={clsx(
          'wds-list-item',
          `wds-list-item-${controlType}`,
          getFeatureClassName(),
          {
            'wds-list-item-interactive': isFullyInteractive,
            'wds-list-item-partially-interactive': isPartiallyInteractive,
            [`wds-list-item-spotlight wds-list-item-spotlight-${spotlight}`]:
              isFullyInteractive && !!spotlight,
            disabled: disabled && !isPartiallyInteractive,
            'disabled--has-prompt-reason':
              !disabledPromptMessage && disabled && !isPartiallyInteractive,
          },
          className,
        )}
        id={id}
        aria-disabled={disabled}
        style={
          {
            '--wds-list-item-value-min-height': mediaSize ? `${mediaSize}px` : undefined,
          } as React.CSSProperties
        }
      >
        {isFullyInteractive && spotlight === 'inactive' && (
          <svg aria-hidden="true" className="wds-list-item-spotlight__border">
            <rect />
          </svg>
        )}

        <View
          {...{
            isPartiallyInteractive,
            subtitle,
            additionalInfo,
            disabled,
            disabledPromptMessage,
            prompt,
            controlType,
            controlProps,
          }}
          className={getFeatureClassName()}
        >
          {media && <div className="wds-list-item-media">{media}</div>}

          {/* Title + Subtitle + Values - Group */}
          <div
            className="wds-list-item-body"
            style={valueColumnWidth ? gridColumnsStyle : undefined}
          >
            {/* Title + Subtitle + Values - Group */}
            <span className="wds-list-item-titles">
              {(() => {
                const titles = [
                  <Body
                    key={ids.title}
                    id={ids.title}
                    type={Typography.BODY_LARGE_BOLD}
                    className="wds-list-item-title"
                  >
                    {title}
                  </Body>,
                ];
                if (subtitle) {
                  titles.push(
                    <Body key={ids.subtitle} id={ids.subtitle} className="wds-list-item-subtitle">
                      {subtitle}
                    </Body>,
                  );
                }
                return inverted ? [...titles].reverse() : titles;
              })()}
            </span>

            {(valueTitle || valueSubtitle) && (
              <span
                className={clsx('wds-list-item-value', {
                  'flex-column': valueTitle !== undefined || valueSubtitle !== undefined,
                })}
              >
                {(() => {
                  const values = [];
                  if (valueTitle) {
                    values.push(
                      <Body
                        key={ids.valueTitle}
                        id={ids.valueTitle}
                        type={Typography.BODY_LARGE_BOLD}
                        className="wds-list-item-title-value"
                      >
                        {valueTitle}
                      </Body>,
                    );
                  }
                  if (valueSubtitle) {
                    values.push(
                      <Body
                        key={ids.valueSubtitle}
                        id={ids.valueSubtitle}
                        className="wds-list-item-subtitle-value"
                      >
                        {valueSubtitle}
                      </Body>,
                    );
                  }
                  return inverted ? [...values].reverse() : values;
                })()}
              </span>
            )}
          </div>

          {control === null ? null : (
            <Body
              className={clsx('wds-list-item-control-wrapper', {
                'wds-list-item-button-control': controlType === 'button',
                'wds-list-item-button-control--hasPrompt':
                  controlType === 'button' && Boolean(prompt),
              })}
              style={
                {
                  '--wds-list-item-control-wrapper-height': mediaSize ? `${mediaSize}px` : 'auto',
                } as React.CSSProperties
              }
            >
              {control}
            </Body>
          )}
        </View>
      </ListItemElement>
    </ListItemContext.Provider>
  );
};

type ViewProps = PropsWithChildren<{
  isPartiallyInteractive: boolean;
  controlType?: ListItemTypes;
  controlProps?: ListItemControlProps;
}> &
  Pick<
    ListItemProps,
    'subtitle' | 'additionalInfo' | 'prompt' | 'disabled' | 'disabledPromptMessage' | 'className'
  >;

function View({
  children,
  additionalInfo,
  prompt,
  disabled,
  disabledPromptMessage,
  isPartiallyInteractive,
  controlType = 'non-interactive',
  controlProps,
  className = '',
}: ViewProps) {
  const { ids, describedByIds } = useContext<ListItemContextData>(ListItemContext);
  const isLinkControl = ['navigation'].includes(controlType);

  const isHrefProvided = isLinkControl && !!(controlProps as ListItemNavigationProps)?.href;

  const renderExtras = () => {
    const resolvedPrompt =
      disabled && disabledPromptMessage && !prompt ? (
        <Prompt sentiment={Sentiment.NEUTRAL}>{disabledPromptMessage}</Prompt>
      ) : (
        prompt
      );

    return (
      <>
        {additionalInfo}
        {resolvedPrompt}
      </>
    );
  };

  if (isLinkControl && isHrefProvided) {
    return (
      // for link instances of .Navigation, .IconButton, .Button
      <div className={clsx('wds-list-item-gridWrapper', className)}>
        <PrimitiveAnchor
          aria-describedby={describedByIds}
          href={(controlProps as ListItemNavigationProps)?.href}
          target={(controlProps as ListItemNavigationProps)?.target}
          className={clsx('wds-list-item-view', {
            'wds-list-item-control': controlType === 'navigation',
            fullyInteractive: !isPartiallyInteractive,
          })}
          disabled={disabled}
          onClick={(controlProps as PrimitiveAnchorProps | undefined)?.onClick}
        >
          {children}
        </PrimitiveAnchor>

        {renderExtras()}
      </div>
    );
  }

  if (isPartiallyInteractive || controlType === 'non-interactive') {
    return (
      <div className={clsx('wds-list-item-gridWrapper', className)}>
        <div className="wds-list-item-view">{children}</div>

        {renderExtras()}
      </div>
    );
  }

  // for form control instances of .Radio, .Checkbox, .Switch, .Button, .Navigation etc
  // Radio cannot be wrapped in a <fieldset> element to announce it as a group.
  const InputWrapper = controlType === 'radio' ? 'div' : 'fieldset';
  return (
    <InputWrapper className={clsx('wds-list-item-gridWrapper', className)}>
      <label
        htmlFor={ids.control}
        className={clsx('wds-list-item-view', {
          clickable: !disabled,
          fullyInteractive: !isPartiallyInteractive,
        })}
      >
        {children}
      </label>

      {renderExtras()}
    </InputWrapper>
  );
}

ListItem.Image = Image;
ListItem.AvatarView = AvatarView;
ListItem.AvatarLayout = AvatarLayout;
ListItem.AdditionalInfo = AdditionalInfo;
ListItem.Checkbox = Checkbox;
ListItem.Radio = Radio;
ListItem.IconButton = IconButton;
ListItem.Navigation = Navigation;
ListItem.Button = Button;
ListItem.Switch = Switch;
ListItem.Prompt = Prompt;

export default ListItem;
