import { Component, computed, effect, inject, input, untracked } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormFieldComponent } from '@shared/ui/form-field/form-field.component';
import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
import { RadioGroupComponent, JA_NEE } from '@shared/ui/radio-group/radio-group.component';
import { ButtonComponent } from '@shared/ui/button/button.component';
import { AlertComponent } from '@shared/ui/alert/alert.component';
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
import { WizardShellComponent, WizardError, WizardStatus } from '@shared/layout/wizard-shell/wizard-shell.component';
import { createStore } from '@shared/application/store';
import { whenTag } from '@shared/kernel/fp';
import { BigProfileStore } from '@registratie/application/big-profile.store';
import {
  IntakeState,
  IntakeMsg,
  Answers,
  StepId,
  initial,
  reduce,
  STEPS,
  lageUren,
  hasProgress,
  SCHOLING_THRESHOLD_DEFAULT,
} from '@herregistratie/domain/intake.machine';
import { createDraftSync } from '@registratie/application/draft-sync';
import { IntakePolicyAdapter } from '@herregistratie/infrastructure/intake-policy.adapter';

/** Organism: a BRANCHING intake questionnaire. All state lives in one signal
    driven by the pure `reduce` (intake.machine.ts). Which step renders is derived
    from the answers via `visibleSteps`, never stored — so editing an earlier
    answer immediately changes the remaining steps. Answers are persisted to
    sessionStorage so a page reload keeps the user's progress (cleared on tab close). */
@Component({
  selector: 'app-intake-wizard',
  imports: [FormsModule, FormFieldComponent, TextInputComponent, RadioGroupComponent, ButtonComponent, AlertComponent, DataRowComponent, WizardShellComponent],
  template: `
    <app-wizard-shell
      [steps]="stepLabels"
      [current]="cursor()"
      [stepTitle]="stepTitle()"
      [status]="shellStatus()"
      [primaryLabel]="primaryLabel()"
      [canGoBack]="cursor() > 0"
      [errors]="errorList()"
      [errorMessage]="errorMessage()"
      (primary)="onPrimary()"
      (back)="dispatch({ tag: 'Back' })"
      (cancel)="restart()"
      (retry)="onRetry()">

      @switch (step()) {
        @case ('buitenland') {
          <app-form-field i18n-label="@@intake.q.buitenland" label="Heeft u de afgelopen 5 jaar buiten Nederland gewerkt?" fieldId="buitenlandGewerkt" required [error]="err('buitenlandGewerkt')">
            <app-radio-group name="buitenlandGewerkt" [options]="jaNee"
              [ngModel]="answers().buitenlandGewerkt ?? ''" (ngModelChange)="set('buitenlandGewerkt', $event)" />
          </app-form-field>
          @if (answers().buitenlandGewerkt === 'ja') {
            <app-form-field i18n-label="@@intake.q.land" label="In welk land?" fieldId="land" required [error]="err('land')">
              <app-text-input inputId="land" [ngModel]="answers().land ?? ''" (ngModelChange)="set('land', $event)" name="land" i18n-placeholder="@@intake.q.landPlaceholder" placeholder="bijv. België" />
            </app-form-field>
            <app-form-field i18n-label="@@intake.q.buitenlandseUren" label="Hoeveel uur heeft u daar gewerkt?" fieldId="buitenlandseUren" required [error]="err('buitenlandseUren')">
              <app-text-input inputId="buitenlandseUren" [ngModel]="answers().buitenlandseUren ?? ''" (ngModelChange)="set('buitenlandseUren', $event)" name="buitenlandseUren" i18n-placeholder="@@intake.q.buitenlandseUrenPlaceholder" placeholder="bijv. 800" />
            </app-form-field>
          }
        }
        @case ('werk') {
          <app-form-field i18n-label="@@intake.q.urenNl" label="Gewerkte uren in Nederland (afgelopen 5 jaar)" fieldId="uren" required [error]="err('uren')">
            <app-text-input inputId="uren" [ngModel]="answers().uren ?? ''" (ngModelChange)="set('uren', $event)" name="uren" i18n-placeholder="@@intake.q.urenNlPlaceholder" placeholder="bijv. 4160" />
          </app-form-field>
          @if (scholingZichtbaar()) {
            <app-form-field i18n-label="@@intake.q.scholing" label="U werkte relatief weinig uren. Heeft u aanvullende scholing gevolgd?" fieldId="scholingGevolgd" required [error]="err('scholingGevolgd')">
              <app-radio-group name="scholingGevolgd" [options]="jaNee"
                [ngModel]="answers().scholingGevolgd ?? ''" (ngModelChange)="set('scholingGevolgd', $event)" />
            </app-form-field>
          }
          @if (answers().scholingGevolgd === 'ja') {
            <app-form-field i18n-label="@@intake.q.punten" label="Behaalde nascholingspunten" fieldId="punten" required [error]="err('punten')">
              <app-text-input inputId="punten" [ngModel]="answers().punten ?? ''" (ngModelChange)="set('punten', $event)" name="punten" i18n-placeholder="@@intake.q.puntenPlaceholder" placeholder="bijv. 200" />
            </app-form-field>
          }
        }
        @case ('review') {
          <app-alert type="info" i18n="@@intake.review.controleer">Controleer uw antwoorden en dien de aanvraag in.</app-alert>
          <dl class="app-section">
            <app-data-row i18n-key="@@intake.review.buitenNl" key="Buiten NL gewerkt" [value]="answers().buitenlandGewerkt ?? '—'" />
            @if (answers().buitenlandGewerkt === 'ja') {
              <app-data-row i18n-key="@@intake.review.land" key="Land" [value]="answers().land ?? ''" />
              <app-data-row i18n-key="@@intake.review.buitenlandseUren" key="Buitenlandse uren" [value]="answers().buitenlandseUren ?? ''" />
            }
            <app-data-row i18n-key="@@intake.review.urenNl" key="Uren NL" [value]="answers().uren ?? ''" />
            @if (scholingZichtbaar()) {
              <app-data-row i18n-key="@@intake.review.scholing" key="Aanvullende scholing" [value]="answers().scholingGevolgd ?? ''" />
            }
            @if (answers().scholingGevolgd === 'ja') {
              <app-data-row i18n-key="@@intake.review.punten" key="Nascholingspunten" [value]="answers().punten ?? ''" />
            }
          </dl>
        }
      }

      <div wizardSuccess>
        <app-alert type="ok" i18n="@@intake.success">Uw aanvraag tot herregistratie is ontvangen.</app-alert>
        <div class="app-section">
          <app-button variant="secondary" (click)="restart()" i18n="@@intake.opnieuw">Opnieuw beginnen</app-button>
        </div>
      </div>
    </app-wizard-shell>
  `,
})
export class IntakeWizardComponent {
  private profile = inject(BigProfileStore);
  private policy = inject(IntakePolicyAdapter);
  private store = createStore<IntakeState, IntakeMsg>(initial, reduce);

  // Server-owned policy: the scholing threshold is fetched from the backend, not
  // hardcoded. The backend stays the authority and re-validates on submit.
  private policyRes = this.policy.policyResource();

  /** Optional seed so Storybook / the showcase can mount any state directly. */
  seed = input<IntakeState>(initial);

  readonly jaNee = JA_NEE;
  readonly state = this.store.model;
  readonly dispatch = this.store.dispatch;

  // Backend draft-sync (replaces sessionStorage); the intake has no uploads.
  private draftSync = createDraftSync({
    type: 'intake',
    snapshot: () => {
      const s = this.state();
      if (s.tag !== 'Answering' || !hasProgress(s)) return null;
      return { draft: s, stepIndex: s.cursor, stepCount: STEPS.length, documentIds: [] };
    },
    onResume: (draft) => this.dispatch({ tag: 'Seed', state: draft as IntakeState }),
    enabled: () => this.seed() === initial,
  });

  private answering = computed(() => whenTag(this.state(), 'Answering'));
  /** Public so the showcase can render the (fixed) step list next to the wizard. */
  readonly steps = STEPS;
  protected cursor = computed(() => this.answering()?.cursor ?? 0);
  protected answers = computed<Answers>(() => this.answering()?.answers ?? {});
  protected step = computed<StepId>(() => STEPS[Math.min(this.cursor(), STEPS.length - 1)]);
  /** Server-owned threshold from the policy endpoint (mirrored into machine state). */
  protected scholingThreshold = computed(() => this.answering()?.scholingThreshold ?? SCHOLING_THRESHOLD_DEFAULT);
  /** Whether the inline scholing question is shown (and required) in the 'werk' step. */
  protected scholingZichtbaar = computed(() => lageUren(this.answers(), this.scholingThreshold()));
  protected failedError = computed(() => whenTag(this.state(), 'Failed')?.error ?? '');

  // --- Presentational wiring for the shared wizard shell ---------------------
  readonly stepLabels = [$localize`:@@intake.step.buitenland:Buitenland`, $localize`:@@intake.step.werk:Werk`, $localize`:@@intake.step.controle:Controle`];
  private stepTitles: Record<StepId, string> = {
    buitenland: $localize`:@@intake.title.buitenland:Werken in het buitenland`,
    werk: $localize`:@@intake.title.werk:Werkervaring in Nederland`,
    review: $localize`:@@intake.title.review:Controleren en indienen`,
  };
  protected stepTitle = computed(() => this.stepTitles[this.step()]);
  protected primaryLabel = computed(() => (this.step() === 'review' ? $localize`:@@intake.indienen:Aanvraag indienen` : $localize`:@@wizard.volgende:Volgende`));
  protected errorMessage = computed(() => $localize`:@@wizard.indienenMislukt:Indienen mislukt:` + ` ${this.failedError()}`);
  protected shellStatus = computed<WizardStatus>(() => {
    switch (this.state().tag) {
      case 'Answering': return 'editing';
      case 'Submitting': return 'submitting';
      case 'Submitted': return 'submitted';
      case 'Failed': return 'failed';
    }
  });
  /** Current step's field errors, flattened for the shell's error summary. The
      field ids match the answer keys, so the summary anchors jump to the field. */
  protected errorList = computed<WizardError[]>(() => {
    const e = this.answering()?.errors ?? {};
    return (Object.keys(e) as (keyof Answers)[])
      .filter((k) => e[k])
      .map((k) => ({ id: k, message: e[k]! }));
  });

  protected err = (k: keyof Answers) => this.answering()?.errors[k] ?? '';
  protected set = (key: keyof Answers, value: string) => this.dispatch({ tag: 'SetAnswer', key, value });

  constructor() {
    // An explicit seed (stories/tests) wins; otherwise resume the backend draft
    // (`?aanvraag=<id>`) or start fresh. Persistence is the draftSync controller's job.
    const seeded = this.seed();
    queueMicrotask(() => (seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume()));
    // Apply the server-owned threshold into machine state as it arrives. Track
    // only the policy value; untrack the dispatch (it reads the state signal
    // internally, which would otherwise make this effect loop on its own write).
    effect(() => {
      const scholingThreshold = this.policyRes.value()?.scholingThreshold ?? SCHOLING_THRESHOLD_DEFAULT;
      untracked(() => this.dispatch({ tag: 'SetPolicy', scholingThreshold }));
    });
  }

  onPrimary() {
    const s = this.state();
    if (s.tag !== 'Answering') return;
    this.dispatch(this.step() === 'review' ? { tag: 'Submit' } : { tag: 'Next' });
    this.runIfSubmitting();
  }

  onRetry() {
    this.dispatch({ tag: 'Retry' });
    this.runIfSubmitting();
  }

  restart() {
    this.draftSync.reset();
    this.dispatch({ tag: 'Seed', state: initial });
  }

  /** The effect: when we enter Submitting, submit through the aanvraag lifecycle,
      flip the optimistic cross-page flag, then dispatch the outcome (commit/rollback). */
  private async runIfSubmitting() {
    const s = this.state();
    if (s.tag !== 'Submitting') return;
    this.profile.beginHerregistratie();
    const r = await this.draftSync.submit({ uren: s.data.uren });
    if (r.ok) {
      this.dispatch({ tag: 'SubmitConfirmed' });
      this.profile.confirmHerregistratie();
    } else {
      this.dispatch({ tag: 'SubmitFailed', error: r.error });
      this.profile.rollbackHerregistratie();
    }
  }
}
