import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
import { HlmComboboxImports } from '@spartan-ng/helm/combobox';
import type { Option } from '../types';

/**
 * Synchronous, searchable single-select for many-to-one relationships with a
 * medium catalog (20–500 options) preloaded by the route resolver. Filter is
 * client-side via Spartan's default combobox filter — no debounce, no fetch.
 *
 * For multi-select, see `<au-multiple-search-select>`.
 * For server-side search (>500 options), see `<au-async-search-select>`.
 *
 * @example
 * <au-search-select
 *   [inputId]="'clientId'"
 *   [value]="form.controls.clientId.value"
 *   [options]="clientOptions()"
 *   [emptyMessage]="t('Aurora.NoResults')"
 *   (valueChange)="form.controls.clientId.setValue($event)"
 *   (touched)="form.controls.clientId.markAsTouched()"
 * />
 */
@Component({
  selector: 'au-search-select',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [HlmComboboxImports],
  host: { class: 'block' },
  template: `
    <hlm-combobox
      class="w-full"
      [value]="selectedOption()"
      [disabled]="disabled()"
      [itemToString]="optionToString"
      [isItemEqualToValue]="optionEqual"
      (valueChange)="onValueChange($event)"
      (closed)="touched.emit()"
    >
      <hlm-combobox-input
        [inputId]="inputId()"
        [placeholder]="placeholder()"
      />
      <hlm-combobox-content *hlmComboboxPortal>
        <hlm-combobox-empty>{{ emptyMessage() }}</hlm-combobox-empty>
        <div hlmComboboxList>
          @for (opt of options(); track opt.value) {
            <hlm-combobox-item
              [value]="opt"
              [disabled]="opt.disabled ?? false"
            >
              {{ opt.label }}
            </hlm-combobox-item>
          }
        </div>
      </hlm-combobox-content>
    </hlm-combobox>

    @if (errorMessage(); as err) {
      <p class="text-destructive mt-1 text-xs">{{ err }}</p>
    }
  `,
})
export class SearchSelect {
  readonly value = input<string | null>(null);
  readonly options = input<Option[]>([]);
  readonly inputId = input<string>('');
  readonly placeholder = input<string>('Search...');
  readonly disabled = input<boolean>(false);
  readonly emptyMessage = input<string>('No results');
  readonly errorMessage = input<string | null>(null);

  readonly valueChange = output<string | null>();
  readonly touched = output<void>();

  protected readonly selectedOption = computed<Option | null>(() => {
    const current = this.value();
    if (current === null) return null;
    return this.options().find((o) => o.value === current) ?? null;
  });

  protected readonly optionToString = (opt: Option | null) => opt?.label ?? '';
  protected readonly optionEqual = (a: Option | null, b: Option | null) => a?.value === b?.value;

  protected onValueChange(option: Option | null): void {
    this.valueChange.emit(option?.value ?? null);
  }
}
