/**
 * RESTFUL API 包装类
 * 
 * 功能介绍：
 *    为 RESTful 的 API 提供了一套快速使用的请求方法。
 * 
 * Author: imengyu
 * Date: 2020/09/12
 * 
 * Copyright (c) 2021 imengyu.top. Licensed under the MIT License. 
 * See License.txt in the project root for license information.
 */

import { DataModel, type KeyValue } from '@imengyu/js-request-transform';
import { ArrayUtils, assertNotNull, RequestApiConfig, RequestApiResult, StringUtils, type TypeAll } from '@imengyu/imengyu-utils';
import { CommonArrayResult, CommonListResult, CommonPageResult } from './types';
import { UpdateServerRequestModule } from '../RequestModules';

/**
 * 可搜索类型定义
 */
export type SearchableType = string|number|boolean|Array<string>|Array<number>|Array<Date>|null|bigint; 

/**
 * 搜索参数
 */
export interface SearchArrayParams {
  /**
   * Key为搜索的字段（用户可能输入多个字段搜索，此字段可能为多个）
   * 值是用户输入的。如果为Array，则为数组
   */
  [index: string]: SearchableType|{
    fuzzy?: boolean,
    range?: boolean,
    value: string|string[]
  };
}

/**
 * 搜索参数
 */
export interface SearchParams {
  /**
   * 排序方法（可能为空，为空则不排序或使用默认排序）
   */
  sort?: {
    /**
     * 用来排序的字段
     */
    field: string|undefined;
    /**
     * 排序顺序（可能为空，为空则服务器默认排序）
     */
    order: 'ascend'|'descend'|string|undefined;
  };
  /**
   * 搜索参数（为空则用户没有搜索）
   */
  search?: SearchArrayParams;
  /**
   * 筛选器（为空则用户没有筛选）
   */
  filter?: {
    /**
     * Key为要筛选的字段
     * 值是一个数组，表示用户希望筛选出指定字段值在数组中的数据
     * （用户可能输入多个字段筛选，此字段可能为多个）
     */
    [index: string]: TypeAll[]|undefined;
  };
  /**
   * 分页页码
   */
  page?: number;
  /**
   * 分页页大小
   */
  size?: number;
}

/**
 * URL参数
 */
export interface QueryParams {
  /**
   * URL参数
   */
  [index: string]: TypeAll;
}

/**
 * 将参数转为斜杠“/”分隔的URL
 * @param params 输入参数
 * @returns 例如：输入 [ 'a', 'b', 'c' ] 将返回 a/b/c
 */
export function buildParams(params: Array<string>) : string {
  let rs = '';
  if(params && params.length > 0) {
    for(let i = 0; i < params.length; i++) {
      rs += '/' + params[i];
    }
  }
  return rs
}
/**
 * 转换参数结构为URL参数。
 * * 此方法将会把参数对象中的属性按key-value展开为URL参数，例如：{ a: 1, b: 2 } 展开为 a=1&b=2。
 * * 如果对象中的属性值是对象，它将被转为 JSON 字符串。
 * * 所有的参数都已经URL编码。
 * @param querys 参数结构
 * @returns URL参数字符串
 */
export function buildQueryParams(querys: QueryParams) : string {
  let rs = '?';
  if(querys) {
    const keys = Object.keys(querys);
    let j = 0;
    for(let i = 0; i < keys.length; i++) {
      let obj = querys[keys[i]];
      if (!StringUtils.isNullOrEmpty(obj as any)) {
        if(typeof obj === 'object') obj = JSON.stringify(obj);
        if(j == 0) rs += keys[i] + '=' + encodeURIComponent('' + obj);
        else rs += '&' + keys[i] + '=' + encodeURIComponent('' + obj);
        j++;
      }
    }
  }
  return rs
}


/**
 * 通用 Restful 资源请求类
 */
export class RestfulApi<T extends DataModel> extends UpdateServerRequestModule<T> {

  /**
   * api 子路径
   */
  public subResKey: string;

  public apiName: string;
  /**
   * 类型
   */
  public modulInstance: new () => T;

  /**
   * 创建 通用资源请求类
   * @param subResKey api 子路径
   */
  public constructor(subResKey: string, modulInstance : new () => T) {
    super();
    this.subResKey = subResKey;
    this.modulInstance = modulInstance;
    this.apiName = (new modulInstance() as any)._classDebugName as string;
  }

  public commonRquest<T = object>(
    method: "OPTIONS" | "GET" | "HEAD" | "POST" | "PUT" | "DELETE", 
    url: string = `/${this.subResKey}`, 
    apiName: string,
    data?: Record<string, unknown>, 
    search: SearchParams = {},
    querys: QueryParams = {}, 
    appendParams: string[] = [],
    prependParams: string[] = [],
    model?: new () => T,
  ) : Promise<RequestApiResult<T>> {
    return this.request<T>(
      buildParams(prependParams) + url + buildParams(appendParams) + buildQueryParams(querys) + buildSearchParams(search),
      {},
      {
        method,
        data
      },
      apiName,
      model
    ) as any;
  }
  
  /**
   * 请求下拉框数据
   * @param id 资源ID
   */
  public getList(search: SearchParams = {}, querys: QueryParams = {}, appendParams: string[] = [ 'list' ], prependParams: string[] = []) : Promise<RequestApiResult<CommonArrayResult<T>>> {
    return this.commonRquest<KeyValue[]>(
      'GET',
      `/${this.subResKey}`,
      `获取 ${this.apiName} 列表`,
      undefined,
      search,
      querys,
      appendParams,
      prependParams
    ).then((rs) => {
      assertNotNull(rs.data, "rs.data is null! ");
      return rs.cloneWithOtherData(new CommonArrayResult<T>().fromData(rs.data, undefined, this.modulInstance));
    });
  }
  /**
   * 请求下拉框数据 (无getList时的兼容, 默认分页大小100)
   */
  public getListPool(search: string, page: number = 1, size: number = 100, otherSearch: SearchArrayParams = {}, labelKey = 'name', querys: QueryParams = {}, appendParams: string[] = [ 'index' ], prependParams: string[] = []) : Promise<CommonListResult> {
    return new Promise<CommonListResult>((resolve, reject) => {
      if (search)
        otherSearch[labelKey] = search;
      this.getPage({
        page,
        size,
        search: otherSearch
      }, querys, appendParams, prependParams).then((d) => {
        resolve(new CommonListResult().fromData(d.requireData().items));
      }).catch(e => reject(e));
    });
  }
  /**
   * 请求分页
   * @param page 页码，1 开始。默认1
   * @param pageSize 页大小。默认10
   * @param searchParams 搜索筛选键值
   */
  public getPage(searchParams: SearchParams = { page: 1, size: 10 }, querys: QueryParams = {}, appendParams: string[] = [ '' ], prependParams: string[] = []) : Promise<RequestApiResult<CommonPageResult<T>>> {
    const page = searchParams.page || 1;
    const size = searchParams.size || 10;
    return this.commonRquest<{
      allCount?: number | undefined;
      allPage?: number | undefined;
      items?: KeyValue[] | null | undefined;
    }>(
      'GET',
      `/${this.subResKey}/${page}/${size}`,
      `获取 ${this.apiName} 分页列表`,
      undefined,
      searchParams,
      querys,
      appendParams,
      prependParams
    ).then((rs) => {
      assertNotNull(rs.data, "rs.data is null! ");
      const data = rs.cloneWithOtherData(new CommonPageResult<T>().fromData(rs.data, page, size, this.modulInstance));
      if (RequestApiConfig.getConfig().EnableApiDataLog)
        console.log('[Debug] transformPageResult: ', data);
      return data;
    });
  }
  /**
   * 请求资源
   * @param id 资源ID
   */
  public getItem(id: number, querys: QueryParams = {}, appendParams: string[] = [], prependParams: string[] = []) : Promise<RequestApiResult<T>> {
    return this.commonRquest(
      'GET',
      `/${this.subResKey}/${id}`,
      `获取 ${this.apiName} 详情`,
      undefined,
      undefined,
      querys,
      appendParams,
      prependParams,
      this.modulInstance
    );
  }
  /**
   * 添加资源
   * @param data 资源数据
   */
  public addItem(data: T, querys: QueryParams = {}, appendParams: string[] = [ '' ], prependParams: string[] = []) : Promise<RequestApiResult<number>> {
    return this.commonRquest<number>(
      'POST',
      `/${this.subResKey}`,
      `新增 ${this.apiName}`,
      data.toServerSide(),
      undefined,
      querys,
      appendParams,
      prependParams
    );
  }
  /**
   * 更新资源
   * @param id 资源ID
   * @param data 资源数据
   */
  public updateItem(id: number, data: T, querys: QueryParams = {}, appendParams: string[] = [ '' ], prependParams: string[] = []): Promise<RequestApiResult<void>> {
    return this.commonRquest<void>(
      'PUT',
      `/${this.subResKey}/${id}`,
      `更新 ${this.apiName}`,
      data.toServerSide(),
      undefined,
      querys,
      appendParams,
      prependParams
    );
  }
  /**
   * 更新资源状态
   * @param id 资源ID
   * @param state 状态
   */
  public toggleItemState(id: number, state: boolean|number, querys: QueryParams = {}, appendParams: string[] = [ 'toggle-state' ], prependParams: string[] = []): Promise<RequestApiResult<void>> {
    return this.commonRquest<void>(
      'POST',
      `/${this.subResKey}/${id}`,
      `更新 ${this.apiName} 状态为 ${state}`,
      { state: typeof state === 'boolean' ? (state ? 1 : 0) : state },
      undefined,
      querys,
      appendParams,
      prependParams
    );
  }
  /**
   * 删除资源
   * @param id 资源ID
   */
  public deleteItem(id: number, querys: QueryParams = {}, appendParams: string[] = [ '' ], prependParams: string[] = []) : Promise<RequestApiResult<void>>  { 
    return this.commonRquest<void>(
      'DELETE',
      `/${this.subResKey}/${id}`,
      `删除 ${this.apiName}`,
      undefined,
      undefined,
      querys,
      appendParams,
      prependParams
    );
  }
}

/**
 * 分页的返回标准
 */
export type PageReturn<T extends DataModel> = Promise<RequestApiResult<CommonPageResult<T>>>;
/**
 * 分页的API标准
 */
export type PageFunction<T extends DataModel> = (searchParams: SearchParams, querys?: QueryParams, appendParams?: string[], prependParams?: string[]) => PageReturn<T>;

/**
 * 排序标识转换到后端
 */
export function transformOrder(or : string) : string {
  switch(or) {
    case 'ascend': return 'asc';//升序
    case 'descend': return 'desc';//降序
  }
  return or;
}
/**
 * 搜索参数的转换到后端
 * @param params 
 * @param expandSearchs 把搜索参数分离成为单独的query
 * @returns 
 */
export function buildSearchParams(params: SearchParams) : string {
  let rs = '';
  if(params) {
    if(params.search && Object.keys(params.search).length > 0) {
      const newO = {} as { [index: string]: SearchableType };
      for(const key in params.search) {
        const v = params.search[key]
        if(typeof v === 'undefined' || v === null || v === "") continue;
        if(v instanceof Array && (v.length === 0 || ArrayUtils.isAllNullOrEmpty(v))) continue;
          
        newO[key] = v as SearchableType;
      }
      if(Object.keys(newO).length > 0) {
        rs += '&search=' + encodeURIComponent(JSON.stringify(newO));
      }
    }
    if(params.sort)
      rs += '&sort=' + encodeURIComponent(JSON.stringify(params.sort));

    if(params.filter && Object.keys(params.filter).length > 0)
      rs += '&filter=' + encodeURIComponent(JSON.stringify(params.filter));
  }
  return rs;
}
