/**
 * diffLayouts — pure function; compares two LayoutBody snapshots and returns
 * the semantic delta needed by PublishDialog.
 *
 * Strategy:
 *   Build a Map<instanceId, { device, idx, mod }> for each body using only the
 *   first occurrence of each instanceId (mobile takes precedence over desktop).
 *   Then compute:
 *     added     — instanceIds present in B but not in A
 *     removed   — instanceIds present in A but not in B
 *     reordered — shared instanceIds whose device or idx changed
 *     edited    — shared instanceIds whose config or visibility JSON differs
 */

import type { LayoutBody } from '@/server/page-layout/types';

export interface LayoutDiff {
  added: string[];
  removed: string[];
  reordered: string[];
  edited: string[];
}

function indexOf(
  b: LayoutBody,
): Map<string, { device: 'mobile' | 'desktop'; idx: number; mod: LayoutBody['mobile'][number] }> {
  const m = new Map<
    string,
    { device: 'mobile' | 'desktop'; idx: number; mod: LayoutBody['mobile'][number] }
  >();
  (['mobile', 'desktop'] as const).forEach((d) => {
    b[d].forEach((mod, idx) => {
      if (!m.has(mod.instanceId)) {
        m.set(mod.instanceId, { device: d, idx, mod });
      }
    });
  });
  return m;
}

export function diffLayouts(prev: LayoutBody, next: LayoutBody): LayoutDiff {
  const A = indexOf(prev);
  const B = indexOf(next);

  const added = [...B.keys()].filter((k) => !A.has(k));
  const removed = [...A.keys()].filter((k) => !B.has(k));
  const shared = [...A.keys()].filter((k) => B.has(k));
  const sharedSet = new Set(shared);

  // Rank each shared module by its position among shared-only entries in a
  // device array. Non-shared (added/removed) entries are skipped so that an
  // insertion or deletion before a shared module does not shift its rank and
  // produce a false "reordered" signal.
  function sharedRankInDevice(body: LayoutBody, device: 'mobile' | 'desktop'): Map<string, number> {
    const ranks = new Map<string, number>();
    let rank = 0;
    for (const mod of body[device]) {
      if (sharedSet.has(mod.instanceId)) ranks.set(mod.instanceId, rank++);
    }
    return ranks;
  }

  const rankA = {
    mobile: sharedRankInDevice(prev, 'mobile'),
    desktop: sharedRankInDevice(prev, 'desktop'),
  };
  const rankB = {
    mobile: sharedRankInDevice(next, 'mobile'),
    desktop: sharedRankInDevice(next, 'desktop'),
  };

  const reordered: string[] = [];
  const edited: string[] = [];

  for (const id of shared) {
    const a = A.get(id)!;
    const b = B.get(id)!;

    if (a.device !== b.device || rankA[a.device].get(id) !== rankB[b.device].get(id)) {
      reordered.push(id);
    }

    if (
      JSON.stringify(a.mod.config) !== JSON.stringify(b.mod.config) ||
      JSON.stringify(a.mod.visibility) !== JSON.stringify(b.mod.visibility)
    ) {
      edited.push(id);
    }
  }

  return { added, removed, reordered, edited };
}
