/**
 * Internal schema shape helpers shared by server route registration and
 * OpenAPI generation.
 *
 * These helpers are intentionally zod-free: zod is an optional peer
 * dependency, so shape extraction duck-types Zod's `_def.shape` internals and
 * returns undefined for schemas it cannot introspect. This module is not
 * exported from the package index.
 */

/**
 * Extract the field shape (field name → field schema) from an object schema.
 *
 * Returns undefined when the schema is not an introspectable object schema,
 * for example a non-Zod Standard Schema.
 */
export function getObjectSchemaShape(
  schema: unknown,
): Record<string, unknown> | undefined {
  if (!schema || typeof schema !== "object") return undefined;

  const schemaDef = (schema as Record<string, unknown>)?._def;
  if (!schemaDef || typeof schemaDef !== "object") return undefined;

  if (!("shape" in schemaDef)) return undefined;

  let shape: unknown;
  if (typeof schemaDef.shape === "function") {
    shape = schemaDef.shape();
  } else if (typeof schemaDef.shape === "object") {
    shape = schemaDef.shape;
  }

  if (!shape || typeof shape !== "object") return undefined;

  return shape as Record<string, unknown>;
}

/**
 * Compare a path template's dynamic keys against a pathParams schema shape.
 */
export function comparePathParamsToTemplate(args: {
  pathKeys: readonly string[];
  shapeKeys: readonly string[];
}): { missingKeys: string[]; extraKeys: string[] } {
  const missingKeys = args.pathKeys.filter(
    (key) => !args.shapeKeys.includes(key),
  );
  const extraKeys = args.shapeKeys.filter(
    (key) => !args.pathKeys.includes(key),
  );
  return { missingKeys, extraKeys };
}

/**
 * Format a pathParams/template mismatch for error messages.
 *
 * Shared so server registration and OpenAPI generation report the same
 * diagnostic details.
 */
export function formatPathParamsMismatch(args: {
  missingKeys: readonly string[];
  extraKeys: readonly string[];
}): string {
  return [
    args.missingKeys.length > 0
      ? `missing pathParams keys: ${args.missingKeys.join(", ")}`
      : undefined,
    args.extraKeys.length > 0
      ? `extra pathParams keys: ${args.extraKeys.join(", ")}`
      : undefined,
  ]
    .filter(Boolean)
    .join("; ");
}

/** Compare an introspected query schema's fields with its transport fields. */
export function compareQueryTransportFields(args: {
  schemaKeys: readonly string[];
  transportKeys: readonly string[];
}): boolean {
  const schemaKeys = [...args.schemaKeys].sort();
  const transportKeys = [...args.transportKeys].sort();
  return (
    schemaKeys.length === transportKeys.length &&
    schemaKeys.every((key, index) => key === transportKeys[index])
  );
}

/** Format a query schema/transport mismatch consistently across consumers. */
export function formatQueryTransportMismatch(args: {
  contractName: string;
  schemaKeys: readonly string[];
  transportKeys: readonly string[];
}): string {
  const schemaKeys = [...args.schemaKeys].sort();
  const transportKeys = [...args.transportKeys].sort();
  return `Query transport fields for contract "${args.contractName}" must match its query schema fields (schema: ${schemaKeys.join(", ") || "none"}; transport: ${transportKeys.join(", ") || "none"}).`;
}
