import { applyDecorators } from '@nestjs/common';
import { IsNumber, IsString, Matches, ValidateIf } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

/**
 * IsNumberOrStringDto - Custom decorator factory for validating numeric or numeric string fields
 *
 * This decorator factory creates a compound decorator that validates a field as either:
 * 1. A number (JavaScript number type)
 * 2. A string containing a numeric value (e.g., "123", "45.67", "-100")
 *
 * This is useful for API endpoints that need to accept numeric values in either
 * format, often seen when dealing with form data, query parameters, or flexible APIs.
 *
 * Features:
 * - Validates both number types and numeric strings
 * - Provides clear, field-specific error messages
 * - Includes Swagger/OpenAPI documentation with proper schema
 * - Uses conditional validation for performance
 *
 * @param example - Example value for Swagger documentation (number or string)
 * @param name - Field name for error messages (e.g., "age", "price", "quantity")
 * @returns A compound decorator combining validation and API documentation
 *
 * @example
 * // In a DTO class
 * export class ProductDto {
 *   @IsNumberOrStringDto(29.99, 'price')
 *   price: number | string;
 *
 *   @IsNumberOrStringDto(100, 'quantity')
 *   quantity: number | string;
 * }
 *
 * @example
 * // Valid inputs
 * { price: 29.99 }                 // number
 * { price: "29.99" }               // numeric string
 * { price: "100" }                 // integer string
 * { price: "-50.25" }              // negative decimal string
 *
 * @example
 * // Invalid inputs
 * { price: "twenty" }              // non-numeric string
 * { price: true }                  // boolean
 * { price: null }                  // null
 * { price: "123abc" }              // partially numeric string
 */
export function IsNumberOrStringDto(example: number | string, name: string) {
  return applyDecorators(
    // Conditional validation: If value is not a string, validate as number
    ValidateIf((_, value) => typeof value !== 'string'),
    IsNumber({}, { message: `${name} must be a number` }),

    // Conditional validation: If value is a string, validate as numeric string
    ValidateIf((_, value) => typeof value === 'string'),
    Matches(/^-?\d+(\.\d+)?$/, {
      message: `${name} must be a numeric string`,
    }),

    // Swagger/OpenAPI documentation
    ApiProperty({
      example,
      oneOf: [
        {
          type: 'string',
          example: typeof example === 'string' ? example : String(example),
        },
        {
          type: 'number',
          example: typeof example === 'number' ? example : Number(example),
        },
      ],
      description: `Accepts either a number or numeric string`,
      // Optional additional API properties:
      // required: true,
      // nullable: false,
    }),
  );
}
