import { makeAutoObservable } from 'mobx';
import { AppLayoutType } from '../models/AppLayout';
import { Route } from './routing/route.interface';
import { omit } from '../utils/obj/omit';
import { keys } from '../utils/obj/keys';
const DEFAULT_APP_NAME = 'Apphouse App';

class PartialView {
  id: string;
  open: boolean;
  updatedAt: number;
  constructor(props: { id: string; open: boolean }) {
    const { id, open } = props;
    this.id = id;
    this.open = open;
    this.updatedAt = new Date().getTime();
    if (open) {
      this.updatedAt = new Date().getTime();
    }
    makeAutoObservable(this);
  }
  setOpen(open: boolean) {
    this.open = open;
    this.updatedAt = new Date().getTime();
  }
}

export interface IView {
  appName?: string;
  appLayout?: AppLayoutType;
  routes?: Route[];
}

/**
 * A model to handle views in the app.
 * This will be similar to a route in a traditional web app.
 */
class View {
  path: string;
  title: string;
  updated: number;
  tabIndex?: number;
  tabId?: string;
  sectionId?: string;
  params?: { [key: string]: string };
  routes: Route[];
  /**
   * Handles partial views states such as tabs, sections, etc.
   * To be able to access them from anywhere in the app.
   */
  views: { [key: string]: PartialView };

  constructor(props: IView = {}) {
    this.title = props.appName || DEFAULT_APP_NAME;
    this.path = '/';
    this.updated = 0;
    this.sectionId = undefined;
    this.tabId = undefined;
    this.tabIndex = 0;
    this.params = {};
    this.views = {};
    this.routes = props.routes || [];

    makeAutoObservable(this);
  }

  get openedViewsByMostRecent() {
    return Object.keys(this.views)
      .map((key) => this.views[key])
      .filter((view) => view.open)
      .sort((a, b) => b.updatedAt - a.updatedAt)
      .slice()
      .reverse();
  }

  get currentPath() {
    let path = this.path;
    const params = omit(this.params, ['path']);
    const openParams = keys(params).filter((key) => this.views[key]?.open);
    openParams.forEach((key) => {
      if (path.includes(`:${key}`) && params && params[key]) {
        path = path.replace(`:${key}`, params[key]);
      }
    });

    const mostRecentViewsPath = this.openedViewsByMostRecent.map((v) => v.id);
    // We still have a bug here that yields to a wrong path
    if (mostRecentViewsPath.length > 1) {
      const tail = path.split('/').pop();
      const _mostRecent = mostRecentViewsPath.slice();
      const mostRecent = _mostRecent.shift();
      if (tail === mostRecent) {
        return `${path}/${_mostRecent.join('')}`;
      }
      return `${path}/${mostRecentViewsPath.join('/')}`;
    } else {
      const mostRecent = mostRecentViewsPath.join('/');
      if (path.includes(mostRecent) || path.includes(`:${mostRecent}`)) {
        return path;
      }
      return `${path}/${mostRecent}`;
    }
  }

  get currentParams() {
    if (this.updated > 0) {
      return this.params || {};
    }

    return this.params || {};
  }

  get crumbs() {
    return this.path.split('/').filter((crumb) => crumb !== '');
  }

  open = (path: string, title?: string, onOpen?: () => void) => {
    this.setPath(path);
    this.setTitle(title || DEFAULT_APP_NAME);
    this.setTabId(undefined);
    onOpen && onOpen();
  };

  openTab = (tabId?: string, tabIndex?: number) => {
    this.setTabId(tabId);
    this.setTabIndex(tabIndex || 0);
  };

  closeTab = () => {
    this.setTabId(undefined);
    this.setTabIndex(0);
  };

  openSection = () => {
    this.setSectionId(this.tabId);
  };

  setParams = (params: { [key: string]: string }): void => {
    this.params = params;
  };

  setRoutes = (routes: Route[]): void => {
    this.routes = routes;
  };

  /**
   * Sets a partial view in the app
   * @param id string the id of the partial view
   * @param open boolean to set the partial view open or closed
   */
  setView = (id: string, open: boolean) => {
    if (this.views[id]) {
      this.views[id].setOpen(open);
    } else {
      this.views[id] = new PartialView({ id, open });
    }
    if (!open) {
      this.setPath(this.params?.path || '/');
    }
  };

  toggleView = (id: string) => {
    if (this.views[id]) {
      this.views[id].setOpen(!this.views[id].open);
    } else {
      this.setView(id, true);
    }
  };

  closeAllOpenViews = () => {
    Object.keys(this.views).forEach((key) => {
      this.setView(key, false);
    });
  };

  getViewStatus = (id: string): boolean => {
    if (this.views[id]) {
      return this.views[id].open;
    }
    return false;
  };

  deleteAllViews = () => {
    this.views = {};
  };

  closeMostRecentOpenedView = () => {
    // Get the most recent opened view
    const mostRecentOpenedView = Object.values(this.views)
      .filter((v) => v.open)
      .sort((a, b) => b.updatedAt - a.updatedAt)[0];
    if (mostRecentOpenedView) {
      this.setView(mostRecentOpenedView.id, false);
      this.setPath(this.params?.path || '/');
    }
  };

  private setSectionId = (sectionId?: string): void => {
    this.sectionId = sectionId;
  };

  private setTabIndex = (index: number) => {
    this.tabIndex = index;
  };

  private setTitle = (title: string) => {
    this.title = title;
  };

  private setTabId = (locationTabId?: string) => {
    this.tabId = locationTabId;
  };

  private setPath = (value: string) => {
    this.path = value;
  };
}

export default View;
