/**
 * Sanitization Utilities
 *
 * Provides HTML/XSS sanitization for user-controlled input.
 * Uses sanitize-html to prevent XSS attacks.
 */

import sanitizeHtml from "sanitize-html";

/**
 * Sanitize options for strict mode (default)
 * Allows only safe tags and attributes, removes all scripts and dangerous content
 */
const STRICT_OPTIONS: sanitizeHtml.IOptions = {
  allowedTags: ["p", "b", "i", "em", "strong", "a", "br", "ul", "ol", "li"],
  allowedAttributes: {
    a: ["href"],
  },
  allowedSchemes: ["http", "https", "mailto"],
  disallowedTagsMode: "discard",
};

/**
 * Sanitize a string value to prevent XSS attacks
 * Removes dangerous HTML tags, scripts, and event handlers
 *
 * @param value - String to sanitize
 * @param options - Optional sanitize-html options (defaults to strict)
 * @returns Sanitized string
 */
export function sanitizeString(
  value: string | null | undefined,
  options: sanitizeHtml.IOptions = STRICT_OPTIONS
): string {
  if (value === null || value === undefined) {
    return "";
  }

  // Convert non-strings to strings
  const stringValue = typeof value === "string" ? value : String(value);

  return sanitizeHtml(stringValue, options);
}

/**
 * Sanitize all string values in an object recursively
 * Preserves object structure and non-string values
 *
 * @param obj - Object to sanitize
 * @param options - Optional sanitize-html options
 * @returns Sanitized object
 */
export function sanitizeObject<T extends Record<string, unknown> | null | undefined>(
  obj: T,
  options: sanitizeHtml.IOptions = STRICT_OPTIONS
): T {
  // Handle null/undefined
  if (obj == null) {
    return obj;
  }

  // Handle arrays
  if (Array.isArray(obj)) {
    return obj.map((item: unknown) => {
      if (typeof item === "string") {
        return sanitizeString(item, options);
      }
      if (typeof item === "object" && item !== null) {
        return sanitizeObject(item as Record<string, unknown>, options);
      }
      return item;
    }) as unknown as T;
  }

  // Handle objects
  const sanitized: Record<string, unknown> = {};

  for (const [key, value] of Object.entries(obj)) {
    if (typeof value === "string") {
      sanitized[key] = sanitizeString(value, options);
    } else if (Array.isArray(value)) {
      sanitized[key] = sanitizeArray(value, options);
    } else if (typeof value === "object" && value !== null) {
      sanitized[key] = sanitizeObject(value as Record<string, unknown>, options);
    } else {
      sanitized[key] = value;
    }
  }

  return sanitized as T;
}

/**
 * Sanitize all string values in an array recursively
 * Preserves array structure and non-string values
 *
 * @param arr - Array to sanitize
 * @param options - Optional sanitize-html options
 * @returns Sanitized array
 */
export function sanitizeArray<T extends unknown[] = unknown[]>(
  arr: T,
  options: sanitizeHtml.IOptions = STRICT_OPTIONS
): T {
  if (!Array.isArray(arr)) {
    return arr;
  }

  const result = arr.map((item: unknown) => {
    if (typeof item === "string") {
      return sanitizeString(item, options);
    }
    if (Array.isArray(item)) {
      return sanitizeArray(item as unknown[], options);
    }
    if (typeof item === "object" && item !== null) {
      return sanitizeObject(item as Record<string, unknown>, options);
    }
    return item;
  });

  return result as T;
}
