/**
 * Zod 默认值辅助器（处理 `null | undefined` → 默认值）
 *
 * 防御性解析：后端对 Facebook / Apple 等 OAuth 用户可能返回 `null`（如 `email: null`），
 * Zod 默认的 `.default("")` 只接受 `undefined`，遇到 `null` 会抛 ZodError → 登录静默失败。
 * 用 `z.string().nullable().transform(v => v ?? "").default("")` 模式：
 *   - input 接受 `string | null | undefined`（保持 optional）
 *   - output 永远是 `string`（User 类的 `email: string` 声明不会破）
 *
 * 这是前端防御性解析（不要求后端修改）—— 和后端对齐更稳。
 */
import { z } from "zod";

type SchemaDefault<T extends z.ZodType> = Exclude<z.output<T>, undefined>;

/** `string | null | undefined` → `string` */
export function stringOr(defaultValue: string) {
  return z
    .string()
    .nullable()
    .transform((v) => v ?? defaultValue)
    .default(defaultValue);
}

/** `string | null | undefined` → `string`（默认空串） */
export const stringOrEmpty = stringOr("");

/** required `string | null` → `string`（缺字段仍报错，null 收敛为空串） */
export const requiredStringOrEmpty = z
  .string()
  .nullable()
  .transform((v) => v ?? "");

/** `string | null | undefined` → `string | null`（默认 null） */
export const stringOrNull = z.string().nullable().default(null);

/** `number | null | undefined` → `number` */
export function numberOr(defaultValue: number) {
  return z
    .number()
    .nullable()
    .transform((v) => v ?? defaultValue)
    .default(defaultValue);
}

/** `number | null | undefined` → `number`，默认值在 parse 时惰性计算 */
export function numberOrLazy(defaultValue: () => number) {
  return z
    .number()
    .nullable()
    .transform((v) => v ?? defaultValue())
    .default(defaultValue);
}

/** `number | null | undefined` → `number`（默认 0） */
export const numberOrZero = numberOr(0);

/** required `number | null` → `number`（缺字段仍报错，null 收敛为 0） */
export const requiredNumberOrZero = z
  .number()
  .nullable()
  .transform((v) => v ?? 0);

/** `number | null | undefined` → `number | null`（默认 null） */
export const numberOrNull = z.number().nullable().default(null);

/** `boolean | null | undefined` → `boolean` */
export function booleanOr(defaultValue: boolean) {
  return z
    .boolean()
    .nullable()
    .transform((v) => v ?? defaultValue)
    .default(defaultValue);
}

/** `boolean | null | undefined` → `boolean`（默认 false） */
export const booleanOrFalse = booleanOr(false);

/** `boolean | null | undefined` → `boolean`（默认 true） */
export const booleanOrTrue = booleanOr(true);

/** required `boolean | null` → `boolean`（缺字段仍报错，null 收敛为 false） */
export const requiredBooleanOrFalse = z
  .boolean()
  .nullable()
  .transform((v) => v ?? false);

/** `T[] | null | undefined` → `T[]`（默认空数组） */
export function arrayOrEmpty<T extends z.ZodType>(schema: T) {
  return z
    .array(schema)
    .nullable()
    .transform((v) => v ?? [])
    .default(() => []);
}

/** `Record<string, T> | null | undefined` → `Record<string, T>`（默认空对象） */
export function recordOrEmpty<T extends z.ZodType>(schema: T) {
  return z
    .record(z.string(), schema)
    .nullable()
    .transform((v) => v ?? {})
    .default(() => ({}));
}

/** `Record<string, unknown> | null | undefined` → `Record<string, unknown>` */
export const unknownRecordOrEmpty = recordOrEmpty(z.unknown());

/** `Record<string, unknown> | null | undefined` → `Record<string, unknown> | null` */
export const unknownRecordOrNull = z
  .record(z.string(), z.unknown())
  .nullable()
  .default(null);

function resolveDefault<T>(defaultValue: T | (() => T)): T {
  return typeof defaultValue === "function"
    ? (defaultValue as () => T)()
    : defaultValue;
}

/** `T | null | undefined` → `T`（默认对象会继续经过 schema 解析） */
export function schemaOr<T extends z.ZodType>(
  schema: T,
  defaultValue: SchemaDefault<T> | (() => SchemaDefault<T>),
) {
  const preprocessed = z.preprocess(
    (value) => value ?? resolveDefault(defaultValue),
    schema,
  );

  if (typeof defaultValue === "function") {
    return preprocessed.default(defaultValue as () => SchemaDefault<T>);
  }

  return preprocessed.default(defaultValue);
}

/** `T | null | undefined` → `T | null`（默认 null） */
export function schemaOrNull<T extends z.ZodType>(schema: T) {
  return schema.nullable().default(null);
}
