import { isPlainObject } from "es-toolkit";

import { resolveContentExpectations } from "./content";
import { CONTENT_EXTRACTOR_KEY_SET, MATCHER_KEY_SET } from "./keys";
import { isValueMatcherObject, isValueMatcherPrimitive, resolveValueExpectation } from "./value";

type ExpectRecord = Record<string, unknown>;

export function compactExpect(original: ExpectRecord, overrides: ExpectRecord): ExpectRecord {
  const result: ExpectRecord = {};
  for (const [key, value] of Object.entries(original)) {
    if (value !== undefined) result[key] = value;
  }
  for (const [key, value] of Object.entries(overrides)) {
    if (value !== undefined) result[key] = value;
  }
  return result;
}

export function normalizeContent(value: unknown): unknown {
  if (value === undefined) return undefined;
  if (!Array.isArray(value)) return value;
  return (value as unknown[]).map((entry): unknown => {
    if (!canNormalizeContentEntry(entry)) return entry;
    const resolved = resolveContentExpectations([entry] as never);
    return resolved?.[0];
  });
}

export function normalizeKeyed(value: unknown): unknown {
  if (value === undefined) return undefined;
  if (!isPlainObject(value)) return value;
  return Object.entries(value as ExpectRecord).map(([key, item]) => ({ key, matcher: normalizeValue(item) }));
}

export function normalizeValue(value: unknown): unknown {
  if (value === undefined) return undefined;
  if (isValueMatcherPrimitive(value) || isValueMatcherObject(value)) return resolveValueExpectation(value);
  return value;
}

function canNormalizeContentEntry(value: unknown): boolean {
  if (!isPlainObject(value)) return false;
  const keys = Object.keys(value);
  const extractorKeys = keys.filter((key) => CONTENT_EXTRACTOR_KEY_SET.has(key));
  const matcherKeys = keys.filter((key) => MATCHER_KEY_SET.has(key));
  if (extractorKeys.length === 0) return matcherKeys.length > 0 && matcherKeys.length === keys.length;
  if (extractorKeys.length !== 1 || matcherKeys.length > 0 || keys.length !== 1) return false;
  return isPlainObject((value as ExpectRecord)[extractorKeys[0]!]);
}
