import {
  AfterViewInit,
  ChangeDetectionStrategy,
  Component,
  computed,
  contentChild,
  contentChildren,
  effect,
  ElementRef,
  inject,
  input,
  model,
  Signal,
  signal,
  viewChild,
} from '@angular/core';

import { isEqual } from 'lodash';

import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionChevronUp, isaActionChevronDown } from '@isa/icons';
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import {
  CdkConnectedOverlay,
  CdkOverlayOrigin,
  ConnectedPosition,
  ScrollStrategyOptions,
} from '@angular/cdk/overlay';
import { DropdownAppearance } from './dropdown.types';
import { DropdownService } from './dropdown.service';
import { CloseOnScrollDirective } from '@isa/ui/layout';
import { logger } from '@isa/core/logging';

import { DropdownOptionComponent } from './dropdown-option.component';
import { DropdownFilterComponent } from './dropdown-filter.component';
import { DROPDOWN_HOST, DropdownHost } from './dropdown-host';

@Component({
  selector: 'ui-dropdown',
  templateUrl: './dropdown.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  hostDirectives: [
    CdkOverlayOrigin,
    {
      directive: CloseOnScrollDirective,
      outputs: ['closeOnScroll'],
    },
  ],
  imports: [NgIconComponent, CdkConnectedOverlay],
  providers: [
    provideIcons({ isaActionChevronUp, isaActionChevronDown }),
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: DropdownButtonComponent,
      multi: true,
    },
    // Provide self for child DropdownOptionComponent to inject
    {
      provide: DROPDOWN_HOST,
      useExisting: DropdownButtonComponent,
    },
  ],
  host: {
    '[class]':
      '["ui-dropdown", appearanceClass(), isOpenClass(), disabledClass(), valueClass()]',
    'role': 'combobox',
    'aria-haspopup': 'listbox',
    '[attr.id]': 'id()',
    '[attr.tabindex]': 'disabled() ? -1 : tabIndex()',
    'aria-expanded': 'isOpen()',
    '(keydown)': 'keyManager?.onKeydown($event)',
    '(keydown.enter)': 'select(keyManager!.activeItem); close()',
    '(keydown.escape)': 'close()',
    '(keydown.space)': 'onSpaceKey($event)',
    '(click)':
      'disabled() ? $event.stopImmediatePropagation() : (isOpen() ? close() : open())',
    '(closeOnScroll)': 'close()',
  },
})
export class DropdownButtonComponent<T>
  implements ControlValueAccessor, AfterViewInit, DropdownHost<T>
{
  #logger = logger({ component: 'DropdownButtonComponent' });
  #dropdownService = inject(DropdownService);
  #scrollStrategy = inject(ScrollStrategyOptions);
  #closeOnScroll = inject(CloseOnScrollDirective, { self: true });

  readonly init = signal(false);
  private elementRef = inject(ElementRef);

  /** Reference to the options panel for scroll exclusion */
  optionsPanel = viewChild<ElementRef<HTMLElement>>('optionsPanel');

  get overlayMinWidth() {
    return this.elementRef.nativeElement.offsetWidth;
  }

  get blockScrollStrategy() {
    return this.#scrollStrategy.block();
  }

  /** Offset in pixels between the trigger and the overlay panel */
  readonly #overlayOffset = 12;

  /**
   * Position priority for the overlay panel.
   * Order: bottom-left, bottom-right, top-left, top-right,
   *        right-top, right-bottom, left-top, left-bottom
   */
  readonly overlayPositions: ConnectedPosition[] = [
    // Bottom left
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'top',
      offsetY: this.#overlayOffset,
    },
    // Bottom right
    {
      originX: 'end',
      originY: 'bottom',
      overlayX: 'end',
      overlayY: 'top',
      offsetY: this.#overlayOffset,
    },
    // Top left
    {
      originX: 'start',
      originY: 'top',
      overlayX: 'start',
      overlayY: 'bottom',
      offsetY: -this.#overlayOffset,
    },
    // Top right
    {
      originX: 'end',
      originY: 'top',
      overlayX: 'end',
      overlayY: 'bottom',
      offsetY: -this.#overlayOffset,
    },
    // Right top
    {
      originX: 'end',
      originY: 'top',
      overlayX: 'start',
      overlayY: 'top',
      offsetX: this.#overlayOffset,
    },
    // Right bottom
    {
      originX: 'end',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'bottom',
      offsetX: this.#overlayOffset,
    },
    // Left top
    {
      originX: 'start',
      originY: 'top',
      overlayX: 'end',
      overlayY: 'top',
      offsetX: -this.#overlayOffset,
    },
    // Left bottom
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'end',
      overlayY: 'bottom',
      offsetX: -this.#overlayOffset,
    },
  ];

  appearance = input<DropdownAppearance>(DropdownAppearance.AccentOutline);

  appearanceClass = computed(() => `ui-dropdown__${this.appearance()}`);

  disabledClass = computed(() => (this.disabled() ? 'disabled' : ''));

  valueClass = computed(() => (this.value() ? 'has-value' : ''));

  id = input<string>();

  value = model<T | null>(null);

  filter = model<string>('');

  tabIndex = input<number>(0);

  label = input<string>();

  disabled = model<boolean>(false);

  showSelectedValue = input<boolean>(true);

  equals = input(isEqual);

  options = contentChildren(DropdownOptionComponent);

  /** Optional filter component projected as content */
  filterComponent = contentChild(DropdownFilterComponent);

  cdkOverlayOrigin = inject(CdkOverlayOrigin, { self: true });

  selectedOption = computed(() => {
    const options = this.options();
    if (!options) {
      return undefined;
    }
    const currentValue = this.value();
    const equalsFn = this.equals();
    return options.find((option) => equalsFn(option.value(), currentValue));
  });

  keyManager?: ActiveDescendantKeyManager<DropdownOptionComponent<T>>;

  onChange?: (value: T | null) => void;

  onTouched?: () => void;

  isOpen = signal(false);

  isOpenClass = computed(() => (this.isOpen() ? 'open' : ''));

  isOpenIcon = computed(() =>
    this.isOpen() ? 'isaActionChevronUp' : 'isaActionChevronDown',
  );

  viewLabel = computed(() => {
    if (!this.showSelectedValue()) {
      return this.label() ?? this.value();
    }

    const selectedOption = this.selectedOption();

    if (!selectedOption || selectedOption.value() === null) {
      return this.label() ?? this.value();
    }

    return selectedOption.getLabel();
  });

  constructor() {
    effect(() => {
      if (!this.init()) {
        return;
      }
      this.keyManager?.destroy();
      this.keyManager = new ActiveDescendantKeyManager<
        DropdownOptionComponent<T>
      >(this.options())
        .withWrap()
        .skipPredicate((option) => option.disabled || option.isFiltered());
    });

    // Configure CloseOnScrollDirective: activate when open, exclude options panel
    effect(() => {
      this.#closeOnScroll.closeOnScrollWhen.set(this.isOpen());
      this.#closeOnScroll.closeOnScrollExclude.set(
        this.optionsPanel()?.nativeElement,
      );
    });
  }

  open(): void {
    this.#logger.debug('Opening dropdown');
    const selected = this.selectedOption();
    if (selected) {
      this.keyManager?.setActiveItem(selected);
    } else {
      this.keyManager?.setFirstItemActive();
    }
    this.#dropdownService.open(this); // #5298 Fix
    this.isOpen.set(true);

    // Focus filter input if present (after overlay renders)
    const filterComp = this.filterComponent();
    if (filterComp) {
      setTimeout(() => filterComp.focus());
    }
  }

  close(): void {
    this.#logger.debug('Closing dropdown');
    this.filter.set(''); // Clear filter on close
    this.isOpen.set(false);
    this.#dropdownService.close(this); // #5298 Fix
  }

  /**
   * Handles space key press.
   * Opens the dropdown if closed, or selects active item if open.
   */
  onSpaceKey(event: Event): void {
    event.preventDefault(); // Prevent page scroll

    if (this.isOpen()) {
      if (this.keyManager?.activeItem) {
        this.select(this.keyManager.activeItem);
      }
      this.close();
    } else {
      this.open();
    }
  }

  focusout(): void {
    // this.close();
  }

  /**
   * Handle keyboard events, forwarding to the keyManager for navigation.
   * Called by host binding and can be called by child components (e.g., filter).
   */
  onKeydown(event: KeyboardEvent): void {
    this.keyManager?.onKeydown(event);
  }

  /**
   * Select the currently active item from the keyManager.
   * Called by child components (e.g., filter) on Enter key.
   */
  selectActiveItem(): void {
    if (this.keyManager?.activeItem) {
      this.select(this.keyManager.activeItem);
    }
  }

  ngAfterViewInit(): void {
    this.init.set(true);
  }

  writeValue(obj: unknown): void {
    this.value.set(obj as T);
  }

  registerOnChange(fn: unknown): void {
    this.onChange = fn as (value: T | null) => void;
  }

  registerOnTouched(fn: unknown): void {
    this.onTouched = fn as () => void;
  }

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

  select(
    option: DropdownOptionComponent<T> | null,
    options: { emit: boolean } = { emit: true },
  ) {
    let nextValue: T | null = null;
    if (option === null) {
      this.value.set(null);
    } else {
      nextValue = option.value();
    }

    this.value.set(nextValue);

    if (options.emit) {
      this.onChange?.(nextValue);
    }
    this.onTouched?.();
  }
}
