import {
  attr,
  customElement,
  GenesisElement,
  html,
  observable,
  volatile,
} from '@genesislcap/web-core';
import type { AgentConfig } from '../../config/config';
import type { AgentPickerMode } from '../../main/main.types';
import { AGENT_PICKER_AUTO_VALUE } from './agent-picker.constants';
import { styles } from './agent-picker.styles';
import { AgentPickerTemplate } from './agent-picker.template';

export { AGENT_PICKER_AUTO_VALUE };

/** Tolerance for `scrollWidth > clientWidth` overflow detection (sub-pixel rounding). */
const OVERFLOW_TOLERANCE_PX = 1;

/**
 * User-facing agent picker rendered above the chat input.
 *
 * Renders `Auto` plus each top-level agent that has `manualSelection.enabled`.
 * Sub-agents and non-selectable specialists are excluded. Hidden when fewer
 * than two agents are configured or none are manually selectable.
 *
 * @fires agent-pinned - Fired when the user changes selection with the
 *   agent name or `null` for Auto. detail: `string | null`
 *
 * @beta
 */
@customElement({
  name: 'foundation-ai-agent-picker',
  template: html`
    ${(x: AgentPicker) => AgentPickerTemplate(x.designSystemPrefix)}
  `,
  styles,
})
export class AgentPicker extends GenesisElement {
  /** Design-system tag prefix, e.g. `'rapid'` for `rapid-segmented-control`. */
  @attr({ attribute: 'design-system-prefix' }) designSystemPrefix: string = 'rapid';
  /** Picker variant, sourced from the assistant's `chatConfig.agentPicker`. */
  @observable mode: AgentPickerMode = 'disabled';
  /** Top-level agents passed to the assistant. */
  @observable agents: AgentConfig[] = [];
  /** Currently pinned agent name, or `null` for Auto. */
  @observable pinnedAgentName: string | null = null;

  /** @internal — set via the template `${ref(...)}` binding. */
  segmentedRowEl?: HTMLElement;

  /**
   * @internal — true when the segmented row's content overflows its container.
   * Drives the fallback to `select` rendering. Only meaningful when
   * `mode === 'segmented-control'`.
   */
  @observable overflowing: boolean = false;

  private resizeObserver?: ResizeObserver;

  /** Top-level agents that opted in to manual selection. */
  @volatile
  get selectableAgents(): AgentConfig[] {
    return (this.agents ?? []).filter((a) => a.manualSelection?.enabled);
  }

  /** Whether the picker should render at all. */
  @volatile
  get visible(): boolean {
    if (this.mode === 'disabled') return false;
    if ((this.agents?.length ?? 0) <= 1) return false;
    if (this.selectableAgents.length === 0) return false;
    return true;
  }

  /**
   * The variant actually rendered: `'select'` if user-configured or if the
   * segmented row currently overflows; `'segmented-control'` otherwise.
   */
  @volatile
  get effectiveMode(): 'select' | 'segmented-control' {
    if (this.mode === 'segmented-control') {
      return this.overflowing ? 'select' : 'segmented-control';
    }
    return 'select';
  }

  override connectedCallback(): void {
    super.connectedCallback();
    if (typeof ResizeObserver === 'undefined') return;
    this.resizeObserver = new ResizeObserver(() => this.measureOverflow());
    this.resizeObserver.observe(this);
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback();
    this.resizeObserver?.disconnect();
    this.resizeObserver = undefined;
  }

  /**
   * Re-measure when agents change — newly added selectable agents may push the
   * segmented row past the container's width.
   */
  agentsChanged(): void {
    queueMicrotask(() => this.measureOverflow());
  }

  modeChanged(): void {
    queueMicrotask(() => this.measureOverflow());
  }

  /**
   * Measures whether the segmented row's natural content width exceeds the
   * container's visible width. Bails when the row isn't currently rendered
   * (e.g. when `mode !== 'segmented-control'`).
   */
  private measureOverflow(): void {
    const row = this.segmentedRowEl;
    if (!row) {
      this.overflowing = false;
      return;
    }
    this.overflowing = row.scrollWidth > row.clientWidth + OVERFLOW_TOLERANCE_PX;
  }

  selectAgent(name: string | null): void {
    this.pinnedAgentName = name;
    // Always emit — the host may close a slide-out panel on click even when
    // the selection didn't change.
    this.$emit('agent-pinned', name);
  }

  /**
   * Maps a value from the segmented-control or select (which uses
   * {@link AGENT_PICKER_AUTO_VALUE} for Auto) back to the `string | null` form
   * expected by `selectAgent`.
   */
  selectByValue(value: string): void {
    this.selectAgent(value === AGENT_PICKER_AUTO_VALUE ? null : value);
  }

  /** Current pinned agent name as a value the segmented-control / select can bind to. */
  @volatile
  get currentValue(): string {
    return this.pinnedAgentName ?? AGENT_PICKER_AUTO_VALUE;
  }
}
