import { OnDestroy } from '@angular/core';
import { QueryList } from '@angular/core';
import { ContentChildren } from '@angular/core';
import {
  Component,
  ChangeDetectionStrategy,
  ViewEncapsulation,
  AfterContentInit,
  ContentChild,
  ViewChild,
} from '@angular/core';
import { Subscription } from 'rxjs';
import { ErrorComponent } from './error.component';
import { IndicatorComponent } from './indicator.component';
import { InputDirective } from './input.directive';
import { OutletDirective } from './outlet.directive';

@Component({
  selector: 'shared-input-control',
  templateUrl: 'input-control.component.html',
  styleUrls: ['input-control.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'shared-input-control' },
  encapsulation: ViewEncapsulation.None,
  standalone: false,
})
export class InputControlComponent implements AfterContentInit, OnDestroy {
  @ContentChild(InputDirective, { static: false, read: InputDirective })
  inputDirective: InputDirective;

  @ViewChild('errorOutlet', { read: OutletDirective, static: true })
  errorOutlet: OutletDirective;

  @ViewChild('indicatorOutlet', { read: OutletDirective, static: true })
  indicatorOutlet: OutletDirective;

  @ContentChildren(ErrorComponent, { read: ErrorComponent })
  errorTemplates: QueryList<ErrorComponent>;

  @ContentChildren(IndicatorComponent, { read: IndicatorComponent })
  indicatorTemplates: QueryList<IndicatorComponent>;

  currentError: ErrorComponent;

  currentIndicator: IndicatorComponent;

  private _subscriptions = new Subscription();

  constructor() {}

  renderError(): void {
    const errors = this.inputDirective?.control.errors;

    if (!errors || this.inputDirective?.control.pristine) {
      this.errorOutlet.viewContainerRef.clear();
      return;
    }

    const errorTemplate = this.errorTemplates.find((x) => x.error in errors);

    const tempalteChanged = errorTemplate !== this.currentError;

    if (tempalteChanged) {
      this.errorOutlet.viewContainerRef.clear();

      if (errorTemplate) {
        this.errorOutlet.viewContainerRef.createEmbeddedView(errorTemplate.tempalteRef);
      }
    }

    this.currentError = errorTemplate;
  }

  renderIndicator(): void {
    const { invalid, valid, dirty, disabled, enabled, pristine, pending } = this.inputDirective?.control;

    const indicatorTemplate = this.indicatorTemplates.find((i) => {
      // find the first indicator that matches the current state of the control
      // id state is undefined then it will not be checked
      return (
        (i.invalid === invalid || i.invalid === undefined) &&
        (i.valid === valid || i.valid === undefined) &&
        (i.dirty === dirty || i.dirty === undefined) &&
        (i.disabled === disabled || i.disabled === undefined) &&
        (i.enabled === enabled || i.enabled === undefined) &&
        (i.pristine === pristine || i.pristine === undefined) &&
        (i.pending === pending || i.pending === undefined)
      );
    });

    const tempalteChanged = indicatorTemplate !== this.currentIndicator;

    if (tempalteChanged) {
      this.indicatorOutlet.viewContainerRef.clear();

      if (indicatorTemplate) {
        this.indicatorOutlet.viewContainerRef.createEmbeddedView(indicatorTemplate.tempalteRef);
      }
    }

    this.currentIndicator = indicatorTemplate;
  }

  ngAfterContentInit(): void {
    if (!this.inputDirective) {
      console.error(new Error(`No input[sharedInput] found in \`<shared-input-control>\` component`));
    }

    const statusChangesSub = this.inputDirective.control.statusChanges.subscribe((s) => {
      this.renderError();
      this.renderIndicator();
    });
    const tempalteChangesSub = this.errorTemplates.changes.subscribe(() => {
      this.renderError();
      this.renderIndicator();
    });
    this.renderError();
    this.renderIndicator();

    this._subscriptions.add(statusChangesSub);
    this._subscriptions.add(tempalteChangesSub);
  }

  ngOnDestroy(): void {
    this._subscriptions.unsubscribe();
  }
}
