import { Directive, ElementRef, HostListener } from '@angular/core';

/**
 * Directive that ensures only positive integers can be entered into number input fields.
 *
 * This directive provides three layers of protection:
 * 1. Keyboard input blocking - prevents invalid characters from being typed
 * 2. Paste sanitization - cleans pasted content to extract only positive integers
 * 3. General input validation - catches any other input methods (drag & drop, autofill, etc.)
 *
 * **Blocked characters:** `.`, `,`, `-`, `+`, `e`, `E`, and all other non-digit characters
 *
 * **Examples:**
 * - Typing `1-2-3` → only `123` appears (minus signs blocked)
 * - Pasting `-100` → becomes `100` (negative sign removed)
 * - Pasting `1.000` → becomes `1000` (decimal point removed)
 * - Pasting `1,58` → becomes `158` (comma removed)
 * - Typing `007` → becomes `7` (leading zeros removed)
 *
 * **Important Notes:**
 * - This directive removes ALL non-digit characters, so `1.58` becomes `158`, not `1` or `2`
 * - Leading zeros are automatically removed
 * - A single `0` input results in an empty field
 * - Works seamlessly with Angular forms (ngModel, formControl)
 *
 * @example
 * ```html
 * <!-- Basic usage -->
 * <input type="number" positiveIntegerInput />
 *
 * <!-- With ngModel -->
 * <input type="number" positiveIntegerInput [(ngModel)]="points" />
 *
 * <!-- With form control -->
 * <input type="number" positiveIntegerInput [formControl]="pointsControl" />
 * ```
 *
 * @see {@link https://angular.dev/guide/directives} Angular Directives Documentation
 */
@Directive({
  selector: 'input[type="number"][positiveIntegerInput]',
  standalone: true,
})
export class PositiveIntegerInputDirective {
  /**
   * Set of characters that should be blocked during keyboard input.
   * These characters are common in numeric input but not allowed for positive integers.
   */
  private readonly blockedKeys = new Set(['.', ',', '-', '+', 'e', 'E']);

  /**
   * Injects the ElementRef to get direct access to the native input element.
   * This is needed for the paste and input handlers to manipulate the input value.
   *
   * @param elementRef Reference to the host input element
   */
  constructor(private elementRef: ElementRef<HTMLInputElement>) {}

  /**
   * Handles keyboard input events to prevent invalid characters from being entered.
   *
   * This is the first line of defense - it blocks keys before they can affect the input value.
   * Blocked keys include: `.`, `,`, `-`, `+`, `e`, `E`
   *
   * Navigation keys (arrows, backspace, delete, tab, etc.) are NOT blocked,
   * allowing normal text editing operations.
   *
   * @example
   * User presses `-` → Event prevented, nothing happens
   * User presses `5` → Allowed, `5` appears in the input
   * User presses `.` → Event prevented, nothing happens
   *
   * @param event The keyboard event to evaluate
   */
  @HostListener('keydown', ['$event'])
  onKeyDown(event: KeyboardEvent): void {
    // Check if the pressed key is in our blocked list
    if (this.blockedKeys.has(event.key)) {
      // Prevent the key from affecting the input
      event.preventDefault();
    }
  }

  /**
   * Handles paste events to sanitize pasted content before it enters the input.
   *
   * This is the second line of defense - it prevents users from bypassing keyboard
   * restrictions by pasting invalid content (e.g., from Excel, calculators, or text editors).
   *
   * The handler:
   * 1. Prevents the default paste behavior
   * 2. Extracts text from the clipboard
   * 3. Sanitizes it to contain only positive integers (removes all non-digits)
   * 4. Sets the sanitized value in the input
   * 5. Triggers an input event so Angular forms (ngModel, formControl) detect the change
   *
   * @example
   * User pastes `-100` → Input value becomes `100`
   * User pastes `1.000` → Input value becomes `1000`
   * User pastes `1,58` → Input value becomes `158`
   * User pastes `abc123xyz` → Input value becomes `123`
   * User pastes `00042` → Input value becomes `42`
   *
   * @param event The clipboard event containing the pasted data
   */
  @HostListener('paste', ['$event'])
  onPaste(event: ClipboardEvent): void {
    // Prevent the default paste behavior to control what gets inserted
    event.preventDefault();

    // Get the text from the clipboard
    const pastedText = event.clipboardData?.getData('text');
    if (!pastedText) {
      return;
    }

    // Remove all non-digit characters and leading zeros
    const sanitized = this.sanitizeInput(pastedText);

    if (sanitized) {
      // Insert the sanitized value into the input
      const input = this.elementRef.nativeElement;
      input.value = sanitized;

      // Trigger input event to update Angular form binding (ngModel/formControl)
      // The 'bubbles: true' ensures the event propagates up to Angular's listeners
      input.dispatchEvent(new Event('input', { bubbles: true }));
    }
  }

  /**
   * Handles input events to catch any other input methods that bypass keydown and paste.
   *
   * This is the third line of defense - a catch-all for:
   * - Drag and drop operations
   * - Browser autofill/autocomplete
   * - Programmatic value changes
   * - Right-click paste (on some browsers)
   * - Voice input
   * - IME (Input Method Editor) input
   *
   * If the current input value contains invalid characters, they are removed and
   * the sanitized value is set back to the input.
   *
   * @example
   * User drags `-50` from a text document → Input value becomes `50`
   * Browser autofills `1.234` → Input value becomes `1234`
   *
   * @param event The input event triggered by any value change
   */
  @HostListener('input', ['$event'])
  onInput(event: Event): void {
    const input = event.target as HTMLInputElement;
    const value = input.value;

    // Sanitize the current value (catches any other ways of setting invalid values)
    const sanitized = this.sanitizeInput(value);

    // Only update if the value actually changed after sanitization
    // This prevents unnecessary updates and potential infinite loops
    if (value !== sanitized) {
      input.value = sanitized;
      // Trigger input event again to ensure Angular picks up the sanitized value
      // This is necessary because we're modifying the value programmatically
      input.dispatchEvent(new Event('input', { bubbles: true }));
    }
  }

  /**
   * Sanitizes a string to contain only positive integers.
   *
   * This method performs two operations:
   * 1. Removes all non-digit characters using regex `\D` (matches anything that's not 0-9)
   * 2. Removes leading zeros to prevent issues with number parsing
   *
   * **Examples:**
   * - `sanitizeInput('-100')` → `'100'`
   * - `sanitizeInput('1.000')` → `'1000'`
   * - `sanitizeInput('1,58')` → `'158'` (NOT `'1'` or `'2'`!)
   * - `sanitizeInput('+42')` → `'42'`
   * - `sanitizeInput('3.14e2')` → `'314'`
   * - `sanitizeInput('abc123xyz')` → `'123'`
   * - `sanitizeInput('00123')` → `'123'`
   * - `sanitizeInput('007')` → `'7'`
   * - `sanitizeInput('0')` → `''` (empty string)
   * - `sanitizeInput('abc')` → `''` (no digits found)
   *
   * **Important:** This method does NOT round or parse decimals.
   * It simply removes all non-digit characters, so `1.58` becomes `158`, not `1` or `2`.
   *
   * @param value The input string to sanitize
   * @returns A string containing only digits, with leading zeros removed
   *
   * @private This method is for internal use only
   */
  private sanitizeInput(value: string): string {
    // Remove all non-digit characters (including -, +, ., ,, e, E, spaces, etc.)
    // \D matches any character that is NOT a digit (0-9)
    let sanitized = value.replace(/\D/g, '');

    // Remove leading zeros
    // ^0+ matches one or more zeros at the start of the string
    // This prevents issues like '007' remaining as '007' instead of '7'
    // Note: A single '0' will become an empty string ''
    sanitized = sanitized.replace(/^0+/, '');

    return sanitized;
  }
}
