import type { ScreenDefinition, ScreenVariantDefinition } from "@/lib/funnel/types";

const EXCLUDED_KEYS = new Set(["id", "template", "variants", "position"]);

type AnyRecord = Record<string, unknown>;

function isPlainObject(value: unknown): value is AnyRecord {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function deepClone<T>(value: T): T {
  if (Array.isArray(value)) {
    return value.map((item) => deepClone(item)) as unknown as T;
  }

  if (isPlainObject(value)) {
    const clonedEntries = Object.entries(value).map(([key, entry]) => [key, deepClone(entry)]);
    return Object.fromEntries(clonedEntries) as T;
  }

  return value;
}

function deepMerge<T>(base: T, patch?: Partial<T>): T {
  const result = deepClone(base);

  if (!patch) {
    return result;
  }

  Object.entries(patch as AnyRecord).forEach(([key, patchValue]) => {
    if (patchValue === undefined) {
      (result as AnyRecord)[key] = undefined;
      return;
    }

    const currentValue = (result as AnyRecord)[key];

    if (isPlainObject(currentValue) && isPlainObject(patchValue)) {
      (result as AnyRecord)[key] = deepMerge(currentValue, patchValue);
      return;
    }

    (result as AnyRecord)[key] = deepClone(patchValue);
  });

  return result;
}

function deepEqual(a: unknown, b: unknown): boolean {
  if (a === b) {
    return true;
  }

  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) {
      return false;
    }

    return a.every((item, index) => deepEqual(item, b[index]));
  }

  if (isPlainObject(a) && isPlainObject(b)) {
    const keys = new Set([...Object.keys(a), ...Object.keys(b)]);

    for (const key of keys) {
      if (!deepEqual(a[key], b[key])) {
        return false;
      }
    }

    return true;
  }

  return false;
}

function diff(base: unknown, target: unknown): unknown {
  if (deepEqual(base, target)) {
    return undefined;
  }

  if (Array.isArray(target) || Array.isArray(base)) {
    return deepClone(target);
  }

  if (isPlainObject(target) && isPlainObject(base)) {
    const entries: [string, unknown][] = [];
    const keys = new Set([...Object.keys(target), ...Object.keys(base)]);

    keys.forEach((key) => {
      if (EXCLUDED_KEYS.has(key)) {
        return;
      }

      const baseValue = (base as AnyRecord)[key];
      const targetValue = (target as AnyRecord)[key];

      const nestedDiff = diff(baseValue, targetValue);

      if (nestedDiff !== undefined) {
        entries.push([key, nestedDiff]);
      }
    });

    return entries.length > 0 ? Object.fromEntries(entries) : undefined;
  }

  return deepClone(target);
}

export function mergeScreenWithOverrides<T extends ScreenDefinition>(
  base: T,
  overrides?: ScreenVariantDefinition<T>["overrides"]
): T {
  return deepMerge(base, overrides as Partial<T> | undefined);
}

export function applyScreenUpdates<T extends ScreenDefinition>(screen: T, updates: Partial<T>): T {
  return deepMerge(screen, updates);
}

export function extractVariantOverrides<T extends ScreenDefinition>(
  base: T,
  target: T
): ScreenVariantDefinition<T>["overrides"] {
  return diff(base, target) as ScreenVariantDefinition<T>["overrides"];
}

export function listOverridePaths(value: unknown, prefix = ""): string[] {
  if (!value || typeof value !== "object") {
    return prefix ? [prefix.slice(0, -1)] : [];
  }

  if (Array.isArray(value)) {
    return [prefix ? `${prefix.slice(0, -1)}[]` : "[]"];
  }

  const result: string[] = [];

  Object.entries(value).forEach(([key, entry]) => {
    const nextPrefix = `${prefix}${key}.`;
    if (!entry || typeof entry !== "object") {
      result.push(prefix ? `${prefix.slice(0, -1)} · ${key}` : key);
      return;
    }

    if (Array.isArray(entry)) {
      result.push(prefix ? `${prefix.slice(0, -1)} · ${key}[]` : `${key}[]`);
      return;
    }

    const nested = listOverridePaths(entry, nextPrefix);
    if (nested.length === 0) {
      result.push(prefix ? `${prefix.slice(0, -1)} · ${key}` : key);
      return;
    }

    result.push(...nested);
  });

  return result;
}

export function formatOverridePath(path: string): string {
  if (!path) {
    return path;
  }

  const [head, ...tail] = path.split(" · ");

  const labelMap: Record<string, string> = {
    title: "Заголовок",
    subtitle: "Подзаголовок",
    bottomActionButton: "Нижняя кнопка",
    list: "Список",
    header: "Хедер",
    dateInput: "Поле даты",
    coupon: "Купон",
    description: "Описание",
    infoMessage: "Инфо сообщение",
  };

  const formattedHead = labelMap[head] ?? head;

  if (tail.length === 0) {
    return formattedHead;
  }

  return `${formattedHead} · ${tail.join(" · ")}`;
}
