import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  ViewEncapsulation,
  afterNextRender,
  computed,
  effect,
  inject,
  signal,
  viewChild,
} from '@angular/core';
import { Icon } from 'shared-ui';
import { ChatbotCitationPanel } from './chatbot-citation-panel';
import { ChatbotService } from './chatbot.service';
import type { CitationRef } from './chatbot.types';
// `ElementRef` is imported for typing `viewChild`. The host injection
// (`inject(ElementRef)`) is not needed here — the launcher reference
// is the only DOM anchor required for focus restoration.

interface SuggestionItem {
  readonly key: string;
  readonly label: string;
}

/**
 * Single host for the chatbot widget — mounts the launcher button,
 * the chat panel (windowed or fullscreen) and the citation-detail
 * side panel.
 *
 * Accessibility decisions, per the project's WCAG 2.2 AA + targeted
 * AAA baseline ([ADR-0016](../../../../../../docs/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md)):
 *
 *   - **Non-modal dialog.** `role="dialog"` + `aria-modal="false"`
 *     so the page chrome stays operable behind the panel; no focus
 *     trap required, no `inert` attribute on `<main>`. The
 *     launcher is fixed bottom-right; the panel anchors next to
 *     it (windowed) or covers the viewport with `inset` margins
 *     (fullscreen).
 *   - **No drag.** The stargate POC's draggable bubble is dropped;
 *     mouse-only gestures without keyboard equivalents fail the
 *     project's bar. The widget is anchored bottom-right with a
 *     fullscreen toggle.
 *   - **Live region for streaming.** The message list is
 *     `role="log" aria-live="polite" aria-relevant="additions"` so
 *     a screen reader announces the assistant's incoming tokens
 *     without re-reading the full history. The "typing" dots
 *     animation is hidden from AT (`aria-hidden`) and gated by
 *     `prefers-reduced-motion`.
 *   - **Focus return.** Closing the panel returns focus to the
 *     launcher (a hidden render of the launcher would lose this
 *     anchor; the component keeps the launcher reference alive
 *     and uses `viewChild` to focus it on close).
 *   - **44 × 44 touch targets.** Every interactive control (launcher,
 *     header buttons, send/stop, citation chips) honours the
 *     project minimum.
 *   - **Reduced motion.** Panel open/close transitions and typing
 *     dots are gated by `prefers-reduced-motion` (see SCSS).
 *
 * Strings are tagged with `$localize` per ADR-0019 — French is the
 * default locale; English bundles read the @@key catalogue at
 * build time.
 */
@Component({
  selector: 'app-chatbot-host',
  imports: [Icon, ChatbotCitationPanel],
  templateUrl: './chatbot-host.html',
  styleUrl: './chatbot-host.scss',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChatbotHost {
  private readonly service = inject(ChatbotService);

  protected readonly view = this.service.view;
  protected readonly isOpen = this.service.isOpen;
  protected readonly isFullscreen = this.service.isFullscreen;
  protected readonly isStreaming = this.service.isStreaming;
  protected readonly isEmpty = this.service.isEmpty;
  protected readonly messages = this.service.messages;
  protected readonly selectedCitation = this.service.selectedCitation;

  /** Local draft text. Cleared on submit. */
  protected readonly draft = signal('');

  /** Stable element ids for aria-* attributes. */
  protected readonly titleId = `chatbot-title-${nextHostId()}`;
  protected readonly inputId = `chatbot-input-${nextHostId()}`;

  /** Hard-coded starter prompts. v2 will move them server-side. */
  protected readonly suggestions: readonly SuggestionItem[] = [
    { key: 's1', label: $localize`:@@chatbot.suggestion.1:Find a document` },
    { key: 's2', label: $localize`:@@chatbot.suggestion.2:Request leave` },
    { key: 's3', label: $localize`:@@chatbot.suggestion.3:Contact IT support` },
    { key: 's4', label: $localize`:@@chatbot.suggestion.4:My open alerts` },
  ];

  // ---- localised strings ----------------------------------

  protected readonly launcherLabel = $localize`:@@chatbot.trigger.open:Open the assistant`;
  protected readonly titleLabel = $localize`:@@chatbot.title:AI assistant`;
  protected readonly closeLabel = $localize`:@@chatbot.close:Close the assistant`;
  protected readonly enterFullscreenLabel = $localize`:@@chatbot.fullscreen.enter:Open in full screen`;
  protected readonly exitFullscreenLabel = $localize`:@@chatbot.fullscreen.exit:Exit full screen`;
  protected readonly greetingLabel = $localize`:@@chatbot.empty.greeting:Ask me anything about the portal.`;
  protected readonly inputLabel = $localize`:@@chatbot.input.label:Your question for the assistant`;
  protected readonly placeholderLabel = $localize`:@@chatbot.input.placeholder:Write a message…`;
  protected readonly sendLabel = $localize`:@@chatbot.input.send:Send the message`;
  protected readonly stopLabel = $localize`:@@chatbot.input.stop:Stop generating`;
  protected readonly streamingLabel = $localize`:@@chatbot.streaming:Assistant is replying.`;
  protected readonly citationPanelTitleLabel = $localize`:@@chatbot.citation.panel.title:Citation details`;
  protected readonly citationPanelCloseLabel = $localize`:@@chatbot.citation.panel.close:Close citation details`;
  protected readonly citationSourceLabel = $localize`:@@chatbot.citation.source:Source`;
  protected readonly citationScoreLabel = $localize`:@@chatbot.citation.score:Score`;

  protected fullscreenLabel = computed(() =>
    this.isFullscreen() ? this.exitFullscreenLabel : this.enterFullscreenLabel,
  );

  // ---- focus restoration on close -------------------------

  /** Launcher button — focus target after the panel closes. */
  private readonly launcherRef = viewChild<ElementRef<HTMLButtonElement>>('launcher');

  /** Previous `isOpen` value so the effect runs the transition. */
  private wasOpen = false;

  constructor() {
    effect(() => {
      const open = this.isOpen();
      if (this.wasOpen && !open) {
        this.launcherRef()?.nativeElement.focus();
      }
      this.wasOpen = open;
    });

    afterNextRender(() => {
      this.wasOpen = this.isOpen();
    });
  }

  // ---- handlers -------------------------------------------

  protected onOpen(): void {
    this.service.open();
  }

  protected onClose(): void {
    this.service.close();
  }

  protected onToggleFullscreen(): void {
    this.service.toggleFullscreen();
  }

  protected onDraftInput(event: Event): void {
    this.draft.set((event.target as HTMLTextAreaElement).value);
  }

  protected onSubmit(event: Event): void {
    event.preventDefault();
    const prompt = this.draft();
    if (prompt.trim() === '' || this.isStreaming()) {
      return;
    }
    this.draft.set('');
    void this.service.send(prompt);
  }

  protected onEnterKey(event: Event): void {
    // Angular's `(keydown.enter)` binding narrows the dispatched
    // event by key, but the template type checker still surfaces
    // `Event`. Cast to read `shiftKey`; the binding guarantees the
    // event is a KeyboardEvent.
    if ((event as KeyboardEvent).shiftKey) {
      return; // Shift+Enter inserts a newline.
    }
    event.preventDefault();
    this.onSubmit(event);
  }

  protected onSelectSuggestion(label: string): void {
    if (this.isStreaming()) {
      return;
    }
    this.draft.set('');
    void this.service.send(label);
  }

  protected onStop(): void {
    this.service.stop();
  }

  protected onSelectCitation(citation: CitationRef | null): void {
    this.service.selectCitation(citation);
  }

  protected citationButtonLabel(citation: CitationRef): string {
    // i18n composition stays compatible with @angular/localize's
    // simple template-literal substitution.
    return $localize`:@@chatbot.citation.button:Show citation ${citation.index}:INDEX:`;
  }

  /**
   * Belt-and-suspenders: native `<button>` already activates on
   * Enter / Space, but an explicit keydown handler keeps the
   * launcher's contract obvious if the element changes shape
   * later. Typed as `Event` because Angular's template-type-
   * checker surfaces `Event` for the unparameterised `(keydown)`
   * binding under `exactOptionalPropertyTypes`.
   */
  protected onLauncherKeydown(event: Event): void {
    const keyEvent = event as KeyboardEvent;
    if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
      event.preventDefault();
      this.onOpen();
    }
  }
}

let hostIdCounter = 0;
function nextHostId(): number {
  hostIdCounter += 1;
  return hostIdCounter;
}
