import {
  ChangeDetectionStrategy,
  Component,
  computed,
  DestroyRef,
  effect,
  ElementRef,
  inject,
  input,
  model,
  signal,
  viewChild,
  viewChildren,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { logger } from '@isa/core/logging';
import { coerceNumberProperty } from '@angular/cdk/coercion';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
import { NgIcon, provideIcons } from '@ng-icons/core';
import {
  isaActionChevronUp,
  isaActionChevronDown,
  isaActionEdit,
} from '@isa/icons';
import { ActiveDescendantKeyManager, LiveAnnouncer } from '@angular/cdk/a11y';
import { TooltipDirective } from '@isa/ui/tooltip';
import { QuantityControlOptionComponent } from './quantity-control-option.component';
import {
  QUANTITY_CONSTRAINTS,
  OnChangeFn,
  OnTouchedFn,
  SUPPORTED_KEYS,
} from './quantity-control.types';

@Component({
  selector: 'shared-quantity-control',
  templateUrl: './quantity-control.component.html',
  styleUrls: ['./quantity-control.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  hostDirectives: [CdkOverlayOrigin],
  imports: [
    CdkConnectedOverlay,
    NgIcon,
    QuantityControlOptionComponent,
    TooltipDirective,
  ],
  providers: [
    provideIcons({ isaActionChevronUp, isaActionChevronDown, isaActionEdit }),
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: QuantityControlComponent,
      multi: true,
    },
  ],
  host: {
    'role': 'combobox',
    '[attr.aria-expanded]': 'isOpen()',
    '[attr.aria-haspopup]': '"listbox"',
    '[attr.aria-controls]': 'listboxId',
    '[attr.aria-activedescendant]': 'activeDescendantId()',
    '[attr.aria-disabled]': 'disabled()',
    '[attr.aria-label]': 'ariaLabel() || "Quantity selector"',
    '[class.disabled]': 'disabled()',
    '[class.open]': 'isOpen()',
    '[class.edit-mode]': 'isEditMode()',
    '(keydown)': 'onKeydown($event)',
    '[attr.tabindex]': 'disabled() ? -1 : 0',
  },
})
export class QuantityControlComponent implements ControlValueAccessor {
  readonly #logger = logger(() => ({
    component: 'QuantityControlComponent',
  }));

  private readonly elementRef = inject(ElementRef);
  private readonly destroyRef = inject(DestroyRef);
  private readonly liveAnnouncer = inject(LiveAnnouncer);

  // ControlValueAccessor callbacks - replaced by registerOnChange/registerOnTouched
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  private _onChange: OnChangeFn = () => {};
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  private _onTouched: OnTouchedFn = () => {};
  private timeouts = new Set<ReturnType<typeof setTimeout>>();

  /**
   * Unique ID for the listbox element
   */
  readonly listboxId = `quantity-control-listbox-${crypto.randomUUID()}`;

  /**
   * Current quantity value
   * @default 0
   */
  value = model<number>(0);

  /**
   * Whether the control is disabled
   * @default false
   */
  disabled = model(false);

  /**
   * Minimum selectable value (starting point for dropdown options).
   * @default 0
   */
  min = input<number, unknown>(0, {
    transform: coerceNumberProperty,
  });

  /**
   * Maximum selectable value (e.g., stock level, available quantity).
   * If undefined, there is no upper limit and "Edit" is always shown.
   * @default undefined
   */
  max = input<number | undefined, unknown>(undefined, {
    transform: (value: unknown) => {
      if (value === undefined || value === null) {
        return undefined;
      }
      return coerceNumberProperty(value);
    },
  });

  /**
   * Number of preset options to show in the dropdown before requiring "Edit".
   * Options shown: min, min+1, min+2, ... up to (min + presetLimit - 1).
   * If max <= presetLimit, no "Edit" option is shown.
   * @default 10
   */
  presetLimit = input<number, unknown>(10, {
    transform: coerceNumberProperty,
  });

  /**
   * Custom ARIA label for the combobox
   */
  ariaLabel = input<string>();

  /**
   * Dropdown open state
   */
  isOpen = signal(false);

  /**
   * Edit mode state (showing input overlay)
   */
  isEditMode = signal(false);

  /**
   * Current input value being typed in edit mode (for real-time validation)
   */
  currentInputValue = signal<string>('');

  /**
   * Flag to prevent double-exit from edit mode (Enter + blur)
   */
  private isExitingEditMode = false;

  /**
   * View children - auto-generated options in template
   */
  options = viewChildren(QuantityControlOptionComponent);

  /**
   * Reference to the scrollable options list container
   */
  optionsList = viewChild<ElementRef<HTMLUListElement>>('optionsList');

  /**
   * Reference to the edit mode input
   */
  editInput = viewChild<ElementRef<HTMLInputElement>>('editInput');

  /**
   * Reference to the tooltip directive for programmatic control
   */
  tooltipDir = viewChild(TooltipDirective);

  /**
   * Stored button width to maintain consistent sizing in edit mode
   */
  buttonWidth = signal<number | null>(null);

  /**
   * CDK Overlay origin for positioning
   */
  cdkOverlayOrigin = inject(CdkOverlayOrigin, { self: true });

  /**
   * Keyboard manager for dropdown navigation
   */
  private keyManager?: ActiveDescendantKeyManager<QuantityControlOptionComponent>;

  /**
   * Chevron icon based on dropdown state
   */
  chevronIcon = computed(() =>
    this.isOpen() ? 'isaActionChevronUp' : 'isaActionChevronDown',
  );

  /**
   * Active descendant ID for ARIA (current keyboard-focused option)
   */
  activeDescendantId = computed(() => {
    if (!this.isOpen() || !this.keyManager?.activeItem) {
      return null;
    }
    const activeItem = this.keyManager.activeItem;
    return `${this.listboxId}-option-${activeItem.value()}`;
  });

  /**
   * Validation message for the current input value
   * Returns empty string if valid, error message if invalid
   */
  validationMessage = computed(() => {
    const inputValue = this.currentInputValue();
    if (!inputValue) {
      return '';
    }

    const num = parseInt(inputValue, QUANTITY_CONSTRAINTS.PARSE_RADIX);
    if (isNaN(num)) {
      return '';
    }

    const minVal = this.min();
    const maxVal = this.max();

    if (num < minVal) {
      return `Minimum ist ${minVal}`;
    }

    if (maxVal !== undefined && num > maxVal) {
      return `Maximum ist ${maxVal}`;
    }

    return '';
  });

  /**
   * Auto-generated options: min to (min + presetLimit - 1) + optional "Edit"
   * Edit option is shown when max > presetLimit or max is undefined
   * Options are capped at max if defined
   */
  generatedOptions = computed(() => {
    const minVal = this.min();
    const maxVal = this.max();
    const limit = this.presetLimit();
    const opts: Array<{ value: number | 'edit'; label: string }> = [];

    // Calculate the effective upper bound for preset options
    const presetMax = minVal + limit - 1;
    const effectiveMax =
      maxVal !== undefined ? Math.min(presetMax, maxVal) : presetMax;

    // Generate preset options from min to effectiveMax
    for (let i = minVal; i <= effectiveMax; i++) {
      opts.push({ value: i, label: `${i}` });
    }

    // Add "Edit" option if:
    // - max is undefined (unlimited) OR
    // - max exceeds presetLimit (need Edit for higher values)
    const shouldShowEdit = maxVal === undefined || maxVal > presetMax;
    if (shouldShowEdit) {
      opts.push({ value: 'edit', label: 'Edit' });
    }

    return opts;
  });

  /**
   * Overlay minimum width (match button width)
   */
  get overlayMinWidth() {
    return this.elementRef.nativeElement.offsetWidth;
  }

  constructor() {
    // Setup cleanup on component destruction
    this.destroyRef.onDestroy(() => {
      this.cleanup();
    });

    // Reinitialize keyManager when options change
    effect(() => {
      const options = this.options();
      if (options.length > 0) {
        this.initializeKeyManager();
      }
    });
  }

  /**
   * Cleanup resources on component destruction
   */
  private cleanup(): void {
    this.keyManager?.destroy();
    this.clearAllTimeouts();
  }

  /**
   * Clear all scheduled timeouts
   */
  private clearAllTimeouts(): void {
    this.timeouts.forEach((timeout) => clearTimeout(timeout));
    this.timeouts.clear();
  }

  /**
   * Schedule a timeout and track it for cleanup
   */
  private scheduleTimeout(callback: () => void, delay = 0): void {
    const timeout = setTimeout(() => {
      this.timeouts.delete(timeout);
      callback();
    }, delay);
    this.timeouts.add(timeout);
  }

  writeValue(obj: unknown): void {
    const value = coerceNumberProperty(obj);
    const minVal = this.min();
    if (isNaN(value) || value < minVal) {
      this.#logger.warn(
        `Invalid value provided to QuantityControl: ${String(obj)}. Resetting to ${minVal}.`,
      );
      this.value.set(minVal);
    } else {
      this.value.set(value);
    }
  }

  registerOnChange(fn: OnChangeFn): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: OnTouchedFn): void {
    this._onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled.set(isDisabled);
  }

  /**
   * Initialize the keyboard manager for dropdown navigation
   */
  private initializeKeyManager(): void {
    this.keyManager?.destroy();

    this.keyManager =
      new ActiveDescendantKeyManager<QuantityControlOptionComponent>(
        this.options(),
      ).withWrap();

    // Subscribe to changes in the active item to scroll it into view
    this.keyManager.change
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => {
        this.scrollActiveItemIntoView();
      });
  }

  /**
   * Scroll the active keyboard-focused item into view
   */
  private scrollActiveItemIntoView(): void {
    const activeItem = this.keyManager?.activeItem;
    if (activeItem) {
      const activeElement = activeItem.elementRef.nativeElement;
      if (activeElement) {
        activeElement.scrollIntoView({
          block: 'nearest',
          inline: 'nearest',
        });
      }
    }
  }

  /**
   * Toggle dropdown open/closed
   */
  toggleDropdown() {
    if (this.disabled()) {
      return;
    }
    if (this.isOpen()) {
      this.close();
    } else {
      this.open();
    }
  }

  /**
   * Open dropdown
   */
  open(): void {
    if (this.disabled()) {
      return;
    }
    this.isOpen.set(true);
    this._onTouched();

    // Try to find and highlight the currently selected value
    this.scheduleTimeout(() => {
      const selectedOption = this.options().find(
        (opt: QuantityControlOptionComponent) => opt.value() === this.value(),
      );

      if (selectedOption) {
        this.keyManager?.setActiveItem(selectedOption);
      } else {
        // If current value not in dropdown, highlight first item
        this.keyManager?.setFirstItemActive();
      }
    });
  }

  /**
   * Close dropdown
   */
  close(): void {
    this.isOpen.set(false);
    // Return focus to the component
    this.elementRef.nativeElement.focus();
  }

  /**
   * Select a numeric value from dropdown
   */
  selectValue(n: number): void {
    this.value.set(n);
    this._onChange(n);
    this._onTouched();
    this.close();

    // Announce change to screen readers
    this.liveAnnouncer.announce(`Quantity changed to ${n}`, 'polite');
  }

  /**
   * Enter edit mode - replace button with input
   */
  enterEditMode(): void {
    // Capture current button width before switching modes
    const currentWidth = this.elementRef.nativeElement.offsetWidth;
    this.buttonWidth.set(currentWidth);

    this.isOpen.set(false); // Close dropdown
    this.isEditMode.set(true); // Switch to input mode
    this.isExitingEditMode = false; // Reset flag
    this.currentInputValue.set(this.value().toString()); // Initialize with current value

    // Announce edit mode to screen readers
    this.liveAnnouncer.announce(
      'Edit mode activated. Type a quantity and press Enter to confirm or Escape to cancel.',
      'polite',
    );

    // Auto-focus input after it renders and select all content for easy replacement
    this.scheduleTimeout(() => {
      const input = this.editInput()?.nativeElement;
      if (input) {
        input.focus();
        input.select(); // Select all text for easy replacement
      }
    });
  }

  /**
   * Exit edit mode - clamp value to valid range and update
   */
  exitEditMode(inputValue: string): void {
    // Prevent double-exit (e.g., Enter key triggering both keydown.enter and blur)
    if (this.isExitingEditMode) {
      return;
    }
    this.isExitingEditMode = true;

    const num = parseInt(inputValue, QUANTITY_CONSTRAINTS.PARSE_RADIX);
    const minVal = this.min();
    const maxVal = this.max();

    // Reject only if truly invalid (NaN)
    if (isNaN(num)) {
      this.liveAnnouncer.announce(
        `Invalid input. Please enter a valid number.`,
        'assertive',
      );
      this.isEditMode.set(false);
      this.elementRef.nativeElement.focus();
      return;
    }

    // Clamp value to valid range
    let clampedValue = Math.max(num, minVal); // Ensure >= min
    if (maxVal !== undefined) {
      clampedValue = Math.min(clampedValue, maxVal); // Ensure <= max (if defined)
    }

    // Determine announcement message
    let announcement: string;
    if (num < minVal) {
      announcement = `Adjusted to minimum ${clampedValue}`;
    } else if (maxVal !== undefined && num > maxVal) {
      announcement = `Adjusted to maximum ${clampedValue}`;
    } else {
      announcement = `Quantity set to ${clampedValue}`;
    }

    // Update value
    this.value.set(clampedValue);
    this._onChange(clampedValue);
    this._onTouched();
    this.liveAnnouncer.announce(announcement, 'polite');
    this.isEditMode.set(false);
    // Return focus to the component
    this.elementRef.nativeElement.focus();
  }

  /**
   * Cancel edit mode without saving
   */
  cancelEditMode(): void {
    // Prevent double-exit (e.g., Escape key triggering both keydown.escape and blur)
    if (this.isExitingEditMode) {
      return;
    }
    this.isExitingEditMode = true;

    this.isEditMode.set(false);
    this.liveAnnouncer.announce('Edit mode cancelled', 'polite');
    // Return focus to the component
    this.elementRef.nativeElement.focus();
  }

  /**
   * Handle input value changes in edit mode
   * Validates input and shows/hides tooltip based on validity
   */
  onInputChange(value: string): void {
    this.currentInputValue.set(value);

    const message = this.validationMessage();
    const tooltip = this.tooltipDir();

    if (message && tooltip) {
      // Show tooltip with validation message
      tooltip.show();
    } else if (tooltip) {
      // Hide tooltip if input is valid
      tooltip.hide();
    }
  }

  /**
   * Handle keyboard navigation in dropdown
   */
  onKeydown(event: KeyboardEvent): void {
    if (this.disabled()) {
      return;
    }

    if (this.isEditMode()) {
      return; // Let input handle its own keys
    }

    if (!this.isOpen()) {
      if (
        event.key === SUPPORTED_KEYS.ENTER ||
        event.key === SUPPORTED_KEYS.SPACE
      ) {
        event.preventDefault();
        event.stopPropagation(); // Prevent space from scrolling page
        this.open();
      }
      return;
    }

    // Dropdown is open - handle all navigation keys
    if (event.key === SUPPORTED_KEYS.ESCAPE) {
      event.preventDefault();
      this.close();
      return;
    }

    if (
      event.key === SUPPORTED_KEYS.ENTER ||
      event.key === SUPPORTED_KEYS.SPACE
    ) {
      event.preventDefault();
      event.stopPropagation(); // Prevent space from scrolling page
      const activeItem = this.keyManager?.activeItem;
      if (activeItem) {
        activeItem.select();
      }
      return;
    }

    // Arrow keys - prevent default and let key manager handle
    if (
      event.key === SUPPORTED_KEYS.ARROW_DOWN ||
      event.key === SUPPORTED_KEYS.ARROW_UP ||
      event.key === SUPPORTED_KEYS.HOME ||
      event.key === SUPPORTED_KEYS.END
    ) {
      event.preventDefault();
      this.keyManager?.onKeydown(event);
      return;
    }
  }
}
