import type { EditorObject, Page, TableCell } from "./editorStore.types";
import { normalizeCustomShapeObject, normalizePencilObject } from "../utils/customShape";

export const createTableCellId = (row: number, col: number) =>
  `cell-${row}-${col}`;

const createUuid = (): string => {
  if (typeof globalThis.crypto?.randomUUID === "function") {
    return globalThis.crypto.randomUUID();
  }

  if (typeof globalThis.crypto?.getRandomValues === "function") {
    const bytes = new Uint8Array(16);
    globalThis.crypto.getRandomValues(bytes);

    bytes[6] = (bytes[6] & 0x0f) | 0x40;
    bytes[8] = (bytes[8] & 0x3f) | 0x80;

    const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
    return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex
      .slice(6, 8)
      .join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
  }

  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
};

export const createScopedId = (prefix: string) => `${prefix}-${createUuid()}`;

export const isInvalidPageId = (id?: string) =>
  !id || id === "page-undefined" || id.endsWith("-undefined");

export const createInitialCells = (
  rows: number,
  cols: number,
  cellWidth: number,
  cellHeight: number,
): Record<string, TableCell> => {
  const cells: Record<string, TableCell> = {};
  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      const cellId = createTableCellId(row, col);
      cells[cellId] = {
        id: cellId,
        text: "",
        background: "#f5f5f5",
        width: cellWidth,
        height: cellHeight,
      };
    }
  }
  return cells;
};

export const createInitialPage = (name: string): Page => ({
  id: createScopedId("page"),
  name,
  objects: [],
  guides: [],
  backgroundType: "color",
  backgroundColor: "#ffffff",
  backgroundGradient: {
    from: "#ffffff",
    to: "#e2e8f0",
    angle: 135,
  },
  backgroundImageUrl: "",
  backgroundVideoUrl: "",
});

export const getDefaultPageName = (index: number): string =>
  index === 0 ? "جلد" : String(index + 1);

export const applyDefaultPageNames = (pages: Page[]): Page[] =>
  pages.map((page, index) => ({
    ...page,
    name: getDefaultPageName(index),
  }));

export const reorderByIds = <T extends { id: string }>(
  items: T[],
  draggedId: string,
  targetId: string,
): T[] => {
  const fromIndex = items.findIndex((item) => item.id === draggedId);
  const toIndex = items.findIndex((item) => item.id === targetId);

  if (fromIndex === -1 || toIndex === -1) return items;

  const nextItems = [...items];
  const [movedItem] = nextItems.splice(fromIndex, 1);
  nextItems.splice(toIndex, 0, movedItem);
  return nextItems;
};

export const getObjectBounds = (obj: EditorObject) => {
  // TextShape: متن با align="right" در Konva؛ گوشهٔ بالا-راست جعبه در (x,y)، نقاط بدنه به‌سمت چپ است.
  if (obj.type === "text") {
    let w = obj.width || 0;
    let h = obj.height || 0;
    if (!w) {
      const textLength = (obj.text || "").length;
      const fontSize = obj.fontSize || 24;
      w = Math.max(textLength * fontSize * 0.6, 50);
    }
    if (!h) {
      h = obj.fontSize || 24;
    }

    const px = obj.x ?? 0;
    const py = obj.y ?? 0;
    const rot = ((obj.rotation || 0) * Math.PI) / 180;

    const cornersLocal = [
      { x: -w, y: 0 },
      { x: 0, y: 0 },
      { x: 0, y: h },
      { x: -w, y: h },
    ];

    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;

    cornersLocal.forEach(({ x: lx, y: ly }) => {
      const rx = lx * Math.cos(rot) - ly * Math.sin(rot);
      const ry = lx * Math.sin(rot) + ly * Math.cos(rot);
      const wx = px + rx;
      const wy = py + ry;
      minX = Math.min(minX, wx);
      minY = Math.min(minY, wy);
      maxX = Math.max(maxX, wx);
      maxY = Math.max(maxY, wy);
    });

    return { minX, minY, maxX, maxY };
  }

  const w = obj.width || 0;
  const h = obj.height || 0;

  if (obj.type === "rectangle" && obj.shapeType === "circle") {
    const radius = w / 2;
    const cx = obj.x || 0;
    const cy = obj.y || 0;
    return {
      minX: cx - radius,
      minY: cy - radius,
      maxX: cx + radius,
      maxY: cy + radius,
    };
  }

  if (
    obj.type === "rectangle" &&
    (obj.shapeType === "triangle" || obj.shapeType === "abstract")
  ) {
    const cx = (obj.x || 0) + w / 2;
    const cy = (obj.y || 0) + h / 2;
    const rot = ((obj.rotation || 0) * Math.PI) / 180;

    const baseRadius = Math.min(w / 2, h / 2);
    const actualRadius =
      obj.shapeType === "abstract" ? baseRadius * 0.85 : baseRadius;

    const corners = [
      { x: -actualRadius, y: -actualRadius },
      { x: actualRadius, y: -actualRadius },
      { x: actualRadius, y: actualRadius },
      { x: -actualRadius, y: actualRadius },
    ];

    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;

    corners.forEach((corner) => {
      const rotatedX = corner.x * Math.cos(rot) - corner.y * Math.sin(rot);
      const rotatedY = corner.x * Math.sin(rot) + corner.y * Math.cos(rot);
      const finalX = cx + rotatedX;
      const finalY = cy + rotatedY;
      minX = Math.min(minX, finalX);
      minY = Math.min(minY, finalY);
      maxX = Math.max(maxX, finalX);
      maxY = Math.max(maxY, finalY);
    });

    return { minX, minY, maxX, maxY };
  }

  if (obj.type === "arrow" || obj.type === "line") {
    const startX = obj.x || 0;
    const startY = obj.y || 0;
    const endX = obj.width || 0;
    const endY = obj.height || 0;
    return {
      minX: Math.min(startX, endX),
      minY: Math.min(startY, endY),
      maxX: Math.max(startX, endX),
      maxY: Math.max(startY, endY),
    };
  }

  const cx = (obj.x || 0) + w / 2;
  const cy = (obj.y || 0) + h / 2;
  const rot = ((obj.rotation || 0) * Math.PI) / 180;

  const corners = [
    { x: -w / 2, y: -h / 2 },
    { x: w / 2, y: -h / 2 },
    { x: w / 2, y: h / 2 },
    { x: -w / 2, y: h / 2 },
  ];

  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;

  corners.forEach((corner) => {
    const rotatedX = corner.x * Math.cos(rot) - corner.y * Math.sin(rot);
    const rotatedY = corner.x * Math.sin(rot) + corner.y * Math.cos(rot);
    const finalX = cx + rotatedX;
    const finalY = cy + rotatedY;
    minX = Math.min(minX, finalX);
    minY = Math.min(minY, finalY);
    maxX = Math.max(maxX, finalX);
    maxY = Math.max(maxY, finalY);
  });

  return { minX, minY, maxX, maxY };
};

export const buildGroupResult = (
  objects: EditorObject[],
  objectIds: string[],
  nextGroupId?: string,
) => {
  if (objectIds.length < 2) return null;

  const allObjectIdsToGroup = new Set<string>(objectIds);
  const oldGroupIdsToRemove = new Set<string>();

  objectIds.forEach((id) => {
    const obj = objects.find((o) => o.id === id);
    if (obj?.type === "group") {
      const oldGroupMembers = objects.filter((o) => o.groupId === id);
      oldGroupMembers.forEach((member) => {
        if (member.type !== "group") {
          allObjectIdsToGroup.add(member.id);
        }
      });
      oldGroupIdsToRemove.add(id);
    } else if (obj?.groupId) {
      const oldGroupMembers = objects.filter((o) => o.groupId === obj.groupId);
      oldGroupMembers.forEach((member) => {
        if (member.type !== "group") {
          allObjectIdsToGroup.add(member.id);
        }
      });
      oldGroupIdsToRemove.add(obj.groupId);
    }
  });

  if (allObjectIdsToGroup.size < 2) return null;

  const groupId = nextGroupId ?? createScopedId("group");
  const objectsToGroup = objects.filter(
    (obj) => allObjectIdsToGroup.has(obj.id) && obj.type !== "group",
  );
  if (objectsToGroup.length === 0) return null;

  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;

  objectsToGroup.forEach((obj) => {
    const bounds = getObjectBounds(obj);
    minX = Math.min(minX, bounds.minX);
    minY = Math.min(minY, bounds.minY);
    maxX = Math.max(maxX, bounds.maxX);
    maxY = Math.max(maxY, bounds.maxY);
  });

  const groupObject: EditorObject = {
    id: groupId,
    type: "group",
    x: minX,
    y: minY,
    width: maxX - minX,
    height: maxY - minY,
  };

  const nextObjects = objects
    .map((obj) => {
      if (allObjectIdsToGroup.has(obj.id) && obj.type !== "group") {
        return { ...obj, groupId };
      }
      if (obj.groupId && oldGroupIdsToRemove.has(obj.groupId)) {
        return { ...obj, groupId: undefined };
      }
      return obj;
    })
    .filter((obj) => !oldGroupIdsToRemove.has(obj.id));

  return {
    groupId,
    nextObjects: [...nextObjects, groupObject],
  };
};

export const ungroupById = (objects: EditorObject[], groupId: string) =>
  objects
    .map((obj) =>
      obj.groupId === groupId ? { ...obj, groupId: undefined } : obj,
    )
    .filter((obj) => obj.id !== groupId);

/**
 * اگر اعضا groupId دارند ولی شیء type:group با همان id در آرایه نیست (ذخیره/سنک ناقص)،
 * همانند buildGroupResult یک wrapper اضافه می‌کنیم تا ادیتور گروه را یکپارچه جابه‌جا کند.
 * اگر فقط یک عضو با آن groupId باشد، groupId حذف می‌شود (گروه نامعتبر).
 */
export const ensureMissingGroupObjects = (
  objects: EditorObject[],
): EditorObject[] => {
  let next = [...objects];

  const referencedIds = new Set<string>();
  for (const o of next) {
    if (o.type !== "group" && o.groupId) {
      referencedIds.add(o.groupId);
    }
  }

  for (const gid of referencedIds) {
    const members = next.filter((o) => o.groupId === gid && o.type !== "group");
    if (members.length < 2) {
      next = next.map((o) =>
        o.groupId === gid && o.type !== "group"
          ? { ...o, groupId: undefined }
          : o,
      );
    }
  }

  const stillReferenced = new Set<string>();
  for (const o of next) {
    if (o.type !== "group" && o.groupId) {
      stillReferenced.add(o.groupId);
    }
  }

  const existingGroupIds = new Set(
    next.filter((o) => o.type === "group").map((o) => o.id),
  );

  const toAppend: EditorObject[] = [];
  for (const gid of stillReferenced) {
    if (existingGroupIds.has(gid)) continue;

    const members = next.filter((o) => o.groupId === gid && o.type !== "group");
    if (members.length < 2) continue;

    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;
    for (const m of members) {
      const b = getObjectBounds(m);
      minX = Math.min(minX, b.minX);
      minY = Math.min(minY, b.minY);
      maxX = Math.max(maxX, b.maxX);
      maxY = Math.max(maxY, b.maxY);
    }

    toAppend.push({
      id: gid,
      type: "group",
      x: minX,
      y: minY,
      width: maxX - minX,
      height: maxY - minY,
    });
  }

  return toAppend.length > 0 ? [...next, ...toAppend] : next;
};

export const normalizePageObjects = (objects: EditorObject[]): EditorObject[] =>
  ensureMissingGroupObjects(objects)
    .map(normalizeCustomShapeObject)
    .map(normalizePencilObject);
