import React, { useCallback, useEffect, useRef, useState } from 'react';

import { HighlightColor } from '@/types/book';
import { Point, rangeFromAnchorToPoint, TextSelection } from '@/utils/sel';
import { useEnv } from '@/context/EnvContext';
import { useThemeStore } from '@/store/themeStore';
import { useReaderStore } from '@/store/readerStore';
import { useSettingsStore } from '@/store/settingsStore';
import { getHandlePositionsFromRange, getHighlightColorHex } from '../../utils/annotatorUtil';
import MagnifierLoupe from './MagnifierLoupe';
import { Handle } from './AnnotationRangeEditor';

interface SelectionRangeEditorProps {
  bookKey: string;
  isVertical: boolean;
  selection: TextSelection;
  handleColor: HighlightColor;
  onRangeChange: (range: Range, index: number, commit: boolean) => void;
  onStartDrag: () => void;
  noteAutoTurnPoint: (point: Point | null) => void;
  cancelAutoTurn: () => void;
  onAutoTurn: (cb: () => void) => () => void;
}

// Drag handles for a plain (not yet annotated) text selection. Used on
// Android when the native selection handles had to be suppressed because of
// the Blink hyphen selection-bounds bug (issue #1553): the DOM selection
// stays as the visible highlight while these handles replace the native
// ones for adjusting the range.
const SelectionRangeEditor: React.FC<SelectionRangeEditorProps> = ({
  bookKey,
  isVertical,
  selection,
  handleColor,
  onRangeChange,
  onStartDrag,
  noteAutoTurnPoint,
  cancelAutoTurn,
  onAutoTurn,
}) => {
  const { appService } = useEnv();
  const { settings } = useSettingsStore();
  const { isDarkMode } = useThemeStore();
  const { getViewSettings } = useReaderStore();
  const viewSettings = getViewSettings(bookKey);
  const isEink = settings.globalViewSettings.isEink;
  const einkFgColor = isDarkMode ? '#ffffff' : '#000000';
  const handleColorHex = getHighlightColorHex(settings, handleColor) ?? '#FFFF00';

  const draggingRef = useRef<'start' | 'end' | null>(null);
  const startRef = useRef<Point>({ x: 0, y: 0 });
  const endRef = useRef<Point>({ x: 0, y: 0 });
  const lastBuiltRef = useRef<{ range: Range; index: number } | null>(null);
  // Unsubscribe for the after-turn re-emit while a handle is being dragged.
  const autoTurnUnsubRef = useRef<(() => void) | null>(null);
  const [draggingHandle, setDraggingHandle] = useState<'start' | 'end' | null>(null);
  const [currentStart, setCurrentStart] = useState<Point>({ x: 0, y: 0 });
  const [currentEnd, setCurrentEnd] = useState<Point>({ x: 0, y: 0 });
  const [loupePoint, setLoupePoint] = useState<Point | null>(null);

  useEffect(() => {
    if (draggingRef.current) return;
    const positions = getHandlePositionsFromRange(bookKey, selection.range, isVertical);
    if (positions) {
      setCurrentStart(positions.start);
      setCurrentEnd(positions.end);
      startRef.current = positions.start;
      endRef.current = positions.end;
      lastBuiltRef.current = { range: selection.range, index: selection.index };
    }
  }, [bookKey, selection, isVertical]);

  // The non-dragged end is anchored as a DOM position captured at drag start.
  // Anchoring it to its window coordinate instead would silently re-target it
  // whenever the content shifts underneath — e.g. the corner-dwell auto page
  // turn (#1354) mid-drag — losing the previous page's part of the selection.
  const fixedAnchorRef = useRef<{ node: Node; offset: number } | null>(null);

  const updateFromDraggedPoint = useCallback(
    (point: Point) => {
      const anchor = fixedAnchorRef.current;
      if (!anchor) return;
      const doc = anchor.node.ownerDocument;
      const win = doc?.defaultView;
      if (!doc || !win) return;
      const feRect = win.frameElement?.getBoundingClientRect();
      const built = rangeFromAnchorToPoint(
        doc,
        anchor.node,
        anchor.offset,
        point.x - (feRect?.left ?? 0),
        point.y - (feRect?.top ?? 0),
      );
      if (!built) return;
      lastBuiltRef.current = { range: built, index: selection.index };
      onRangeChange(built, selection.index, false);
      // Drag the handle into the corner to turn the page; the anchored end keeps
      // the previous page's part of the selection across the turn.
      noteAutoTurnPoint(viewSettings?.scrolled ? null : point);
    },
    [selection.index, onRangeChange, noteAutoTurnPoint, viewSettings?.scrolled],
  );

  // Rebuild the range from the held handle position after an auto page-turn, so
  // the selection extends onto the new page without waiting for the next move.
  const subscribeAutoTurnReemit = useCallback(() => {
    autoTurnUnsubRef.current?.();
    autoTurnUnsubRef.current = onAutoTurn(() => {
      const point = draggingRef.current === 'start' ? startRef.current : endRef.current;
      updateFromDraggedPoint(point);
    });
  }, [onAutoTurn, updateFromDraggedPoint]);

  const handleStartDragStart = useCallback(() => {
    const base = lastBuiltRef.current?.range ?? selection.range;
    fixedAnchorRef.current = { node: base.endContainer, offset: base.endOffset };
    draggingRef.current = 'start';
    setDraggingHandle('start');
    setLoupePoint({ ...startRef.current });
    subscribeAutoTurnReemit();
    onStartDrag();
  }, [selection, onStartDrag, subscribeAutoTurnReemit]);

  const handleEndDragStart = useCallback(() => {
    const base = lastBuiltRef.current?.range ?? selection.range;
    fixedAnchorRef.current = { node: base.startContainer, offset: base.startOffset };
    draggingRef.current = 'end';
    setDraggingHandle('end');
    setLoupePoint({ ...endRef.current });
    subscribeAutoTurnReemit();
    onStartDrag();
  }, [selection, onStartDrag, subscribeAutoTurnReemit]);

  const handleStartDrag = useCallback(
    (point: Point) => {
      setCurrentStart(point);
      setLoupePoint(point);
      startRef.current = point;
      updateFromDraggedPoint(point);
    },
    [updateFromDraggedPoint],
  );

  const handleEndDrag = useCallback(
    (point: Point) => {
      setCurrentEnd(point);
      setLoupePoint(point);
      endRef.current = point;
      updateFromDraggedPoint(point);
    },
    [updateFromDraggedPoint],
  );

  const handleDragEnd = useCallback(() => {
    draggingRef.current = null;
    setDraggingHandle(null);
    setLoupePoint(null);
    cancelAutoTurn();
    autoTurnUnsubRef.current?.();
    autoTurnUnsubRef.current = null;
    const last = lastBuiltRef.current;
    if (last) {
      onRangeChange(last.range, last.index, true);
    }
  }, [onRangeChange, cancelAutoTurn]);

  if (currentStart.x === 0 && currentStart.y === 0) {
    return null;
  }

  const showLoupe = appService?.isMobile && !viewSettings?.isEink && !viewSettings?.vertical;

  return (
    <div className='pointer-events-none fixed inset-0 z-50'>
      <Handle
        hidden={draggingHandle === 'end'}
        position={currentStart}
        isVertical={isVertical}
        type='start'
        color={isEink ? einkFgColor : handleColorHex}
        onDragStart={handleStartDragStart}
        onDrag={handleStartDrag}
        onDragEnd={handleDragEnd}
      />
      <Handle
        hidden={draggingHandle === 'start'}
        position={currentEnd}
        isVertical={isVertical}
        type='end'
        color={isEink ? einkFgColor : handleColorHex}
        onDragStart={handleEndDragStart}
        onDrag={handleEndDrag}
        onDragEnd={handleDragEnd}
      />
      {showLoupe && loupePoint && (
        <MagnifierLoupe
          bookKey={bookKey}
          dragPoint={loupePoint}
          isVertical={isVertical}
          color={handleColorHex}
        />
      )}
    </div>
  );
};

export default SelectionRangeEditor;
