import type { Selection } from "../../editor";
import { isArray } from "lodash-es";
import { PID } from "../../persist";
import { getRectsByOffset } from "./rect";
import type { Controller } from "../../controller";

// -1 mean point on left of the rect
// 0 mean point in the rect
// 1 mean point on right of the rect
function comparePointAndRect(rect: DOMRect, x: number, y: number) {
  if (y < rect.top) {
    return -1;
  } else if (y > rect.bottom) {
    return 1;
  }

  if (x < rect.left) {
    return -1;
  } else if (x > rect.right) {
    return 1;
  }

  return 0;
}

function findChar(
  controller: Controller,
  pid: PID,
  viewWordsElement: HTMLDivElement,
  x: number,
  _y: number
) {
  const { top, bottom } = viewWordsElement.getBoundingClientRect();
  let y = _y < top ? top + 1 : _y;
  y = _y > bottom ? bottom - 1 : y;

  const textContent = controller.getTextContent(pid);
  let start = 0;
  let end = textContent.length - 1;
  let middle = 0;

  let result: [number, DOMRect | undefined] = [-1, undefined];

  while (start <= end) {
    middle = Math.floor((end - start) / 2) + start;

    const rects =
      getRectsByOffset(controller, pid, middle, Number(middle) + Number(1), viewWordsElement) || [];

    if (rects.length === 0) {
      // console.log('rects.length === 0', pid, middle, viewWordsElement);
      // todo: throw error;
      continue;
    }
    const rect = rects[0];
    const comparedResult = comparePointAndRect(rect, x, y);

    if (comparedResult === 0) {
      result = [middle, rect];
      break;
    }

    if (start === end) {
      break; // not find;
    }

    if (comparedResult < 0) {
      end = middle - 1;
    } else if (comparedResult > 0) {
      start = middle + 1;
    }
  }

  // 非末行的末尾，没有找到字的情况（末行暂不考虑）
  if (result[0] === -1 && !result[1]) {
    for (let offset = middle - 1; offset <= middle + 1; offset++) {
      const currentLineRects =
        getRectsByOffset(controller, pid, offset, offset + 1, viewWordsElement) || [];
      const nextLineRects =
        getRectsByOffset(controller, pid, offset + 1, offset + 2, viewWordsElement) || [];

      if (currentLineRects[0]?.top < nextLineRects[0]?.top) {
        result = [offset, currentLineRects[0]];
        break;
      }
    }
  }

  return result;
}

function findOffsetAndRect(
  controller: Controller,
  pid: PID,
  viewWordsElement: HTMLDivElement,
  x: number,
  y: number
) {
  const [index, rect] = findChar(controller, pid, viewWordsElement, x, y);
  let plus = 0;
  if (index > -1 && rect) {
    const center = rect.left + rect.width / 2;
    plus = x < center ? 0 : 1;
  }

  return [index + plus, rect] as [number, DOMRect | undefined];
}

export function walkElementToFindOffset(
  controller: Controller,
  pid: PID,
  element: Element,
  x: number,
  y: number
): [number, "before" | "after"] {
  const textContent = controller.getTextContent(pid);
  const { length: textContentLength } = textContent;

  const viewWordsElement = element.querySelector(".view-words")! as HTMLDivElement;
  const elementRect = viewWordsElement.getBoundingClientRect();

  const lastCharRects =
    getRectsByOffset(controller, pid, textContentLength - 1, textContentLength, viewWordsElement) ||
    [];

  // 最后一个词的右侧及右下侧
  if (lastCharRects.length > 0) {
    const lastRect = lastCharRects[0];
    if (x > lastRect.right && y >= lastRect.top) {
      return [textContentLength, "after"];
    }
  }

  if (x < elementRect.left) {
    return [0, "after"];
  }

  const [offset, rect] = findOffsetAndRect(controller, pid, viewWordsElement, x, y);

  if (offset > -1 && rect) {
    if (textContentLength - 1 > offset) {
      const rects =
        getRectsByOffset(controller, pid, offset, Number(offset) + Number(1), viewWordsElement) ||
        [];

      if (rects.length > 0 && rects[0].top > rect.top) {
        return [offset, "before"];
      }
    }
    return [offset, "after"];
  }
  return [0, "after"];
}

export function getCaretIndexByGoUp(
  controller: Controller,
  pid: PID,
  contentElement: HTMLDivElement,
  offset: number
) {
  let startOffset = offset;
  let endOffset = offset + 1;
  const textContent = controller.getTextContent(pid);
  let isLastChar = false;
  if (textContent.length === startOffset) {
    startOffset = offset - 1;
    endOffset = offset;
    isLastChar = true;
  }
  const currentRect = (getRectsByOffset(controller, pid, startOffset, endOffset, contentElement) ||
    [])[0];

  if (!currentRect) {
    return offset;
  }

  const [newOffset] = findOffsetAndRect(
    controller,
    pid,
    contentElement,
    isLastChar ? currentRect.right : currentRect.left,
    currentRect.top - currentRect.height / 2
  );

  return newOffset > -1 ? newOffset : 0;
}

export function getCaretIndexByGoDown(
  controller: Controller,
  pid: PID,
  contentElement: HTMLDivElement,
  offset: number
) {
  const currentRect = (getRectsByOffset(controller, pid, offset, offset + 1, contentElement) ||
    [])[0];

  if (!currentRect) {
    return offset;
  }

  const [newOffset] = findOffsetAndRect(
    controller,
    pid,
    contentElement,
    currentRect.x,
    currentRect.bottom + currentRect.height / 2
  );

  if (newOffset > -1) {
    return newOffset;
  } else {
    const textContent = controller.getTextContent(pid);
    return textContent.length;
  }
}

export function getPosAndEdge(position: number | [number, "after" | "before"]) {
  return (isArray(position) ? position : [position, "after"]) as [number, "before" | "after"];
}

export function getCaretPosition(
  controller: Controller,
  selection: Selection,
  contentElement: HTMLDivElement
) {
  const { pid, offset, edge } = selection.range.start;
  const textContent = controller.getTextContent(pid);
  const { length: textContentLength } = textContent;

  let startOffset = offset;
  let endOffset = offset + 1;

  const isBefore = offset === textContentLength || (edge === "before" && offset > 0);

  if (isBefore) {
    startOffset = offset - 1;
    endOffset = offset;
  }

  const rects = getRectsByOffset(controller, pid, startOffset, endOffset, contentElement) || [];

  const extSize = 4;
  const contentRect = contentElement.getBoundingClientRect();
  const rect = rects[0];

  if (!rect) return;

  return {
    top: rect.top - contentRect.top + extSize,
    left: (isBefore ? rect.right + 0.5 : rect.left - 0.5) - contentRect.left,
    height: rect.height - extSize * 2,
  };
}
