import React, { useState, useCallback } from "react";
import { promptForFileReferences } from "../../fileUtils";
import * as Selectors from "../../selectors";
import * as Actions from "../../actionCreators";
import { clamp } from "../../utils";
import { AppState, Dispatch, TransitionType } from "../../types";
import { connect } from "react-redux";
import { useUnmountedRef } from "../../hooks";

const ENTRY_HEIGHT = 14;
const HEIGHT_PADDING = 15;
const WIDTH_PADDING = 20;

const LOADING_STYLE: React.CSSProperties = {
  position: "absolute",
  top: 0,
  left: 0,
  color: "white",
  background: "rgba(0.33, 0.33, 0.33, 0.33)",
};

const OUTER_WRAPPER_STYLE: React.CSSProperties = {
  position: "absolute",
  top: 0,
  left: 0,
  padding: "15px 10px 0 10px",
};

const INNER_WRAPPER_STYLE: React.CSSProperties = {
  display: "inline-block",
  whiteSpace: "nowrap",
  overflow: "hidden",
  background: "rgba(0, 0, 0, 0.815)",
  fontSize: "12px",
};

interface StateProps {
  presetKeys: string[];
  currentPresetIndex: number | null; // Index
}

interface DispatchProps {
  requestPresetAtIndex(i: number): void;
  togglePresetOverlay(): void;
  appendPresetFileList(fileList: FileList): void;
}

interface OwnProps {
  height: number;
  width: number;
}

type Props = StateProps & DispatchProps & OwnProps;

function presetIndexFromListIndex(listIndex: number) {
  return listIndex - 1;
}

function listIndexFromPresetIndex(listIndex: number) {
  return listIndex + 1;
}

function PresetOverlay({
  currentPresetIndex,
  presetKeys,
  height,
  width,
  requestPresetAtIndex,
  togglePresetOverlay,
  appendPresetFileList,
}: Props) {
  const unmountedRef = useUnmountedRef();
  const [selectedListIndex, setSelectedListIndex] = useState(() => {
    if (currentPresetIndex != null) {
      return listIndexFromPresetIndex(currentPresetIndex);
    }
    return 0;
  });

  // Number of presets, plus one for the "Load Local Directory" option, minus
  // one to convert a length to an index.
  const maxListIndex = presetKeys.length; // - 1 + 1;

  const renderList = useCallback(() => {
    const maxVisibleRows = Math.floor((height - HEIGHT_PADDING) / ENTRY_HEIGHT);
    const rowsToShow = Math.floor(maxVisibleRows * 0.75); // Only fill 3/4 of the screen.
    const [startIndex, endIndex] = getRangeCenteredOnIndex(
      maxListIndex + 1, // Add one to convert an index to a length
      rowsToShow,
      selectedListIndex
    );

    const presetElms = [];
    for (let i = startIndex; i <= endIndex; i++) {
      const presetIndex = presetIndexFromListIndex(i);
      const isSelected = i === selectedListIndex;
      const isCurrent = presetIndex === currentPresetIndex;
      let color: string;
      if (isSelected) {
        color = isCurrent ? "#FFCC22" : "#FF5050";
      } else {
        color = isCurrent ? "#CCFF03" : "#CCCCCC";
      }
      presetElms.push(
        <li key={i} style={{ color, lineHeight: `${ENTRY_HEIGHT}px` }}>
          {i === 0 ? "Load Local Directory" : presetKeys[presetIndex]}
        </li>
      );
    }

    return presetElms;
  }, [currentPresetIndex, height, maxListIndex, presetKeys, selectedListIndex]);

  const loadLocalDir = useCallback(async () => {
    const fileReferences = await promptForFileReferences({ directory: true });
    if (unmountedRef.current) {
      return;
    }
    appendPresetFileList(fileReferences);
  }, [appendPresetFileList, unmountedRef]);

  const handleFocusedKeyboardInput = useCallback(
    (e: React.KeyboardEvent<HTMLDivElement>) => {
      switch (e.keyCode) {
        case 38: // up arrow
          setSelectedListIndex(value => Math.max(value - 1, 0));
          e.stopPropagation();
          break;
        case 40: // down arrow
          setSelectedListIndex(value => Math.min(value + 1, maxListIndex));
          e.stopPropagation();
          break;
        case 13: // enter
          if (selectedListIndex === 0) {
            loadLocalDir();
          } else {
            requestPresetAtIndex(presetIndexFromListIndex(selectedListIndex));
          }
          e.stopPropagation();
          break;
        case 27: // escape
          togglePresetOverlay();
          e.stopPropagation();
          break;
      }
    },
    [
      loadLocalDir,
      maxListIndex,
      requestPresetAtIndex,
      selectedListIndex,
      togglePresetOverlay,
    ]
  );

  const handleNode = useCallback((node: HTMLDivElement | null) => {
    if (node != null && document.activeElement !== node) {
      node.focus();
    }
  }, []);

  if (presetKeys == null) {
    return (
      <div style={LOADING_STYLE}>
        <span>Loading presets</span>
      </div>
    );
  }
  return (
    <div
      ref={handleNode}
      tabIndex={-1}
      style={OUTER_WRAPPER_STYLE}
      onKeyDown={handleFocusedKeyboardInput}
    >
      <div
        style={{
          ...INNER_WRAPPER_STYLE,
          width: width - 20 - WIDTH_PADDING,
          maxHeight: height - HEIGHT_PADDING,
        }}
      >
        <ul style={{ listStyleType: "none", padding: 0, margin: 0 }}>
          {renderList()}
        </ul>
      </div>
    </div>
  );
}

// Find a tuple `[startIndex, endIndex]` representing start/end indexes into an
// array of length `length`, that descripe a range of size up to `rangeSize`
// where a best effort is made to center `indexToCenter`.
export function getRangeCenteredOnIndex(
  length: number,
  maxRangeSize: number,
  indexToCenter: number
): [number, number] {
  const rangeSize = Math.min(length, maxRangeSize);
  const halfRangeSize = Math.floor(rangeSize / 2);
  const idealStartIndex = indexToCenter - halfRangeSize;
  const startIndex = clamp(idealStartIndex, 0, length - rangeSize);
  const endIndex = startIndex + rangeSize - 1;
  return [startIndex, endIndex];
}

function mapStateToProps(state: AppState): StateProps {
  return {
    presetKeys: Selectors.getPresetNames(state),
    currentPresetIndex: Selectors.getCurrentPresetIndex(state),
  };
}

function mapDispatchToProps(dispatch: Dispatch): DispatchProps {
  return {
    requestPresetAtIndex: (i: number) => {
      dispatch(Actions.requestPresetAtIndex(i, TransitionType.DEFAULT, true));
    },
    togglePresetOverlay: () => dispatch(Actions.togglePresetOverlay()),
    appendPresetFileList: (fileList: FileList) =>
      dispatch(Actions.appendPresetFileList(fileList)),
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(PresetOverlay);
