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

export class Indexea {
  private widgetIdent: string;
  private searchApi: SearchApi;
  private widgetApi: WidgetsApi;
  private p_token: string;
  private p_userid: string;

  constructor(ident: string, props: any = {}) {
    var config = cfg(props);
    this.searchApi = new SearchApi(config);
    this.widgetApi = new WidgetsApi(config);
    this.widgetIdent = ident;
    this.p_token = props['accessToken'];
    this.p_userid = props['userid'];
  }

  get() {
    return this.widgetApi.widgetDetail({ ident: this.widgetIdent, xToken: this.p_token }).catch(rejectError)
  }

  /**
   * search on widget
   * @param {*} query
   * @param {*} q
   * @param {*} original
   * @param {*} params
   * @param {*} from
   * @param {*} size
   * @returns
   */
  wsearch(query: number, q: string, original: string, params: any, from: number, size: number) {
    return this.searchApi
      .searchWidgetSearch({ widget: this.widgetIdent, query, q, params, from, size, userid: this.p_userid || userid(), xToken: this.p_token, original })
      .catch(rejectError)
  }

  /**
   * get hotwords by query
   * @param {*} query
   * @param {*} scope
   * @param {*} count
   * @returns
   */
  hotwords(query: number, scope: SearchWidgetHotWordsScopeEnum, count: number) {
    return this.searchApi
      .searchWidgetHotWords({ widget: this.widgetIdent, query, scope, count, userid: this.p_userid || userid(), xToken: this.p_token })
      .catch(rejectError)
  }

  /**
   * get search histories of user
   * @param query 
   * @param count 
   * @returns 
   */
  histories(query: number, count: number) {
    return this.searchApi
      .searchHistories({ widget: this.widgetIdent, strategy: SearchHistoriesStrategyEnum.Recent, query, size: count, userid: this.p_userid || userid(), xToken: this.p_token })
      .catch(rejectError);
  }

  autocomplete(query: number, q: string, size: number) {
    return this.searchApi.searchWidgetAutoComplete({ widget: this.widgetIdent, query, q, userid: this.p_userid || userid(), size, xToken: this.p_token })
      .catch(rejectError)
  }

  /**
   * click on search result
   * @param actionId 
   * @param docId 
   * @returns 
   */
  click(actionId: string, docId: string) {
    return this.searchApi.searchClick({ widget: this.widgetIdent, actionId, docId, userid: this.p_userid || userid(), xToken: this.p_token })
  }

  static getField(obj: any, key: string) {
    return getFieldValue(obj, key);
  }

  static getFields(obj: any, keys: Array<string>) {
    var newobj = {};
    keys.filter(k => k).forEach((k) => (newobj[k] = getFieldValue(obj, k)));
    return newobj;
  }

  static evalTempl(templ: string): string {
    try {
      return eval(templ);
    } catch (e) {
      console.log(e);
      return e.message;
    }
  }
}

export class IndexeaRecomm {
  private recommIdent: string;
  private recommApi: RecommendApi
  private p_token: string;
  private p_userid: string;

  constructor(ident: string, props: any = {}) {
    var config = cfg(props);
    this.recommApi = new RecommendApi(config);
    this.recommIdent = ident;
    this.p_token = props['accessToken'];
    this.p_userid = props['userid'];
  }

  get() {
    return this.recommApi.recommendDetail({ ident: this.recommIdent, xToken: this.p_token }).catch(rejectError);
  }

  /**
   * fetch recomm results
   * @param {*} query
   * @param {*} q
   * @param {*} params
   * @param {*} from
   * @param {*} size
   * @returns
   */
  fetch(condition: { [key: string]: string; }, from: number, count: number) {
    return this.recommApi.recommendFetch({ ident: this.recommIdent, condition, from, count, userid: this.p_userid || userid(), xToken: this.p_token })
      .catch(rejectError)
  }

  /**
   * click on recommend result
   * @param actionId 
   * @param docId 
   * @returns 
   */
  click(actionId: string, docId: string) {
    return this.recommApi.recommendClick({ ident: this.recommIdent, userid: this.p_userid || userid(), actionId, docId, xToken: this.p_token })
      .catch(rejectError);
  }

}

function cfg(props: any = {}) {
  return new Configuration({
    basePath: props.basePath,
    headers: { accept: 'application/json' },
    accessToken: () => props.accessToken || '',
    credentials: 'include',
    middleware: [],
    queryParamsStringify
  });
}

/**
 * get value of object, support nest field such as user.name
 * @param fields 
 * @param key 
 * @returns 
 */
function getFieldValue(fields: any, key: string) {
  if (!key) return null;
  var v = fields[key];
  if (!v) {
    //nest properties like user.region.city
    var keys = key.split(".");
    for (var i = 0; i < keys.length; i++) {
      v = v ? v[keys[i]] : fields[keys[i]];
      if (typeof v != "object") {
        break;
      }
    }
  }
  return v || "";
}

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

/**
 * 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 => Promise.reject(json))
      .catch(e =>
        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'
    })
  }
}
