/**
 * Escape special HTML characters to prevent XSS attacks in template interpolations
 */

/**
 * Map of characters that need to be escaped in HTML
 */
const HTML_ESCAPE_MAP: Record<string, string> = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#39;',
  '/': '&#x2F;',
  '`': '&#x60;',
  '=': '&#x3D;',
};

/**
 * Regular expression to match characters that need to be escaped
 */
const HTML_ESCAPE_REGEX = /[&<>"'`=\/]/g;

/**
 * Escape HTML special characters to prevent XSS attacks
 * @param value String value to escape
 * @returns Escaped HTML string
 */
export function escapeHtml(value: string): string {
  return String(value).replace(HTML_ESCAPE_REGEX, (char) => HTML_ESCAPE_MAP[char] || char);
}

/**
 * Options for the template escaper
 */
export interface TemplateEscaperOptions {
  /**
   * Whether to escape all interpolated values by default
   * @default true
   */
  escapeByDefault: boolean;

  /**
   * Whether to allow raw HTML through the raw tag function
   * @default false
   */
  allowRawHtml: boolean;
}

/**
 * Template escaper for automatic HTML escaping in template interpolations
 */
export class TemplateEscaper {
  private options: TemplateEscaperOptions;

  /**
   * Create a new TemplateEscaper instance
   * @param options Template escaper options
   */
  constructor(options: Partial<TemplateEscaperOptions> = {}) {
    this.options = {
      escapeByDefault: true,
      allowRawHtml: false,
      ...options,
    };
  }

  /**
   * Escape a value for safe HTML interpolation
   * @param value Value to escape
   * @returns Escaped value
   */
  escape(value: unknown): string {
    if (value === null || value === undefined) {
      return '';
    }

    if (typeof value === 'object') {
      return escapeHtml(JSON.stringify(value));
    }

    return escapeHtml(String(value));
  }

  /**
   * Create a tagged template function that automatically escapes interpolated values
   * @returns Tagged template function
   */
  createEscapedTemplate(): (strings: TemplateStringsArray, ...values: unknown[]) => string {
    return (strings: TemplateStringsArray, ...values: unknown[]): string => {
      let result = '';

      for (let i = 0; i < strings.length; i++) {
        result += strings[i];

        if (i < values.length) {
          result += this.escape(values[i]);
        }
      }

      return result;
    };
  }

  /**
   * Create a tagged template function that allows raw HTML (use with caution)
   * @returns Tagged template function for raw HTML
   * @throws Error if raw HTML is not allowed in options
   */
  createRawTemplate(): (strings: TemplateStringsArray, ...values: unknown[]) => string {
    if (!this.options.allowRawHtml) {
      throw new Error('Raw HTML templates are not allowed. Set allowRawHtml: true in options to enable.');
    }

    return (strings: TemplateStringsArray, ...values: unknown[]): string => {
      let result = '';

      for (let i = 0; i < strings.length; i++) {
        result += strings[i];

        if (i < values.length) {
          result += String(values[i]);
        }
      }

      return result;
    };
  }
}

/**
 * Default template escaper instance
 */
export const defaultTemplateEscaper = new TemplateEscaper();

/**
 * Tagged template function that automatically escapes interpolated values
 * @example html\`<div>\${userInput}</div>\` // userInput is automatically escaped
 */
export const html = defaultTemplateEscaper.createEscapedTemplate();

/**
 * Create a raw HTML tagged template (use with caution)
 * Must be explicitly enabled by setting allowRawHtml: true
 */
export function createRawHtmlTemplate(): (strings: TemplateStringsArray, ...values: unknown[]) => string {
  const escaper = new TemplateEscaper({ allowRawHtml: true });
  return escaper.createRawTemplate();
}
