import { Injectable } from "@angular/core";
import { NanoService } from "../nano/nano.service";
import {
  DriveFile,
  DriveFileParam,
} from "../../components/resource-page/drive-window/drive-layout/drive-file";
import { RoomData, RoomService } from "./room.service";
import { ID } from "./query-records/common-records";

export interface SearchResultParam extends DriveFileParam {
  snippets: String[];
  score: number;
  path: string;
  rid: string;
  resourceName: string;
  resourceAvatar: Blob;
  dir: string;
}

export class SearchResult {
  public file: DriveFile;
  public snippets: String[];
  public score: number;
  public path: string;
  public resourceId: string;
  public resourceName: string;
  public resourceAvatar: Blob;
  public dir: string;

  public constructor(data: SearchResultParam) {
    this.file = new DriveFile(data);
    this.snippets = data.snippets || [];
    this.path = data.path;
    this.score = data.score;
    this.resourceId = data.rid;
    this.resourceName = data.resourceName;
    this.resourceAvatar = data.resourceAvatar;

    if (this.path == this.file.fullName) {
      this.dir = "";
    } else {
      this.dir = this.path.replace("/" + this.file.fullName, "");
    }
  }
}

@Injectable({
  providedIn: "root",
})
export class SearchService {
  private searchResults: SearchResult[] = [];
  private subscribeHandlers: Function[] = [];
  private lastQuery: string;

  private lastHistoryIndex = 0;

  public static QUERY_ALL = "all";

  constructor(
    private nanoService: NanoService,
    private roomService: RoomService
  ) {}

  /**
   *
   * @param resourceId
   * @param query
   * @param lang
   * @param doneCallback the subscribed function will be called with these parameters:
   * callback(resourceId, query, searchResults)
   * @param errorCallback
   * @param progressCallback
   */
  public search(
    resourceIds: ID | ID[] | string,
    query,
    lang
  ): Promise<SearchResult[]> {
    let roomIdsPromise: Promise<ID[]>;

    // decide what to search
    if (resourceIds == SearchService.QUERY_ALL) {
      roomIdsPromise = this.roomService.getAllRoom().then((allRoom) => {
        let ids = [];
        allRoom.forEach((room) => {
          ids.push(room.id);
        });
        return ids;
      });
    } else {
      if (!Array.isArray(resourceIds)) {
        roomIdsPromise = Promise.resolve([resourceIds]);
      } else {
        roomIdsPromise = Promise.resolve(resourceIds);
      }
    }

    // search them all
    return roomIdsPromise.then((ids) => {
      let actualHistoryIndex = ++this.lastHistoryIndex;

      this.searchResults = [];
      this.lastQuery = query;

      let p: Promise<any>[] = [];

      ids.forEach((resourceId) => {
        p.push(this.runOneSearch(resourceId, query, lang, actualHistoryIndex));
      });

      return Promise.all(p).then(() => {
        return this.searchResults;
      });
    });
  }

  private runOneSearch(
    resourceId: ID,
    query: string,
    lang: string,
    actualHistoryIndex: number
  ): Promise<any> {
    return new Promise((resolve, reject) => {
      this.nanoService.search(
        resourceId,
        query,
        lang,
        (res) => {
          console.log("search result", res);

          let promises: Promise<any>[] = [];

          for (let i = 0; i < res.length; i++) {
            let slice = res[i]["path"].split("/");
            res[i]["name"] = slice[slice.length - 1];

            ((data) => {
              promises.push(
                this.roomService.getData(data["rid"]).then((roomData) => {
                  data["resourceName"] = (<RoomData>roomData.data)?.name;
                  data["resourceAvatar"] = (<RoomData>roomData.data)?.avatar;

                  this.searchResults.push(new SearchResult(data));
                  return;
                })
              );
            })(res[i]);
          }

          Promise.all(promises)
            .then(() => {
              this.searchResults.sort((a, b) => {
                return a.score - b.score;
              });

              if (this.lastHistoryIndex == actualHistoryIndex) {
                this.subscribeHandlers.forEach((handler) => {
                  handler(resourceId, query, this.searchResults);
                });
              }

              resolve(true);
            })
            .catch(reject);
        },
        (err)=>{
          // lot of room can response with "No nano available for target"
          // just handle this and ignore
          //console.warn('can not search', err)
          resolve(true);

          if (this.lastHistoryIndex == actualHistoryIndex) {
            this.subscribeHandlers.forEach((handler) => {
              handler(resourceId, query, this.searchResults);
            });
          }
        }
      );
    });
  }

  public getResults(): SearchResult[] {
    return this.searchResults;
  }

  /**
   * Be carefull when you are using these methods, async different query calls, can modify these
   * Only call this, when you are sure, the user didnt run any other search in time
   * @returns
   */
  public getLastQuery(): string {
    return this.lastQuery;
  }

  /**
   * Subscribe when a new search triggered
   * the subscribed function will be called with these parameters:
   * callback(resourceId, query, searchResults)
   */
  public subscribe(handler) {
    this.subscribeHandlers.push(handler);
  }

  public unsubscribe(handler) {
    this.subscribeHandlers.splice(this.subscribeHandlers.indexOf(handler), 1);
  }
}
