export class Router {
  private readonly _routes: Map<string, (e: HTMLFormElement, data?: any) => void>;
  private readonly _paramsResolvers: Map<string, Callback>;
  private readonly _urlParams: Map<string, string>;
  private readonly _loading: HTMLElement;
  private readonly _location: Location;

  constructor(loading: HTMLElement, location) {
    this._routes = new Map<string, (e: HTMLFormElement) => void>();
    this._paramsResolvers = new Map<string, Callback>();
    this._loading = loading;
    this._location = location;
    this._urlParams = this.parseURLParams();

    document.body.addEventListener('click', (event) => {
      const target = <HTMLElement>event.target;
      if (target?.tagName === 'A' && target?.dataset?.link !== undefined) {
        const href = <string>target.getAttribute('href');
        if (href.indexOf('://') > 0) {
          return true;
        }
        event.preventDefault();
        this.show(href);
      }
    });
  }

  public add(id: string | string[], fn: (e: HTMLFormElement, data: any) => void) {
    if (typeof id === 'string') {
      this._routes.set(id, fn);
      return;
    }
    for (const v of id) {
      this._routes.set(v, fn);
    }
  }

  public addParamsResolver(param: string, fn: (params: Map<string, string>) => void, weight?: number) {
    if (!weight) {
      weight = 0;
    }
    this._paramsResolvers.set(param, {
      fn,
      weight,
    });
  }

  public show(id: string, data?: any): void {
    const ele = <HTMLElement>document.querySelector(`.l-${id}`);
    const currentEle = <HTMLFormElement>document.querySelector('.l-block:not(.hidden)');
    if (currentEle) {
      currentEle.classList.add('hidden');
      if (currentEle instanceof HTMLFormElement) {
        currentEle.reset();
      }
    }
    ele.classList.remove('hidden');
    const action = this._routes.get(id);
    if (action && ele instanceof HTMLFormElement) {
      action(ele, data);
    }
  }

  public start() {
    if (this._urlParams.size > 0) {
      let callback!: Callback;
      this._urlParams.forEach((value, key) => {
        const tmp = this._paramsResolvers.get(key);
        if (tmp) {
          if (!callback) {
            callback = tmp;
          } else {
            if (tmp.weight > callback.weight) {
              callback = tmp;
            }
          }
        }
      });
      if (callback) {
        callback.fn(this._urlParams);
        return;
      }
    }
    this.show('main');
  }

  public loading(): void {
    this._loading.style.display = 'block';
  }

  public loaded(): void {
    this._loading.style.display = 'none';
  }

  public redirectURIorBackTo(backTo?: string) {
    const redirectUri = decodeURIComponent(this._urlParams.get('redirect_uri') || '');
    if (redirectUri) {
      this._location.replace(backTo || redirectUri);
    }
  }

  public getUrlParams(key: string): string {
    return this._urlParams.get(key);
  }

  public setUrlParams(key: string, value: string): void {
    this._urlParams.set(key, value);
  }

  private checkIfHasParamsResolver(): boolean {
    for (const [key] of this._paramsResolvers) {
      if (this._urlParams.get(key)) {
        return true;
      }
    }
    return false;
  }

  private parseURLParams(): Map<string, string> {
    let search!: string;
    const location = this._location;
    if (location.search.length > 2) {
      search = location.search.substring(1);
    } else if (location.hash.indexOf('=') > 0) {
      search = location.hash.substring(1);
    }
    const params = new Map<string, string>();
    if (!search) {
      return params;
    }

    search.split('&').forEach((item) => {
      const [key, value] = item.split('=');
      params.set(key, value);
    });

    return params;
  }
}

interface Callback {
  fn: (params: Map<string, string>) => void;
  weight: number;
}
