import { Directive, inject } from '@angular/core';
import { PageDataService } from 'src/app/core/base/page.data.service';
import { SpinnerService } from 'src/app/service/spinner.service';
import { FilterService } from 'src/app/service/filter.service';
import { FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';

@Directive()
export abstract class FormPageBase<
  T,
  TCreateInput,
  TUpdateInput,
  S extends PageDataService<T> = PageDataService<T>,
> {
  protected nodeId: number | undefined;
  protected node: T = this.new();
  form!: FormGroup;

  get isUpdate(): boolean {
    return this.nodeId !== undefined;
  }

  protected router = inject(Router);
  protected route = inject(ActivatedRoute);
  protected spinner = inject(SpinnerService);
  protected filterService = inject(FilterService);
  protected dataService = inject(PageDataService) as S;

  private backRoute: string = '..';

  protected constructor(idKey: string = 'id', backRoute: string = '..') {
    const id = this.route.snapshot.params[idKey];
    this.backRoute = backRoute;

    this.validateRoute(id);

    this.buildForm();
  }

  validateRoute(id: string | undefined) {
    const url = this.router.url;
    if (url.endsWith('create') && id !== undefined) {
      throw new Error('Route ends with create but id is defined');
    }
    if (url.endsWith('edit') && (id === undefined || isNaN(+id))) {
      throw new Error('Route ends with edit but id is not a number');
    }

    this.nodeId = id ? +id : undefined;
  }

  close() {
    const backRoute = this.nodeId ? `${this.backRoute}/..` : this.backRoute;

    this.router.navigate([backRoute], { relativeTo: this.route }).then(() => {
      this.filterService.onLoad.emit();
      this.spinner.hide();
    });
  }

  save(): void {
    if (this.isUpdate) {
      this.update(this.getUpdateInput());
    } else {
      this.create(this.getCreateInput());
    }
  }

  abstract new(): T;

  abstract buildForm(): void;

  abstract setForm(node: T): void;

  abstract create(obj: TCreateInput): void;

  abstract update(obj: TUpdateInput): void;

  abstract getCreateInput(): TCreateInput;

  abstract getUpdateInput(): TUpdateInput;
}
