/**
 * Combined account + user form for iam/account.
 * Overrides the codegen scaffold (cliter/iam/account.aurora.yaml — see excludedFiles).
 * See `openspec/specs/iam-account-management-ui/spec.md` for the contract.
 */
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  inject,
  Injector,
  input,
  output,
  signal,
} from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import {
  HlmPasswordStrength,
  HlmPasswordStrengthRules,
  itemLabelOf,
  matchOtherValidator,
  passwordRulesValidator,
  usePasswordGenerator,
  usePasswordToggle,
  ValidationMessagePipe,
} from '@aurora';
import { provideTranslocoScope, TranslocoDirective } from '@jsverse/transloco';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideEye, lucideEyeOff, lucideRefreshCw } from '@ng-icons/lucide';
import { HlmButtonImports } from '@spartan-ng/helm/button';
import { HlmFieldImports } from '@spartan-ng/helm/field';
import { HlmIconImports } from '@spartan-ng/helm/icon';
import { HlmInputImports } from '@spartan-ng/helm/input';
import { HlmLabelImports } from '@spartan-ng/helm/label';
import { HlmSelectImports } from '@spartan-ng/helm/select';
import { HlmSwitchImports } from '@spartan-ng/helm/switch';
import {
  IamAccountTypeEnum,
  OAuthClientGrantTypeEnum,
  type CoreLang,
  type IamAccount,
  type IamRole,
  type IamTag,
  type IamTenant,
  type OAuthClient,
} from '@src/src/app/aurora.types';
import { Subscription } from 'rxjs';
import { uniqueEmailValidator } from '../../shared/validators/unique-email.validator';
import { uniqueUsernameValidator } from '../../shared/validators/unique-username.validator';

const PASSWORD_GENERATOR_DEFAULTS = {
  length: 10,
  numbers: true,
  symbols: true,
  lowercase: true,
  uppercase: true,
  exclude: '"\'~`^()-_=+[{]}\\|;:,<.>/',
  strict: true,
};

const PASSWORD_RULES: HlmPasswordStrengthRules = {
  minLength: 8,
  digit: true,
  specialCharacter: true,
  lowerCase: true,
  upperCase: true,
};

@Component({
  selector: 'au-account-form',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    ReactiveFormsModule,
    TranslocoDirective,
    ValidationMessagePipe,
    NgIcon,
    HlmFieldImports,
    HlmInputImports,
    HlmLabelImports,
    HlmSelectImports,
    HlmSwitchImports,
    HlmButtonImports,
    HlmIconImports,
    HlmPasswordStrength,
  ],
  providers: [
    provideTranslocoScope('iam'),
    provideIcons({ lucideEye, lucideEyeOff, lucideRefreshCw }),
  ],
  template: `
    <ng-container *transloco="let t; scope: 'iam'">
      <form
        class="grid grid-cols-1 gap-x-4 gap-y-5 md:grid-cols-12"
        [formGroup]="signalForm"
        (ngSubmit)="submit()"
      >
        <!-- #region ACCOUNT block ─────────────────────────────────────────── -->
        <hlm-field class="col-span-12 md:col-span-3">
          <label
            hlmLabel
            for="type"
          >
            {{ t('iam.account.Type') }}
            <span
              aria-hidden="true"
              class="text-muted-foreground/70 align-top text-xs"
            >
              *
            </span>
          </label>
          <hlm-select
            formControlName="type"
            (valueChange)="onTypeChange($event)"
          >
            <hlm-select-trigger buttonId="type">
              <hlm-select-placeholder>{{ t('Select') }}</hlm-select-placeholder>
              <ng-template
                hlmSelectValueTemplate
                let-value
              >
                {{ t('iam.account.Type.' + value) }}
              </ng-template>
            </hlm-select-trigger>
            <hlm-select-content *hlmSelectPortal>
              <hlm-select-item value="USER">{{ t('iam.account.Type.USER') }}</hlm-select-item>
              <hlm-select-item value="SERVICE">{{ t('iam.account.Type.SERVICE') }}</hlm-select-item>
            </hlm-select-content>
          </hlm-select>
          <hlm-field-error>
            {{ signalForm.get('type')?.errors | validationMessage }}
          </hlm-field-error>
        </hlm-field>

        <hlm-field class="col-span-12 md:col-span-3">
          <label
            hlmLabel
            for="code"
          >
            {{ t('iam.account.Code') }}
          </label>
          <input
            id="code"
            hlmInput
            formControlName="code"
            maxlength="64"
          />
          <hlm-field-error>
            {{ signalForm.get('code')?.errors | validationMessage }}
          </hlm-field-error>
        </hlm-field>

        @if (showClientSelector()) {
          <hlm-field class="col-span-12 md:col-span-6">
            <label
              hlmLabel
              for="clientId"
            >
              {{ t('iam.account.Client') }}
              <span
                aria-hidden="true"
                class="text-muted-foreground/70 align-top text-xs"
              >
                *
              </span>
            </label>
            <hlm-select
              [itemToString]="clientItemToString"
              formControlName="clientId"
              (valueChange)="onClientChange($event)"
            >
              <hlm-select-trigger buttonId="clientId">
                <hlm-select-value placeholder="{{ t('Select') }}" />
              </hlm-select-trigger>
              <hlm-select-content *hlmSelectPortal>
                @for (client of filteredClients(); track client.id) {
                  <hlm-select-item [value]="client.id">{{ client.name }}</hlm-select-item>
                }
              </hlm-select-content>
            </hlm-select>
            <hlm-field-error>
              {{ signalForm.get('clientId')?.errors | validationMessage }}
            </hlm-field-error>
          </hlm-field>
        }

        <hlm-field class="col-span-12">
          <label
            hlmLabel
            for="email"
          >
            {{ t('iam.account.Email') }}
          </label>
          <input
            id="email"
            hlmInput
            formControlName="email"
            maxlength="128"
            autocomplete="username"
            style="text-transform: lowercase"
          />
          <hlm-field-error>
            {{ signalForm.get('email')?.errors | validationMessage }}
          </hlm-field-error>
        </hlm-field>

        @if (signalForm.get('type')?.value === 'SERVICE') {
          <hlm-field class="col-span-12 md:col-span-6">
            <label
              hlmLabel
              for="username-service"
            >
              {{ t('iam.account.Username') }}
              <span
                aria-hidden="true"
                class="text-muted-foreground/70 align-top text-xs"
              >
                *
              </span>
            </label>
            <input
              id="username-service"
              hlmInput
              formControlName="username"
              maxlength="128"
              autocomplete="username"
              style="text-transform: lowercase"
            />
            <hlm-field-error>
              {{ signalForm.get('username')?.errors | validationMessage }}
            </hlm-field-error>
          </hlm-field>
        }

        @if (showTenantsInput()) {
          <hlm-field class="col-span-12 md:col-span-8">
            <label
              hlmLabel
              for="tenantIds"
            >
              {{ t('iam.account.Tenants') }}
              <span
                aria-hidden="true"
                class="text-muted-foreground/70 align-top text-xs"
              >
                *
              </span>
            </label>
            <hlm-select-multiple
              [itemToString]="tenantItemToString"
              formControlName="tenantIds"
            >
              <hlm-select-trigger buttonId="tenantIds">
                <hlm-select-placeholder>{{ t('Select') }}</hlm-select-placeholder>
                <ng-template
                  hlmSelectValues
                  let-values
                >
                  <hlm-select-values-content>
                    {{ tenantItemToString(values[0]) }}
                    @if (values.length > 1) {
                      (+{{ values.length - 1 }})
                    }
                  </hlm-select-values-content>
                </ng-template>
              </hlm-select-trigger>
              <hlm-select-content *hlmSelectPortal>
                @for (option of tenantOptions(); track option.id) {
                  <hlm-select-item [value]="option.id">{{ option.name }}</hlm-select-item>
                }
              </hlm-select-content>
            </hlm-select-multiple>
            <hlm-field-error>
              {{ signalForm.get('tenantIds')?.errors | validationMessage }}
            </hlm-field-error>
          </hlm-field>

          <hlm-field class="col-span-12 pt-9 md:col-span-4">
            <label
              class="flex items-center"
              hlmLabel
              for="hasAddChildTenants"
            >
              <hlm-switch
                class="mr-2"
                inputId="hasAddChildTenants"
                formControlName="hasAddChildTenants"
              />
              {{ t('iam.account.AddChildTenants') }}
            </label>
          </hlm-field>
        }

        @if (tagOptions().length > 0) {
          <hlm-field class="col-span-12 md:col-span-4">
            <label
              hlmLabel
              for="tags"
            >
              {{ t('iam.account.Tags') }}
            </label>
            <hlm-select-multiple formControlName="tags">
              <hlm-select-trigger buttonId="tags">
                <hlm-select-placeholder>{{ t('Select') }}</hlm-select-placeholder>
                <ng-template
                  hlmSelectValues
                  let-values
                >
                  <hlm-select-values-content>
                    {{ values[0] }}
                    @if (values.length > 1) {
                      (+{{ values.length - 1 }})
                    }
                  </hlm-select-values-content>
                </ng-template>
              </hlm-select-trigger>
              <hlm-select-content *hlmSelectPortal>
                @for (tag of tagOptions(); track tag.name) {
                  <hlm-select-item [value]="tag.name">{{ tag.name }}</hlm-select-item>
                }
              </hlm-select-content>
            </hlm-select-multiple>
          </hlm-field>
        }

        @if (scopeOptions().length > 0) {
          <hlm-field class="col-span-12 md:col-span-3">
            <label
              hlmLabel
              for="scopes"
            >
              {{ t('iam.account.Scopes') }}
            </label>
            <hlm-select-multiple formControlName="scopes">
              <hlm-select-trigger buttonId="scopes">
                <hlm-select-placeholder>{{ t('Select') }}</hlm-select-placeholder>
                <ng-template
                  hlmSelectValues
                  let-values
                >
                  <hlm-select-values-content>
                    {{ values[0] }}
                    @if (values.length > 1) {
                      (+{{ values.length - 1 }})
                    }
                  </hlm-select-values-content>
                </ng-template>
              </hlm-select-trigger>
              <hlm-select-content *hlmSelectPortal>
                @for (scope of scopeOptions(); track scope) {
                  <hlm-select-item [value]="scope">{{ scope }}</hlm-select-item>
                }
              </hlm-select-content>
            </hlm-select-multiple>
          </hlm-field>
        }

        @if (roleOptions().length > 0) {
          <hlm-field class="col-span-12 md:col-span-4">
            <label
              hlmLabel
              for="roleIds"
            >
              {{ t('iam.account.Roles') }}
            </label>
            <hlm-select-multiple
              [itemToString]="roleItemToString"
              formControlName="roleIds"
            >
              <hlm-select-trigger buttonId="roleIds">
                <hlm-select-placeholder>{{ t('Select') }}</hlm-select-placeholder>
                <ng-template
                  hlmSelectValues
                  let-values
                >
                  <hlm-select-values-content>
                    {{ roleItemToString(values[0]) }}
                    @if (values.length > 1) {
                      (+{{ values.length - 1 }})
                    }
                  </hlm-select-values-content>
                </ng-template>
              </hlm-select-trigger>
              <hlm-select-content *hlmSelectPortal>
                @for (role of roleOptions(); track role.id) {
                  <hlm-select-item [value]="role.id">{{ role.name }}</hlm-select-item>
                }
              </hlm-select-content>
            </hlm-select-multiple>
          </hlm-field>
        }

        <hlm-field class="col-span-12 pt-9 md:col-span-3">
          <label
            class="flex items-center"
            hlmLabel
            for="isActive"
          >
            <hlm-switch
              class="mr-2"
              inputId="isActive"
              formControlName="isActive"
            />
            {{ t('iam.account.IsActive') }}
          </label>
        </hlm-field>
        <!-- #endregion ACCOUNT block -->

        <!-- #region USER block ────────────────────────────────────────────── -->
        @if (signalForm.get('type')?.value === 'USER') {
          <div class="col-span-12 mt-4 flex items-center gap-2 border-b pb-2 text-sm font-semibold">
            {{ t('iam.account.User') }}
          </div>

          <ng-container formGroupName="user">
            <hlm-field class="col-span-12 md:col-span-6">
              <label
                hlmLabel
                for="user-name"
              >
                {{ t('iam.account.Name') }}
                <span
                  aria-hidden="true"
                  class="text-muted-foreground/70 align-top text-xs"
                >
                  *
                </span>
              </label>
              <input
                id="user-name"
                hlmInput
                formControlName="name"
                maxlength="128"
              />
              <hlm-field-error>
                {{ signalForm.get('user.name')?.errors | validationMessage }}
              </hlm-field-error>
            </hlm-field>

            <hlm-field class="col-span-12 md:col-span-6">
              <label
                hlmLabel
                for="user-surname"
              >
                {{ t('iam.account.Surname') }}
                <span
                  aria-hidden="true"
                  class="text-muted-foreground/70 align-top text-xs"
                >
                  *
                </span>
              </label>
              <input
                id="user-surname"
                hlmInput
                formControlName="surname"
                maxlength="255"
              />
              <hlm-field-error>
                {{ signalForm.get('user.surname')?.errors | validationMessage }}
              </hlm-field-error>
            </hlm-field>
          </ng-container>

          <hlm-field class="col-span-12 md:col-span-6">
            <label
              hlmLabel
              for="username-user"
            >
              {{ t('iam.account.Username') }}
              <span
                aria-hidden="true"
                class="text-muted-foreground/70 align-top text-xs"
              >
                *
              </span>
            </label>
            <input
              id="username-user"
              hlmInput
              formControlName="username"
              maxlength="128"
              autocomplete="username"
              style="text-transform: lowercase"
            />
            <hlm-field-error>
              {{ signalForm.get('username')?.errors | validationMessage }}
            </hlm-field-error>
          </hlm-field>

          <ng-container formGroupName="user">
            @if (langOptions().length > 0) {
              <hlm-field class="col-span-12 md:col-span-6">
                <label
                  hlmLabel
                  for="user-langId"
                >
                  {{ t('iam.account.Language') }}
                </label>
                <hlm-select
                  [itemToString]="langItemToString"
                  formControlName="langId"
                >
                  <hlm-select-trigger buttonId="user-langId">
                    <hlm-select-value placeholder="{{ t('Select') }}" />
                  </hlm-select-trigger>
                  <hlm-select-content *hlmSelectPortal>
                    @for (lang of langOptions(); track lang.id) {
                      <hlm-select-item [value]="lang.id">{{ lang.name }}</hlm-select-item>
                    }
                  </hlm-select-content>
                </hlm-select>
              </hlm-field>
            }

            <hlm-field class="col-span-12 md:col-span-6">
              <label
                hlmLabel
                for="user-password"
              >
                {{ t('iam.account.Password') }}
                <span
                  aria-hidden="true"
                  class="text-muted-foreground/70 align-top text-xs"
                >
                  *
                </span>
              </label>
              <div class="flex gap-2">
                <input
                  id="user-password"
                  class="flex-1"
                  [type]="passwordToggle.type()"
                  hlmInput
                  formControlName="password"
                  maxlength="30"
                  autocomplete="new-password"
                />
                <button
                  type="button"
                  [attr.aria-label]="
                    passwordToggle.visible()
                      ? t('iam.account.HidePassword')
                      : t('iam.account.ShowPassword')
                  "
                  hlmBtn
                  variant="outline"
                  size="sm"
                  (click)="passwordToggle.toggle()"
                >
                  <ng-icon
                    [name]="passwordToggle.visible() ? 'lucideEyeOff' : 'lucideEye'"
                    hlm
                    size="sm"
                  />
                </button>
                <button
                  type="button"
                  [attr.aria-label]="t('iam.account.GeneratePassword')"
                  hlmBtn
                  variant="outline"
                  size="sm"
                  (click)="onGeneratePassword()"
                >
                  <ng-icon
                    name="lucideRefreshCw"
                    hlm
                    size="sm"
                  />
                </button>
              </div>
              <hlm-field-error>
                {{ signalForm.get('user.password')?.errors | validationMessage }}
              </hlm-field-error>
            </hlm-field>

            <div class="col-span-12 md:col-span-6">
              <au-hlm-password-strength
                [password]="passwordValue()"
                [rules]="passwordRules"
              />
            </div>

            <hlm-field class="col-span-12 md:col-span-6">
              <label
                hlmLabel
                for="user-repeatPassword"
              >
                {{ t('iam.account.RepeatPassword') }}
              </label>
              <input
                id="user-repeatPassword"
                type="password"
                hlmInput
                formControlName="repeatPassword"
                maxlength="30"
                autocomplete="new-password"
              />
              <hlm-field-error>
                {{ signalForm.get('user.repeatPassword')?.errors | validationMessage }}
              </hlm-field-error>
            </hlm-field>
          </ng-container>
        }
        <!-- #endregion USER block -->
      </form>
    </ng-container>
  `,
})
export class AccountFormComponent {
  // injections
  private readonly fb = inject(FormBuilder);
  private readonly injector = inject(Injector);

  // inputs
  readonly initial = input<IamAccount | null>(null);
  readonly mode = input.required<'new' | 'edit'>();
  readonly tenantOptions = input<IamTenant[]>([]);
  readonly roleOptions = input<IamRole[]>([]);
  readonly tagOptions = input<IamTag[]>([]);
  readonly clientOptions = input<OAuthClient[]>([]);
  readonly langOptions = input<CoreLang[]>([]);

  // outputs
  readonly save = output<IamAccount>();
  readonly cancel = output<void>();

  // composables
  protected readonly passwordToggle = usePasswordToggle();
  private readonly passwordGenerator = usePasswordGenerator(PASSWORD_GENERATOR_DEFAULTS);

  // signals
  protected readonly passwordValue = signal('');
  protected readonly scopeOptions = signal<string[]>([]);
  protected readonly passwordRules = PASSWORD_RULES;

  // form
  readonly signalForm: FormGroup = this.fb.group({
    id: [null],
    type: [null, [Validators.required]],
    code: ['', [Validators.maxLength(64)]],
    email: ['', { validators: [Validators.email, Validators.maxLength(128)], updateOn: 'blur' }],
    username: [
      '',
      {
        validators: [Validators.required, Validators.maxLength(128)],
        updateOn: 'blur',
      },
    ],
    isActive: [true],
    clientId: [null, [Validators.required]],
    tags: [[]],
    scopes: [[]],
    tenantIds: [[], [Validators.required]],
    hasAddChildTenants: [false],
    roleIds: [[]],
    dApplicationCodes: [[]],
    dPermissions: [{}],
    dTenants: [[]],
    user: this.fb.group({
      id: [null],
      name: [''],
      surname: [''],
      langId: [null],
      password: [''],
      repeatPassword: [''],
      isTwoFactorAuthenticationEnabled: [false],
    }),
  });

  // derived
  protected readonly filteredClients = computed<OAuthClient[]>(() => {
    const type = this.signalForm.get('type')?.value as IamAccountTypeEnum | null;
    if (!type) return this.clientOptions();
    const wantedGrantType =
      type === IamAccountTypeEnum.USER
        ? OAuthClientGrantTypeEnum.PASSWORD
        : OAuthClientGrantTypeEnum.CLIENT_CREDENTIALS;
    return this.clientOptions().filter((client) => client.grantType === wantedGrantType);
  });

  protected readonly showClientSelector = computed(() => {
    if (this.mode() !== 'new') return false;
    return this.filteredClients().length > 1;
  });

  protected readonly showTenantsInput = computed(() => this.tenantOptions().length > 1);

  protected readonly tenantItemToString = itemLabelOf(this.tenantOptions);
  protected readonly roleItemToString = itemLabelOf(this.roleOptions);
  protected readonly clientItemToString = itemLabelOf(this.clientOptions);
  protected readonly langItemToString = itemLabelOf(this.langOptions);

  private statusSubscriptions: Subscription[] = [];

  constructor() {
    // Sync password value into signal for the strength indicator
    this.statusSubscriptions.push(
      this.signalForm.get('user.password')!.valueChanges.subscribe((value: string) => {
        this.passwordValue.set(value ?? '');
        this.signalForm.get('user.repeatPassword')?.updateValueAndValidity({ emitEvent: false });
      }),
    );

    // Re-run autoselect logic when option lists change
    effect(() => {
      const tenants = this.tenantOptions();
      if (tenants.length === 1 && this.mode() === 'new') {
        this.signalForm.get('tenantIds')?.setValue([tenants[0].id]);
      }

      const clients = this.filteredClients();
      if (clients.length === 1 && this.mode() === 'new') {
        this.signalForm.get('clientId')?.setValue(clients[0].id);
        this.scopeOptions.set(this.extractScopeOptions(clients[0]));
      }
    });

    // Hydrate from `initial` when it changes
    effect(() => {
      const init = this.initial();
      if (init) {
        this.hydrate(init);
      }
    });

    // Wire async validators on email/username once injector is ready
    this.registerAsyncValidators();
  }

  // ─── methods ───────────────────────────────────────────────

  private hydrate(account: IamAccount): void {
    const userValue = (account as IamAccount & { user?: { id?: string } | null }).user ?? null;
    // Full form-shape value — every control listed. SignalForms migration target
    // is `model.set(value)` which replaces the entire state, so any field missing
    // here would land as null. Keep this object exhaustive.
    this.signalForm.reset({
      ...account,
      hasAddChildTenants: false,
      tenantIds: (account.tenants ?? []).flatMap((t) => (t?.id ? [t.id] : [])),
      roleIds: (account.roles ?? []).flatMap((r) => (r?.id ? [r.id] : [])),
      user: userValue
        ? {
            id: userValue.id,
            name: (userValue as { name?: string }).name ?? '',
            surname: (userValue as { surname?: string }).surname ?? '',
            langId: (userValue as { langId?: string | null }).langId ?? null,
            password: '',
            repeatPassword: '',
            isTwoFactorAuthenticationEnabled:
              (userValue as { isTwoFactorAuthenticationEnabled?: boolean })
                .isTwoFactorAuthenticationEnabled ?? false,
          }
        : {
            id: null,
            name: '',
            surname: '',
            langId: null,
            password: '',
            repeatPassword: '',
            isTwoFactorAuthenticationEnabled: false,
          },
    });

    // Edit-mode locks the type
    if (this.mode() === 'edit') {
      this.signalForm.get('type')?.disable({ emitEvent: false });
    }

    // Re-register async validators with avoidValues = [original email/username]
    this.registerAsyncValidators(account.email ?? undefined, account.username);

    // Initialize scopeOptions from the existing client's scopeOptions if available
    const client = (account as IamAccount & { client?: OAuthClient | null }).client;
    if (client) {
      this.scopeOptions.set(this.extractScopeOptions(client));
    }

    // Re-apply password validators for the current type
    this.applyPasswordValidators(account.type as IamAccountTypeEnum);
  }

  private registerAsyncValidators(avoidEmail?: string, avoidUsername?: string): void {
    const emailControl = this.signalForm.get('email');
    const usernameControl = this.signalForm.get('username');
    emailControl?.setAsyncValidators([
      uniqueEmailValidator(this.injector, avoidEmail ? [avoidEmail.toLowerCase()] : []),
    ]);
    usernameControl?.setAsyncValidators([
      uniqueUsernameValidator(this.injector, avoidUsername ? [avoidUsername.toLowerCase()] : []),
    ]);
    emailControl?.updateValueAndValidity({ emitEvent: false });
    usernameControl?.updateValueAndValidity({ emitEvent: false });
  }

  private applyPasswordValidators(type: IamAccountTypeEnum): void {
    const passwordControl = this.signalForm.get('user.password');
    const repeatControl = this.signalForm.get('user.repeatPassword');
    const nameControl = this.signalForm.get('user.name');
    const surnameControl = this.signalForm.get('user.surname');

    if (type !== IamAccountTypeEnum.USER) {
      passwordControl?.clearValidators();
      repeatControl?.clearValidators();
      nameControl?.clearValidators();
      surnameControl?.clearValidators();
    } else {
      const baseRules = [
        Validators.minLength(8),
        Validators.maxLength(30),
        passwordRulesValidator({
          digit: true,
          specialCharacter: true,
          lowerCase: true,
          upperCase: true,
        }),
      ];
      const required = this.mode() === 'new' ? [Validators.required] : [];

      passwordControl?.setValidators([...required, ...baseRules]);
      repeatControl?.setValidators([
        ...required,
        Validators.maxLength(30),
        matchOtherValidator('password'),
      ]);
      nameControl?.setValidators([Validators.required, Validators.maxLength(128)]);
      surnameControl?.setValidators([Validators.required, Validators.maxLength(255)]);
    }

    [passwordControl, repeatControl, nameControl, surnameControl].forEach((control) =>
      control?.updateValueAndValidity({ emitEvent: false }),
    );
  }

  private extractScopeOptions(client: OAuthClient): string[] {
    const raw = (client as OAuthClient & { scopeOptions?: unknown }).scopeOptions;
    if (Array.isArray(raw)) return raw.filter((item): item is string => typeof item === 'string');
    return [];
  }

  protected onTypeChange(value: IamAccountTypeEnum | null): void {
    if (!value) return;
    this.applyPasswordValidators(value);
    if (this.mode() === 'new') {
      const filtered = this.filteredClients();
      if (filtered.length === 1) {
        this.signalForm.get('clientId')?.setValue(filtered[0].id);
        this.scopeOptions.set(this.extractScopeOptions(filtered[0]));
      } else if (filtered.length === 0) {
        this.signalForm.get('clientId')?.setValue(null);
        this.scopeOptions.set([]);
      }
    }
  }

  protected onClientChange(clientId: string | null): void {
    const client = this.clientOptions().find((option) => option.id === clientId);
    this.scopeOptions.set(client ? this.extractScopeOptions(client) : []);
  }

  protected onGeneratePassword(): void {
    const generated = this.passwordGenerator.generate();
    const userGroup = this.signalForm.get('user') as FormGroup;
    userGroup.get('password')?.setValue(generated);
    userGroup.get('repeatPassword')?.setValue(generated);
    userGroup.get('password')?.markAsDirty();
    userGroup.get('repeatPassword')?.markAsDirty();
  }

  submit(): void {
    if (this.signalForm.pending) {
      // Wait for async validators to settle before evaluating
      const sub = this.signalForm.statusChanges.subscribe((status) => {
        if (status !== 'PENDING') {
          sub.unsubscribe();
          this.submit();
        }
      });
      return;
    }

    if (this.signalForm.invalid) {
      this.signalForm.markAllAsTouched();
      return;
    }

    const raw = this.signalForm.getRawValue() as IamAccount & {
      user?: Record<string, unknown> | null;
    };

    // GraphQL contract requires non-null ids on IamCreateAccountInput and IamCreateUserInput.
    // The backend handler overwrites these internally, but coerceInputValue rejects the request
    // before reaching the resolver if they are null. Generate them client-side for new flows.
    if (this.mode() === 'new') {
      if (!raw.id) raw.id = crypto.randomUUID();
      if (raw.type === IamAccountTypeEnum.USER && raw.user) {
        if (!raw.user['id']) raw.user['id'] = crypto.randomUUID();
        raw.user['accountId'] = raw.id;
      }
    }

    // Sanitize payload
    if (raw.user) {
      delete (raw.user as Record<string, unknown>)['repeatPassword'];
      if (raw.type !== IamAccountTypeEnum.USER) {
        raw.user = null;
      }
    }
    if (typeof raw.email === 'string') raw.email = raw.email.toLowerCase();
    if (typeof raw.username === 'string') raw.username = raw.username.toLowerCase();

    this.save.emit(raw);
  }
}
