import {
  METAMORPHIC_RELATION_CLASSES,
  type MetamorphicRelationClass,
  type MetamorphicRelationResult,
  type MetamorphicRelationSpec,
} from "./types.js";

function assertNonEmptyString(value: string, field: string): void {
  if (value.trim().length === 0) {
    throw new Error(`${field} must be a non-empty string`);
  }
}

function assertRelationClass(value: string): asserts value is MetamorphicRelationClass {
  if (!(METAMORPHIC_RELATION_CLASSES as readonly string[]).includes(value)) {
    throw new Error(`unknown relation class: ${value}`);
  }
}

function cloneValue<T>(value: T): T {
  if (typeof structuredClone === "function") {
    return structuredClone(value);
  }
  return JSON.parse(JSON.stringify(value)) as T;
}

function validateSpec<TBase, TTransformed>(
  spec: MetamorphicRelationSpec<TBase, TTransformed>,
): void {
  assertNonEmptyString(spec.id, "id");
  assertRelationClass(spec.relationClass);
  if (typeof spec.base !== "function") {
    throw new Error("base must be a function");
  }
  if (typeof spec.transform !== "function") {
    throw new Error("transform must be a function");
  }
  if (typeof spec.invariantOn !== "function") {
    throw new Error("invariantOn must be a function");
  }
}

export async function runMetamorphicRelation<TBase, TTransformed = TBase>(
  spec: MetamorphicRelationSpec<TBase, TTransformed>,
): Promise<MetamorphicRelationResult> {
  validateSpec(spec);

  try {
    const baseResult = await spec.base();
    const transformedResult = await spec.transform(baseResult);
    spec.invariantOn(baseResult, transformedResult);
    return {
      id: spec.id,
      relationClass: spec.relationClass,
      holds: true,
      baseResult: cloneValue(baseResult),
      transformedResult: cloneValue(transformedResult),
    };
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    return {
      id: spec.id,
      relationClass: spec.relationClass,
      holds: false,
      error: message,
    };
  }
}
