import { t } from "./i18n/instance.js";
import { is_key_type, noop, not_empty, select, to_snake_case } from "./prototype.common.js";
import { platform } from "./platform.common.js";
export async function delay(milliseconds, options) {
    return platform.delay(milliseconds, options);
}
export function assert(assertion, message) {
    if (!assertion) {
        debugger;
        throw new Error(`${t('断言失败')}: ${message ? `${message}: ` : ''}`);
    }
    return assertion;
}
/** 做参数校验，逻辑检查 */
export function check(condition, message) {
    if (!condition) {
        debugger;
        throw new Error(message || t('检查失败'));
    }
    return condition;
}
/** 生成随机 id (number) */
export function genid() {
    return Math.random() * 2 ** 53;
}
export function log(...args) {
    if (args.length === 2) {
        const [label, obj] = args;
        console.log(label, obj);
        return obj;
    }
    else {
        const obj = args[0];
        console.log(obj);
        return obj;
    }
}
/** 生成 0, 1, ..., n - 1 (不包括 n) 的数组，支持传入 generator 函数，通过 index 生成各个元素
    @example seq(10, i => `item-${i}`) */
export function seq(n, generator) {
    let a = new Array(n);
    for (let i = 0; i < n; i++)
        a[i] = generator ? generator(i) : i;
    return a;
}
/** 数组或 iterable 去重（可按 mapper 选择或计算某个值来去重），重复值保留最后出现的那个
    - mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
export function unique(iterable, mapper) {
    if (!mapper)
        return [...new Set(iterable)];
    if (is_key_type(mapper))
        mapper = select(mapper);
    let map = new Map();
    for (const x of iterable)
        map.set(mapper(x), x);
    return [...map.values()];
}
/** 字符串字典序比较 */
export function strcmp(l, r) {
    if (l === r)
        return 0;
    if (l < r)
        return -1;
    return 1;
}
/** 排序对象中 keys 的顺序，返回新的对象 */
export function sort_keys(obj) {
    return Object.fromEntries(Object.entries(obj)
        .sort(([key_l], [key_r]) => strcmp(key_l, key_r)));
}
/** 比较 1.10.02 这种版本号
    - l, r: 两个版本号字符串
    - loose?: `false` 宽松模式，允许两个版本号格式（位数）不一致 */
export function vercmp(l, r, loose = false) {
    const lparts = l.split('.').map(x => Number(x));
    const rparts = r.split('.').map(x => Number(x));
    if (!loose && lparts.length !== rparts.length)
        throw new Error('传入 vercmp 的两个版本号格式不一致');
    const minlen = Math.min(lparts.length, rparts.length);
    for (let i = 0; i < minlen; ++i) {
        const l = lparts[i];
        const r = rparts[i];
        check(!isNaN(l) && !isNaN(r), '传入 vercmp 的版本非法');
        if (l !== r)
            return l - r;
    }
    // loose 下按短的优先，否则应该一样，为 0
    return lparts.length - rparts.length;
}
/** 将 keys, values 数组按对应的顺序组合成一个对象 */
export function zip_object(keys, values) {
    return keys.reduce((obj, key, i) => {
        obj[key] = values[i];
        return obj;
    }, {});
}
/** 映射对象中的 keys, 返回新对象
    - obj: 对象
    - mapper?: `to_snake_case` (key: string) => string 或者 Record<string, string> 一对一映射键
    - overrider?: 添加一些键到返回的新对象上 */
export function map_keys(obj, mapper = to_snake_case, overrider) {
    const obj_ = Object.fromEntries(Object.entries(obj)
        .map(typeof mapper === 'function' ?
        ([key, value]) => [mapper(key), value]
        :
            ([key, value]) => [mapper[key] || key, value]));
    return (overrider ? { ...obj_, ...overrider(obj_) } : obj_);
}
/** 返回一个映射对象 keys 的函数，通常和 .map 函数一起使用 */
export function get_key_mapper(mapper, overrider) {
    return (obj) => map_keys(obj, mapper, overrider);
}
/** 映射对象中的 values, 返回新对象 */
export function map_values(obj, mapper) {
    return Object.fromEntries(Object.entries(obj)
        .map(([key, value]) => [key, mapper(value, key)]));
}
/** 过滤对象中的 keys, 返回新对象 */
export function filter_keys(obj, filter) {
    return Object.fromEntries(Object.entries(obj)
        .filter(([key]) => filter(key)));
}
/** 过滤对象中的 values, 返回新对象
    - obj
    - filter?: `not_empty` */
export function filter_values(obj, filter = not_empty) {
    return Object.fromEntries(Object.entries(obj)
        .filter(([, value]) => filter(value)));
}
/** 简单选择对象中的部分 keys, 返回新对象 */
export function pick(obj, keys) {
    return keys.reduce((acc, key) => {
        acc[key] = obj[key];
        return acc;
    }, {});
}
/** 忽略对象中的 keys, 返回新对象 */
export function omit(obj, omit_keys) {
    const set = omit_keys instanceof Set;
    return filter_keys(obj, set ?
        key => !(omit_keys.has(key))
        :
            key => !(omit_keys.includes(key)));
}
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
export function concat(arrays) {
    let length = 0;
    for (const a of arrays)
        length += a.byteLength;
    let buf = platform.get_buffer(length);
    let offset = 0;
    for (const a of arrays) {
        const uint8view = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
        buf.set(uint8view, offset);
        offset += uint8view.byteLength;
    }
    return buf;
}
export let encoder = new TextEncoder();
/** 将字符串简单的编码为 utf-8 的 buffer (Uint8Array)。高频使用或者在流式处理时，考虑使用 TextEncoder 的 encodeInto 方法 */
export function encode(str) {
    return platform.encode(str);
}
export function encode_into(str, buf) {
    // 这个是直接用 v8 String::WriteUtf8 最高效的方法了
    return encoder.encodeInto(str, buf);
}
let decoder = new TextDecoder();
/** 将 utf-8 buffer (Uint8Array) 简单的解码为 string。
    在流式处理 (buffer 可能不完整) 时，应使用独立的 TextDecoder 实例调用 decode(buffer, { stream: true }) */
export function decode(buffer) {
    return decoder.decode(buffer);
}
/** 比较两个 buffer 内容是否相同，第二个可以传入 string 自动编码转换后比较，
    高频调用时建议提前编码 right 并缓存 */
export function buffer_equals(left, right) {
    if (typeof right === 'string')
        right = encode(right);
    if (left.length !== right.length)
        return false;
    for (let i = 0; i < left.length; i++)
        if (left[i] !== right[i])
            return false;
    return true;
}
/** 在指定的时间 (milliseconds) 内运行某个任务，超时之后抛出错误或调用 on_timeout
    - milliseconds: 限时毫秒数
    - action?: 要等待运行的任务, async function 或 promise
    - on_timeout?: 超时后调用的函数
        - 若传: 超时时调用会调用 on_timeout，参数为 TimeoutError，然后等待 on_timeout 执行完成
            最后 timeout 函数正常返回 null，如果 on_timeout 报错，同样会 reject
        - 若不传: 直接抛出 TimeoutError
    - print?: `true` 打印已超时任务的错误 */
export async function timeout(milliseconds, action, on_timeout, print = true) {
    const error = new TimeoutError();
    let presult = defer2();
    let waiting_on_timeout = false;
    let timeout = setTimeout(async () => {
        if (on_timeout) {
            try {
                waiting_on_timeout = true;
                await on_timeout(error);
                presult.resolve(null);
            }
            catch (error) {
                presult.reject(error);
            }
        }
        else
            presult.reject(error);
    }, milliseconds);
    const paction = typeof action === 'function' ? action() : action;
    paction.then(result => {
        if (presult.settled) {
            if (print)
                console.log('已超时任务最终完成了');
        }
        else if (!waiting_on_timeout) {
            presult.resolve(result);
            clearTimeout(timeout);
        }
    }, error => {
        if (presult.settled) {
            if (print)
                console.error(`已超时任务的错误: ${error.message}`);
        }
        else if (!waiting_on_timeout) {
            presult.reject(error);
            clearTimeout(timeout);
        }
    });
    return presult;
}
/** 轮询尝试 action 共 times 次，每次间隔 duration
    action 返回 trusy 值时认为成功，返回 action 的结果
    如果次数用尽仍然失败，返回 null */
export async function poll(duration, times, action) {
    let break_flag = false;
    function _break() {
        break_flag = true;
    }
    for (let i = 0; i < times; ++i) {
        const result = await action(_break);
        if (result)
            return result;
        if (break_flag)
            break;
        await delay(duration);
    }
    return null;
}
/** 模糊过滤字符串列表或对象列表，常用于根据用户输入补全或搜索过滤
    如果有完全匹配关键词的，只返回完全匹配关键词的候选项
    - query: 查询字符串，要求为全小写
    - items: TItem[], 要过滤的列表
    - keywords: string[][] 每个 item 对应一个全小写字符串的关键词数组，用于实际筛选匹配 */
export function fuzzyfilter(query, items, keywords, single_char_startswith = false) {
    if (!query)
        return items;
    if (!items.length)
        return [];
    const query_lower = query.toLowerCase();
    let fullmatches = [];
    keywords.forEach((words, index) => {
        if (words.includes(query_lower))
            fullmatches.push(items[index]);
    });
    if (fullmatches.length)
        return fullmatches;
    if (single_char_startswith && query.length === 1) {
        const c = query[0];
        return items.filter((_, i) => keywords[i].some(x => x.startsWith(c)));
    }
    return items.filter((_, i) => {
        for (const str_lower of keywords[i]) {
            let j = 0;
            let not_found = false;
            for (const c of query_lower) {
                j = str_lower.indexOf(c, j) + 1;
                if (!j) { // 找不到则 j === 0
                    not_found = true;
                    break;
                }
            }
            if (!not_found)
                return true;
        }
        return false;
    });
}
export function get(obj, keypath) {
    let obj_ = obj;
    for (const key of keypath.split('.'))
        obj_ = obj_[key];
    return obj_;
}
export function global_get(keypath) {
    return get(globalThis, keypath);
}
export function invoke(obj, funcpath, args) {
    const paths = funcpath.split('.');
    let obj_ = obj;
    for (let i = 0; i < paths.length - 1; i++)
        obj_ = obj_[paths[i]];
    return obj_[paths.at(-1)].call(obj_, ...args);
}
/** 时间间隔 (milliseconds) 格式化 */
export function delta2str(delta) {
    if (delta < 1)
        return '0 ms';
    // [1, 100) ms
    if (delta < 100)
        return `${delta.toFixed(0)} ms`;
    // [100, 1000) ms
    // 0.8 s
    if (delta <= 950)
        return `${(delta / 1000).toFixed(1)} s`;
    // 3 s
    if (delta < 1000 * 60)
        return `${(delta / 1000).toFixed()} s`;
    // 1 min 12 s [1 min 0s, 60 min)
    const seconds = Math.trunc(delta / 1000);
    if (seconds < 60 * 60)
        return `${Math.trunc(seconds / 60)} min ${seconds % 60} s`;
    const hour = Math.trunc(seconds / 3600);
    return `${hour} h ${Math.trunc((seconds - 3600 * hour) / 60)} min ${seconds % 60} s`;
}
/** 默认日期时间格式 */
export const datetime_format = 'YYYY.MM.DD HH:mm:ss';
/** 默认日期格式 */
export const date_format = 'YYYY.MM.DD';
/** 默认时间格式 */
export const time_format = 'HH:mm:ss';
export class Timer {
    started = Date.now();
    ended = 0;
    /** 停止秒表，保存读数 */
    stop() {
        this.ended = Date.now();
    }
    /** 如果秒表未停止，获取当前秒表读数；
        如果秒表已停止，获取停止时的秒表读数; */
    get() {
        return (this.ended || Date.now()) - this.started;
    }
    /** 获取时间表示字符串，如 1.2 s
        - parenthesis?: `true` 字符串前后加上括号，如 (1.2 s) */
    getstr(parenthesis = false) {
        let s = delta2str(this.get());
        if (parenthesis)
            return s.bracket();
        else
            return s;
    }
    print() {
        console.log(this.getstr(true));
    }
    /** 重置 started */
    reset() {
        this.started = Date.now();
        this.ended = 0;
    }
    get_and_reset() {
        const result = this.get();
        this.reset();
        return result;
    }
    getstr_and_reset(parenthesis) {
        const result = this.getstr(parenthesis);
        this.reset();
        return result;
    }
}
export class TimeoutError extends Error {
    name = 'TimeoutError';
}
/** 创建一个 promise，后续可调用 promise.resolve, promise.reject 方法设置其状态和值
    - initial?: `undefined` 传入非 undefined 值（包括 null）时直接设置为 resolved 状态
    注: 下面的方法不能标记为 aysnc function, 否则会对返回值再做一层 Promise.resolve() 导致 reject, resolve 属性丢失 */
// eslint-disable-next-line @typescript-eslint/promise-function-async
export function defer(initial) {
    if (initial === undefined) {
        let { promise, resolve, reject } = Promise.withResolvers();
        let p = promise;
        p.resolve = resolve;
        p.reject = reject;
        return p;
    }
    else {
        let p = Promise.resolve(initial);
        p.resolve = noop;
        p.reject = noop;
        return p;
    }
}
/** 有 settled 状态的 defer */
// eslint-disable-next-line @typescript-eslint/promise-function-async
export function defer2(initial) {
    if (initial === undefined) {
        let settled = false;
        let { promise, resolve, reject } = Promise.withResolvers();
        let p = promise;
        p.resolve = (value) => {
            settled = true;
            resolve(value);
        };
        p.reject = (error) => {
            settled = true;
            reject(error);
        };
        Object.defineProperty(p, 'settled', {
            get() { return settled; },
            enumerable: true,
            configurable: true
        });
        return p;
    }
    else {
        let p = Promise.resolve(initial);
        p.resolve = noop;
        p.reject = noop;
        p.settled = true;
        return p;
    }
}
/** @example
    let lock = new Lock(redis)
    
    // 锁定资源后在 action 回调中操作资源，回调 promise 完成后自动释放资源，
    // 三秒内未成功独占资源直接抛出 TimeoutError (开始执行后不受 signal abort 影响)
    await lock.request(async redis => {
        const value = await redis.get('key')
        await redis.set('key', value * 2)
    }, AbortSignal.timeout(3000))
    
    参考:
    https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
    https://github.com/metarhia/web-locks
    https://www.npmjs.com/package/async-lock  */
export class Lock {
    /** 如果操作不需要独占资源，可以直接通过 lock.resource 访问，否则需要通过 await lock.request() 独占资源后再访问 */
    resource;
    /** 等待链，新的 await lock.request() 调用会等待当前等待链尾部的 promise 完成，并作为新的尾部 */
    ptail = defer(null);
    /** 查询当前资源是否属于被锁定的状态，方便在资源闲置时做一些可选操作（操作前仍需锁定），或者做一些状态展示 */
    locked = false;
    /** 可以不传 resource，表示管理某个抽象或虚拟的资源 */
    constructor(resource) {
        this.resource = resource;
    }
    /** 通过 await lock.request() 锁定资源以便独占访问，锁定资源后在 action 回调中操作资源，回调 promise 完成 (fullfilled 或 rejected) 后自动释放资源 */
    async request(action, signal) {
        signal?.throwIfAborted();
        const ptail = this.ptail;
        let pcurrent = this.ptail = defer();
        this.locked = true;
        return new Promise((resolve, reject) => {
            // 下面两种情况，先发生的决定 request 返回的 promise 状态
            // 不管是否 aborted, 都要等资源被前一次调用释放，先 aborted 只是将控制权交回给调用者，在锁定资源后不执行 action， 直接释放
            /** 防止执行过程中 signal abort */
            let executing = false;
            signal?.addEventListener('abort', () => {
                if (!executing)
                    // 这里不能释放锁，需要等 ptail resolve 后拿到资源再释放
                    reject(signal.reason);
            }, { once: true });
            ptail.then(async () => {
                // 这里已经能保证独占访问资源
                // 如果 aborted, 可以理解为独占资源失败，调用者不会使用资源，直接释放
                if (signal?.aborted)
                    reject(signal.reason);
                else
                    // 由调用者去操作资源
                    try {
                        executing = true;
                        resolve(await action(this.resource));
                    }
                    catch (error) {
                        reject(error);
                    }
                // 下面开始释放锁
                this.locked = false;
                pcurrent.resolve();
            });
        });
    }
}
/** 过滤符合 pattern 的行 */
export function grep(str, pattern) {
    return str.split_lines()
        .filter(typeof pattern === 'string'
        ? line => line.includes(pattern)
        : line => pattern.test(line))
        .join_lines();
}
export function lowercase_first_letter(str) {
    return str[0].toLowerCase() + str.slice(1);
}
/** 大于 n 的最小的 2 的幂次 */
export function ceil2(n) {
    let power = 1;
    // 不能用 power <<= 1, 结果可能会超过 32 bit 整数导致位运算溢出为 0，死循环
    for (; power <= n; power += power)
        ;
    return power;
}
/** 节流，最多只在时间间隔末尾调用一次，可以设置首次调用是否延后 */
export function throttle(duration, func, delay_first = false) {
    let timeout = 0;
    let last = 0;
    let saved_this;
    let saved_args;
    return function throttled(...args) {
        // 当前时间间隔已预定执行，本次调用仅更新调用参数
        if (timeout) {
            saved_this = this;
            saved_args = args;
            return;
        }
        const now = Date.now();
        if (last === 0 && delay_first)
            last = now;
        const ellapsed = now - last;
        // 过了时间间隔末尾，直接执行
        if (ellapsed >= duration) {
            last = now;
            func.apply(this, args);
        }
        else { // 预定在间隔末尾执行
            saved_this = this;
            saved_args = args;
            timeout = setTimeout(() => {
                timeout = null;
                last = Date.now();
                func.apply(saved_this, saved_args);
                saved_this = null;
                saved_args = null;
            }, duration - ellapsed);
        }
    };
}
/** 防抖，间隔一段时间不再触发时调用，同时大幅优化减少 setTimeout, clearTimeout 的调用次数 */
export function debounce(duration, func) {
    const half = Math.floor(duration / 2);
    let timeout;
    let last = 0;
    let saved_args;
    let saved_this;
    function debounce_callback() {
        timeout = null;
        func.apply(saved_this, saved_args);
        saved_this = null;
        saved_args = null;
    }
    return function debounced(...args) {
        if (!timeout) {
            timeout = setTimeout(debounce_callback, duration);
            last = Date.now();
            return;
        }
        const now = Date.now();
        saved_args = args;
        saved_this = this;
        if (now - last < half)
            return;
        clearTimeout(timeout);
        setTimeout(debounce_callback, duration);
        last = now;
    };
}
export function tomorrow(date) {
    date = typeof date === 'undefined' ? new Date() : new Date(date);
    date.setDate(date.getDate() + 1);
    return date;
}
export function to_csv_field(str) {
    return /[,"\n\r]/.test(str) ? str.replaceAll('"', '""').quote('double') : str;
}
/** 设置 error.message 同时更新 error.stack */
export function set_error_message(error, message) {
    error.message = message;
    error.stack = `${error.name}: ${message}\n` +
        error.stack.slice_from('\n', { optional: true });
    return error;
}
/** 比较两个数组中的元素完全相同，数组元素用引用比较 */
export function array_equals(a, b) {
    if (!a && !b)
        return true;
    if (a && !b || !a && b)
        return false;
    return a.every((x, i) => x === b[i]);
}
export function nowstr(with_date = false) {
    const date = new Date();
    return with_date ? date.to_str() : date.to_time_str();
}
//# sourceMappingURL=utils.common.js.map