import { U, StrRemoveSome, If_StrIncludes, Or, Equal } from "typescript-treasure";

export type InjectDataFn<Expr> = <const Data>
    ( fill_data: Data extends string | number | undefined | null ? never : Data ) =>
    (
        If_HasNumFmt<Expr, Data> extends true ?
        number | GetWrapErr<Data>
        :
        string | GetWrapErr<Data>
    );


export type InjectExprFn<Data> = <const Expr>
    ( expr: Expr extends string | number ? Expr : never ) =>
    (
        If_HasNumFmt<Expr, Data> extends true ?
        number | GetWrapErr<Data>
        :
        string | GetWrapErr<Data>
    );


type If_IncludesVar<T> = U.If_IncludeVar<StrRemoveSome<T, ["!n","!e","!u","!N","!E","!U"]>>;


type If_NumOrStr<T> = T extends string | number ? true : false;
type If_FillData<T> = T extends string | number | undefined | null ? false : true;
type Not<T> = T extends true ? false : true;

type GetWrapErr<T> = T extends { _error: infer I; } ? I : "-";
type GetFmt<T> = T extends { _fmt: infer I; } ? I : never;

type If_HasNumFmt<Expr, Data> =
    Or<
        If_StrIncludes<Expr, ["!n"]> | If_StrIncludes<GetFmt<Data>, ["!n"]>
    >;


type DirectReturn<Expr, Data> =
    If_HasNumFmt<Expr, Data> extends true ?
    (
        number | GetWrapErr<Data>
    )
    :
    (
        string | GetWrapErr<Data>
    );


type FuncReturn<Arg1> =
    If_IncludesVar<Arg1> extends true ?
    (
        InjectDataFn<Arg1>
    )
    :
    (
        If_FillData<Arg1> extends true ?
        InjectExprFn<Arg1>
        :
        never
    );

type If_DirectReturn<Arg1, Arg2> =
    Or<
        If_NumOrStr<Arg1> & If_FillData<Arg2>
        |
        If_NumOrStr<Arg1> & Not<If_IncludesVar<Arg1>>
    >;

type CalcConfig<Fmt extends string, Err> =
    any[] | Partial<{
        _error: Err,
        _fill_data: any,
        _unit: boolean,
        _fmt: Fmt,
        _memo: boolean,
        _mode: "space" | "space-all" | "normal", // space 只会影响表达式部分 space_all 也会影响 fmt 部分
        [Prop: string]: any;
    }>;

export type Calc = <const Expr extends string | number, const Fmt extends string, const Err = never>
    ( expr: Expr, options?: CalcConfig<Fmt, Err> ) =>
    (
        If_StrIncludes<Fmt, ["!n"]> & If_StrIncludes<Expr, ["!n"]> extends true ?
        number | Err
        :
        string | Err
    );

export interface CalcWrap
{
    <const Expr extends string | number>
        ( expr: Expr ): If_IncludesVar<Expr> extends true ? FuncReturn<Expr> : If_StrIncludes<Expr, ["!n", "!N"]> extends true ? number | "-" : string | "-";

    <const Expr extends string | number, const Fmt extends string, const Err, const Options extends CalcConfig<Fmt, Err>>
        ( expr: Expr, options: Options ): DirectReturn<Expr, Options>;

    <const Fmt extends string, const Err, const Options extends CalcConfig<Fmt, Err>>
        ( options: Options ): FuncReturn<Options>;
}

declare const calc_util: {
    /**
     * 检查当前库是否有更新版本
     */
    check_update(): void;
    /**
     * 打印当前库的版本
     */
    print_version(): void;
};

/**
 * 传入字符串算术式或数字返回计算的结果
 * @param expr 一个字符串算术式或一个数字
 * @param options 一个配置或者用来填充算术式的数据源,或者两者的结合
 * @returns 返回进过计算和格式化之后的结果,结果可能是字符串或数字或自定义的错误类型
 *
 * @example:
 * ```typescript
 * calc("a + b", {a: 1, b: 2}) // 3
 * calc("1.00") // 1
 * calc("1.00 | =3") // 1.000
 * calc("a + b | =2", {a: 0.1, b: 0.2}) // 0.30
 * calc("a++(b", {_error: null}) // null
 * calc("1% + 2%", {_unit: true}) // 3%
 * ```
 */
export const calc: Calc;
/**
 *  calc方法的别名.传入字符串算术式或数字返回计算的结果
 * @param expr 一个字符串算术式或一个数字
 * @param options 一个配置或者用来填充算术式的数据源,或者两者的结合
 * @returns 返回进过计算和格式化之后的结果,结果可能是字符串或数字或自定义的错误类型
 */
export const fmt: Calc;
/**
 * calc方法的包装版本,除了支持calc所有的功能还额外提供了更强大的类型推导和更灵活的编写方式
 * @param expr 一个字符串算术式或一个数字
 * @param options 一个配置或者用来填充算术式的数据源,或者两者的结合
 * @returns 返回进过计算和格式化之后的结果,结果可能是字符串或数字或自定义的错误类型
 *
 * @example:
 * ```typescript
 * // 先传入表达式还是先注入数据源都是可以的
 * calc_wrap("a + b")({a: 1, b: 2}) // 3
 * calc_wrap("a + b",{a: 1, b: 2}) // 3
 * calc_wrap({a: 0.1, b: 0.2})("a + b | =2") // 0.30
 * ```
 */
export const calc_wrap: CalcWrap;
declare const version: string;

export const parse_thousands: (str: string) => string;

/**
 * 传入两个数字或数字字符串进行相加并返回结果,可以指定第三个参数控制返回数字还是数字字符串
 * @param a 一个数字或数字字符串
 * @param b 另一个数字或数字字符串
 * @param type 输出数字还是字符串
 * @returns 计算得到的结果
 */
export const plus: <const T extends "number" | "string" = "number">(a: number|string, b: number|string, type?: T) => T extends "number" ? number : string;
/**
 * 传入两个数字或数字字符串进行相减并返回结果,可以指定第三个参数控制返回数字还是数字字符串
 * @param a 一个数字或数字字符串
 * @param b 另一个数字或数字字符串
 * @param type 输出数字还是字符串
 * @returns 计算得到的结果
 */
export const sub: <const T extends "number" | "string" = "number">(a: number|string, b: number|string, type?: T) => T extends "number" ? number : string;
/**
 * 传入两个数字或数字字符串进行相乘并返回结果,可以指定第三个参数控制返回数字还是数字字符串
 * @param a 一个数字或数字字符串
 * @param b 另一个数字或数字字符串
 * @param type 输出数字还是字符串
 * @returns 计算得到的结果
 */
export const mul: <const T extends "number" | "string" = "number">(a: number|string, b: number|string, type?: T) => T extends "number" ? number : string;
/**
 * 传入两个数字或数字字符串进行相除并返回结果,可以指定第三个参数控制返回数字还是数字字符串
 * @param a 一个数字或数字字符串
 * @param b 另一个数字或数字字符串
 * @param type 输出数字还是字符串
 * @returns 计算得到的结果
 */
export const div: <const T extends "number" | "string" = "number">(a: number|string, b: number|string, type?: T) => T extends "number" ? number : string;
/**
 * 传入两个数字或数字字符串进行整除并返回结果,可以指定第三个参数控制返回数字还是数字字符串
 * @param a 一个数字或数字字符串
 * @param b 另一个数字或数字字符串
 * @param type 输出数字还是字符串
 * @returns 计算得到的结果
 */
export const idiv: <const T extends "number" | "string" = "number">(a: number|string, b: number|string, type?: T) => T extends "number" ? number : string;
/**
 * 传入两个数字或数字字符串进行幂运算并返回结果,可以指定第三个参数控制返回数字还是数字字符串
 * @param a 一个数字或数字字符串
 * @param b 另一个数字或数字字符串
 * @param type 输出数字还是字符串
 * @returns 计算得到的结果
 */
export const pow: <const T extends "number" | "string" = "number">(a: number|string, b: number|string, type?: T) => T extends "number" ? number : string;
/**
 * 传入两个数字或数字字符串进行取模并返回结果,可以指定第三个参数控制返回数字还是数字字符串
 * @param a 一个数字或数字字符串
 * @param b 另一个数字或数字字符串
 * @param type 输出数字还是字符串
 * @returns 计算得到的结果
 */
export const mod: <const T extends "number" | "string" = "number">(a: number|string, b: number|string, type?: T) => T extends "number" ? number : string;

/**
 * calc方法的精简版本,去除了对单位计算的支持但是这个方法性能更好一些,该方法永远不会报错,在计算出错的时候返回 -
 * @param expr 一个字符串计算式或数字,如果是计算式那么内部的所有单元都需要使用一个空格严格分割
 * @param fmt_expr 一个用于描述如何格式化计算结果的字符串表达式
 * @param data 用于填充算术式中变量的数据源可以是对象也可以是对象数组
 * @param err_value 计算错误时返回的结果,模式是 -
 * @returns string | number | Err
 */
export const calc_lite: <const T extends string | null | undefined, const Err = "-">
    (calc_expr: string | number, fmt_expr?: T, data?: any, err_value?: Err) =>
    (
        Or<Equal<T, null> | Equal<T, undefined>> extends true ?
        ( string | Err )
        :
        (
            If_StrIncludes<T, ["!n"]> extends true ?
            number | Err
            :
            string | Err
        )
    )
/**
 * 较为原始的计算方法,使用中缀表达式的顺序来传入参数
 * @param value1 计算数1
 * @param op "+" | "-" | "*" | "/" | "%" | "**" | "//"
 * @param value2 计算数2
 * @returns 计算结果
 */
export const calc_mini: (value1: number | string, op: "+" | "-" | "*" | "/" | "%" | "**" | "//", value2: number | string) => string