/* eslint-disable max-classes-per-file */
import { Injector } from '@angular/core';
import {
  combineLatest,
  Observable,
  of,
} from 'rxjs';
import {
  map,
  take,
} from 'rxjs/operators';

import {
  HandleThemeConfig,
  NamedThemeConfig,
  RegExThemeConfig,
  ThemeConfig,
  UUIDThemeConfig,
} from '../../../config/theme.config';
import { getDSORoute } from '../../app-routing-paths';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { HandleObject } from '../../core/shared/handle-object.model';
import {
  hasNoValue,
  hasValue,
  isNotEmpty,
} from '../empty.util';
import { HandleService } from '../handle.service';

export class Theme {
  constructor(public config: NamedThemeConfig) {
  }

  matches(url: string, dso: DSpaceObject): Observable<boolean> {
    return of(true);
  }
}

export class RegExTheme extends Theme {
  regex: RegExp;

  constructor(public config: RegExThemeConfig) {
    super(config);
    this.regex = new RegExp(this.config.regex);
  }

  matches(url: string, dso: DSpaceObject): Observable<boolean> {
    let match: RegExpMatchArray;
    const route = getDSORoute(dso);

    if (isNotEmpty(route)) {
      match = route.match(this.regex);
    }

    if (hasNoValue(match)) {
      match = url.match(this.regex);
    }

    return of(hasValue(match));
  }
}

export class HandleTheme extends Theme {

  constructor(public config: HandleThemeConfig,
              protected handleService: HandleService,
  ) {
    super(config);
  }

  matches<T extends DSpaceObject & HandleObject>(url: string, dso: T): Observable<boolean> {
    if (hasValue(dso?.handle)) {
      return combineLatest([
        this.handleService.normalizeHandle(dso?.handle),
        this.handleService.normalizeHandle(this.config.handle),
      ]).pipe(
        map(([handle, normalizedHandle]: [string | null, string | null]) => {
          return hasValue(dso) && hasValue(dso.handle) && handle === normalizedHandle;
        }),
        take(1),
      );
    } else {
      return of(false);
    }
  }
}

export class UUIDTheme extends Theme {
  constructor(public config: UUIDThemeConfig) {
    super(config);
  }

  matches(url: string, dso: DSpaceObject): Observable<boolean> {
    return of(hasValue(dso) && dso.uuid === this.config.uuid);
  }
}

export const themeFactory = (config: ThemeConfig, injector: Injector): Theme => {
  if (hasValue((config as RegExThemeConfig).regex)) {
    return new RegExTheme(config as RegExThemeConfig);
  } else if (hasValue((config as HandleThemeConfig).handle)) {
    return new HandleTheme(config as HandleThemeConfig, injector.get(HandleService));
  } else if (hasValue((config as UUIDThemeConfig).uuid)) {
    return new UUIDTheme(config as UUIDThemeConfig);
  } else {
    return new Theme(config as NamedThemeConfig);
  }
};
