import { memo, Fragment, useEffect, useMemo, useCallback, useState, ReactNode, SetStateAction, Dispatch, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { SearchInput, PlusIcon, InlineAlert, UndoIcon, Paragraph, TakeActionIcon, IconButton, Button, DeleteIcon, AddIcon, Heading, Text, Dialog } from 'evergreen-ui';
import { FaMouse, FaPlus, FaStepForward, FaStepBackward } from 'react-icons/fa';
import Mousetrap from 'mousetrap';
import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import uniq from 'lodash/uniq';

import useUserSettings from '../hooks/useUserSettings';
import Swal from '../swal';
import SetCutpointButton from './SetCutpointButton';
import SegmentCutpointButton from './SegmentCutpointButton';
import { getModifier } from '../hooks/useTimelineScroll';
import { KeyBinding, KeyboardAction } from '../../../../types';
import { StateSegment } from '../types';


type Category = string;

type ActionsMap = Record<KeyboardAction, { name: string, category?: Category, before?: ReactNode }>;

const renderKeys = (keys: string[]) => keys.map((key, i) => (
  <Fragment key={key}>
    {i > 0 && <FaPlus size={8} style={{ marginLeft: 4, marginRight: 4, color: 'rgba(0,0,0,0.5)' }} />}
    <kbd>{key.toUpperCase()}</kbd>
  </Fragment>
));

// From https://craig.is/killing/mice
// For modifier keys you can use shift, ctrl, alt, or meta.
// You can substitute option for alt and command for meta.
const allModifiers = new Set(['shift', 'ctrl', 'alt', 'meta']);
function fixKeys(keys: string[]) {
  const replaced = keys.map((key) => {
    if (key === 'option') return 'alt';
    if (key === 'command') return 'meta';
    return key;
  });
  const uniqed = uniq(replaced);
  const nonModifierKeys = keys.filter((key) => !allModifiers.has(key));
  if (nonModifierKeys.length === 0) return []; // only modifiers is invalid
  if (nonModifierKeys.length > 1) return []; // can only have one non-modifier
  return orderBy(uniqed, [(key) => key !== 'shift', (key) => key !== 'ctrl', (key) => key !== 'alt', (key) => key !== 'meta', (key) => key]);
}

const CreateBinding = memo(({
  actionsMap, action, setCreatingBinding, onNewKeyBindingConfirmed,
}: {
  actionsMap: ActionsMap, action: KeyboardAction | undefined, setCreatingBinding: Dispatch<SetStateAction<KeyboardAction | undefined>>, onNewKeyBindingConfirmed: (a: KeyboardAction, keys: string[]) => void,
}) => {
  const { t } = useTranslation();

  const [keysDown, setKeysDown] = useState<string[]>([]);

  const validKeysDown = useMemo(() => fixKeys(keysDown), [keysDown]);

  const isShown = action != null;

  useEffect(() => {
    if (isShown) {
      setKeysDown([]);
    }
  }, [isShown]);

  const addKeyDown = useCallback((character: string) => setKeysDown((old) => [...new Set([...old, character])]), []);

  useEffect(() => {
    if (!isShown) return undefined;

    const mousetrap = new Mousetrap();
    function handleKey(character: string, _modifiers: unknown, e: { type: string, preventDefault: () => void }) {
      if (['keydown', 'keypress'].includes(e.type)) {
        addKeyDown(character);
      }
      e.preventDefault();
    }
    const handleKeyOrig = mousetrap.handleKey;
    mousetrap.handleKey = handleKey;

    return () => {
      mousetrap.handleKey = handleKeyOrig;
    };
  }, [addKeyDown, isShown]);

  const isComboInvalid = validKeysDown.length === 0 && keysDown.length > 0;

  return (
    <Dialog
      title={t('Bind new key to action')}
      isShown={action != null}
      confirmLabel={t('Save')}
      cancelLabel={t('Cancel')}
      onCloseComplete={() => setCreatingBinding(undefined)}
      onConfirm={() => action != null && onNewKeyBindingConfirmed(action, keysDown)}
      onCancel={() => setCreatingBinding(undefined)}
    >
      {isShown ? (
        <div style={{ color: 'black' }}>
          <Paragraph marginBottom={10}><TakeActionIcon verticalAlign="middle" marginRight={5} /> {actionsMap[action].name} <span style={{ color: 'rgba(0,0,0,0.5)' }}>({action})</span></Paragraph>

          <Paragraph>{t('Please press your desired key combination. Make sure it doesn\'t conflict with any other binding or system hotkeys.')}</Paragraph>

          <div style={{ margin: '20px 0' }}>{renderKeys(validKeysDown.length > 0 ? validKeysDown : keysDown)}</div>

          {isComboInvalid && <InlineAlert marginBottom={20} intent="danger">{t('Combination is invalid')}</InlineAlert>}

          <div>
            {!keysDown.includes('esc') && <Button iconBefore={PlusIcon} onClick={() => addKeyDown('esc')}>ESC</Button>}
            {keysDown.length > 0 && <Button intent="warning" iconBefore={UndoIcon} onClick={() => setKeysDown([])}>{t('Start over')}</Button>}
          </div>
        </div>
      ) : <div />}
    </Dialog>
  );
});

const rowStyle = { display: 'flex', alignItems: 'center', margin: '.2em 0', borderBottom: '1px solid rgba(0,0,0,0.1)', paddingBottom: '.5em' };

const KeyboardShortcuts = memo(({
  keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg,
}: {
  keyBindings: KeyBinding[], setKeyBindings: Dispatch<SetStateAction<KeyBinding[]>>, resetKeyBindings: () => void, currentCutSeg: StateSegment,
}) => {
  const { t } = useTranslation();

  const { mouseWheelZoomModifierKey } = useUserSettings();

  const { actionsMap, extraLinesPerCategory } = useMemo(() => {
    const playbackCategory = t('Playback');
    const selectivePlaybackCategory = t('Playback/preview segments only');
    const seekingCategory = t('Seeking');
    const segmentsAndCutpointsCategory = t('Segments and cut points');
    const zoomOperationsCategory = t('Timeline/zoom operations');
    const outputCategory = t('Output actions');
    const batchFilesCategory = t('Batch file list');
    const otherCategory = t('Other operations');
    const streamsCategory = t('Tracks');

    // eslint-disable-next-line no-shadow
    const actionsMap: ActionsMap = {
      toggleLastCommands: {
        name: t('Last ffmpeg commands'),
      },
      toggleKeyboardShortcuts: {
        name: t('Keyboard & mouse shortcuts'),
      },

      // playbackCategory
      togglePlayResetSpeed: {
        name: t('Play/pause'),
        category: playbackCategory,
      },
      togglePlayNoResetSpeed: {
        name: t('Play/pause (no reset speed)'),
        category: playbackCategory,
      },
      play: {
        name: t('Play'),
        category: playbackCategory,
      },
      pause: {
        name: t('Pause'),
        category: playbackCategory,
      },
      increasePlaybackRate: {
        name: t('Speed up playback'),
        category: playbackCategory,
      },
      reducePlaybackRate: {
        name: t('Slow down playback'),
        category: playbackCategory,
      },
      increasePlaybackRateMore: {
        name: t('Speed up playback more'),
        category: playbackCategory,
      },
      reducePlaybackRateMore: {
        name: t('Slow down playback more'),
        category: playbackCategory,
      },
      increaseVolume: {
        name: t('Increase audio volume'),
        category: playbackCategory,
      },
      decreaseVolume: {
        name: t('Decrease audio volume'),
        category: playbackCategory,
      },
      reloadFile: {
        name: t('Reload current media'),
        category: playbackCategory,
      },
      html5ify: {
        name: t('Convert to supported format'),
        category: playbackCategory,
      },

      // selectivePlaybackCategory
      togglePlayOnlyCurrentSegment: {
        name: t('Play current segment once'),
        category: selectivePlaybackCategory,
      },
      toggleLoopOnlyCurrentSegment: {
        name: t('Loop current segment'),
        category: selectivePlaybackCategory,
      },
      toggleLoopStartEndOnlyCurrentSegment: {
        name: t('Loop beginning and end of current segment'),
        category: selectivePlaybackCategory,
      },
      toggleLoopSelectedSegments: {
        name: t('Play selected segments in order'),
        category: selectivePlaybackCategory,
      },

      // seekingCategory
      seekPreviousFrame: {
        name: t('Step backward 1 frame'),
        category: seekingCategory,
      },
      seekNextFrame: {
        name: t('Step forward 1 frame'),
        category: seekingCategory,
      },
      seekBackwards: {
        name: t('Seek backward 1 sec'),
        category: seekingCategory,
      },
      seekForwards: {
        name: t('Seek forward 1 sec'),
        category: seekingCategory,
      },
      seekBackwardsKeyframe: {
        name: t('Seek previous keyframe'),
        category: seekingCategory,
      },
      seekForwardsKeyframe: {
        name: t('Seek next keyframe'),
        category: seekingCategory,
      },
      seekBackwardsPercent: {
        name: t('Seek backward 1% of timeline at current zoom'),
        category: seekingCategory,
      },
      seekForwardsPercent: {
        name: t('Seek forward 1% of timeline at current zoom'),
        category: seekingCategory,
      },
      jumpCutStart: {
        name: t('Jump to current segment\'s start time'),
        category: seekingCategory,
        before: <SegmentCutpointButton currentCutSeg={currentCutSeg} side="start" Icon={FaStepBackward} style={{ verticalAlign: 'middle', marginRight: 5 }} />,
      },
      jumpCutEnd: {
        name: t('Jump to current segment\'s end time'),
        category: seekingCategory,
        before: <SegmentCutpointButton currentCutSeg={currentCutSeg} side="end" Icon={FaStepForward} style={{ verticalAlign: 'middle', marginRight: 5 }} />,
      },
      jumpTimelineStart: {
        name: t('Jump to start of video'),
        category: seekingCategory,
      },
      jumpTimelineEnd: {
        name: t('Jump to end of video'),
        category: seekingCategory,
      },
      goToTimecode: {
        name: t('Seek to timecode'),
        category: seekingCategory,
      },

      // segmentsAndCutpointsCategory
      addSegment: {
        name: t('Add cut segment'),
        category: segmentsAndCutpointsCategory,
      },
      removeCurrentSegment: {
        name: t('Remove current segment'),
        category: segmentsAndCutpointsCategory,
      },
      setCutStart: {
        name: t('Start current segment at current time'),
        category: segmentsAndCutpointsCategory,
        before: <SetCutpointButton currentCutSeg={currentCutSeg} side="start" style={{ verticalAlign: 'middle', marginRight: 5 }} />,
      },
      setCutEnd: {
        name: t('End current segment at current time'),
        category: segmentsAndCutpointsCategory,
        before: <SetCutpointButton currentCutSeg={currentCutSeg} side="end" style={{ verticalAlign: 'middle', marginRight: 5 }} />,
      },
      labelCurrentSegment: {
        name: t('Label current segment'),
        category: segmentsAndCutpointsCategory,
      },
      editCurrentSegmentTags: {
        name: t('Edit current segment tags'),
        category: segmentsAndCutpointsCategory,
      },
      splitCurrentSegment: {
        name: t('Split segment at cursor'),
        category: segmentsAndCutpointsCategory,
      },
      duplicateCurrentSegment: {
        name: t('Duplicate current segment'),
        category: segmentsAndCutpointsCategory,
      },
      jumpPrevSegment: {
        name: t('Jump to previous segment'),
        category: segmentsAndCutpointsCategory,
      },
      jumpNextSegment: {
        name: t('Jump to next segment'),
        category: segmentsAndCutpointsCategory,
      },
      jumpFirstSegment: {
        name: t('Jump to first segment'),
        category: segmentsAndCutpointsCategory,
      },
      jumpLastSegment: {
        name: t('Jump to last segment'),
        category: segmentsAndCutpointsCategory,
      },
      reorderSegsByStartTime: {
        name: t('Reorder segments by start time'),
        category: segmentsAndCutpointsCategory,
      },
      invertAllSegments: {
        name: t('Invert all segments on timeline'),
        category: segmentsAndCutpointsCategory,
      },
      fillSegmentsGaps: {
        name: t('Fill gaps between segments'),
        category: segmentsAndCutpointsCategory,
      },
      shiftAllSegmentTimes: {
        name: t('Shift all segments on timeline'),
        category: segmentsAndCutpointsCategory,
      },
      alignSegmentTimesToKeyframes: {
        name: t('Align segment times to keyframes'),
        category: segmentsAndCutpointsCategory,
      },
      createSegmentsFromKeyframes: {
        name: t('Create segments from keyframes'),
        category: segmentsAndCutpointsCategory,
      },
      createFixedDurationSegments: {
        name: t('Create fixed duration segments'),
        category: segmentsAndCutpointsCategory,
      },
      createNumSegments: {
        name: t('Create num segments'),
        category: segmentsAndCutpointsCategory,
      },
      createRandomSegments: {
        name: t('Create random segments'),
        category: segmentsAndCutpointsCategory,
      },
      detectBlackScenes: {
        name: t('Detect black scenes'),
        category: segmentsAndCutpointsCategory,
      },
      detectSilentScenes: {
        name: t('Detect silent scenes'),
        category: segmentsAndCutpointsCategory,
      },
      detectSceneChanges: {
        name: t('Detect scene changes'),
        category: segmentsAndCutpointsCategory,
      },
      shuffleSegments: {
        name: t('Shuffle segments order'),
        category: segmentsAndCutpointsCategory,
      },
      combineOverlappingSegments: {
        name: t('Combine overlapping segments'),
        category: segmentsAndCutpointsCategory,
      },
      combineSelectedSegments: {
        name: t('Combine selected segments'),
      },
      clearSegments: {
        name: t('Clear all segments'),
        category: segmentsAndCutpointsCategory,
      },
      toggleSegmentsList: {
        name: t('Show sidebar'),
        category: segmentsAndCutpointsCategory,
      },
      selectOnlyCurrentSegment: {
        name: t('Select only this segment'),
        category: segmentsAndCutpointsCategory,
      },
      deselectAllSegments: {
        name: t('Deselect all segments'),
        category: segmentsAndCutpointsCategory,
      },
      selectAllSegments: {
        name: t('Select all segments'),
        category: segmentsAndCutpointsCategory,
      },
      toggleCurrentSegmentSelected: {
        name: t('Toggle current segment selected'),
        category: segmentsAndCutpointsCategory,
      },
      invertSelectedSegments: {
        name: t('Invert selected segments'),
        category: segmentsAndCutpointsCategory,
      },
      removeSelectedSegments: {
        name: t('Remove selected segments'),
        category: segmentsAndCutpointsCategory,
      },

      // streamsCategory
      toggleStreamsSelector: {
        name: t('Edit tracks / metadata tags'),
        category: streamsCategory,
      },
      extractAllStreams: {
        name: t('Extract all tracks'),
        category: streamsCategory,
      },
      showStreamsSelector: {
        name: t('Edit tracks / metadata tags'),
        category: streamsCategory,
      },
      showIncludeExternalStreamsDialog: {
        name: t('Include more tracks from other file'),
        category: streamsCategory,
      },

      // zoomOperationsCategory
      timelineZoomIn: {
        name: t('Zoom in timeline'),
        category: zoomOperationsCategory,
      },
      timelineZoomOut: {
        name: t('Zoom out timeline'),
        category: zoomOperationsCategory,
      },
      timelineToggleComfortZoom: {
        name: t('Toggle zoom between 1x and a calculated comfortable zoom level'),
        category: zoomOperationsCategory,
      },

      // outputCategory
      export: {
        name: t('Export segment(s)'),
        category: outputCategory,
      },
      captureSnapshot: {
        name: t('Capture snapshot'),
        category: outputCategory,
      },
      captureSnapshotAsCoverArt: {
        name: t('Set current frame as cover art'),
        category: outputCategory,
      },
      extractCurrentSegmentFramesAsImages: {
        name: t('Extract frames from current segment as image files'),
        category: outputCategory,
      },
      extractSelectedSegmentsFramesAsImages: {
        name: t('Extract frames from selected segments as image files'),
        category: outputCategory,
      },
      cleanupFilesDialog: {
        name: t('Delete source file'),
        category: outputCategory,
      },
      convertFormatBatch: {
        name: t('Batch convert files to supported format'),
        category: outputCategory,
      },
      convertFormatCurrentFile: {
        name: t('Convert current file to supported format'),
        category: outputCategory,
      },
      fixInvalidDuration: {
        name: t('Fix incorrect duration'),
        category: outputCategory,
      },

      // batchFilesCategory
      batchPreviousFile: {
        name: t('Previous file'),
        category: batchFilesCategory,
      },
      batchOpenPreviousFile: {
        name: t('Open previous file'),
        category: batchFilesCategory,
      },
      batchNextFile: {
        name: t('Next file'),
        category: batchFilesCategory,
      },
      batchOpenNextFile: {
        name: t('Open next file'),
        category: batchFilesCategory,
      },
      batchOpenSelectedFile: {
        name: t('Open selected file'),
        category: batchFilesCategory,
      },
      closeBatch: {
        name: t('Close batch'),
        category: batchFilesCategory,
      },
      concatBatch: {
        name: t('Merge/concatenate files'),
        category: batchFilesCategory,
      },

      // otherCategory
      toggleKeyframeCutMode: {
        name: t('Cut mode'),
        category: otherCategory,
      },
      toggleCaptureFormat: {
        name: t('Capture frame format'),
        category: otherCategory,
      },
      toggleStripAudio: {
        name: t('Keep or discard audio tracks'),
        category: otherCategory,
      },
      toggleStripThumbnail: {
        name: t('Keep or discard thumbnail tracks'),
        category: otherCategory,
      },
      increaseRotation: {
        name: t('Change rotation'),
        category: otherCategory,
      },
      setStartTimeOffset: {
        name: t('Set custom start offset/timecode'),
        category: otherCategory,
      },
      undo: {
        name: t('Undo'),
        category: otherCategory,
      },
      redo: {
        name: t('Redo'),
        category: otherCategory,
      },
      copySegmentsToClipboard: {
        name: t('Copy selected segments times to clipboard'),
        category: otherCategory,
      },
      toggleWaveformMode: {
        name: t('Show waveform'),
        category: otherCategory,
      },
      toggleShowThumbnails: {
        name: t('Show thumbnails'),
        category: otherCategory,
      },
      toggleShowKeyframes: {
        name: t('Show keyframes'),
        category: otherCategory,
      },
      toggleFullscreenVideo: {
        name: 'Toggle full screen video',
        category: otherCategory,
      },
      toggleSettings: {
        name: t('Settings'),
        category: otherCategory,
      },
      openSendReportDialog: {
        name: t('Report an error'),
        category: otherCategory,
      },
      openFilesDialog: {
        name: t('Open'),
        category: otherCategory,
      },
      exportYouTube: {
        name: t('Start times as YouTube Chapters'),
        category: otherCategory,
      },
      closeActiveScreen: {
        name: t('Close current screen'),
        category: otherCategory,
      },
      closeCurrentFile: {
        name: t('Close current file'),
        category: otherCategory,
      },
      quit: {
        name: t('Quit LosslessCut'),
        category: otherCategory,
      },
    };

    // eslint-disable-next-line no-shadow
    const extraLinesPerCategory: Record<Category, ReactNode> = {
      [zoomOperationsCategory]: [
        <div key="1" style={{ ...rowStyle, alignItems: 'center' }}>
          <Text>{t('Zoom in/out timeline')}</Text>
          <div style={{ flexGrow: 1 }} />
          <FaMouse style={{ marginRight: 3 }} />
          <Text>{t('Mouse scroll/wheel up/down')}</Text>
        </div>,

        <div key="2" style={{ ...rowStyle, alignItems: 'center' }}>
          <Text>{t('Pan timeline')}</Text>
          <div style={{ flexGrow: 1 }} />
          {getModifier(mouseWheelZoomModifierKey).map((v) => <kbd key={v} style={{ marginRight: '.7em' }}>{v}</kbd>)}
          <FaMouse style={{ marginRight: 3 }} />
          <Text>{t('Mouse scroll/wheel up/down')}</Text>
        </div>,
      ],
    };

    return {
      extraLinesPerCategory,
      actionsMap,
    };
  }, [currentCutSeg, mouseWheelZoomModifierKey, t]);

  useEffect(() => {
    // cleanup invalid bindings, to prevent renamed actions from blocking user to rebind
    const validBindings = keyBindings.filter(({ action }) => actionsMap[action]);

    if (validBindings.length !== keyBindings.length) {
      console.log(`Auto deleting ${keyBindings.length - validBindings.length} invalid key binding(s)`);
      setKeyBindings(validBindings);
    }
  }, [actionsMap, keyBindings, setKeyBindings]);

  const [creatingBinding, setCreatingBinding] = useState<KeyboardAction>();
  const [searchQuery, setSearchQuery] = useState('');

  const searchInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    searchInputRef.current?.focus();
  }, []);

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const actionEntries = useMemo(() => (Object.entries(actionsMap) as any as [keyof typeof actionsMap, typeof actionsMap[keyof typeof actionsMap]][]).filter(([, { name, category }]) => {
    const searchQueryTrimmed = searchQuery.toLowerCase().trim();
    return (
      !searchQuery
      || name.toLowerCase().includes(searchQueryTrimmed)
      || (category != null && category.toLowerCase().includes(searchQueryTrimmed))
    );
  }), [actionsMap, searchQuery]);

  const categoriesWithActions = useMemo(() => Object.entries(groupBy(actionEntries, ([, { category }]) => category)), [actionEntries]);

  const onDeleteBindingClick = useCallback(({ action, keys }) => {
    // eslint-disable-next-line no-alert
    if (!window.confirm(t('Are you sure?'))) return;

    console.log('delete key binding', action, keys);
    setKeyBindings((existingBindings) => existingBindings.filter((existingBinding) => !(existingBinding.keys === keys && existingBinding.action === action)));
  }, [setKeyBindings, t]);


  const onResetClick = useCallback(() => {
    // eslint-disable-next-line no-alert
    if (!window.confirm(t('Are you sure?'))) return;

    resetKeyBindings();
  }, [resetKeyBindings, t]);

  const onAddBindingClick = useCallback((action: KeyboardAction) => {
    setCreatingBinding(action);
  }, []);

  const stringifyKeys = (keys: string[]) => keys.join('+');

  const onNewKeyBindingConfirmed = useCallback((action: KeyboardAction, keys: string[]) => {
    const fixedKeys = fixKeys(keys);
    if (fixedKeys.length === 0) return;
    const keysStr = stringifyKeys(fixedKeys);
    console.log('new key binding', action, keysStr);

    setKeyBindings((existingBindings) => {
      const duplicate = existingBindings.find((existingBinding) => existingBinding.keys === keysStr);
      if (duplicate) {
        Swal.fire({ icon: 'error', title: t('Duplicate keyboard combination'), text: t('Combination is already bound to "{{alreadyBoundKey}}"', { alreadyBoundKey: actionsMap[duplicate.action]?.name }) });
        console.log('trying to add duplicate');
        return existingBindings;
      }

      console.log('saving key binding');
      setCreatingBinding(undefined);
      return [...existingBindings, { action, keys: keysStr }];
    });
  }, [actionsMap, setKeyBindings, t]);

  return (
    <>
      <div style={{ color: 'black', marginBottom: '1em' }}>
        <div>
          <SearchInput ref={searchInputRef} value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search" width="100%" />
        </div>

        {categoriesWithActions.map(([category, actionsInCategory]) => (
          <div key={category}>
            {category !== 'undefined' && <Heading marginTop={30} marginBottom={14}>{category}</Heading>}

            {actionsInCategory.map(([action, actionObj]) => {
              const actionName = (actionObj && actionObj.name) || action;
              const beforeContent = actionObj && actionObj.before;

              const bindingsForThisAction = keyBindings.filter((keyBinding) => keyBinding.action === action);

              return (
                <div key={action} style={rowStyle}>
                  <div>
                    {beforeContent}
                    <Text title={action} marginRight={10}>{actionName}</Text>
                    <div style={{ fontSize: '.8em', opacity: 0.4 }} title={t('API action name: {{action}}', { action })}>{action}</div>
                  </div>

                  <div style={{ flexGrow: 1 }} />

                  <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
                    {bindingsForThisAction.map(({ keys }) => (
                      <div key={keys} style={{ display: 'flex', alignItems: 'center' }}>
                        {renderKeys(keys.split('+'))}

                        <IconButton title={t('Remove key binding')} appearance="minimal" intent="danger" icon={DeleteIcon} onClick={() => onDeleteBindingClick({ action, keys })} />
                      </div>
                    ))}

                    {bindingsForThisAction.length === 0 && <Text color="muted">{t('No binding')}</Text>}
                  </div>

                  <IconButton title={t('Bind new key to action')} appearance="minimal" intent="success" icon={AddIcon} onClick={() => onAddBindingClick(action)} />
                </div>
              );
            })}

            {extraLinesPerCategory[category] && <div style={{ marginTop: '.8em' }}>{extraLinesPerCategory[category]}</div>}
          </div>
        ))}
      </div>

      <Button intent="danger" onClick={onResetClick}>{t('Reset')}</Button>

      <CreateBinding actionsMap={actionsMap} action={creatingBinding} setCreatingBinding={setCreatingBinding} onNewKeyBindingConfirmed={onNewKeyBindingConfirmed} />
    </>
  );
});

const KeyboardShortcutsDialog = memo(({
  isShown, onHide, keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg,
}: {
  isShown: boolean, onHide: () => void, keyBindings: KeyBinding[], setKeyBindings: Dispatch<SetStateAction<KeyBinding[]>>, resetKeyBindings: () => void, currentCutSeg: StateSegment,
}) => {
  const { t } = useTranslation();

  return (
    <Dialog
      title={t('Keyboard & mouse shortcuts')}
      isShown={isShown}
      confirmLabel={t('Done')}
      hasCancel={false}
      onCloseComplete={onHide}
      onConfirm={onHide}
      topOffset="3vh"
    >
      {isShown ? <KeyboardShortcuts keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> : <div />}
    </Dialog>
  );
});

export default KeyboardShortcutsDialog;
