import {
  NgFor,
  NgIf,
} from '@angular/common';
import {
  Component,
  Input,
  OnInit,
} from '@angular/core';
import {
  FormsModule,
  NgForm,
} from '@angular/forms';
import {
  NavigationExtras,
  Router,
  RouterLink,
} from '@angular/router';
import {
  TranslateModule,
  TranslateService,
} from '@ngx-translate/core';

import { ScriptDataService } from '../../core/data/processes/script-data.service';
import { RemoteData } from '../../core/data/remote-data';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { isEmpty } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { getProcessListRoute } from '../process-page-routing.paths';
import { Process } from '../processes/process.model';
import { ProcessParameter } from '../processes/process-parameter.model';
import { Script } from '../scripts/script.model';
import { ScriptParameter } from '../scripts/script-parameter.model';
import { ProcessParametersComponent } from './process-parameters/process-parameters.component';
import { ScriptHelpComponent } from './script-help/script-help.component';
import { ScriptsSelectComponent } from './scripts-select/scripts-select.component';

/**
 * Component to create a new script
 */
@Component({
  selector: 'ds-process-form',
  templateUrl: './process-form.component.html',
  styleUrls: ['./process-form.component.scss'],
  standalone: true,
  imports: [FormsModule, ScriptsSelectComponent, ProcessParametersComponent, RouterLink, ScriptHelpComponent, NgIf, NgFor, TranslateModule],
})
export class ProcessFormComponent implements OnInit {
  /**
   * The currently selected script
   */
  @Input() public selectedScript: Script = undefined;

  /**
   * The process to create
   */
  @Input() public process: Process = undefined;

  /**
   * The parameter values to use to start the process
   */
  @Input() public parameters: ProcessParameter[] = [];

  /**
   * Optional files that are used as parameter values
   */
  public files: File[] = [];

  /**
   * Message key for the header of the form
   */
  @Input() public headerKey: string;

  /**
   * Contains the missing parameters on submission
   */
  public missingParameters = [];

  constructor(
    private scriptService: ScriptDataService,
    private notificationsService: NotificationsService,
    private translationService: TranslateService,
    private router: Router) {
  }

  ngOnInit(): void {
    this.process = new Process();
  }

  /**
   * Validates the form, sets the parameters to correct values and invokes the script with the correct parameters
   * @param form
   */
  submitForm(form: NgForm) {
    if (isEmpty(this.parameters)) {
      this.parameters = [];
    }
    if (!this.validateForm(form) || this.isRequiredMissing()) {
      return;
    }

    const stringParameters: ProcessParameter[] = this.parameters.map((parameter: ProcessParameter) => {
      return {
        name: parameter.name,
        value: this.checkValue(parameter),
      };
    },
    );
    this.scriptService.invoke(this.selectedScript.id, stringParameters, this.files)
      .pipe(getFirstCompletedRemoteData())
      .subscribe((rd: RemoteData<Process>) => {
        if (rd.hasSucceeded) {
          const title = this.translationService.get('process.new.notification.success.title');
          const content = this.translationService.get('process.new.notification.success.content');
          this.notificationsService.success(title, content);
          this.sendBack(rd.payload);
        } else {
          const title = this.translationService.get('process.new.notification.error.title');
          const content = this.translationService.get('process.new.notification.error.content');
          this.notificationsService.error(title, content);
        }
      });
  }

  /**
   * Checks whether the parameter values are files
   * Replaces file parameters by strings and stores the files in a separate list
   * @param processParameter The parameter value to check
   */
  private checkValue(processParameter: ProcessParameter): string {
    if (typeof processParameter.value === 'object') {
      this.files = [...this.files, processParameter.value];
      return processParameter.value.name;
    }
    return processParameter.value;
  }

  /**
   * Validates the form
   * Returns false if the form is invalid
   * Returns true if the form is valid
   * @param form The NgForm object to validate
   */
  private validateForm(form: NgForm) {
    let valid = true;
    Object.keys(form.controls).forEach((key) => {
      if (form.controls[key].invalid) {
        form.controls[key].markAsDirty();
        valid = false;
      }
    });
    return valid;
  }

  private isRequiredMissing() {
    this.missingParameters = [];
    const setParams: string[] = this.parameters
      .map((param) => param.name);
    const requiredParams: ScriptParameter[] = this.selectedScript.parameters.filter((param) => param.mandatory);
    for (const rp of requiredParams) {
      if (!setParams.includes(rp.name)) {
        this.missingParameters.push(rp.name);
      }
    }
    return this.missingParameters.length > 0;
  }

  /**
   * Redirect the user to the processes overview page with the new process' ID,
   * so it can be highlighted in the overview table.
   * @param newProcess The newly created process
   * @private
   */
  private sendBack(newProcess: Process) {
    const extras: NavigationExtras = {
      queryParams: { new_process_id: newProcess.processId },
    };
    void this.router.navigate([getProcessListRoute()], extras);
  }

  updateScript($event: Script) {
    this.selectedScript = $event;
    this.parameters = undefined;
  }

  get generatedProcessName() {
    const paramsString = this.parameters?.map((p: ProcessParameter) => {
      const value = this.parseValue(p.value);
      return isEmpty(value) ? p.name : `${p.name} ${value}`;
    }).join(' ') || '';
    return isEmpty(paramsString) ? this.selectedScript.name : `${this.selectedScript.name} ${paramsString}`;
  }

  private parseValue(value: any) {
    if (typeof value === 'boolean') {
      return undefined;
    }
    if (value instanceof File) {
      return value.name;
    }
    return value?.toString();
  }
}