/**
 * 异常模块
 * @date 2022-03
 * @author 何华东
 */

import { AxiosError } from "axios";

/**
 * 参数异常类
 * 
 * ```ts
 * import { ParamError } from "refs/nslib";
 * 
 * throw new ParamError('id is null.','id');
 * ```
 * @param paramName 参数名
 */
export class ParamError extends Error {
    paramName?: string;

    constructor(message: string, paramName?: string) {
        super();

        this.name = this.constructor.name;
        this.message = message;
        this.paramName = paramName;
    }
}

/**
 * 参数无效异常类
 * 
 * ```ts
 * import { ParamInvalidError } from "refs/nslib";
 * 
 * throw new ParamInvalidError('id');
 * ```
 * @param paramName 参数名
 */
export class ParamInvalidError extends ParamError {
    constructor(paramName?: string) {
        super(`invalid ${paramName || 'param'}.`, paramName);

        this.name = this.constructor.name;
        Object.setPrototypeOf(this, ParamInvalidError.prototype);
    }
}

/**
 * 参数为空异常类
 * ```ts
 * import { ParamEmptyError } from "refs/nslib";
 * 
 * throw new ParamEmptyError('id');
 * ``` 
 * @param paramName 参数名
 */
export class ParamEmptyError extends ParamError {
    constructor(paramName?: string) {
        super(`${paramName || 'param'} is empty.`, paramName);

        this.name = this.constructor.name;
        Object.setPrototypeOf(this, ParamEmptyError.prototype);
    }
}

export class FriendlyError extends Error {
    constructor(message: string) {
        super();

        this.name = this.constructor.name;
        this.message = message;
        Object.setPrototypeOf(this, FriendlyError.prototype);
    }
}

/**
 * API异常类
 * 
 * 由于`Axios`的`AxiosError`异常属性比较多, 且在PROD环境下使用多线程记录日志, 使用`postMessage`传输日志信息(`HTML结构化克隆算法`), 
 * 只能传输`AxiosError`的`Message`信息, 所以使用`ApiError`代替`AxiosError`
 * 
 * ```ts
 * import { throwApiErrorFromAxiosError } from "refs/nslib";
 * 
 * const axiosInstance = axios.create({
 *   baseURL: 'xxx'
 * });
 * 
 * axiosInstance.interceptors.request.use(config => {
 *   return config;
 * }, error => {
 *   throwApiErrorFromAxiosError(error);
 * });
 * 
 * axiosInstance.interceptors.response.use(response => {
 *   return response;
 * }, error => {
 *   throwApiErrorFromAxiosError(error);
 * }); 
 * ```
 */
export class ApiError {
    /**
     * 接口分类，例如：统一接口，仅为记录日志
     */
    apiCategory?: string;

    /**
     * 接口描述, 仅为记录日志
     */
    apiDesc?: string;

    /**
     * 错误信息
     */
    message?: string;

    /**
     * 接口地址
     */
    url: string;

    /**
     * 请求头
     */
    headers?: any;

    /**
     * 请求数据
     */
    data?: any;

    /**
     * 明文，请求数据可能是密文
     */
    plaintext?: any;

    /**
     * axiosError.code, 例如：`ECONNABORTED` 请求超时
     */
    code?: string;

    /**
     * http状态码 `200`、`500`、`404`
     */
    status?: any;

    /**
     * 接口返回值
     */
    response?: any;

    toString() {
        return `${this.apiCategory || ''}${this.apiDesc ? '`' + this.apiDesc + '`' : ''}调用失败,错误信息:${this.message},地址:${this.url},请求头:${JSON.stringify(this.headers)},请求参数:${JSON.stringify(this.data)}${this.plaintext ? ',明文:' + JSON.stringify(this.plaintext) : ''},HTTP状态码:${this.status},返回值:${JSON.stringify(this.response)}`;
    }
}

export function generateApiErrorFromAxiosError(axiosError: AxiosError, apiCategory?: string) {
    let apiError = new ApiError();

    if (apiCategory) apiError.apiCategory = apiCategory;
    apiError.message = axiosError.message;
    apiError.url = axiosError.config.url?.startsWith('http') ? axiosError.config.url : (axiosError.config.baseURL + (axiosError.config.url || ''));
    apiError.headers = axiosError.config.headers;
    apiError.data = axiosError.config.data;
    apiError.code = axiosError.code;
    apiError.status = axiosError.response?.status;
    apiError.response = axiosError.response?.data;

    if (axiosError.config['apiDesc']) apiError.apiDesc = axiosError.config['apiDesc'];
    if (axiosError.config['plaintext']) apiError.plaintext = axiosError.config['plaintext'];

    return apiError;
}

/**
 * 从AxiosError抛出ApiError异常
 * 
 * @param apiCategory 接口分类
 */
export function throwApiErrorFromAxiosError(error: any, apiCategory?: string) {
    if (error.isAxiosError) throw generateApiErrorFromAxiosError(error, apiCategory);
    else throw error;
}