import {
  type CopiedShiftCell,
  type ShiftAssignCellVC,
  type ShiftCalendarUser,
} from 'src/types/shifts';

import { type ShiftSelection } from '../contexts/ShiftsSelectionContext';
import { type ShiftFormData } from '../form';

/**
 * Gets user and day data from registers. Exported for use in copyPaste.
 */
export const getUserDayData = (
  registers: ShiftCalendarUser[],
  userId: number,
  date: string,
) => {
  const user = registers.find(r => r.userId === userId);
  if (!user) return undefined;
  const dayData = user.days[date];
  if (!dayData) return undefined;
  return { user, dayData };
};

export const validateSingleCollaboratorSelection = (
  selectedCells: ShiftAssignCellVC[],
  selectedShifts: Map<number, ShiftSelection>,
) => {
  const selectedUsers = new Set<number>();
  selectedCells.forEach(cell => {
    selectedUsers.add(cell.userId);
  });
  selectedShifts.forEach(shift => {
    selectedUsers.add(shift.userId);
  });
  return selectedUsers.size;
};

export const getCellShifts = (
  cell: ShiftAssignCellVC,
  registers: ShiftCalendarUser[],
) => {
  const userDayData = getUserDayData(registers, cell.userId, cell.date);
  if (!userDayData) return [];
  return userDayData.dayData.shifts.map(shift => shift.shiftAssignedShiftId);
};

export const buildCopiedCellsWithShifts = (
  selectedCells: ShiftAssignCellVC[],
  selectedShifts: Map<number, ShiftSelection>,
  registers: ShiftCalendarUser[],
): CopiedShiftCell[] => {
  const copiedCells: CopiedShiftCell[] = [];
  const processedCells = new Set<string>();

  selectedCells.forEach(cell => {
    const cellKey = `${cell.userId}-${cell.date}`;
    if (processedCells.has(cellKey)) return;
    processedCells.add(cellKey);
    const selectedCellsShifts = getCellShifts(cell, registers);
    copiedCells.push({
      userId: cell.userId,
      date: cell.date,
      shiftIds: selectedCellsShifts.length > 0 ? selectedCellsShifts : [],
    });
  });

  selectedShifts.forEach(shift => {
    const cellKey = `${shift.userId}-${shift.date}`;
    const userDayData = getUserDayData(registers, shift.userId, shift.date);
    if (!userDayData) return;
    const shiftData = userDayData.dayData.shifts.find(
      s => s.shiftAssignedShiftId === shift.shiftId,
    );
    if (!shiftData || !shiftData.shiftAssignedShiftId) return;
    const existingEntry = copiedCells.find(
      r => r.userId === shift.userId && r.date === shift.date,
    );
    if (existingEntry) {
      if (!existingEntry.shiftIds.includes(shiftData.shiftAssignedShiftId)) {
        existingEntry.shiftIds.push(shiftData.shiftAssignedShiftId);
      }
    } else {
      copiedCells.push({
        userId: shift.userId,
        date: shift.date,
        shiftIds: [shiftData.shiftAssignedShiftId],
      });
      processedCells.add(cellKey);
    }
  });

  return copiedCells;
};

// ---------------------------------------------------------------------------
// Calculations & cell selection
// ---------------------------------------------------------------------------

export const calculateAffectedCounts = (
  selectedDates: Set<string>,
  selectedCells: ShiftAssignCellVC[],
  totalUserCount: number,
  formData?: ShiftFormData,
): { affectedUsers: number; affectedDates: number } => {
  const hasSelectedDates = selectedDates?.size > 0;
  const hasSelectedCells = selectedCells?.length > 0;

  if (hasSelectedDates && !hasSelectedCells) {
    return { affectedUsers: totalUserCount, affectedDates: selectedDates.size };
  }
  if (hasSelectedCells) {
    return {
      affectedUsers: new Set(selectedCells.map(cell => cell.userId)).size,
      affectedDates: new Set(selectedCells.map(cell => cell.date)).size,
    };
  }
  if (formData) {
    return {
      affectedUsers: formData.collaborators?.length ?? 0,
      affectedDates: formData.date ? 1 : 0,
    };
  }
  return { affectedUsers: 0, affectedDates: 0 };
};

type IsCellSelectedParams = {
  cellKey: string;
  dateKey: string;
  cellSelectionMap: Map<string, boolean>;
  selectedDates: Set<string>;
  cellFullySelected: boolean;
  isAdvancedMode: boolean;
};

export const checkCellSelection = ({
  cellKey,
  dateKey,
  cellSelectionMap,
  selectedDates,
  cellFullySelected,
  isAdvancedMode,
}: IsCellSelectedParams) => {
  const explicitCellSelection = cellSelectionMap.get(cellKey);
  return (
    explicitCellSelection ||
    (explicitCellSelection === undefined &&
      (selectedDates.has(dateKey) || (isAdvancedMode && cellFullySelected)))
  );
};

export const filterCellsByDate = (cells: ShiftAssignCellVC[], date: string) =>
  cells.filter(cell => cell.date !== date);

export const filterCellsByCollaborators = (
  cells: ShiftAssignCellVC[],
  userIds: number[],
) => {
  const userIdSet = new Set(userIds);
  return cells.filter(cell => !userIdSet.has(cell.userId));
};

export const filterShiftsByDate = (
  shifts: Map<number, ShiftSelection>,
  date: string,
) => {
  const newShifts = new Map(shifts);
  for (const [shiftId, shift] of shifts.entries()) {
    if (shift.date === date) newShifts.delete(shiftId);
  }
  return newShifts;
};

export const filterShiftsByCollaborator = (
  shifts: Map<number, ShiftSelection>,
  userId: number,
) => {
  const newShifts = new Map(shifts);
  for (const [shiftId, shift] of shifts.entries()) {
    if (shift.userId === userId) newShifts.delete(shiftId);
  }
  return newShifts;
};

export const filterShiftsByCells = (
  shifts: Map<number, ShiftSelection>,
  cells: ShiftAssignCellVC[],
) => {
  const newShifts = new Map(shifts);
  const cellKeys = new Set(cells.map(cell => `${cell.userId}-${cell.date}`));
  for (const [shiftId, shift] of shifts.entries()) {
    if (cellKeys.has(`${shift.userId}-${shift.date}`))
      newShifts.delete(shiftId);
  }
  return newShifts;
};

export const filterShiftsByIds = (
  shifts: Map<number, ShiftSelection>,
  shiftIds: number[],
) => {
  const newShifts = new Map(shifts);
  for (const id of shiftIds) newShifts.delete(id);
  return newShifts;
};

export const allDateCellsSelected = (
  date: string,
  userIds: number[],
  selectedCells: ShiftAssignCellVC[],
) =>
  userIds.every(userId =>
    selectedCells.some(cell => cell.userId === userId && cell.date === date),
  );

export const allDatesSelected = (dates: string[], selectedDates: Set<string>) =>
  dates.length > 0 && dates.every(date => selectedDates.has(date));

export const checkCellSelectionPromotions = (
  cellData: ShiftAssignCellVC,
  currentSelectedCells: ShiftAssignCellVC[],
  currentSelectedDates: Set<string>,
  dates: string[],
  visibleUserIds: number[],
) => {
  let promotedDates = new Set(currentSelectedDates);
  let promotedCells = currentSelectedCells;
  let shouldPromoteToAll = false;

  if (
    !currentSelectedDates.has(cellData.date) &&
    allDateCellsSelected(cellData.date, visibleUserIds, currentSelectedCells)
  ) {
    promotedDates = new Set(promotedDates);
    promotedDates.add(cellData.date);
    promotedCells = filterCellsByDate(promotedCells, cellData.date);
    if (allDatesSelected(dates, promotedDates)) shouldPromoteToAll = true;
  }

  const allDatesForUserSelected = dates.every(date => promotedDates.has(date));
  if (allDatesForUserSelected) {
    promotedCells = filterCellsByCollaborators(promotedCells, [
      cellData.userId,
    ]);
  }

  return { promotedDates, promotedCells, shouldPromoteToAll };
};

export const getCellsFromShifts = (
  shifts: Map<number, ShiftSelection>,
): ShiftAssignCellVC[] =>
  Array.from(shifts.values()).map(shift => ({
    userId: shift.userId,
    date: shift.date,
  }));
