import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  input,
  signal
} from '@angular/core';
import { AbstractControl } from '@angular/forms';

export type ValidationMessageMap = Partial<Record<string, string>>;

@Component({
  selector: 'app-form-validation-message',
  standalone: true,
  templateUrl: './form-validation-message.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class FormValidationMessage {
  readonly control = input<AbstractControl | null>(null);

  readonly fieldName = input('Field');

  readonly messages = input<ValidationMessageMap>({});

  readonly showWhenDirty = input(false);

  readonly submitAttempted = input(false);

  readonly customClass = input(
    'mt-1 text-[0.75rem] text-danger'
  );

  private readonly controlStateVersion = signal(0);

  constructor() {
    effect((onCleanup) => {
      const control = this.control();

      if (!control) {
        return;
      }

      const statusSubscription = control.statusChanges.subscribe(() => {
        this.controlStateVersion.update(value => value + 1);
      });

      const valueSubscription = control.valueChanges.subscribe(() => {
        this.controlStateVersion.update(value => value + 1);
      });

      const eventsSubscription = control.events?.subscribe(() => {
        this.controlStateVersion.update(value => value + 1);
      });

      onCleanup(() => {
        statusSubscription.unsubscribe();
        valueSubscription.unsubscribe();
        eventsSubscription?.unsubscribe();
      });
    });
  }

  readonly shouldShow = computed(() => {
    this.controlStateVersion();

    const control = this.control();

    if (!control || !control.invalid) {
      return false;
    }

    return (
      this.submitAttempted() ||
      (this.showWhenDirty() && control.dirty)
    );
  });

  readonly message = computed(() => {
    this.controlStateVersion();
    this.submitAttempted();

    const control = this.control();

    if (!control?.errors) {
      return '';
    }

    const errorKey = Object.keys(control.errors)[0];

    if (!errorKey) {
      return '';
    }

    const customMessage = this.messages()[errorKey];

    if (customMessage) {
      return customMessage;
    }

    return this.resolveDefaultMessage(
      errorKey,
      control.errors[errorKey]
    );
  });

  private resolveDefaultMessage(
    errorKey: string,
    errorValue: unknown
  ): string {
    const fieldName = this.fieldName();

    switch (errorKey) {
      case 'required':
        return `${fieldName} is required.`;

      case 'email':
        return `Enter a valid ${fieldName.toLowerCase()}.`;

      case 'minlength': {
        const error = errorValue as {
          requiredLength?: number;
          actualLength?: number;
        };

        return `${fieldName} must be at least ${
          error.requiredLength ?? 0
        } characters.`;
      }

      case 'maxlength': {
        const error = errorValue as {
          requiredLength?: number;
          actualLength?: number;
        };

        return `${fieldName} cannot exceed ${
          error.requiredLength ?? 0
        } characters.`;
      }

      case 'min': {
        const error = errorValue as {
          min?: number;
          actual?: number;
        };

        return `${fieldName} must be at least ${error.min}.`;
      }

      case 'max': {
        const error = errorValue as {
          max?: number;
          actual?: number;
        };

        return `${fieldName} cannot exceed ${error.max}.`;
      }

      case 'pattern':
        return `Enter a valid ${fieldName.toLowerCase()}.`;

      default:
        return `${fieldName} is invalid.`;
    }
  }
}
