import { objectEntries } from "@homarr/common";
import { logger } from "@homarr/log";
import type { BoardSize, OldmarrApp, OldmarrConfig, OldmarrWidget } from "@homarr/old-schema";
import { boardSizes } from "@homarr/old-schema";

import { mapColumnCount } from "./mappers/map-column-count";
import type { OldmarrImportConfiguration } from "./settings";

export const moveWidgetsAndAppsIfMerge = (
  old: OldmarrConfig,
  wrapperIdsToMerge: string[],
  configuration: OldmarrImportConfiguration,
) => {
  const firstId = wrapperIdsToMerge[0];
  if (!firstId) {
    return { apps: old.apps, widgets: old.widgets };
  }

  const affectedMap = new Map<string, { apps: OldmarrApp[]; widgets: OldmarrWidget[] }>(
    wrapperIdsToMerge.map((id) => [
      id,
      {
        apps: old.apps.filter((app) => app.area.type !== "sidebar" && id === app.area.properties.id),
        widgets: old.widgets.filter((app) => app.area.type !== "sidebar" && id === app.area.properties.id),
      },
    ]),
  );

  logger.debug(`Merging wrappers at the end of the board count=${wrapperIdsToMerge.length}`);

  const offsets = boardSizes.reduce(
    (previous, screenSize) => {
      previous[screenSize] = 0;
      return previous;
    },
    {} as Record<BoardSize, number>,
  );
  for (const id of wrapperIdsToMerge) {
    const requiredHeights = boardSizes.reduce(
      (previous, screenSize) => {
        previous[screenSize] = 0;
        return previous;
      },
      {} as Record<BoardSize, number>,
    );
    const affected = affectedMap.get(id);
    if (!affected) {
      continue;
    }

    const apps = affected.apps;
    const widgets = affected.widgets;

    for (const app of apps) {
      if (app.area.type === "sidebar") continue;
      // Move item to first wrapper
      app.area.properties.id = firstId;

      for (const screenSize of boardSizes) {
        const screenSizeShape = app.shape[screenSize];
        if (!screenSizeShape) {
          continue;
        }

        // Find the highest widget in the wrapper to increase the offset accordingly
        if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeights[screenSize]) {
          requiredHeights[screenSize] = screenSizeShape.location.y + screenSizeShape.size.height;
        }

        // Move item down as much as needed to not overlap with other items
        screenSizeShape.location.y += offsets[screenSize];
      }
    }

    for (const widget of widgets) {
      if (widget.area.type === "sidebar") continue;
      // Move item to first wrapper
      widget.area.properties.id = firstId;

      for (const screenSize of boardSizes) {
        const screenSizeShape = widget.shape[screenSize];
        if (!screenSizeShape) {
          continue;
        }

        // Find the highest widget in the wrapper to increase the offset accordingly
        if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeights[screenSize]) {
          requiredHeights[screenSize] = screenSizeShape.location.y + screenSizeShape.size.height;
        }

        // Move item down as much as needed to not overlap with other items
        screenSizeShape.location.y += offsets[screenSize];
      }
    }

    for (const screenSize of boardSizes) {
      offsets[screenSize] += requiredHeights[screenSize];
    }
  }

  if (configuration.sidebarBehaviour === "last-section") {
    const areas = [...old.apps.map((app) => app.area), ...old.widgets.map((widget) => widget.area)];
    if (
      old.settings.customization.layout.enabledLeftSidebar ||
      areas.some((area) => area.type === "sidebar" && area.properties.location === "left")
    ) {
      for (const screenSize of boardSizes) {
        offsets[screenSize] = moveWidgetsAndAppsInLeftSidebar(old, firstId, offsets[screenSize], screenSize);
      }
    }

    if (
      old.settings.customization.layout.enabledRightSidebar ||
      areas.some((area) => area.type === "sidebar" && area.properties.location === "right")
    ) {
      for (const screenSize of boardSizes) {
        moveWidgetsAndAppsInRightSidebar(old, firstId, offsets[screenSize], screenSize);
      }
    }
  } else {
    // Remove all widgets and apps in the sidebar
    return {
      apps: old.apps.filter((app) => app.area.type !== "sidebar"),
      widgets: old.widgets.filter((app) => app.area.type !== "sidebar"),
    };
  }

  return { apps: old.apps, widgets: old.widgets };
};

const moveWidgetsAndAppsInLeftSidebar = (
  old: OldmarrConfig,
  firstId: string,
  offset: number,
  screenSize: BoardSize,
) => {
  const columnCount = mapColumnCount(old.settings.customization.gridstack, screenSize);
  let requiredHeight = updateItems({
    // This should work as the reference of the items did not change, only the array reference did
    items: [...old.widgets, ...old.apps],
    screenSize,
    filter: (item) =>
      item.area.type === "sidebar" &&
      item.area.properties.location === "left" &&
      (columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
    update: (item) => {
      item.area = {
        type: "wrapper",
        properties: {
          id: firstId,
        },
      };

      const screenSizeShape = item.shape[screenSize];
      if (!screenSizeShape) return;

      // Reduce width to one if column count is one
      if (screenSizeShape.size.width > columnCount) {
        screenSizeShape.size.width = columnCount;
      }

      screenSizeShape.location.y += offset;
    },
  });

  // Only increase offset if there are less than 3 columns because then the items have to be stacked
  if (columnCount <= 3) {
    offset += requiredHeight;
  }

  // When column count is 0 we need to stack the items of the sidebar on top of each other
  if (columnCount !== 1) {
    return offset;
  }

  requiredHeight = updateItems({
    // This should work as the reference of the items did not change, only the array reference did
    items: [...old.widgets, ...old.apps],
    screenSize,
    filter: (item) =>
      item.area.type === "sidebar" &&
      item.area.properties.location === "left" &&
      item.shape[screenSize]?.location.x === 1,
    update: (item) => {
      item.area = {
        type: "wrapper",
        properties: {
          id: firstId,
        },
      };

      const screenSizeShape = item.shape[screenSize];
      if (!screenSizeShape) return;

      screenSizeShape.location.x = 0;
      screenSizeShape.location.y += offset;
    },
  });

  offset += requiredHeight;
  return offset;
};

const moveWidgetsAndAppsInRightSidebar = (
  old: OldmarrConfig,
  firstId: string,
  offset: number,
  screenSize: BoardSize,
) => {
  const columnCount = mapColumnCount(old.settings.customization.gridstack, screenSize);
  const xOffsetDelta = Math.max(columnCount - 2, 0);
  const requiredHeight = updateItems({
    // This should work as the reference of the items did not change, only the array reference did
    items: [...old.widgets, ...old.apps],
    screenSize,
    filter: (item) =>
      item.area.type === "sidebar" &&
      item.area.properties.location === "right" &&
      (columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
    update: (item) => {
      item.area = {
        type: "wrapper",
        properties: {
          id: firstId,
        },
      };

      const screenSizeShape = item.shape[screenSize];
      if (!screenSizeShape) return;

      // Reduce width to one if column count is one
      if (screenSizeShape.size.width > columnCount) {
        screenSizeShape.size.width = columnCount;
      }

      screenSizeShape.location.y += offset;
      screenSizeShape.location.x += xOffsetDelta;
    },
  });

  // When column count is 0 we need to stack the items of the sidebar on top of each other
  if (columnCount !== 1) {
    return;
  }

  offset += requiredHeight;

  updateItems({
    // This should work as the reference of the items did not change, only the array reference did
    items: [...old.widgets, ...old.apps],
    screenSize,
    filter: (item) =>
      item.area.type === "sidebar" &&
      item.area.properties.location === "left" &&
      item.shape[screenSize]?.location.x === 1,
    update: (item) => {
      item.area = {
        type: "wrapper",
        properties: {
          id: firstId,
        },
      };

      const screenSizeShape = item.shape[screenSize];
      if (!screenSizeShape) return;

      screenSizeShape.location.x = 0;
      screenSizeShape.location.y += offset;
    },
  });
};

const createItemSnapshot = (item: OldmarrApp | OldmarrWidget, screenSize: BoardSize) => ({
  x: item.shape[screenSize]?.location.x,
  y: item.shape[screenSize]?.location.y,
  height: item.shape[screenSize]?.size.height,
  width: item.shape[screenSize]?.size.width,
  section:
    item.area.type === "sidebar"
      ? {
          type: "sidebar",
          location: item.area.properties.location,
        }
      : {
          type: item.area.type,
          id: item.area.properties.id,
        },
  toString(): string {
    return objectEntries(this)
      .filter(([key]) => key !== "toString")
      .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
      .join(" ");
  },
});

const updateItems = (options: {
  items: (OldmarrApp | OldmarrWidget)[];
  filter: (item: OldmarrApp | OldmarrWidget) => boolean;
  update: (item: OldmarrApp | OldmarrWidget) => void;
  screenSize: BoardSize;
}) => {
  const items = options.items.filter(options.filter);
  let requiredHeight = 0;
  for (const item of items) {
    const before = createItemSnapshot(item, options.screenSize);

    options.update(item);

    const screenSizeShape = item.shape[options.screenSize];
    if (!screenSizeShape) return requiredHeight;

    if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeight) {
      requiredHeight = screenSizeShape.location.y + screenSizeShape.size.height;
    }

    const after = createItemSnapshot(item, options.screenSize);

    logger.debug(
      `Moved item ${item.id}\n [snapshot before]: ${before.toString()}\n [snapshot after]: ${after.toString()}`,
    );
  }

  return requiredHeight;
};
