import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Day, EndAction, EntryId, Maybe, OntimeGroup, Playback, TimeStrategy, TimerType } from 'ontime-types';
import { isPlaybackActive } from 'ontime-utils';
import { MouseEvent, useEffect, useRef } from 'react';
import {
  IoAdd,
  IoDuplicateOutline,
  IoFolder,
  IoLink,
  IoReorderTwo,
  IoSwapVertical,
  IoTrash,
  IoUnlink,
} from 'react-icons/io5';
import { TbClockPin, TbFlagFilled, TbListNumbers } from 'react-icons/tb';

import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
import { useEntry } from '../../../common/hooks-query/useRundown';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useEntryCopy } from '../../../common/stores/entryCopyStore';
import { deviceAlt, deviceMod } from '../../../common/utils/deviceUtils';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import { useRenumberCuesDialogStore } from '../renumber-cues-dialog/RenumberCuesDialog';
import { useEventIdSwapping } from '../useEventIdSwapping';
import { getSelectionMode, useEventSelection } from '../useEventSelection';
import RundownEventInner from './RundownEventInner';
import RundownIndicators from './RundownIndicators';

import style from './RundownEvent.module.scss';

interface RundownEventProps {
  eventId: EntryId;
  cue: string;
  timeStart: number;
  timeEnd: number;
  duration: number;
  timeStrategy: TimeStrategy;
  linkStart: boolean;
  flag: boolean;
  countToEnd: boolean;
  eventIndex: number;
  endAction: EndAction;
  timerType: TimerType;
  title: string;
  note: string;
  delay: number;
  automationsEnabled?: boolean;
  colour: string;
  isPast: boolean;
  isNext: boolean;
  skip: boolean;
  parent: EntryId | null;
  loaded: boolean;
  hasCursor: boolean;
  playback?: Playback;
  isRolling: boolean;
  gap: number;
  isNextDay: boolean;
  dayOffset: Day;
  totalGap: number;
  isLinkedToLoaded: boolean;
  hasTriggers: boolean;
}

export default function RundownEvent({
  eventId,
  cue,
  timeStart,
  timeEnd,
  duration,
  timeStrategy,
  linkStart,
  flag,
  countToEnd,
  eventIndex,
  endAction,
  timerType,
  title,
  note,
  delay,
  automationsEnabled,
  colour,
  isPast,
  isNext,
  skip = false,
  parent,
  loaded,
  hasCursor,
  playback,
  isRolling,
  gap,
  isNextDay,
  dayOffset,
  totalGap,
  isLinkedToLoaded,
  hasTriggers,
}: RundownEventProps) {
  'use memo';

  const selectedEventId = useEventIdSwapping((state) => state.selectedEventId);
  const setSelectedEventId = useEventIdSwapping((state) => state.setSelectedEventId);
  const clearSelectedEventId = useEventIdSwapping((state) => state.clearSelectedEventId);
  const openRenumberDialog = useRenumberCuesDialogStore((state) => state.onOpen);

  const parentGroup = useEntry(parent) as Maybe<OntimeGroup>;

  const { updateEntry, batchUpdateEvents, clone, deleteEntry, groupEntries, swapEvents, matchGroupDuration } =
    useEntryActionsContext();

  const isSelected = useEventSelection((state) => state.selectedEvents.has(eventId));
  const unselect = useEventSelection((state) => state.unselect);
  const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
  const selectEntry = useEventSelection((state) => state.setSelectedEvents);

  const selectedEvents = useEventSelection((state) => state.selectedEvents);
  const entryCopyId = useEntryCopy((state) => state.entryCopyId);

  const handleRef = useRef<null | HTMLSpanElement>(null);

  const [enableMatchDuration, groupTargetDurationDescription] = (() => {
    if (!parentGroup || parentGroup.targetDuration === null || parentGroup.duration === parentGroup.targetDuration)
      return [false, ''];
    const { targetDuration, duration } = parentGroup;
    return targetDuration > duration
      ? [true, 'Increase event duration to fit the group target']
      : [true, 'Decrease event duration to fit the group target'];
  })();

  const [onContextMenu] = useContextMenu<HTMLDivElement>(() =>
    selectedEvents.size > 1
      ? [
          {
            type: 'item',
            label: 'Link to previous',
            icon: IoLink,
            onClick: () => {
              batchUpdateEvents({ linkStart: true }, Array.from(selectedEvents));
            },
          },
          {
            type: 'item',
            label: 'Unlink from previous',
            icon: IoUnlink,
            onClick: () => {
              batchUpdateEvents({ linkStart: false }, Array.from(selectedEvents));
            },
          },
          { type: 'divider' },
          {
            type: 'item',
            label: 'Group',
            icon: IoFolder,
            onClick: () => {
              groupEntries(Array.from(selectedEvents));
              clearSelectedEvents();
            },
            disabled: parent !== null,
          },
          { type: 'divider' },
          {
            type: 'item',
            label: 'Renumber cues',
            icon: TbListNumbers,
            onClick: openRenumberDialog,
          },
          { type: 'divider' },
          {
            type: 'item',
            label: 'Delete',
            icon: IoTrash,
            shortcut: `${deviceAlt}+Backspace`,
            onClick: () => {
              clearSelectedEvents();
              deleteEntry(Array.from(selectedEvents));
            },
          },
        ]
      : [
          {
            type: 'item',
            label: flag ? 'Remove flag' : 'Add flag',
            icon: TbFlagFilled,
            onClick: () => {
              updateEntry({ id: eventId, flag: !flag });
            },
          },
          {
            type: 'item',
            label: 'Match Group Target Duration',
            description: groupTargetDurationDescription,
            icon: TbClockPin,
            onClick: () => {
              if (!parent) return;
              matchGroupDuration(eventId);
            },
            disabled: !enableMatchDuration,
          },
          { type: 'divider' },
          {
            type: 'item',
            label: 'Add to swap',
            icon: IoAdd,
            onClick: () => setSelectedEventId(eventId),
          },
          {
            type: 'item',
            label: `Swap this event with ${selectedEventId ?? ''}`,
            icon: IoSwapVertical,
            onClick: () => {
              if (!selectedEventId) return;
              swapEvents(selectedEventId, eventId);
              clearSelectedEventId();
            },
            disabled: selectedEventId == null || selectedEventId === eventId,
          },
          {
            type: 'item',
            label: 'Clone',
            icon: IoDuplicateOutline,
            shortcut: `${deviceMod}+D`,
            onClick: () => clone(eventId, { after: eventId }),
          },
          { type: 'divider' },
          {
            type: 'item',
            label: 'Delete',
            icon: IoTrash,
            shortcut: `${deviceAlt}+Backspace`,
            onClick: () => {
              deleteEntry([eventId]);
              unselect(eventId);
            },
          },
        ],
  );

  const {
    isDragging,
    attributes: dragAttributes,
    listeners: dragListeners,
    setNodeRef,
    transform,
    transition,
  } = useSortable({
    id: eventId,
    data: {
      type: 'event',
      parent,
    },
    animateLayoutChanges: () => false,
  });

  const dragStyle = {
    zIndex: isDragging ? 2 : 'inherit',
    transform: CSS.Translate.toString(transform),
    transition,
  };

  const binderColours = colour && getAccessibleColour(colour);

  // move focus to element if necessary
  useEffect(() => {
    if (!hasCursor || handleRef?.current == null) {
      return;
    }

    const elementInFocus = document.activeElement;
    // we know the group is the grandparent of our binder
    const blockElement = handleRef.current.closest('#event-group');

    // we only move focus if the block doesnt already contain focus
    if (blockElement && !blockElement.contains(elementInFocus)) {
      handleRef.current.focus();
    }
  }, [hasCursor]);

  const blockClasses = cx([
    style.rundownEvent,
    skip && style.skip,
    isPast && style.past,
    loaded && style.loaded,
    playback && style[playback],
    isSelected && style.selected,
    hasCursor && style.hasCursor,
    entryCopyId === eventId && style.copyTarget,
  ]);

  const handleFocusClick = (event: MouseEvent) => {
    event.stopPropagation();

    // event.button === 2 is a right-click
    // disable selection if the user selected events and right clicks
    // so the context menu shows up
    if (selectedEvents.size > 1 && event.button === 2) {
      return;
    }

    // UI indexes are 1 based
    const index = eventIndex - 1;
    const editMode = getSelectionMode(event);
    selectEntry({ id: eventId, index, selectMode: editMode });
  };

  const isPlaying = playback ? isPlaybackActive(playback) : false;

  return (
    <div
      className={blockClasses}
      ref={setNodeRef}
      style={dragStyle}
      onClick={handleFocusClick}
      onContextMenu={onContextMenu}
      data-testid='rundown-event'
      data-selected={isSelected}
      {...(isPlaying ? { 'data-running': true } : {})}
    >
      <RundownIndicators timeStart={timeStart} delay={delay} gap={gap} isNextDay={isNextDay} />

      <div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
        <span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
          <IoReorderTwo />
        </span>
        <span className={style.cue}>{cue}</span>
      </div>

      <RundownEventInner
        timeStart={timeStart}
        timeEnd={timeEnd}
        duration={duration}
        linkStart={linkStart}
        countToEnd={countToEnd}
        timeStrategy={timeStrategy}
        eventId={eventId}
        eventIndex={eventIndex}
        endAction={endAction}
        timerType={timerType}
        title={title}
        note={note}
        delay={delay}
        automationsEnabled={automationsEnabled}
        isNext={isNext}
        skip={skip}
        loaded={loaded}
        playback={playback}
        isRolling={isRolling}
        dayOffset={dayOffset}
        isPast={isPast}
        totalGap={totalGap}
        isLinkedToLoaded={isLinkedToLoaded}
        hasTriggers={hasTriggers}
      />
    </div>
  );
}
