"use client";

export type FileTreeModifierKeys = {
  shiftKey?: boolean;
  metaKey?: boolean;
  ctrlKey?: boolean;
};

export interface FileTreeSelectionState {
  selectedRowIds: Set<string>;
  anchorRowId: string | null;
  focusedRowId: string | null;
}

export type FileTreeSelectionAction =
  | { type: "clear" }
  | {
      type: "click";
      rowId: string;
      visibleRowIds: string[];
      modifiers: FileTreeModifierKeys;
    }
  | { type: "contextmenu"; rowId: string };

function getRangeRowIds(visibleRowIds: string[], fromId: string, toId: string): string[] {
  const fromIndex = visibleRowIds.indexOf(fromId);
  const toIndex = visibleRowIds.indexOf(toId);
  if (fromIndex < 0 || toIndex < 0) return [toId];
  const lo = Math.min(fromIndex, toIndex);
  const hi = Math.max(fromIndex, toIndex);
  return visibleRowIds.slice(lo, hi + 1);
}

export function reduceFileTreeSelection(
  prev: FileTreeSelectionState,
  action: FileTreeSelectionAction,
): FileTreeSelectionState {
  switch (action.type) {
    case "clear":
      return { selectedRowIds: new Set(), anchorRowId: null, focusedRowId: null };
    case "contextmenu": {
      if (prev.selectedRowIds.has(action.rowId)) {
        return { ...prev, focusedRowId: action.rowId };
      }
      return {
        selectedRowIds: new Set([action.rowId]),
        anchorRowId: action.rowId,
        focusedRowId: action.rowId,
      };
    }
    case "click": {
      const { rowId, visibleRowIds, modifiers } = action;
      const withMeta = Boolean(modifiers.metaKey);
      const withCtrl = Boolean(modifiers.ctrlKey);
      const withShift = Boolean(modifiers.shiftKey);
      const withToggle = withMeta || withCtrl;

      if (withShift) {
        const anchor = prev.anchorRowId ?? prev.focusedRowId ?? rowId;
        const range = getRangeRowIds(visibleRowIds, anchor, rowId);
        const next = withToggle ? new Set(prev.selectedRowIds) : new Set<string>();
        range.forEach((id) => next.add(id));
        return {
          selectedRowIds: next,
          anchorRowId: prev.anchorRowId ?? anchor,
          focusedRowId: rowId,
        };
      }

      if (withToggle) {
        const next = new Set(prev.selectedRowIds);
        if (next.has(rowId)) {
          next.delete(rowId);
        } else {
          next.add(rowId);
        }
        return { selectedRowIds: next, anchorRowId: rowId, focusedRowId: rowId };
      }

      return { selectedRowIds: new Set([rowId]), anchorRowId: rowId, focusedRowId: rowId };
    }
  }
}

export function normalizeFileTreeSelectionForVisibleRows(
  prev: FileTreeSelectionState,
  visibleRowIds: string[],
): FileTreeSelectionState {
  const visibleSet = new Set(visibleRowIds);

  let selectedRowIds = prev.selectedRowIds;
  let removedAny = false;
  const kept: string[] = [];
  prev.selectedRowIds.forEach((id) => {
    if (visibleSet.has(id)) {
      kept.push(id);
      return;
    }
    removedAny = true;
  });
  if (removedAny) {
    selectedRowIds = new Set(kept);
  }

  let anchorRowId = prev.anchorRowId;
  if (anchorRowId && !visibleSet.has(anchorRowId)) {
    anchorRowId = null;
  }

  let focusedRowId = prev.focusedRowId;
  if (focusedRowId && !visibleSet.has(focusedRowId)) {
    focusedRowId = null;
  }

  if (!focusedRowId && selectedRowIds.size > 0) {
    focusedRowId = visibleRowIds.find((id) => selectedRowIds.has(id)) ?? null;
  }

  if (!anchorRowId && focusedRowId) {
    anchorRowId = focusedRowId;
  }

  if (
    selectedRowIds === prev.selectedRowIds &&
    anchorRowId === prev.anchorRowId &&
    focusedRowId === prev.focusedRowId
  ) {
    return prev;
  }

  return { selectedRowIds, anchorRowId, focusedRowId };
}
