import type {
  DashboardResolvedMargins,
  DashboardMargin,
  DashboardWidget,
  DashboardLayoutItem,
  GridCellMetrics,
  LayoutDirection,
} from './types.js';

export const DEFAULT_MARGIN = 10;

export const resolveMargins = (margin: DashboardMargin): DashboardResolvedMargins => {
  if (typeof margin === 'number') {
    return {
      horizontal: margin,
      vertical: margin,
      top: margin,
      right: margin,
      bottom: margin,
      left: margin,
    };
  }

  const resolved = {
    top: margin.top ?? DEFAULT_MARGIN,
    right: margin.right ?? DEFAULT_MARGIN,
    bottom: margin.bottom ?? DEFAULT_MARGIN,
    left: margin.left ?? DEFAULT_MARGIN,
  };

  return {
    ...resolved,
    horizontal: (resolved.left + resolved.right) / 2,
    vertical: (resolved.top + resolved.bottom) / 2,
  };
};

export const calculateCellMetrics = (
  containerWidth: number,
  columns: number,
  margins: DashboardResolvedMargins,
  cellHeight: number,
  cellHeightUnit: string,
): GridCellMetrics => {
  const totalMarginWidth = margins.horizontal * (columns + 1);
  const availableWidth = Math.max(containerWidth - totalMarginWidth, 0);
  const cellWidthPx = columns > 0 ? availableWidth / columns : 0;
  const cellHeightPx = cellHeightUnit === 'auto' ? cellWidthPx : cellHeight;

  return {
    containerWidth,
    cellWidthPx,
    marginHorizontalPx: margins.horizontal,
    cellHeightPx,
    marginVerticalPx: margins.vertical,
  };
};

export const calculateGridHeight = (
  widgets: DashboardWidget[],
  margins: DashboardResolvedMargins,
  cellHeight: number,
): number => {
  if (widgets.length === 0) return 0;
  const maxY = Math.max(...widgets.map(widget => widget.y + widget.h), 0);
  return maxY * cellHeight + (maxY + 1) * margins.vertical;
};

const overlaps = (
  widget: DashboardWidget,
  x: number,
  y: number,
  w: number,
  h: number,
) => x < widget.x + widget.w && x + w > widget.x && y < widget.y + widget.h && y + h > widget.y;

export const collectCollisions = (
  widgets: DashboardWidget[],
  target: DashboardWidget,
  nextX: number,
  nextY: number,
  nextW: number = target.w,
  nextH: number = target.h,
): DashboardWidget[] => {
  return widgets.filter(widget => {
    if (widget.id === target.id) return false;
    return overlaps(widget, nextX, nextY, nextW, nextH);
  });
};

export const checkCollision = (
  widgets: DashboardWidget[],
  target: DashboardWidget,
  nextX: number,
  nextY: number,
): boolean => collectCollisions(widgets, target, nextX, nextY).length > 0;

export const cloneWidget = (widget: DashboardWidget): DashboardWidget => ({ ...widget });

export const cloneWidgets = (widgets: DashboardWidget[]): DashboardWidget[] => widgets.map(cloneWidget);

export const findAvailablePosition = (
  widgets: DashboardWidget[],
  width: number,
  height: number,
  columns: number,
): { x: number; y: number } => {
  for (let y = 0; y < 200; y++) {
    for (let x = 0; x <= columns - width; x++) {
      const isFree = !widgets.some(widget => overlaps(widget, x, y, width, height));
      if (isFree) {
        return { x, y };
      }
    }
  }

  const maxY = widgets.reduce((acc, widget) => Math.max(acc, widget.y + widget.h), 0);
  return { x: 0, y: maxY };
};

export interface PlacementResult {
  widgets: DashboardWidget[];
  movedWidgets: string[];
  swappedWith?: string;
}

export const resolveWidgetPlacement = (
  widgets: DashboardWidget[],
  widgetId: string,
  next: { x: number; y: number; w?: number; h?: number },
  columns: number,
  previousPosition?: DashboardLayoutItem,
): PlacementResult | null => {
  const sourceWidgets = cloneWidgets(widgets);
  const moving = sourceWidgets.find(widget => widget.id === widgetId);
  const original = widgets.find(widget => widget.id === widgetId);
  if (!moving || !original) {
    return null;
  }

  const target = {
    x: next.x,
    y: next.y,
    w: next.w ?? moving.w,
    h: next.h ?? moving.h,
  };

  moving.x = target.x;
  moving.y = target.y;
  moving.w = target.w;
  moving.h = target.h;

  const collisions = collectCollisions(sourceWidgets, moving, target.x, target.y, target.w, target.h);

  if (collisions.length === 0) {
    return { widgets: sourceWidgets, movedWidgets: [moving.id] };
  }

  if (collisions.length === 1) {
    const other = collisions[0];
    if (!other.locked && !other.noMove && other.w === moving.w && other.h === moving.h) {
      const otherClone = sourceWidgets.find(widget => widget.id === other.id);
      if (otherClone) {
        // Use the original position of the moving widget for a clean swap
        // This prevents the "snapping together" issue where both widgets end up at the same position
        const swapTarget = original;
        const previousOtherPosition = { x: otherClone.x, y: otherClone.y };
        otherClone.x = swapTarget.x;
        otherClone.y = swapTarget.y;

        const swapValid =
          collectCollisions(sourceWidgets, moving, moving.x, moving.y, moving.w, moving.h).length === 0 &&
          collectCollisions(sourceWidgets, otherClone, otherClone.x, otherClone.y, otherClone.w, otherClone.h).length === 0;

        if (swapValid) {
          return { widgets: sourceWidgets, movedWidgets: [moving.id, otherClone.id], swappedWith: otherClone.id };
        }

        otherClone.x = previousOtherPosition.x;
        otherClone.y = previousOtherPosition.y;
      }
    }
  }

  // attempt displacement cascade
  const movedIds = new Set<string>([moving.id]);
  for (const offending of collisions) {
    if (offending.locked || offending.noMove) {
      return null;
    }
    const clone = sourceWidgets.find(widget => widget.id === offending.id);
    if (!clone) continue;
    const remaining = sourceWidgets.filter(widget => widget.id !== offending.id);
    const position = findAvailablePosition(remaining, clone.w, clone.h, columns);
    clone.x = position.x;
    clone.y = position.y;
    movedIds.add(clone.id);
  }

  // verify no overlaps remain
  const verify = collectCollisions(sourceWidgets, moving, moving.x, moving.y, moving.w, moving.h);
  if (verify.length > 0) {
    return null;
  }

  return { widgets: sourceWidgets, movedWidgets: Array.from(movedIds) };
};

export const compactLayout = (
  widgets: DashboardWidget[],
  direction: LayoutDirection = 'vertical',
) => {
  const sorted = [...widgets].sort((a, b) => {
    if (direction === 'vertical') {
      if (a.y !== b.y) return a.y - b.y;
      return a.x - b.x;
    }

    if (a.x !== b.x) return a.x - b.x;
    return a.y - b.y;
  });

  for (const widget of sorted) {
    if (widget.locked || widget.noMove) continue;

    if (direction === 'vertical') {
      while (widget.y > 0 && !checkCollision(widgets, widget, widget.x, widget.y - 1)) {
        widget.y -= 1;
      }
    } else {
      while (widget.x > 0 && !checkCollision(widgets, widget, widget.x - 1, widget.y)) {
        widget.x -= 1;
      }
    }
  }
};

export const applyLayout = (
  widgets: DashboardWidget[],
  layout: DashboardLayoutItem[],
): DashboardWidget[] => {
  return widgets.map(widget => {
    const layoutItem = layout.find(item => item.id === widget.id);
    return layoutItem ? { ...widget, ...layoutItem } : widget;
  });
};
