import { trim } from "lodash";
import { action, computed, observable } from "mobx";
import { CollectionPermission, FileOperationFormat } from "@shared/types";
import { sortNavigationNodes } from "@shared/utils/collections";
import CollectionsStore from "~/stores/CollectionsStore";
import Document from "~/models/Document";
import ParanoidModel from "~/models/ParanoidModel";
import { NavigationNode } from "~/types";
import { client } from "~/utils/ApiClient";
import Field from "./decorators/Field";

export default class Collection extends ParanoidModel {
  store: CollectionsStore;

  @observable
  isSaving: boolean;

  @observable
  isLoadingUsers: boolean;

  @Field
  @observable
  id: string;

  @Field
  @observable
  name: string;

  @Field
  @observable
  description: string;

  @Field
  @observable
  icon: string;

  @Field
  @observable
  color: string;

  @Field
  @observable
  permission: CollectionPermission | void;

  @Field
  @observable
  sharing: boolean;

  @Field
  @observable
  index: string;

  @Field
  @observable
  sort: {
    field: string;
    direction: "asc" | "desc";
  };

  documents: NavigationNode[];

  url: string;

  urlId: string;

  @computed
  get isEmpty(): boolean {
    return (
      this.documents.length === 0 &&
      this.store.rootStore.documents.inCollection(this.id).length === 0
    );
  }

  @computed
  get documentIds(): string[] {
    const results: string[] = [];

    const travelNodes = (nodes: NavigationNode[]) =>
      nodes.forEach((node) => {
        results.push(node.id);
        travelNodes(node.children);
      });

    travelNodes(this.documents);
    return results;
  }

  @computed
  get hasDescription(): boolean {
    return !!trim(this.description, "\\").trim();
  }

  @computed
  get isStarred(): boolean {
    return !!this.store.rootStore.stars.orderedData.find(
      (star) => star.collectionId === this.id
    );
  }

  @computed
  get sortedDocuments() {
    return sortNavigationNodes(this.documents, this.sort);
  }

  /**
   * Updates the document identified by the given id in the collection in memory.
   * Does not update the document in the database.
   *
   * @param document The document properties stored in the collection
   */
  @action
  updateDocument(document: Pick<Document, "id" | "title" | "url">) {
    const travelNodes = (nodes: NavigationNode[]) =>
      nodes.forEach((node) => {
        if (node.id === document.id) {
          node.title = document.title;
          node.url = document.url;
        } else {
          travelNodes(node.children);
        }
      });

    travelNodes(this.documents);
  }

  /**
   * Removes the document identified by the given id from the collection in
   * memory. Does not remove the document from the database.
   *
   * @param documentId The id of the document to remove.
   */
  @action
  removeDocument(documentId: string) {
    this.documents = this.documents.filter(function f(node): boolean {
      if (node.id === documentId) {
        return false;
      }

      if (node.children) {
        node.children = node.children.filter(f);
      }

      return true;
    });
  }

  @action
  updateIndex(index: string) {
    this.index = index;
  }

  getDocumentChildren(documentId: string) {
    let result: NavigationNode[] = [];

    const travelNodes = (nodes: NavigationNode[]) => {
      nodes.forEach((node) => {
        if (node.id === documentId) {
          result = node.children;
          return;
        }

        return travelNodes(node.children);
      });
    };

    if (this.documents) {
      travelNodes(this.sortedDocuments);
    }

    return result;
  }

  pathToDocument(documentId: string) {
    let path: NavigationNode[] | undefined;
    const document = this.store.rootStore.documents.get(documentId);

    const travelNodes = (
      nodes: NavigationNode[],
      previousPath: NavigationNode[]
    ) => {
      nodes.forEach((node) => {
        const newPath = [...previousPath, node];

        if (node.id === documentId) {
          path = newPath;
          return;
        }

        if (
          document?.parentDocumentId &&
          node?.id === document?.parentDocumentId
        ) {
          path = [...newPath, document.asNavigationNode];
          return;
        }

        return travelNodes(node.children, newPath);
      });
    };

    if (this.documents) {
      travelNodes(this.documents, []);
    }

    return path || [];
  }

  @action
  star = async () => {
    return this.store.star(this);
  };

  @action
  unstar = async () => {
    return this.store.unstar(this);
  };

  export = (format: FileOperationFormat) => {
    return client.post("/collections.export", {
      id: this.id,
      format,
    });
  };
}
