/**
 * Content Security Policy (CSP) manager for XSS protection
 */

/**
 * CSP directive types
 */
export type CspDirective =
  | 'default-src'
  | 'script-src'
  | 'style-src'
  | 'img-src'
  | 'font-src'
  | 'connect-src'
  | 'media-src'
  | 'object-src'
  | 'frame-src'
  | 'child-src'
  | 'worker-src'
  | 'manifest-src'
  | 'base-uri'
  | 'form-action'
  | 'frame-ancestors'
  | 'plugin-types'
  | 'sandbox'
  | 'upgrade-insecure-requests'
  | 'block-all-mixed-content';

/**
 * CSP source values
 */
export type CspSource =
  | "'self'"
  | "'unsafe-inline'"
  | "'unsafe-eval'"
  | "'strict-dynamic'"
  | "'none'"
  | string; // URLs, nonces, hashes

/**
 * CSP policy configuration
 */
export interface CspPolicy {
  [directive: string]: CspSource[];
}

/**
 * CSP manager options
 */
export interface CspManagerOptions {
  /**
   * Whether to report CSP violations
   * @default false
   */
  reportViolations?: boolean;

  /**
   * URL to send CSP violation reports to
   */
  reportUri?: string;

  /**
   * Whether to use CSP in report-only mode
   * @default false
   */
  reportOnly?: boolean;
}

/**
 * Default secure CSP policy
 */
const DEFAULT_CSP_POLICY: CspPolicy = {
  'default-src': ["'self'"],
  'script-src': ["'self'"],
  'style-src': ["'self'", "'unsafe-inline'"], // Allow inline styles for component styling
  'img-src': ["'self'", 'data:', 'https:'],
  'font-src': ["'self'", 'https:'],
  'connect-src': ["'self'"],
  'media-src': ["'self'"],
  'object-src': ["'none'"],
  'frame-src': ["'none'"],
  'base-uri': ["'self'"],
  'form-action': ["'self'"],
  'frame-ancestors': ["'none'"],
  'upgrade-insecure-requests': [],
};

/**
 * Content Security Policy manager for XSS protection
 */
export class CspManager {
  private policy: CspPolicy;
  private options: CspManagerOptions;
  private nonces: Set<string> = new Set();

  /**
   * Create a new CSP manager
   * @param policy Initial CSP policy
   * @param options CSP manager options
   */
  constructor(policy: Partial<CspPolicy> = {}, options: CspManagerOptions = {}) {
    this.policy = { ...DEFAULT_CSP_POLICY };
    // Merge user policy, ensuring all values are arrays
    Object.entries(policy).forEach(([key, value]) => {
      if (value) {
        this.policy[key] = value;
      }
    });
    this.options = {
      reportViolations: false,
      reportOnly: false,
      ...options,
    };
  }

  /**
   * Generate a cryptographically secure nonce for inline scripts/styles
   * @returns Base64 encoded nonce
   */
  generateNonce(): string {
    const array = new Uint8Array(16);
    crypto.getRandomValues(array);
    const nonce = btoa(String.fromCharCode(...array));
    this.nonces.add(nonce);
    return nonce;
  }

  /**
   * Add a nonce to the script-src directive
   * @param nonce The nonce to add
   */
  addScriptNonce(nonce: string): void {
    this.addSourceToDirective('script-src', `'nonce-${nonce}'`);
  }

  /**
   * Add a nonce to the style-src directive
   * @param nonce The nonce to add
   */
  addStyleNonce(nonce: string): void {
    this.addSourceToDirective('style-src', `'nonce-${nonce}'`);
  }

  /**
   * Add a hash to a directive for inline content
   * @param directive The CSP directive
   * @param hash The SHA hash (e.g., 'sha256-abc123...')
   */
  addHash(directive: CspDirective, hash: string): void {
    this.addSourceToDirective(directive, `'${hash}'`);
  }

  /**
   * Add a source to a CSP directive
   * @param directive The CSP directive
   * @param source The source to add
   */
  addSourceToDirective(directive: CspDirective, source: CspSource): void {
    if (!this.policy[directive]) {
      this.policy[directive] = [];
    }

    if (!this.policy[directive].includes(source)) {
      this.policy[directive].push(source);
    }
  }

  /**
   * Remove a source from a CSP directive
   * @param directive The CSP directive
   * @param source The source to remove
   */
  removeSourceFromDirective(directive: CspDirective, source: CspSource): void {
    if (this.policy[directive]) {
      this.policy[directive] = this.policy[directive].filter(s => s !== source);
    }
  }

  /**
   * Generate the CSP header value
   * @returns CSP header value string
   */
  generateHeader(): string {
    const directives = Object.entries(this.policy)
      .filter(([_, sources]) => sources.length > 0)
      .map(([directive, sources]) => {
        if (sources.length === 0) {
          return directive;
        }
        return `${directive} ${sources.join(' ')}`;
      });

    if (this.options.reportUri) {
      directives.push(`report-uri ${this.options.reportUri}`);
    }

    return directives.join('; ');
  }

  /**
   * Get the appropriate CSP header name
   * @returns CSP header name
   */
  getHeaderName(): string {
    return this.options.reportOnly
      ? 'Content-Security-Policy-Report-Only'
      : 'Content-Security-Policy';
  }

  /**
   * Create a strict CSP policy for maximum security
   * @returns New CspManager with strict policy
   */
  static createStrict(): CspManager {
    return new CspManager({
      'default-src': ["'none'"],
      'script-src': ["'self'"],
      'style-src': ["'self'"],
      'img-src': ["'self'"],
      'font-src': ["'self'"],
      'connect-src': ["'self'"],
      'media-src': ["'none'"],
      'object-src': ["'none'"],
      'frame-src': ["'none'"],
      'base-uri': ["'none'"],
      'form-action': ["'self'"],
      'frame-ancestors': ["'none'"],
    });
  }

  /**
   * Create a development-friendly CSP policy
   * @returns New CspManager with development policy
   */
  static createDevelopment(): CspManager {
    return new CspManager({
      'default-src': ["'self'"],
      'script-src': ["'self'", "'unsafe-eval'"], // Allow eval for dev tools
      'style-src': ["'self'", "'unsafe-inline'"],
      'img-src': ["'self'", 'data:', 'https:', 'http:'],
      'font-src': ["'self'", 'https:', 'data:'],
      'connect-src': ["'self'", 'ws:', 'wss:'], // Allow WebSocket for HMR
    });
  }
}

/**
 * Default CSP manager instance
 */
export const defaultCspManager = new CspManager();
