import { Directive, ElementRef, computed, contentChildren, inject, input, model } from '@angular/core';

type <%= classify(name) %>Id = string;

@Directive({
  selector: '[<%= camelize(name) %>]',
  exportAs: '<%= camelize(name) %>',
  host: {
    role: 'tree',
    '[attr.aria-label]': 'label() || null',
  },
})
export class <%= classify(name) %> {
  private readonly items = contentChildren(<%= classify(name) %>Item, { descendants: true });

  readonly label = input('');
  readonly activeId = model<<%= classify(name) %>Id | null>(null);

  private readonly visibleItems = computed(() => this.items().filter((item) => item.visible()));

  private readonly resolvedActiveId = computed<<%= classify(name) %>Id | null>(() => {
    const activeId = this.activeId();
    const visible = this.visibleItems();
    if (activeId !== null && visible.some((item) => item.value() === activeId)) {
      return activeId;
    }

    return visible[0]?.value() ?? null;
  });

  isActive(value: <%= classify(name) %>Id): boolean {
    return this.resolvedActiveId() === value;
  }

  setActive(item: <%= classify(name) %>Item): void {
    this.activeId.set(item.value());
  }

  // Up/Down move between visible items; Right expands or moves into children; Left collapses
  // or moves to the parent; Home/End jump to the first/last visible item (APG tree pattern).
  navigate(current: <%= classify(name) %>Item, event: KeyboardEvent): void {
    const visible = this.visibleItems();
    const index = visible.indexOf(current);
    if (index === -1) {
      return;
    }

    switch (event.key) {
      case 'ArrowDown':
        event.preventDefault();
        if (index < visible.length - 1) {
          this.focusItem(visible[index + 1]);
        }
        break;
      case 'ArrowUp':
        event.preventDefault();
        if (index > 0) {
          this.focusItem(visible[index - 1]);
        }
        break;
      case 'ArrowRight':
        event.preventDefault();
        if (current.hasChildren()) {
          if (!current.expanded()) {
            current.expanded.set(true);
          } else if (index < visible.length - 1) {
            this.focusItem(visible[index + 1]);
          }
        }
        break;
      case 'ArrowLeft':
        event.preventDefault();
        if (current.hasChildren() && current.expanded()) {
          current.expanded.set(false);
        } else if (current.parent) {
          this.focusItem(current.parent);
        }
        break;
      case 'Home':
        event.preventDefault();
        if (visible.length > 0) {
          this.focusItem(visible[0]);
        }
        break;
      case 'End':
        event.preventDefault();
        if (visible.length > 0) {
          this.focusItem(visible[visible.length - 1]);
        }
        break;
      case 'Enter':
      case ' ':
        event.preventDefault();
        if (current.hasChildren()) {
          current.expanded.update((value) => !value);
        }
        break;
    }
  }

  private focusItem(item: <%= classify(name) %>Item): void {
    this.activeId.set(item.value());
    item.focus();
  }
}

@Directive({
  selector: '[<%= camelize(name) %>Item]',
  exportAs: '<%= camelize(name) %>Item',
  host: {
    role: 'treeitem',
    '[attr.aria-expanded]': 'hasChildren() ? expanded() : null',
    '[attr.tabindex]': 'root.isActive(value()) ? 0 : -1',
    '(click)': 'activate($event)',
    '(keydown)': 'handleKeydown($event)',
  },
})
export class <%= classify(name) %>Item {
  readonly root = inject(<%= classify(name) %>);
  readonly parent = inject(<%= classify(name) %>Item, { skipSelf: true, optional: true });
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private readonly childItems = contentChildren(<%= classify(name) %>Item, { descendants: true });

  readonly value = input.required<<%= classify(name) %>Id>();
  readonly expanded = model(false);

  readonly hasChildren = computed(() => this.childItems().length > 0);
  readonly visible = computed((): boolean => !this.parent || (this.parent.expanded() && this.parent.visible()));

  // Nested items live inside their ancestors' DOM subtree, so click/keydown must not
  // bubble up and also re-trigger the parent item's own handlers.
  activate(event: MouseEvent): void {
    event.stopPropagation();
    this.root.setActive(this);
    if (this.hasChildren()) {
      this.expanded.update((value) => !value);
    }
  }

  handleKeydown(event: KeyboardEvent): void {
    event.stopPropagation();
    this.root.navigate(this, event);
  }

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

@Directive({
  selector: '[<%= camelize(name) %>Group]',
  exportAs: '<%= camelize(name) %>Group',
  host: {
    role: 'group',
    '[hidden]': '!parent?.expanded()',
  },
})
export class <%= classify(name) %>Group {
  protected readonly parent = inject(<%= classify(name) %>Item, { optional: true });
}
