// TODO: Consider moving to ui/common (don't exist) or ui/overlay (don't exist) - this directive handles overlay scroll behavior, not layout
import {
  Directive,
  effect,
  inject,
  model,
  OnDestroy,
  output,
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { logger } from '@isa/core/logging';

/**
 * Directive that emits an event when scrolling occurs outside a specified element.
 *
 * This directive listens to all scroll events in capture phase and emits `closeOnScroll`
 * when scrolling happens anywhere except within the excluded element (e.g., dropdown overlay panel).
 *
 * Use case: Close dropdown when user scrolls the page, but keep it open when scrolling within dropdown options.
 *
 * @example
 * ```typescript
 * // As hostDirective with programmatic configuration:
 * hostDirectives: [{ directive: CloseOnScrollDirective, outputs: ['closeOnScroll'] }]
 *
 * // In component:
 * readonly #closeOnScroll = inject(CloseOnScrollDirective, { self: true });
 * readonly panel = viewChild<ElementRef<HTMLElement>>('panel');
 *
 * constructor() {
 *   effect(() => {
 *     this.#closeOnScroll.closeOnScrollWhen.set(this.isOpen());
 *     this.#closeOnScroll.closeOnScrollExclude.set(this.panel()?.nativeElement);
 *   });
 * }
 *
 * // In host:
 * host: { '(closeOnScroll)': 'close()' }
 * ```
 */
@Directive({
  selector: '[uiCloseOnScroll]',
  standalone: true,
})
export class CloseOnScrollDirective implements OnDestroy {
  readonly #logger = logger(() => ({ directive: 'CloseOnScrollDirective' }));
  #document = inject(DOCUMENT);
  #scrollListener?: (event: Event) => void;
  #isActive = false;

  /**
   * When true, the directive listens for scroll events.
   * Bind this to your open state (e.g., `closeOnScrollWhen.set(isOpen())`).
   */
  closeOnScrollWhen = model<boolean>(false);

  /**
   * Element to exclude from scroll detection.
   * If scroll occurs within this element or its children, the closeOnScroll event will NOT be emitted.
   */
  closeOnScrollExclude = model<HTMLElement | undefined>(undefined);

  /**
   * Emitted when scrolling occurs outside the excluded element.
   */
  closeOnScroll = output<void>();

  constructor() {
    // Auto-activate/deactivate based on closeOnScrollWhen input
    effect(() => {
      const shouldBeActive = this.closeOnScrollWhen();
      if (shouldBeActive && !this.#isActive) {
        this.#activate();
      } else if (!shouldBeActive && this.#isActive) {
        this.#deactivate();
      }
    });
  }

  #activate(): void {
    if (this.#isActive) {
      return;
    }

    this.#scrollListener = (event: Event) => {
      const target = event.target as HTMLElement;
      const excludeElement = this.closeOnScrollExclude();

      // Check if scroll happened within the excluded element
      if (excludeElement?.contains(target)) {
        return;
      }

      // Emit close event - scroll happened outside excluded element
      this.#logger.debug('Scroll detected outside panel, emitting close');
      this.closeOnScroll.emit();
    };

    // Use capture: true to catch scroll events from ALL elements (scroll events don't bubble)
    // Use passive: true for better performance (we don't call preventDefault)
    this.#document.defaultView?.addEventListener(
      'scroll',
      this.#scrollListener,
      {
        capture: true,
        passive: true,
      },
    );

    this.#isActive = true;
    this.#logger.debug('Activated scroll listener');
  }

  #deactivate(): void {
    if (!this.#isActive || !this.#scrollListener) {
      return;
    }

    this.#document.defaultView?.removeEventListener(
      'scroll',
      this.#scrollListener,
      { capture: true },
    );

    this.#scrollListener = undefined;
    this.#isActive = false;
    this.#logger.debug('Deactivated scroll listener');
  }

  ngOnDestroy(): void {
    this.#deactivate();
  }
}
