<!--
  @component
  Renders a context menu for a chat message bubble.
-->
<script lang="ts">
  import {afterUpdate, createEventDispatcher} from 'svelte';

  import {globals} from '~/app/globals';
  import {contextmenu} from '~/app/ui/actions/contextmenu';
  import Emoji from '~/app/ui/components/atoms/emoji/Emoji.svelte';
  import ContextMenuProvider from '~/app/ui/components/hocs/context-menu-provider/ContextMenuProvider.svelte';
  import type {ContextMenuItem} from '~/app/ui/components/hocs/context-menu-provider/types';
  import {
    extractHrefFromEventTarget,
    extractSelectedTextFromEventTarget,
    getContextMenuItems,
  } from '~/app/ui/components/partials/conversation/internal/message-list/internal/message-context-menu-provider/helpers';
  import type {MessageContextMenuProviderProps} from '~/app/ui/components/partials/conversation/internal/message-list/internal/message-context-menu-provider/props';
  import type Popover from '~/app/ui/generic/popover/Popover.svelte';
  import type {AnchorPoint} from '~/app/ui/generic/popover/types';
  import {i18n} from '~/app/ui/i18n';
  import {toast} from '~/app/ui/snackbar';
  import MdIcon from '~/app/ui/svelte-components/blocks/Icon/MdIcon.svelte';
  import type {SvelteNullableBinding} from '~/app/ui/utils/svelte';
  import type {SingleUnicodeEmoji} from '~/common/utils/emoji';
  import type {IQueryableStore} from '~/common/utils/store';

  const log = globals.unwrap().uiLogging.logger('ui.component.message.context-menu');

  type $$Props = MessageContextMenuProviderProps;

  export let services: $$Props['services'];
  export let boundary: $$Props['boundary'] = undefined;
  export let caretAnchorName: $$Props['caretAnchorName'];
  export let emojiReactions: $$Props['emojiReactions'];
  export let enabledOptions: $$Props['enabledOptions'];
  export let options: NonNullable<$$Props['options']> = {};
  export let placement: $$Props['placement'];

  const skinTonePreferencesStore: IQueryableStore<
    ReadonlyMap<SingleUnicodeEmoji, SingleUnicodeEmoji>
  > = services.emojis.getEmojiSkinTonePreferences();

  const anchorPoints: AnchorPoint =
    placement === 'right'
      ? {
          reference: {
            horizontal: 'left',
            vertical: 'bottom',
          },
          popover: {
            horizontal: 'left',
            vertical: 'top',
          },
        }
      : {
          reference: {
            horizontal: 'right',
            vertical: 'bottom',
          },
          popover: {
            horizontal: 'right',
            vertical: 'top',
          },
        };

  // Important: Make sure the emojis used here are the fully-qualified variants.
  const defaultEmojiReactions = ['👍', '👎', '❤️', '😂', '😮'] as SingleUnicodeEmoji[];

  let popover: SvelteNullableBinding<Popover> = null;

  let selectedLink: string | undefined = undefined;
  let selectedText: string | undefined = undefined;

  const dispatch = createEventDispatcher<{
    clickcopyimageoption: undefined;
    clickcopymessageoption: undefined;
    clickdeleteoption: undefined;
    clickeditoption: undefined;
    clickfavoriteemoji: {
      readonly emoji: SingleUnicodeEmoji;
      readonly rawEvent: MouseEvent;
    };
    clickforwardoption: undefined;
    clickopendetailsoption: undefined;
    clickopenemojipicker: MouseEvent;
    clickquoteoption: undefined;
    clicksaveasfileoption: undefined;
  }>();

  function handleBeforeOpen(event?: MouseEvent): void {
    selectedLink = undefined;
    selectedText = undefined;

    if (event === undefined) {
      return;
    }

    selectedLink = extractHrefFromEventTarget(event);
    selectedText = extractSelectedTextFromEventTarget(event);
  }

  function handleClickCopyLink(): void {
    popover?.close();

    if (selectedLink !== undefined) {
      navigator.clipboard
        .writeText(selectedLink)
        .then(() =>
          toast.addSimpleSuccess(
            i18n.get().t('messaging.success--copy-message-link', 'Link copied to clipboard'),
          ),
        )
        .catch((error: unknown) => {
          log.error('Could not copy link to clipboard', error);
          toast.addSimpleFailure(
            i18n.get().t('messaging.error--copy-message-link', 'Could not copy link to clipboard'),
          );
        });
    } else {
      log.warn('Attempting to copy undefined link');
    }
  }

  function handleClickCopySelection(): void {
    popover?.close();

    if (selectedText !== undefined) {
      navigator.clipboard
        .writeText(selectedText)
        .then(() =>
          toast.addSimpleSuccess(
            i18n
              .get()
              .t('messaging.success--copy-message-selection', 'Selected text copied to clipboard'),
          ),
        )
        .catch((error: unknown) => {
          log.error('Could not copy selected text to clipboard', error);
          toast.addSimpleFailure(
            i18n
              .get()
              .t(
                'messaging.error--copy-message-selection',
                'Could not copy selection to clipboard',
              ),
          );
        });
    } else {
      log.warn('Attempting to copy undefined text selection');
    }
  }

  function handleClickCopyImage(): void {
    popover?.close();
    dispatch('clickcopyimageoption');
  }

  function handleClickCopy(): void {
    popover?.close();
    dispatch('clickcopymessageoption');
  }

  function handleClickSaveAsFile(): void {
    popover?.close();
    dispatch('clicksaveasfileoption');
  }

  function handleClickQuote(): void {
    popover?.close();
    dispatch('clickquoteoption');
  }

  function handleClickForward(): void {
    popover?.close();
    dispatch('clickforwardoption');
  }

  function handleClickOpenDetails(): void {
    popover?.close();
    dispatch('clickopendetailsoption');
  }

  function handleClickDelete(): void {
    popover?.close();
    dispatch('clickdeleteoption');
  }

  function handleClickEdit(): void {
    popover?.close();
    dispatch('clickeditoption');
  }

  function handleClickFavoriteEmoji(event: MouseEvent, emoji: SingleUnicodeEmoji): void {
    popover?.close();
    dispatch('clickfavoriteemoji', {rawEvent: event, emoji});
  }

  function handleClickOpenEmojiPicker(event: MouseEvent): void {
    popover?.close();
    dispatch('clickopenemojipicker', event);
  }

  function handleContextMenuEvent(event: MouseEvent): void {
    if (event.type === 'contextmenu') {
      popover?.open(event);
    }
  }

  let menuItems: readonly ContextMenuItem[];
  $: menuItems = getContextMenuItems({
    copyLink:
      enabledOptions.copyLink && selectedLink !== undefined ? handleClickCopyLink : undefined,
    copySelection:
      enabledOptions.copySelection && selectedText !== undefined
        ? handleClickCopySelection
        : undefined,
    copyImage: enabledOptions.copyImage ? handleClickCopyImage : undefined,
    copy: enabledOptions.copy ? handleClickCopy : undefined,
    edit:
      enabledOptions.edit === false
        ? undefined
        : {handler: handleClickEdit, disabled: enabledOptions.edit.disabled ? 'pseudo' : false},
    saveAsFile: enabledOptions.saveAsFile ? handleClickSaveAsFile : undefined,
    quote: enabledOptions.quote ? handleClickQuote : undefined,
    forward: enabledOptions.forward ? handleClickForward : undefined,
    openDetails: enabledOptions.openDetails ? handleClickOpenDetails : undefined,
    deleteMessage: enabledOptions.deleteMessage ? handleClickDelete : undefined,
    t: $i18n.t,
  });

  $: defaultEmojiReactionsWithPreferredSkinTone = defaultEmojiReactions.map(
    (emoji) => $skinTonePreferencesStore.get(emoji) ?? emoji,
  );

  afterUpdate(() => {
    popover?.forceReposition();
  });
</script>

<div class={`container ${placement}`}>
  <div class="message" use:contextmenu={handleContextMenuEvent}>
    <slot name="message" />
  </div>

  <ContextMenuProvider
    bind:popover
    {anchorPoints}
    beforeOpen={handleBeforeOpen}
    closeOnClickOutside={true}
    container={boundary}
    items={menuItems}
    offset={{left: 0, top: 4}}
    safetyGap={{
      left: 12,
      right: 12,
      // Account for the `TopBar`.
      top: 64 + 12,
      bottom: 12,
    }}
    triggerBehavior="toggle"
    on:hasclosed
    on:hasopened
    on:willclose
    on:willopen
  >
    <button
      class="caret"
      class:visible={options.alwaysShowCaret === true}
      style:anchor-name={caretAnchorName}
    >
      <MdIcon theme="Outlined">expand_more</MdIcon>
    </button>

    <div slot="before" class="reactions">
      {#if emojiReactions.enabled}
        {#each defaultEmojiReactionsWithPreferredSkinTone as emoji, idx (emoji)}
          {@const active =
            emojiReactions.enabled &&
            emojiReactions.ownReactions.some((ownReaction) => ownReaction.emoji === emoji)}

          <button
            class="emoji"
            class:active
            aria-disabled={emojiReactions.enabled && !emojiReactions.fullSupport && idx > 1}
            on:click={(event) => handleClickFavoriteEmoji(event, emoji)}
          >
            <Emoji unicode={emoji} />
          </button>
        {/each}
        <button
          class="add"
          aria-disabled={!emojiReactions.fullSupport}
          on:click={handleClickOpenEmojiPicker}
        >
          <MdIcon theme="Outlined">add_reaction</MdIcon>
        </button>
      {/if}
    </div>
  </ContextMenuProvider>
</div>

<style lang="scss">
  @use 'component' as *;

  .container {
    display: flex;
    align-items: start;
    justify-content: start;
    gap: rem(4px);

    &.left {
      flex-direction: row-reverse;
    }

    .caret {
      --c-icon-font-size: #{rem(24px)};

      @include clicktarget-button-circle;

      & {
        visibility: hidden;
        color: var(--cc-conversation-message-options-caret-color);
        width: rem(24px);
        height: rem(24px);
        cursor: pointer;
        user-select: none;
      }
    }

    &:hover .caret,
    .caret.visible {
      visibility: visible;
    }

    .reactions {
      display: flex;
      flex-direction: row;
      align-items: center;
      justify-content: start;
      gap: rem(2px);

      padding: rem(4px) rem(8px) rem(12px);

      .emoji,
      .add {
        @extend %neutral-input;

        display: flex;
        flex-direction: row;
        align-items: center;
        justify-content: center;

        width: rem(32px);
        height: rem(32px);
        font-size: rem(24px);
        line-height: rem(24px);
        cursor: pointer;
        border-radius: 50%;

        transition: background-color 0.125s ease-out;

        &:hover {
          background-color: var(--cc-emoji-reactions-favorite-box-item-background-color--hover);
        }

        &[aria-disabled='true'] {
          opacity: var(--cc-emoji-reactions-favorite-box-item-opacity--disabled);
        }
      }

      .emoji {
        &.active {
          background-color: var(--cc-emoji-reactions-favorite-box-item-background-color--active);

          &[aria-disabled='true'] {
            cursor: default;
          }

          &:hover:not([aria-disabled='true']) {
            background-color: var(
              --cc-emoji-reactions-favorite-box-item-background-color--active--hover
            );
          }
        }
      }

      .add {
        font-size: rem(18px);
        line-height: rem(18px);
        padding-top: rem(2px);
      }
    }
  }
</style>
