import { ref } from 'vue';

import type { CellRange } from '../types';
import {
  normalizeRange,
  rangesContainCell,
} from '../commands/FillRangeCommand';

export interface SelectionModifiers {
  shift?: boolean;
  ctrl?: boolean;
}

function fullRowRange(row: number, maxCol: number): CellRange {
  return normalizeRange({
    startRow: row,
    endRow: row,
    startCol: 0,
    endCol: maxCol,
  });
}

function fullColRange(col: number, maxRow: number): CellRange {
  return normalizeRange({
    startRow: 0,
    endRow: maxRow,
    startCol: col,
    endCol: col,
  });
}

function rowRange(r0: number, r1: number, maxCol: number): CellRange {
  return normalizeRange({
    startRow: r0,
    endRow: r1,
    startCol: 0,
    endCol: maxCol,
  });
}

function colRange(c0: number, c1: number, maxRow: number): CellRange {
  return normalizeRange({
    startRow: 0,
    endRow: maxRow,
    startCol: c0,
    endCol: c1,
  });
}

function isFullRowRange(range: CellRange, maxCol: number): boolean {
  const r = normalizeRange(range);
  return r.startCol === 0 && r.endCol === maxCol;
}

function isFullColRange(range: CellRange, maxRow: number): boolean {
  const r = normalizeRange(range);
  return r.startRow === 0 && r.endRow === maxRow;
}

function rangeContainsRow(range: CellRange, row: number, maxCol: number): boolean {
  const r = normalizeRange(range);
  return isFullRowRange(r, maxCol) && row >= r.startRow && row <= r.endRow;
}

function rangeContainsCol(range: CellRange, col: number, maxRow: number): boolean {
  const r = normalizeRange(range);
  return isFullColRange(r, maxRow) && col >= r.startCol && col <= r.endCol;
}

export function useSelection() {
  const ranges = ref<CellRange[]>([]);
  const anchor = ref<{ row: number; col: number } | null>(null);
  const activeCell = ref<{ row: number; col: number } | null>(null);
  const isFillDragging = ref(false);

  /** @deprecated use ranges */
  const selection = ranges;

  function selectCell(row: number, col: number) {
    activeCell.value = { row, col };
    anchor.value = { row, col };
    ranges.value = [
      { startRow: row, startCol: col, endRow: row, endCol: col },
    ];
  }

  function extendSelection(row: number, col: number) {
    if (!anchor.value) {
      selectCell(row, col);
      return;
    }
    ranges.value = [
      {
        startRow: anchor.value.row,
        startCol: anchor.value.col,
        endRow: row,
        endCol: col,
      },
    ];
    activeCell.value = { row, col };
  }

  function selectRange(range: CellRange) {
    const normalized = normalizeRange(range);
    ranges.value = [normalized];
    activeCell.value = {
      row: normalized.startRow,
      col: normalized.startCol,
    };
    anchor.value = { ...activeCell.value };
  }

  function getSelectionRanges(): CellRange[] {
    return ranges.value.map((r) => normalizeRange(r));
  }

  function getNormalizedSelection(): CellRange | null {
    const primary = getPrimaryRange();
    return primary;
  }

  function getPrimaryRange(): CellRange | null {
    if (!ranges.value.length) return null;
    if (!activeCell.value) return normalizeRange(ranges.value[0]!);
    const { row, col } = activeCell.value;
    for (const range of ranges.value) {
      const r = normalizeRange(range);
      if (
        row >= r.startRow &&
        row <= r.endRow &&
        col >= r.startCol &&
        col <= r.endCol
      ) {
        return r;
      }
    }
    return normalizeRange(ranges.value[ranges.value.length - 1]!);
  }

  function containsCell(row: number, col: number): boolean {
    return rangesContainCell(ranges.value, row, col);
  }

  function selectAll(maxRow: number, maxCol: number) {
    const range = normalizeRange({
      startRow: 0,
      endRow: maxRow,
      startCol: 0,
      endCol: maxCol,
    });
    ranges.value = [range];
    activeCell.value = { row: 0, col: 0 };
    anchor.value = { row: 0, col: 0 };
  }

  function selectEntireRow(
    row: number,
    maxCol: number,
    opts: SelectionModifiers = {},
  ) {
    const { shift, ctrl } = opts;
    if (ctrl) {
      const next = [...ranges.value];
      const idx = next.findIndex((r) => rangeContainsRow(r, row, maxCol));
      if (idx >= 0) {
        next.splice(idx, 1);
      } else {
        next.push(fullRowRange(row, maxCol));
      }
      ranges.value = next.length ? next : [];
      if (ranges.value.length) {
        activeCell.value = { row, col: 0 };
        if (!shift) anchor.value = { row, col: 0 };
      } else {
        activeCell.value = null;
      }
      return;
    }
    if (shift && anchor.value) {
      const range = rowRange(anchor.value.row, row, maxCol);
      ranges.value = [range];
      activeCell.value = { row, col: 0 };
      return;
    }
    ranges.value = [fullRowRange(row, maxCol)];
    activeCell.value = { row, col: 0 };
    anchor.value = { row, col: 0 };
  }

  function selectEntireColumn(
    col: number,
    maxRow: number,
    opts: SelectionModifiers = {},
  ) {
    const { shift, ctrl } = opts;
    if (ctrl) {
      const next = [...ranges.value];
      const idx = next.findIndex((r) => rangeContainsCol(r, col, maxRow));
      if (idx >= 0) {
        next.splice(idx, 1);
      } else {
        next.push(fullColRange(col, maxRow));
      }
      ranges.value = next.length ? next : [];
      if (ranges.value.length) {
        activeCell.value = { row: 0, col };
        if (!shift) anchor.value = { row: 0, col };
      } else {
        activeCell.value = null;
      }
      return;
    }
    if (shift && anchor.value) {
      const range = colRange(anchor.value.col, col, maxRow);
      ranges.value = [range];
      activeCell.value = { row: 0, col };
      return;
    }
    ranges.value = [fullColRange(col, maxRow)];
    activeCell.value = { row: 0, col };
    anchor.value = { row: 0, col };
  }

  function extendRowSelection(row: number, maxCol: number) {
    if (!anchor.value) {
      selectEntireRow(row, maxCol);
      return;
    }
    ranges.value = [rowRange(anchor.value.row, row, maxCol)];
    activeCell.value = { row, col: 0 };
  }

  function extendColumnSelection(col: number, maxRow: number) {
    if (!anchor.value) {
      selectEntireColumn(col, maxRow);
      return;
    }
    ranges.value = [colRange(anchor.value.col, col, maxRow)];
    activeCell.value = { row: 0, col };
  }

  function clearSelection() {
    ranges.value = [];
    anchor.value = null;
    activeCell.value = null;
  }

  return {
    ranges,
    selection,
    anchor,
    activeCell,
    isFillDragging,
    selectCell,
    extendSelection,
    selectRange,
    selectAll,
    selectEntireRow,
    selectEntireColumn,
    extendRowSelection,
    extendColumnSelection,
    getSelectionRanges,
    getNormalizedSelection,
    getPrimaryRange,
    containsCell,
    clearSelection,
  };
}
