import {
  createRef,
  FC,
  Fragment,
  MouseEventHandler,
  ReactNode,
  RefObject,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { css } from 'twin.macro';
import { useLedCount } from '../contents/led-count';
import { LedStripConfig } from '../models/led-strip-config';
import { PixelRgb } from '../models/pixel-rgb';
import { StyledPixel } from './styled-pixel';
import { logger } from './completed-led-strip';

interface DraggableStripProp {
  config: LedStripConfig;
  pixels: PixelRgb[];
  index: number;
  onConfigChange?: (config: LedStripConfig) => void;
  onConfigFinish?: (config: LedStripConfig) => void;
}
export const DraggableStrip: FC<DraggableStripProp> = ({
  config,
  pixels,
  index,
  onConfigChange,
  onConfigFinish,
}) => {
  const { ledCount } = useLedCount();

  const startXRef = useRef(0);
  const currentXRef = useRef(0);
  const configRef = useRef<LedStripConfig>();
  const [availableConfig, setAvailableConfig] = useState<LedStripConfig>(config);
  // const currentDiffRef = useRef(0);
  const isDragRef = useRef(false);
  const handleMouseMoveRef = useRef<(ev: MouseEvent) => void>();
  const [boxTranslateX, setBoxTranslateX] = useState(0);

  const ledItems = useMemo(() => {
    const step = config.global_start_position - config.global_end_position < 0 ? 1 : -1;
    return pixels.map((rgb, i) => (
      <StyledPixel
        key={i}
        rgb={rgb}
        css={css`
          grid-column: ${(availableConfig.global_start_position ?? 0) + i * step + 1} /
            span 1;
          grid-row-start: ${index + 1};
          pointer-events: none;
        `}
      />
    ));
  }, [pixels, availableConfig]);

  const [placeholders, placeholderRefs]: [ReactNode[], RefObject<HTMLSpanElement>[]] =
    useMemo(
      () =>
        new Array(ledCount)
          .fill(undefined)
          .map((_, i) => {
            const ref = createRef<HTMLSpanElement>();
            const n = (
              <span
                ref={ref}
                key={i}
                tw=" h-full w-full"
                css={css`
                  grid-column-start: ${i + 1};
                  grid-row-start: ${index + 1};
                `}
              />
            );
            return [n, ref] as [ReactNode, RefObject<HTMLSpanElement>];
          })
          .reduce(
            ([nList, refList], [n, ref]) => [
              [...nList, n],
              [...refList, ref],
            ],
            [[], []] as [ReactNode[], RefObject<HTMLSpanElement>[]],
          ),
      [ledCount],
    );

  // start and moving
  const handleMouseDown: MouseEventHandler<HTMLDivElement> = useCallback(
    (ev) => {
      startXRef.current = ev.pageX;
      ev.currentTarget.requestPointerLock();
      isDragRef.current = true;
      logger('handleMouseDown, config: %o', config);

      const placeholderPositions = placeholderRefs.map((it) => {
        if (!it.current) {
          return [0, 0];
        }
        const viewportOffset = it.current.getBoundingClientRect();
        return [viewportOffset.left, viewportOffset.right] as [number, number];
      });

      logger('placeholderPositions: %o', placeholderPositions);

      // set init position
      const initPos = placeholderPositions.findIndex(
        ([l, r]) => l <= ev.pageX && r >= ev.pageX,
      );
      let prevMatch = 0;

      if (handleMouseMoveRef.current) {
        document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
      }
      handleMouseMoveRef.current = (ev) => {
        if (!isDragRef.current) {
          return;
        }
        currentXRef.current = ev.pageX;
        setBoxTranslateX(currentXRef.current - startXRef.current);
        const match = placeholderPositions.findIndex(
          ([l, r]) => l <= currentXRef.current && r >= currentXRef.current,
        );
        if (match === -1) {
          return;
        }

        if (match === prevMatch) {
          return;
        }
        prevMatch = match;

        const diff = match - initPos;
        const newValue: LedStripConfig = {
          ...config,
          global_start_position: config.global_start_position + diff,
          global_end_position: config.global_end_position + diff,
        };
        configRef.current = newValue;
        setAvailableConfig(newValue);
        logger('change config. old: %o, new: %o', config, newValue);
        onConfigChange?.(newValue);
      };
      document.body.addEventListener('mousemove', handleMouseMoveRef.current);
    },
    [placeholderRefs, availableConfig, setAvailableConfig, config],
  );

  // move event.
  useEffect(() => {
    const handleMouseUp = (ev: MouseEvent) => {
      if (configRef.current && isDragRef.current) {
        onConfigFinish?.(configRef.current);
      }
      startXRef.current = 0;
      isDragRef.current = false;
      document.exitPointerLock();
      if (handleMouseMoveRef.current) {
        document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
      }
    };
    document.body.addEventListener('mouseup', handleMouseUp);
    return () => {
      document.body.removeEventListener('mouseup', handleMouseUp);
    };
  }, [onConfigFinish]);
  // reset translateX when config updated.
  useEffect(() => {
    startXRef.current = currentXRef.current;
    setAvailableConfig(config);
    setBoxTranslateX(0);
    logger('useEffect, config: %o', config);
  }, [config]);

  return (
    <Fragment>
      {placeholders}
      {ledItems}
      <div
        tw="border border-gray-700 h-3 w-full rounded-full"
        css={css`
          grid-column: ${Math.min(
              config?.global_start_position ?? 0,
              config?.global_end_position ?? 0,
            ) + 1} / span
            ${Math.abs(config?.global_start_position - config?.global_end_position) + 1};
          grid-row-start: ${index + 1};
          cursor: ew-resize;
          transform: translateX(${boxTranslateX}px);
        `}
        onMouseDown={handleMouseDown}
      ></div>
    </Fragment>
  );
};
