import {
  Directive,
  ElementRef,
  Injector,
  afterNextRender,
  contentChild,
  contentChildren,
  inject,
  signal,
} from '@angular/core';

let nextId = 0;

@Directive({
  selector: '[<%= camelize(name) %>]',
  exportAs: '<%= camelize(name) %>',
})
export class <%= classify(name) %> {
  private readonly injector = inject(Injector);
  private readonly instanceId = `<%= dasherize(name) %>-${nextId++}`;
  private readonly trigger = contentChild(<%= classify(name) %>Trigger);
  private readonly items = contentChildren(<%= classify(name) %>Item, { descendants: true });

  readonly isOpen = signal(false);

  buttonDomId(): string {
    return `${this.instanceId}-button`;
  }

  menuDomId(): string {
    return `${this.instanceId}-menu`;
  }

  isInsideTrigger(node: Node): boolean {
    return this.trigger()?.contains(node) ?? false;
  }

  openAndFocusFirst(): void {
    this.isOpen.set(true);
    // Focus moves into the menu only after Angular flushes the DOM update that un-hides it.
    afterNextRender(() => this.items()[0]?.focus(), { injector: this.injector });
  }

  close(): void {
    this.isOpen.set(false);
    this.trigger()?.focus();
  }

  select(item: <%= classify(name) %>Item): void {
    // Wire up the selected item's action here.
    this.close();
  }

  navigate(current: <%= classify(name) %>Item, event: KeyboardEvent): void {
    const items = this.items();
    const index = items.indexOf(current);
    if (index === -1) {
      return;
    }

    switch (event.key) {
      case 'Escape':
        event.preventDefault();
        this.close();
        break;
      case 'ArrowDown':
        event.preventDefault();
        items[(index + 1) % items.length]?.focus();
        break;
      case 'ArrowUp':
        event.preventDefault();
        items[(index - 1 + items.length) % items.length]?.focus();
        break;
      case 'Home':
        event.preventDefault();
        items[0]?.focus();
        break;
      case 'End':
        event.preventDefault();
        items[items.length - 1]?.focus();
        break;
      case 'Enter':
      case ' ':
        event.preventDefault();
        this.select(current);
        break;
    }
  }
}

@Directive({
  selector: '[<%= camelize(name) %>Trigger]',
  exportAs: '<%= camelize(name) %>Trigger',
  host: {
    'aria-haspopup': 'true',
    '[id]': 'root.buttonDomId()',
    '[attr.aria-expanded]': 'root.isOpen()',
    '[attr.aria-controls]': 'root.menuDomId()',
    '(click)': 'toggle()',
    '(keydown)': 'handleKeydown($event)',
  },
})
export class <%= classify(name) %>Trigger {
  protected readonly root = inject(<%= classify(name) %>);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);

  toggle(): void {
    if (this.root.isOpen()) {
      this.root.close();
      return;
    }

    this.root.openAndFocusFirst();
  }

  handleKeydown(event: KeyboardEvent): void {
    if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      this.root.openAndFocusFirst();
    }
  }

  contains(node: Node): boolean {
    return this.elementRef.nativeElement.contains(node);
  }

  focus(): void {
    this.elementRef.nativeElement.focus();
  }
}

@Directive({
  selector: '[<%= camelize(name) %>Menu]',
  exportAs: '<%= camelize(name) %>Menu',
  host: {
    role: 'menu',
    '[id]': 'root.menuDomId()',
    '[attr.aria-labelledby]': 'root.buttonDomId()',
    '[hidden]': '!root.isOpen()',
    '(document:click)': 'closeOnOutsideClick($event)',
  },
})
export class <%= classify(name) %>Menu {
  protected readonly root = inject(<%= classify(name) %>);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);

  closeOnOutsideClick(event: MouseEvent): void {
    if (!this.root.isOpen()) {
      return;
    }

    const target = event.target as Node;
    const insideMenu = this.elementRef.nativeElement.contains(target);
    if (!insideMenu && !this.root.isInsideTrigger(target)) {
      this.root.close();
    }
  }
}

@Directive({
  selector: '[<%= camelize(name) %>Item]',
  exportAs: '<%= camelize(name) %>Item',
  host: {
    role: 'menuitem',
    tabindex: '-1',
    '(click)': 'select()',
    '(keydown)': 'root.navigate(this, $event)',
  },
})
export class <%= classify(name) %>Item {
  protected readonly root = inject(<%= classify(name) %>);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);

  select(): void {
    this.root.select(this);
  }

  focus(): void {
    this.elementRef.nativeElement.focus();
  }
}
