import styled from '@emotion/styled';
import { useAnimationFrame } from 'framer-motion';
import { observer } from 'mobx-react-lite';
import {
  ForwardedRef,
  forwardRef,
  useCallback,
  useEffect,
  useRef,
} from 'react';

import { useContextSelector } from '@/hooks/useContextSelector';
import logWebUserEvent from '@/logging/logWebUserEvent';

import StudioContext from './StudioContext';
import { setBeatRangeSelection } from './actions/updateSelection';
import { inFlightDragKeys } from './hooks/useInFlightDrags';
import {
  getAlignedLyricsByTrackId,
  getAllUsedClipIds,
  getSelectionEndBeats,
  getSelectionEndSeconds,
  getSelectionStartBeats,
  getSelectionStartSeconds,
} from './selectors';
import { BeatAlignedLyric } from './types';
import useAlignedLyricsByClipId from './useAlignedLyricsByClipId';
import { getTimingEnd, getTimingStart } from './useStudioLyricsEditController';

const Wrapper = styled.div`
  position: relative;
  border-radius: inherit;
  transition: all 0.2s ease-in-out;
`;

const LyricsDisplay = styled.div<{
  fixAlignment?: boolean;
  replacingLyrics?: boolean;
}>`
  position: relative;
  height: 100%;
  width: 100%;
  white-space: pre-wrap;
  overflow: auto;
  font-size: 16px;
  line-height: 1.25;
  padding: 0;
  color: var(--color-foreground-inactive);

  border: 1px solid transparent;
  border-radius: 8px;

  .selected {
    color: var(--color-foreground-primary);
    background-color: var(--color-background-glass-dense);
  }

  > span {
    position: relative;
    transition: color 0.15s ease-in-out;
    &.playing {
      color: var(--color-accent-brand);
      &::selection {
        background-color: var(--color-background-tertiary);
        color: inherit;
      }
    }
    &:not(.playing) {
      &::selection {
        background-color: var(--color-background-tertiary);
        color: var(--color-foreground-primary);
      }
    }
    > span {
      position: relative;
      pointer-events: none;
      z-index: 2;
    }
  }

  &:focus {
    outline: none;
  }

  cursor: text;

  .first-selected,
  .last-selected {
    &:hover,
    &:active {
      cursor: ${({ fixAlignment }) => (fixAlignment ? 'ew-resize' : '')};
    }
    &:after {
      content: '';
      display: ${({ fixAlignment }) => (fixAlignment ? 'block' : 'none')};
      position: absolute;
      top: -4px;
      width: 11px;
      height: 11px;
      background-color: var(--color-accent-brand);
      border-radius: 100px;
      z-index: 3;
    }

    &:before {
      display: ${({ fixAlignment }) => (fixAlignment ? 'block' : 'none')};
      content: '';
      position: absolute;
      top: 0;
      bottom: 0;
      width: 2px;
      background-color: var(--color-accent-brand);
      z-index: 3;
    }
  }
  .first-selected:before {
    left: -2px;
  }
  .first-selected:after {
    left: -6.5px;
  }

  .last-selected:before {
    right: -2px;
  }
  .last-selected:after {
    right: -6.5px;
  }

  .grab-handle {
    display: inline-block;
    width: 3px;
    position: relative;
    background-color: var(--color-accent-brand);
    border-radius: 100px;
    &:hover,
    &:active {
      cursor: ew-resize;
      background-color: var(--color-foreground-primary);
    }
    &:before {
      content: ' ';
    }
    &:after {
      content: '';
      display: block;
      position: absolute;
      top: -4px;
      left: -4px;
      right: -4px;
      height: 11px;
      background-color: inherit;
      border-radius: 100px;
    }
    &.hidden {
      opacity: 0;
      width: 0;
      pointer-events: none;
      &:before {
        content: '';
      }
    }
  }
`;

const EMPTY_ARRAY: BeatAlignedLyric[] = [];

export default observer(
  forwardRef(function StudioLyricsDisplay(
    {
      fixAlignment,
      defaultSelectionBehavior,
      lockScroll,
    }: {
      fixAlignment?: boolean;
      defaultSelectionBehavior?: boolean;
      lockScroll?: boolean;
    },
    ref: ForwardedRef<HTMLDivElement>
  ) {
    const allUsedClipIds = useContextSelector(StudioContext, (context) =>
      getAllUsedClipIds(context.state)
    );
    const lyricsCorrectionsByClipId = useContextSelector(
      StudioContext,
      (context) => context.state.lyricsCorrectionsByClipId
    );
    const alignedLyricsByClipId = useAlignedLyricsByClipId(
      allUsedClipIds,
      lyricsCorrectionsByClipId
    );

    const fullSongLyrics = useContextSelector(StudioContext, (context) => {
      const targetTrackId =
        context.state.selection.focusedTrackId || context.state.tracks[0]?.id;
      return targetTrackId
        ? getAlignedLyricsByTrackId({
            state: context.state,
            alignedLyricsByClipId,
          })[targetTrackId] || EMPTY_ARRAY
        : EMPTY_ARRAY;
    });
    const interactingInTimelineRef = useContextSelector(
      StudioContext,
      (context) => context.timelineController.interactingInTimelineRef
    );
    const setState = useContextSelector(
      StudioContext,
      (context) => context.setState
    );
    const inFlightDrags = useContextSelector(
      StudioContext,
      (context) => context.inFlightDrags
    );
    const editSessionId = useContextSelector(
      StudioContext,
      (context) => context.editSessionId
    );
    const editClipId = useContextSelector(
      StudioContext,
      (context) => context.state.editClipId
    );
    const stateRef = useContextSelector(
      StudioContext,
      (context) => context.stateRef
    );
    const getSplitLyrics = useContextSelector(
      StudioContext,
      (context) => context.lyricsEditController.getSplitLyrics
    );
    const replacingLyrics = useContextSelector(
      StudioContext,
      (context) => context.lyricsEditController.replacingLyrics
    );
    const setStartIndexOverride = useContextSelector(
      StudioContext,
      (context) => context.lyricsEditController.setStartIndexOverride
    );
    const commitLyricsSelectionOverride = useContextSelector(
      StudioContext,
      (context) => context.lyricsEditController.commitLyricsSelectionOverride
    );
    const setEndIndexOverride = useContextSelector(
      StudioContext,
      (context) => context.lyricsEditController.setEndIndexOverride
    );
    const playing = useContextSelector(
      StudioContext,
      (context) => context.playbackController.playing
    );
    const getCurrentBeats = useContextSelector(
      StudioContext,
      (context) => context.playbackController.getCurrentBeats
    );

    const mouseDownInLyricsDisplayRef = useRef(false);
    const lyricsDisplayRef = useRef<HTMLDivElement>(null);
    const receiveLyricsDisplayRef = useCallback(
      (el: HTMLDivElement) => {
        lyricsDisplayRef.current = el;

        if (ref) {
          if (typeof ref === 'function') {
            ref(el);
          } else {
            ref.current = el;
          }
        }
      },
      [ref]
    );

    const selectionUpdateInProgressRef = useRef(false);

    // Add selection change handler
    useEffect(() => {
      if (defaultSelectionBehavior) return;
      let mouseUpListener: (() => void) | null = null;
      const handleSelectionChange = () => {
        const selection = window.getSelection();
        if (
          !selection ||
          !lyricsDisplayRef.current ||
          interactingInTimelineRef.current ||
          selectionUpdateInProgressRef.current ||
          fixAlignment
        )
          return;

        // Check if there are any ranges in the selection
        if (selection.rangeCount === 0) return;

        const range = selection.getRangeAt(0);
        if (!range) return;

        if (!mouseUpListener) {
          const newMouseUpListener = () => {
            if (interactingInTimelineRef.current) {
              return;
            }

            setState(
              setBeatRangeSelection(
                (prev) =>
                  prev +
                  inFlightDrags.get(inFlightDragKeys.selectionEdge('anchor')),
                (prev) =>
                  prev +
                  inFlightDrags.get(inFlightDragKeys.selectionEdge('focus'))
              )
            );

            inFlightDrags.finishAll();
            window.removeEventListener('mouseup', newMouseUpListener);
            mouseUpListener = null;

            const selection = window.getSelection();
            if (!selection || selection.rangeCount === 0) return;

            const range = selection.getRangeAt(0);
            if (!range) return;

            const firstSpan =
              range.startContainer.parentElement?.closest('span[data-index]');

            const lastSpan =
              range.endContainer.parentElement?.closest('span[data-index]');

            if (firstSpan) {
              const firstTextNode = firstSpan.firstChild?.firstChild;
              if (firstTextNode) {
                range.setStart(firstTextNode, 0);
              }
            }
            if (lastSpan) {
              const lastTextNode = lastSpan.firstChild?.firstChild;
              if (lastTextNode) {
                range.setEnd(
                  lastTextNode,
                  lastTextNode.textContent?.length || 0
                );
              }
            }

            if (mouseDownInLyricsDisplayRef.current) {
              logWebUserEvent({
                actionName: 'EditV3Selection',
                context: {
                  editSessionId: editSessionId,
                  editingClipId: editClipId || 'MISSING_EDIT_CLIP_ID',
                  startBeats: getSelectionStartBeats(stateRef.current),
                  startSeconds: getSelectionStartSeconds(stateRef.current),
                  endBeats: getSelectionEndBeats(stateRef.current),
                  endSeconds: getSelectionEndSeconds(stateRef.current),
                  trigger: 'lyrics-drag',
                },
              });
            }

            setTimeout(() => inFlightDrags.finishAll()); // because this shift will trigger a selection change.
          };
          mouseUpListener = newMouseUpListener;
          window.addEventListener('mouseup', mouseUpListener);
        }

        // Find the first and last spans that contain the selection
        let firstSpan =
          range.startContainer.parentElement?.closest('span[data-index]');

        let lastSpan =
          range.endContainer.parentElement?.closest('span[data-index]');

        if (firstSpan && lastSpan) {
          const firstIndex = parseInt(
            firstSpan.getAttribute('data-index') || '-1'
          );
          const lastIndex = parseInt(
            lastSpan.getAttribute('data-index') || '-1'
          );
          if (firstIndex > lastIndex) {
            [firstSpan, lastSpan] = [lastSpan, firstSpan];
          }
        }

        if (firstSpan && lastSpan) {
          let startIndex = parseInt(
            firstSpan.getAttribute('data-index') || '-1'
          );
          let endIndex = parseInt(lastSpan.getAttribute('data-index') || '-1');

          if (startIndex >= 0 && endIndex >= 0) {
            while (
              startIndex < fullSongLyrics.length &&
              getTimingStart(fullSongLyrics[startIndex].timing) === null
            ) {
              startIndex++;
            }
            while (
              endIndex > 0 &&
              getTimingStart(fullSongLyrics[endIndex].timing) === null
            ) {
              endIndex--;
            }

            if (startIndex < fullSongLyrics.length && endIndex >= 0) {
              const startLyric = fullSongLyrics[startIndex];
              const endLyric = fullSongLyrics[endIndex];
              const targetStartBeats = getTimingStart(startLyric.timing);
              const targetEndBeats = getTimingEnd(endLyric.timing);

              if (targetStartBeats !== null && targetEndBeats !== null) {
                inFlightDrags.update(
                  inFlightDragKeys.selectionEdge('anchor'),
                  targetStartBeats - stateRef.current.selection.anchorBeats
                );
                inFlightDrags.update(
                  inFlightDragKeys.selectionEdge('focus'),
                  targetEndBeats - stateRef.current.selection.focusBeats
                );
              }
            }
          }
        }
      };

      document.addEventListener('selectionchange', handleSelectionChange);
      return () => {
        document.removeEventListener('selectionchange', handleSelectionChange);
      };
    }, [
      fullSongLyrics,
      fixAlignment,
      defaultSelectionBehavior,
      inFlightDrags,
      setState,
      editSessionId,
      editClipId,
      stateRef,
      interactingInTimelineRef,
      selectionUpdateInProgressRef,
    ]);

    const lastSplitLyricsRef = useRef<
      [BeatAlignedLyric[], BeatAlignedLyric[], BeatAlignedLyric[]] | null
    >(null);
    const lastReplacingLyricsRef = useRef<boolean>(false);
    useAnimationFrame(() => {
      const lyricsDisplay = lyricsDisplayRef.current;
      if (!lyricsDisplay) return;

      const splitLyrics = getSplitLyrics();
      const splitLyricsChanged = lastSplitLyricsRef.current !== splitLyrics;
      lastSplitLyricsRef.current = splitLyrics;

      const replacingLyricsChanged =
        lastReplacingLyricsRef.current !== replacingLyrics;
      lastReplacingLyricsRef.current = replacingLyrics;

      if (interactingInTimelineRef.current && splitLyricsChanged) {
        window.getSelection()?.removeAllRanges();
      }

      if (defaultSelectionBehavior) {
        const spans = lyricsDisplay.querySelectorAll('span[data-index]');
        spans.forEach((span) => {
          span.classList.remove('first-selected', 'last-selected', 'selected');
        });
      } else if (splitLyricsChanged) {
        const preSelectLength = splitLyrics[0].length;
        const selectLength = splitLyrics[1].length;
        const spans = lyricsDisplay.querySelectorAll('span[data-index]');
        const firstSelectedSpan = lyricsDisplay.querySelector(
          'span.first-selected'
        );
        const lastSelectedSpan =
          lyricsDisplay.querySelector('span.last-selected');
        if (firstSelectedSpan) {
          firstSelectedSpan.classList.remove('first-selected');
        }
        if (lastSelectedSpan) {
          lastSelectedSpan.classList.remove('last-selected');
        }

        let inSelection = false;
        for (let i = 0; i < spans.length; i++) {
          if (i >= preSelectLength && i < preSelectLength + selectLength) {
            if (!inSelection) {
              spans[i].classList.add('first-selected');
            }
            inSelection = true;
            spans[i].classList.add('selected');
          } else {
            if (inSelection) {
              spans[i - 1]?.classList.add('last-selected');
            }
            inSelection = false;

            spans[i].classList.remove('selected');
          }
        }
      }

      if (
        (splitLyricsChanged && interactingInTimelineRef.current) ||
        replacingLyricsChanged
      ) {
        const firstSelectedSpan = lyricsDisplay.querySelector('span.selected');
        if (firstSelectedSpan) {
          const spanRect = firstSelectedSpan.getBoundingClientRect();
          const displayRect = lyricsDisplay.getBoundingClientRect();
          if (
            spanRect.top < displayRect.top ||
            spanRect.bottom > displayRect.bottom - 160
          ) {
            if (!lockScroll) {
              firstSelectedSpan.scrollIntoView({
                behavior: 'smooth',
                block: 'start',
              });
            }
          }
        }
      }

      if (playing) {
        const currentBeats = getCurrentBeats();
        const playingLyricIndex = fullSongLyrics.findIndex((lyric) => {
          const timingStart = getTimingStart(lyric.timing) || NaN;
          const timingEnd = getTimingEnd(lyric.timing) || NaN;
          return (
            lyric.timing &&
            timingStart <= currentBeats &&
            timingEnd >= currentBeats
          );
        });

        const lastPlayingSpan = lyricsDisplay.querySelector('span.playing');
        const newPlayingSpan =
          playingLyricIndex >= 0 &&
          lyricsDisplay.querySelector(
            `span[data-index="${playingLyricIndex}"]`
          );
        if (newPlayingSpan !== lastPlayingSpan) {
          if (newPlayingSpan) newPlayingSpan.classList.add('playing');
          if (lastPlayingSpan) lastPlayingSpan.classList.remove('playing');
        }
      }
    });

    const handleMouseDown = useCallback(
      (e: React.MouseEvent<HTMLDivElement>) => {
        if (defaultSelectionBehavior) return;

        mouseDownInLyricsDisplayRef.current = true;
        const handleMouseUp = () => {
          setTimeout(() => (mouseDownInLyricsDisplayRef.current = false), 0);
        };
        window.addEventListener('mouseup', handleMouseUp);
        if (fixAlignment) {
          e.preventDefault();
          e.stopPropagation();
        } else {
          return;
        }

        if (
          (e.target as HTMLElement).matches(
            '.first-selected, .first-selected *'
          )
        ) {
          const handleMouseMove = (e: MouseEvent) => {
            const targetElement = e.target as HTMLSpanElement;
            const index = parseInt(
              targetElement.getAttribute('data-index') || '-1'
            );
            if (index < 0) {
              return;
            }
            const targetElementRect = targetElement.getBoundingClientRect();
            const indexOffset =
              e.clientX > targetElementRect.left + targetElementRect.width / 2
                ? 1
                : 0;
            setStartIndexOverride(index + indexOffset);
          };
          const handleMouseUp = () => {
            commitLyricsSelectionOverride();
            window.removeEventListener('mousemove', handleMouseMove);
            window.removeEventListener('mouseup', handleMouseUp);
          };
          window.addEventListener('mousemove', handleMouseMove);
          window.addEventListener('mouseup', handleMouseUp);
        } else if (
          (e.target as HTMLElement).matches('.last-selected, .last-selected *')
        ) {
          const handleMouseMove = (e: MouseEvent) => {
            const targetElement = e.target as HTMLSpanElement;
            const index = parseInt(
              targetElement.getAttribute('data-index') || '-1'
            );
            if (index < 0) {
              return;
            }
            const targetElementRect = targetElement.getBoundingClientRect();
            const indexOffset =
              e.clientX > targetElementRect.left + targetElementRect.width / 2
                ? 1
                : 0;
            setEndIndexOverride(index + indexOffset);
          };
          const handleMouseUp = () => {
            commitLyricsSelectionOverride();
            window.removeEventListener('mousemove', handleMouseMove);
            window.removeEventListener('mouseup', handleMouseUp);
          };
          window.addEventListener('mousemove', handleMouseMove);
          window.addEventListener('mouseup', handleMouseUp);
        }
      },
      [
        defaultSelectionBehavior,
        fixAlignment,
        replacingLyrics,
        setStartIndexOverride,
        setEndIndexOverride,
        commitLyricsSelectionOverride,
      ]
    );

    return (
      <Wrapper className='lyrics-display'>
        <LyricsDisplay
          replacingLyrics={replacingLyrics}
          className='lyrics-display-inner'
          fixAlignment={fixAlignment}
          dir='auto'
          ref={receiveLyricsDisplayRef}
          onMouseDown={handleMouseDown}
        >
          {fullSongLyrics.map((lyric, index) => (
            <span data-index={index} key={index}>
              <span>{lyric.text}</span>
            </span>
          ))}
        </LyricsDisplay>
      </Wrapper>
    );
  })
);
