import type { EquipmentCategory, EquipmentItem, TimelineGroup, UserPreferences } from "@rental/shared";

const getPositionMap = (ids: string[]) => new Map(ids.map((id, index) => [id, index]));

const compareByPreference = (
  leftId: string,
  rightId: string,
  positions: Map<string, number>,
  fallback: () => number
) => {
  const leftPosition = positions.get(leftId);
  const rightPosition = positions.get(rightId);

  if (leftPosition !== undefined && rightPosition !== undefined) {
    return leftPosition - rightPosition;
  }

  if (leftPosition !== undefined) {
    return -1;
  }

  if (rightPosition !== undefined) {
    return 1;
  }

  return fallback();
};

export const defaultUserPreferences: UserPreferences = {
  equipmentCategoryOrder: [],
  equipmentItemOrder: [],
  timelineIncludeInactive: false,
  equipmentIncludeInactive: true
};

export const sortCategoriesByPreference = (categories: EquipmentCategory[], preferences: UserPreferences) => {
  const categoryPositions = getPositionMap(preferences.equipmentCategoryOrder);

  return [...categories].sort((left, right) =>
    compareByPreference(left.id, right.id, categoryPositions, () => left.name.localeCompare(right.name, "pl"))
  );
};

export const sortItemsByPreference = (items: EquipmentItem[], preferences: UserPreferences) => {
  const categoryPositions = getPositionMap(preferences.equipmentCategoryOrder);
  const itemPositions = getPositionMap(preferences.equipmentItemOrder);

  return [...items].sort((left, right) => {
    const itemOrder = compareByPreference(left.id, right.id, itemPositions, () => 0);

    if (itemOrder !== 0) {
      return itemOrder;
    }

    const categoryOrder = compareByPreference(left.categoryId, right.categoryId, categoryPositions, () =>
      left.categoryName.localeCompare(right.categoryName, "pl")
    );

    if (categoryOrder !== 0) {
      return categoryOrder;
    }

    return compareByPreference(left.id, right.id, itemPositions, () => left.name.localeCompare(right.name, "pl"));
  });
};

export const sortTimelineGroupsByPreference = (groups: TimelineGroup[], preferences: UserPreferences) => {
  const itemPositions = getPositionMap(preferences.equipmentItemOrder);
  const getEarliestItemPosition = (group: TimelineGroup) => {
    let earliest = Number.POSITIVE_INFINITY;

    for (const item of group.items) {
      const position = itemPositions.get(item.id);

      if (position !== undefined && position < earliest) {
        earliest = position;
      }
    }

    return earliest;
  };

  return [...groups]
    .map((group) => ({
      ...group,
      items: [...group.items].sort((left, right) =>
        compareByPreference(left.id, right.id, itemPositions, () => left.name.localeCompare(right.name, "pl"))
      )
    }))
    .sort((left, right) => {
      const leftPosition = getEarliestItemPosition(left);
      const rightPosition = getEarliestItemPosition(right);

      if (Number.isFinite(leftPosition) && Number.isFinite(rightPosition) && leftPosition !== rightPosition) {
        return leftPosition - rightPosition;
      }

      if (Number.isFinite(leftPosition) && !Number.isFinite(rightPosition)) {
        return -1;
      }

      if (!Number.isFinite(leftPosition) && Number.isFinite(rightPosition)) {
        return 1;
      }

      return left.categoryName.localeCompare(right.categoryName, "pl");
    });
};

const moveInArray = <T,>(items: T[], fromIndex: number, toIndex: number) => {
  if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= items.length || toIndex >= items.length) {
    return items;
  }

  const nextItems = [...items];
  const [moved] = nextItems.splice(fromIndex, 1);

  if (moved === undefined) {
    return items;
  }

  nextItems.splice(toIndex, 0, moved);
  return nextItems;
};

export const moveCategoryInPreferences = (
  categories: EquipmentCategory[],
  preferences: UserPreferences,
  categoryId: string,
  direction: -1 | 1
) => {
  const orderedIds = sortCategoriesByPreference(categories, preferences).map((category) => category.id);
  const currentIndex = orderedIds.indexOf(categoryId);
  const nextIds = moveInArray(orderedIds, currentIndex, currentIndex + direction);

  return {
    ...preferences,
    equipmentCategoryOrder: nextIds
  };
};

export const moveItemInPreferences = (
  items: EquipmentItem[],
  preferences: UserPreferences,
  itemId: string,
  direction: -1 | 1
) => {
  const orderedItems = sortItemsByPreference(items, preferences);
  const currentItem = orderedItems.find((item) => item.id === itemId);

  if (!currentItem) {
    return preferences;
  }

  const siblingIds = orderedItems.filter((item) => item.categoryId === currentItem.categoryId).map((item) => item.id);
  const currentIndex = siblingIds.indexOf(itemId);
  const nextSiblingIds = moveInArray(siblingIds, currentIndex, currentIndex + direction);
  const remainingIds = preferences.equipmentItemOrder.filter((existingId) => !siblingIds.includes(existingId));

  return {
    ...preferences,
    equipmentItemOrder: [...remainingIds, ...nextSiblingIds]
  };
};
