import { Component, ElementRef, effect, input, output, untracked, viewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonComponent } from '@shared/ui/button/button.component';
import { AlertComponent } from '@shared/ui/alert/alert.component';
import { SpinnerComponent } from '@shared/ui/spinner/spinner.component';
import { StepperComponent } from '@shared/ui/stepper/stepper.component';

/** CIBG procesnavigatie primary-button copy for a non-final step: "Naar stap 2 - Werk".
    Shared so every wizard's `primaryLabel` reads the same way. */
export const naarStapLabel = (stepNumber: number, stepLabel: string) =>
  $localize`:@@wizard.naarStap:Naar stap ${stepNumber}:nummer: - ${stepLabel}:label:`;

/** A flat validation error pointing at a field: `id` matches the field's anchor. */
export interface WizardError {
  readonly id: string;
  readonly message: string;
}

export type WizardStatus = 'editing' | 'submitting' | 'submitted' | 'failed';

/**
 * Template: the canonical shell every wizard renders into, so they cannot drift.
 * It owns the consistent outline — CIBG stappenindicator (title merged in) + error
 * summary + the horizontal <form> + the CIBG procesnavigatie button row + the
 * submitting/submitted/failed states — and the a11y focus management.
 *
 * Presentational and unidirectional: all state stays in the wizard container
 * (the Elm-style store). Inputs flow down; the container reacts to the outputs
 * and dispatches messages. The step's own fields are projected as the default
 * slot; the success screen is projected via [wizardSuccess].
 */
@Component({
  selector: 'app-wizard-shell',
  imports: [FormsModule, ButtonComponent, AlertComponent, SpinnerComponent, StepperComponent],
  // CIBG-GAP EXTENSION: Foutmelding — the vendored build has no error-summary/
  // Veldvalidatie list pattern (verified absent from huisstijl.min.css); the
  // .es-title/.es-list rules below are the hand-rolled surface, see cibg-gaps.mdx.
  // They render inside a vendored `.feedback-error` alert (app-alert).
  styles: [
    `
      .es-title {
        margin: 0 0 var(--rhc-space-max-sm);
      }
      .es-list {
        margin: 0;
        padding-inline-start: var(--rhc-space-max-xl);
      }
      /* Default link color doesn't meet contrast on the error-alert's light-red surface. */
      .es-list a {
        color: var(--rhc-color-lintblauw-700);
      }
    `,
  ],
  template: `
    @switch (status()) {
      @case ('editing') {
        <app-stepper
          class="app-section"
          [steps]="steps()"
          [current]="current()"
          [processName]="processName()"
          [stepTitle]="stepTitle()"
          (stepSelected)="goToStep.emit($event)"
        />
        @if (errors().length) {
          <div
            #errorSummary
            tabindex="-1"
            role="alert"
            aria-labelledby="wizard-error-title"
            class="app-section"
          >
            <app-alert type="error">
              <h3 id="wizard-error-title" class="es-title" i18n="@@wizard.errorTitle">
                Er ging iets mis met uw invoer
              </h3>
              <ul class="es-list">
                @for (e of errors(); track e.id) {
                  <li>
                    <a [href]="'#' + e.id" (click)="goToField($event, e.id)">{{ e.message }}</a>
                  </li>
                }
              </ul>
            </app-alert>
          </div>
        }
        <form (ngSubmit)="primary.emit()" class="form-horizontal app-section">
          <div class="form-header">
            <div class="form-action">
              <span class="meta" i18n="@@form.verplichteVelden">* verplichte velden</span>
            </div>
          </div>
          <ng-content />
          <hr />
          <div class="d-flex flex-column flex-sm-row-reverse">
            <div class="m-0">
              <app-button type="submit" variant="primary">{{ primaryLabel() }}</app-button>
            </div>
            @if (canGoBack()) {
              <app-button
                type="button"
                variant="subtle"
                class="me-auto"
                (click)="back.emit()"
                i18n="@@wizard.terugVorige"
                >Terug naar vorige stap</app-button
              >
            }
          </div>
          <div class="app-section">
            <app-button
              type="button"
              variant="subtle"
              (click)="cancel.emit()"
              i18n="@@wizard.annuleren"
              >Annuleren</app-button
            >
          </div>
        </form>
      }
      @case ('submitting') {
        <app-spinner /> <span>{{ submittingLabel() }}</span>
      }
      @case ('submitted') {
        <ng-content select="[wizardSuccess]" />
      }
      @case ('failed') {
        <app-alert type="error">{{ errorMessage() }}</app-alert>
        <div class="app-section">
          <app-button variant="secondary" (click)="retry.emit()" i18n="@@wizard.opnieuwProberen"
            >Opnieuw proberen</app-button
          >
        </div>
      }
    }
  `,
})
export class WizardShellComponent {
  steps = input.required<string[]>();
  current = input.required<number>();
  stepTitle = input.required<string>();
  /** Overall process name, shown above the step title (e.g. "Herregistratie aanvragen"). */
  processName = input('');
  status = input.required<WizardStatus>();
  primaryLabel = input.required<string>();
  canGoBack = input(false);
  errors = input<readonly WizardError[]>([]);
  errorMessage = input('');
  submittingLabel = input($localize`:@@wizard.submitting:Aanvraag wordt verwerkt…`);

  primary = output<void>();
  back = output<void>();
  cancel = output<void>();
  retry = output<void>();
  /** A visited step number was clicked in the stepper — back-navigation only. */
  goToStep = output<number>();

  /** Error-summary link: focus the field instead of letting the browser navigate.
      A fragment href resolves against <base href="/">, not the current route, so
      a real navigation would reload to "/" and bounce to login. */
  protected goToField(ev: Event, id: string) {
    ev.preventDefault();
    document.getElementById(id)?.focus(); // focus() scrolls the input into view
  }

  private stepper = viewChild(StepperComponent);
  private errorSummary = viewChild<ElementRef<HTMLElement>>('errorSummary');

  constructor() {
    // A11y: move focus to the step title when the step changes (skip first run
    // so we don't grab focus on initial load). Tracks current(), which is value-
    // stable across keystrokes, so typing never steals focus.
    let firstStep = true;
    effect(() => {
      this.current();
      if (firstStep) {
        firstStep = false;
        return;
      }
      untracked(() => queueMicrotask(() => this.stepper()?.focusTitle()));
    });
    // A11y: when validation errors first appear (after a failed submit), move
    // focus to the error summary so it's announced. Only on the rising edge
    // (none → some): typing rebuilds the errors array each keystroke, and
    // re-focusing then would scroll the page up mid-edit. The summary keeps
    // role="alert", so content changes are still announced without the jump.
    let firstErr = true;
    let hadErrors = false;
    effect(() => {
      const has = this.errors().length > 0;
      if (firstErr) {
        firstErr = false;
        hadErrors = has;
        return;
      }
      if (has && !hadErrors)
        untracked(() => queueMicrotask(() => this.errorSummary()?.nativeElement.focus()));
      hadErrors = has;
    });
  }
}
