import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer';

export type QuerySerializer = (query: Record<string, unknown>) => string;

export type BodySerializer = (body: unknown) => unknown;

type QuerySerializerOptionsObject = {
  allowReserved?: boolean;
  array?: Partial<SerializerOptions<ArrayStyle>>;
  object?: Partial<SerializerOptions<ObjectStyle>>;
};

export type QuerySerializerOptions = QuerySerializerOptionsObject & {
  /**
   * Per-parameter serialization overrides. When provided, these settings
   * override the global array/object settings for specific parameter names.
   */
  parameters?: Record<string, QuerySerializerOptionsObject>;
};

const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
  if (typeof value === 'string' || value instanceof Blob) {
    data.append(key, value);
  } else if (value instanceof Date) {
    data.append(key, value.toISOString());
  } else {
    data.append(key, JSON.stringify(value));
  }
};

const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => {
  if (typeof value === 'string') {
    data.append(key, value);
  } else {
    data.append(key, JSON.stringify(value));
  }
};

export const formDataBodySerializer = {
  bodySerializer: (body: unknown): FormData => {
    const data = new FormData();

    Object.entries(body as Record<string, unknown>).forEach(([key, value]) => {
      if (value === undefined || value === null) {
        return;
      }
      if (Array.isArray(value)) {
        value.forEach((v) => serializeFormDataPair(data, key, v));
      } else {
        serializeFormDataPair(data, key, value);
      }
    });

    return data;
  },
};

export const jsonBodySerializer = {
  bodySerializer: (body: unknown): string =>
    JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)),
};

export const urlSearchParamsBodySerializer = {
  bodySerializer: (body: unknown): string => {
    const data = new URLSearchParams();

    Object.entries(body as Record<string, unknown>).forEach(([key, value]) => {
      if (value === undefined || value === null) {
        return;
      }
      if (Array.isArray(value)) {
        value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
      } else {
        serializeUrlSearchParamsPair(data, key, value);
      }
    });

    return data.toString();
  },
};
