/***
 * openapi of indexea
 */
import { v4 as uuidv4 } from 'uuid';
import { Configuration, WidgetsApi, SearchApi, SearchWidgetHotWordsScopeEnum, WidgetBean, SearchWord, AutoCompleteItem } from '@indexea/sdk'

/**
 * A simple wrapper for the Indexea API
 */
export class Indexea {

  _config: Configuration;
  _widget: string;
  accessToken: string;
  searchApi: SearchApi;
  widgetApi: WidgetsApi;

  /**
   * initialize an indexea api wrapper
   * @param endpoint remote endpoint url, ex: `https://api.indexea.com/v1`
   * @param widget widget identifier
   */
  constructor(endpoint: string, accessToken: string, widget: string) {
    this._widget = widget;
    this.accessToken = accessToken;
    this._config = new Configuration({
      basePath: endpoint,
      headers: { accept: 'application/json' },
      accessToken: () => {
        return accessToken
      },
      credentials: 'include',
      middleware: [],
      queryParamsStringify
    });
    this.searchApi = new SearchApi(this._config)
    this.widgetApi = new WidgetsApi(this._config)
  }

  /**
   * get widget detail
   * @returns 
   */
  async widget(): Promise<WidgetBean> {
    return this.widgetApi.widgetDetail({ ident: this._widget }).catch(rejectError)
  }

  /**
   * execute search on widget
   * @param query query id
   * @param q   search keyword
   * @param params  search parameters, generally, it is an aggregation parameter filter condition
   * @param from 
   * @param size 
   * @returns 
   */
  async search(query: number, q: string, params: any, from: number, size: number): Promise<object> {
    let args = { widget: this._widget, query, q, params, from, size, userid: getUserId(), xToken: this.accessToken };
    return this.searchApi.searchWidgetSearch(args).catch(rejectError);
  }

  /**
   * get hotwords of query
   * @param query  query id
   * @param scope 
   * @param count 
   * @returns 
   */
  async hotwords(query: number, scope: SearchWidgetHotWordsScopeEnum, count: number): Promise<Array<SearchWord>> {
    return this.searchApi
      .searchWidgetHotWords({ widget: this._widget, query, scope, count, xToken: this.accessToken })
      .catch(rejectError)
  }

  /**
   * get auto complete items for search box
   * @param query  query id
   * @param q  search keyword
   * @param size 
   * @returns 
   */
  async autocomplete(query: number, q: string, size: number): Promise<Array<AutoCompleteItem>> {
    return this.searchApi.searchWidgetAutoComplete({ widget: this._widget, query, q, userid: getUserId(), size, xToken: this.accessToken })
      .catch(rejectError)
  }

  /**
   * call this method when use click on a search result item.
   * @param actionId 
   * @param docId 
   * @returns 
   */
  async click(actionId: string, docId: string): Promise<boolean> {
    return this.searchApi.searchClick({ widget: this._widget, actionId, docId, userid: getUserId(), xToken: this.accessToken })
  }
}

/**
 * The unique identifier of the visitor, which will be transmitted to the search service through the header
 * @returns 
 */
function getUserId() {
  var userid: string = window.localStorage.getItem('idx_userid') || '';
  if (!userid) {
    userid = uuidv4(); //Date.now().toString(36) + Math.random().toString(36);
    window.localStorage.setItem('idx_userid', userid);
  }
  return userid;
}

/**
 * Object to url request parameters
 * @param obj 
 * @returns 
 */
function queryParamsStringify(obj: Record<string, any>): string {
  return Object.entries(obj).filter(([key, value]) => key && value != undefined)
    .map(([key, value]) => {
      if (typeof value !== 'object') {
        return joinKV(key, value)
      }
      if (Array.isArray(value)) {
        // Array type, using multiple keys, such as a=1&a=2&a=3 becomes a=[1,2,3] during backend processing
        return value.filter(v => v).map(v => joinKV(key, v)).join('&')
      }
      return queryParamsStringify(value)
    })
    .join('&')
}

/**
 * Concatenate url request parameters
 * @param key 
 * @param value 
 * @returns 
 */
function joinKV(key: string, value: any) {
  return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}

/**
 * global error handler
 * @param {*} fn_reject
 * @param {*} err
 */
function rejectError(err: any) {
  try {
    return err.response
      .json()
      .then((json: any) => Promise.reject(json))
      .catch((e: { error: any; message: any; }) =>
        Promise.reject({
          error: e.error || err.status || 999,
          message: e.message || err.statusText || 'Unknown error'
        })
      )
  } catch (e) {
    return Promise.reject({
      error: err.error || err.status || 999,
      message: err.message || err.statusText || 'Unknown error'
    })
  }
}
