import { CommonModule } from '@angular/common';
import {
  AfterContentInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  EventEmitter,
  forwardRef,
  Input,
  OnDestroy,
  Output,
  ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';
import { Utils } from '../../utils/utils.util';
import { IconComponent } from '../icon/icon.component';
import { RadioComponent } from '../radio/radio.component';
import { RadioGroupOrientation } from './radio-group.model';

@Component({
  selector: 'nj-radio-group',
  templateUrl: './radio-group.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => RadioGroupComponent),
      multi: true
    }
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  standalone: true,
  imports: [RadioComponent, CommonModule, IconComponent]
})
export class RadioGroupComponent implements ControlValueAccessor, AfterContentInit, OnDestroy {
  /**
   * @ignore
   */
  private unsubscribe: Subject<void> = new Subject<void>();

  /**
   * @ignore
   */
  private _value;

  /**
   * @ignore
   */
  private _isDisabled;

  /**
   * @ignore
   */
  private _name;

  /**
   * @ignore
   */
  private _required = false;

  /**
   * @ignore
   */
  private _selected: RadioComponent;

  /**
   * @ignore
   */
  private radioGroupClassName = 'nj-radio-group';

  /**
   * Radio group selected value
   */
  @Input()
  set value(newValue: any) {
    if (this._value !== newValue) {
      this._value = newValue;

      this._updateSelectedRadioFromValue();
      this._checkSelectedRadioButton();
    }
  }

  get value(): any {
    return this._value;
  }

  /**
   * Radio group selected radio component
   */
  @Input()
  set selected(selected: RadioComponent) {
    this._selected = selected;
    this.value = selected ? selected.value : null;
    this._checkSelectedRadioButton();
  }

  get selected() {
    return this._selected;
  }

  /**
   * 'Whether the radio group is disabled or not, this will force all the children radios be disabled or not depending on this value
   */
  @Input()
  set isDisabled(value: boolean) {
    this._isDisabled = value;
    this._updateAllRadiosDisableValue();
    this._markRadiosForCheck();
  }

  get isDisabled(): boolean {
    return this._isDisabled;
  }

  /**
   * Radio group name, this will force all the children radios to have this name
   */
  @Input()
  set name(value: string) {
    this._name = value;
    this._updateAllRadiosName();
    this._markRadiosForCheck();
  }

  get name(): string {
    return this._name;
  }

  /**
   * Whether radio is required or not
   */
  @Input()
  set required(value: boolean) {
    this._required = value;
    this._markRadiosForCheck();
  }

  get required(): boolean {
    return this._required;
  }

  /**
   * Whether the radio group should be displayed in column or row
   */
  @Input() orientation: RadioGroupOrientation = 'column';

  /**
   * Legend to label the radio group
   */
  @Input() legend: string;

  /**
   * Message to provide when radio group is in error state
   */
  @Input() errorMessage?: string;

  /**
   * Whether the input group is in error state
   */
  @Input() hasError?: boolean;

  /**
   * Output that emits checked value on change only
   */
  @Output() readonly valueChange: EventEmitter<string> = new EventEmitter<string>();

  /**
   * All children radio components
   */
  @ContentChildren(RadioComponent, { descendants: true }) radios;

  constructor(private cdr: ChangeDetectorRef) {}

  /**
   * @ignore
   */
  private _onChange = (_: any): void => {};

  /**
   * @ignore
   */
  private _onTouched = (): void => {};

  ngAfterContentInit() {
    this._listenForRadioChange();
    this._updateSelectedRadioFromValue();
    this._updateAllRadiosDisableValue();
    this._updateAllRadiosName();
  }

  ngOnDestroy() {
    this.unsubscribe.next();
    this.unsubscribe.complete();
  }

  /**
   * @ignore
   */
  private _listenForRadioChange() {
    if (!this.radios) {
      return;
    }
    this.radios.forEach((radio) => {
      radio?.valueChange.pipe(takeUntil(this.unsubscribe)).subscribe((isSelected) => {
        if (isSelected) {
          this.value = radio?.value;
          this._onChange(this.value);
          this.valueChange.emit(this._value);
        }
      });
    });
  }

  /**
   * @ignore
   */
  private _updateAllRadiosDisableValue() {
    if (!this.radios || Utils.isUndefinedOrNull(this.isDisabled)) {
      return;
    }
    this.radios.forEach((radio) => {
      if (radio) {
        radio.isDisabled = this.isDisabled;
      }
    });
  }

  /**
   * @ignore
   */
  private _updateAllRadiosName() {
    if (!this.radios || Utils.isUndefinedOrNull(this.name)) {
      return;
    }
    this.radios.forEach((radio) => {
      if (radio) {
        radio.name = this.name;
      }
    });
  }

  /**
   * @ignore
   */
  private _checkSelectedRadioButton() {
    if (this._selected && !this._selected.isChecked) {
      this._selected.isChecked = true;
    }
  }

  /**
   * @ignore
   */
  private _markRadiosForCheck() {
    if (this.radios) {
      this.radios.forEach((radio) => radio?._markForCheck());
    }
  }

  /**
   * @ignore
   */
  private _updateSelectedRadioFromValue(): void {
    // If the value already matches the selected radio, do nothing.
    const isAlreadySelected = this._selected && this._selected.value === this._value;

    if (this.radios && !isAlreadySelected) {
      this._selected = null;
      this.radios.forEach((radio) => {
        if (!radio) {
          return;
        }
        radio.isChecked = this.value === radio.value;
        if (radio.isChecked) {
          this._selected = radio;
        }
      });
      this._markRadiosForCheck();
    }
  }

  /**
   * @ignore
   */
  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  /**
   * @ignore
   */
  registerOnTouched(fn: any): void {
    this._onTouched = fn;
  }

  /**
   * @ignore
   */
  setDisabledState(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
    this.cdr.markForCheck();
  }

  /**
   * @ignore
   */
  writeValue(value: any): void {
    this.value = value;
    this.cdr.markForCheck();
  }

  /**
   * @ignore
   */
  getOrientationClass(): string {
    if (this.orientation !== 'row') {
      return '';
    }
    return `${this.radioGroupClassName}--${this.orientation}`;
  }
}
