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

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

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 img0?: Uint8Array;

  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;
    this.img0 = data.img0;

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

export enum SearchMode {
  GLOBAL = 'global',
  ROOM = 'room',
  CUSTOM = 'custom',
}

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

  private lastHistoryIndex = 0;

  private isSearching: boolean = false;

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

  /**
   *
   * @param query
   * @param resourceId
   * @param lang
   */
  public search(query: string, resourceIds: ID[] = [], lang = null): Promise<SearchResult[]> {
    this.isSearching = true;

    let roomIdsPromise: Promise<ID[]>;
    // decide what to search
    if (resourceIds.length == 0) {
      roomIdsPromise = this.roomService.getAllRoomWithActiveDrive().then((allRoom) => {
        let ids = [];
        allRoom.forEach((room) => {
          ids.push(room.id);
        });
        return ids;
      });
    } else {
      roomIdsPromise = Promise.resolve(resourceIds);
    }

    // search them all
    return roomIdsPromise.then((ids) => {
      console.log('SearchService', 'Searching in these drives', ids);
      let actualHistoryIndex = ++this.lastHistoryIndex;

      this.searchResults = [];

      this.lastQuery = query;
      this.selectedResourceIds = ids;

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

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

      return Promise.all(p).then(() => {
        this.isSearching = false;
        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) => {
          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.getRoom(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) {
                let cbCopy = [...this.subscribeHandlers];
                cbCopy.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) {
            let cbCopy = [...this.subscribeHandlers];
            cbCopy.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;
  }

  public getSelectedResourceIds(): string[] {
    return this.selectedResourceIds;
  }

  /**
   * 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);
  }

  public isSearchInProgress(): boolean {
    return this.isSearching;
  }
}
