import { error } from '../utils/report';

import type {
  Page,
  View,
  Panel,
  Modal,
  Popout,

  RouteLike,

  RouteParams,
  Structure,
  RouteList
} from '../types';

import { buildPage, matchPage, parsePath, MatchedPath } from '../utils/route';
import hasOwn from '../utils/hasOwn';

export const VIEW_MAIN = 'view_main';
export const PANEL_MAIN = 'panel_main';
export const PAGE_MAIN = '/';

class Route {
  private static _next: number = 0;

  readonly id: number;

  uri: string;

  page: Page;
  view: View;
  panel: Panel;

  history: Structure[];

  modal: Modal;
  get hasModal() {
    return typeof this.modal === 'string';
  }

  popout: Popout;
  get hasPopout() {
    return typeof this.popout === 'string';
  }

  get hasOverlay() {
    return this.hasModal || this.hasPopout;
  }

  private _params: RouteParams = {};
  get params(): Readonly<RouteParams> {
    return this._params;
  }
  set params(value) {
    this._params = {
      ...this._params,
      ...value
    };
  }

  index: number = -1;

  constructor(
    panel: Panel = PANEL_MAIN,
    view: View = VIEW_MAIN,
    modal: Modal = null,
    popout: Popout = null,
    params: RouteParams = {}
  ) {
    this.id = Route._next++;

    this.panel = panel;
    this.view = view;

    this.modal = modal;
    this.popout = popout;

    this.params = params;
  }

  clone() {
    return new Route(
      this.panel,
      this.view,
      this.modal,
      this.popout,
      this.params
    );
  }

  isSameWith(route: RouteLike) {
    return (
      this.panel === route.panel &&
      this.view === route.view &&
      this.modal === route.modal &&
      this.popout === route.popout
    );
  }

  compile(page: Page) {
    this.page = page;
    this.uri = buildPage(page, this.params);
  }

  static buildFromLocation(routeList: RouteList, path: string) {
    const parsedPath = parsePath(path);

    let match: MatchedPath = null;
    const page = Object.keys(routeList).find((page: Page) => {
      match = matchPage(page, parsedPath.url);
      return match && match.exact;
    });
    if (!match) {
      error('Builder cant find route for ' + path);
    }

    const route = routeList[page];
    if (!route) {
      error('Builder cant find route for ' + path);
    }

    const build = route.clone();
    build.params = match.params;
    build.compile(page);
    return build;
  }

  static buildFromPage(routeList: RouteList, page: Page, params: RouteParams = {}) {
    const route = routeList[page];
    if (!route) {
      error('Builder cant find route for ' + page);
    }

    const build = route.clone();
    build.params = params;
    build.compile(page);
    return build;
  }

  static buildFromState(routeList: RouteList, state: RouteLike) {
    if (!state.page) {
      error('Builder cant resolve page for ' + state);
    }

    const route = routeList[state.page];
    if (!route) {
      error('Builder cant find route for ' + state.page);
    }

    const build = route.clone();
    Object.keys(state).forEach((key) => {
      if (hasOwn(build, key)) {
        build[key] = route[key];
      }
    });
    build.compile(state.page);
    return build;
  }
}

export { Route };
export default Route;
