import { Stream, Writable, Transform } from 'node:stream';
import util from 'node:util';
import ncrypto from 'node:crypto';
import "./prototype.js";
import "./platform.js";
import { assert, decode, defer, delay } from "./utils.common.js";
export * from "./utils.common.js";
/** `180` 输出字符宽度 */
export const output_width = 180;
export const url_width = 52;
export function set_inspect_options(colors = true) {
    util.inspect.defaultOptions.maxArrayLength = 40;
    util.inspect.defaultOptions.maxStringLength = 10000;
    util.inspect.defaultOptions.breakLength = output_width;
    util.inspect.defaultOptions.colors = colors;
    util.inspect.defaultOptions.compact = false;
    util.inspect.defaultOptions.getters = true;
    util.inspect.defaultOptions.depth = 2;
    util.inspect.defaultOptions.sorted = false;
    util.inspect.defaultOptions.showProxy = true;
    util.inspect.styles.number = 'green';
    util.inspect.styles.string = 'cyan';
    util.inspect.styles.boolean = 'blue';
    util.inspect.styles.date = 'magenta';
    util.inspect.styles.special = 'white';
}
export function typed_array_to_buffer(view) {
    return Buffer.from(view.buffer, view.byteOffset, view.byteLength);
}
/** 每天在固定时间执行操作
    - hour: 0 - 23 之间的整数，在这个点执行
    - action */
export async function schedule_everyday(hour, action) {
    const now = new Date();
    let target = new Date();
    target.setHours(hour, 0, 0, 0);
    // 如果目标时间已过，设定为明天的时间
    if (now.getTime() > target.getTime())
        target.setDate(target.getDate() + 1);
    // 等时间到
    await delay(target.getTime() - now.getTime());
    for (;;) {
        try {
            await action();
        }
        catch (error) {
            // 往上抛没什么意义，打印个日志算了
            console.error(error);
        }
        // 等一天
        await delay(1000 * 60 * 60 * 24);
    }
}
export function log_line() {
    console.log('---');
}
export function sha256(data) {
    return ncrypto.hash('sha256', data);
}
export function sha1(data) {
    return ncrypto.hash('sha1', data);
}
export function has_chinese(str) {
    return /[\u4E00-\u9FA5]/.test(str);
}
export function escape_line_feed(str) {
    return str.replace(/\n/g, '\\n');
}
/** util.inspect(obj)
    - options
        - limit?: `10000`  传 0 时不限制
        - omit?: string[] */
export function inspect(obj, options = {}) {
    if (options.omit) {
        obj = { ...obj };
        for (const key of options.omit)
            delete obj[key];
        // 改变了 obj 的引用，去掉 inspect.custom 防止无限递归 inspect
        delete obj[inspect.custom];
    }
    let text = util.inspect(obj, options);
    const limit = options.limit ?? 10000;
    if (limit && text.length > limit)
        return `${text.slice(0, limit)}……'\u001b[39m\n`;
    else
        return text;
}
/** 根据 enabled 选项返回有 / 无颜色的字符串 (str) */
export function colored(str, color, enabled = inspect.defaultOptions.colors) {
    return enabled ? str[color] : str;
}
(function (inspect) {
    inspect.custom = util.inspect.custom;
    inspect.defaultOptions = util.inspect.defaultOptions;
})(inspect || (inspect = {}));
// ------------------------------------ stream
/** npm map-stream
    filter will reemit the data if cb(err,pass) pass is truthy
    
    reduce is more tricky
    maybe we want to group the reductions or emit progress updates occasionally
    the most basic reduce just emits one 'data' event after it has recieved 'end'
    
    create an event stream and apply function to each .write,
    emitting each response as data unless it's an empty callback
 */
export function map_stream(mapper, options) {
    options = options || {};
    let inputs = 0, outputs = 0, ended = false, paused = false, destroyed = false, last_written = 0, in_next = false;
    let stream = Object.assign(new Stream(), {
        readable: true,
        writable: true,
        write(data) {
            if (ended)
                throw new Error('map stream is not writable');
            in_next = false;
            inputs++;
            try {
                // catch sync errors and handle them like async errors
                const written = wrapped_mapper(data, inputs, next);
                paused = (written === false);
                return !paused;
            }
            catch (err) {
                // if the callback has been called syncronously, and the error has occured in an listener, throw it again.
                if (in_next)
                    throw err;
                next(err);
                return !paused;
            }
        },
        end(data) {
            if (ended)
                return;
            _end(data);
        },
        destroy() {
            ended = destroyed = true;
            stream.writable = stream.readable = paused = false;
            process.nextTick(function () {
                stream.emit('close');
            });
        },
        pause() {
            paused = true;
        },
        resume() {
            paused = false;
        }
    });
    let error_event_name = options.failures ? 'failure' : 'error';
    // Items that are not ready to be written yet (because they would come out of order) get stuck in a queue for later.
    let write_queue = {};
    function queue_data(data, number) {
        let next_to_write = last_written + 1;
        if (number === next_to_write) {
            // If it's next, and its not undefined write it
            if (data !== undefined)
                stream.emit('data', data);
            last_written++;
            next_to_write++;
        }
        else
            // Otherwise queue it for later.
            write_queue[number] = data;
        // If the next value is in the queue, write it
        if (Object.prototype.hasOwnProperty.call(write_queue, next_to_write)) {
            let data_to_write = write_queue[next_to_write];
            delete write_queue[next_to_write];
            return queue_data(data_to_write, next_to_write);
        }
        outputs++;
        if (inputs === outputs) {
            if (paused) {
                paused = false;
                stream.emit('drain'); // written all the incoming events
            }
            if (ended)
                _end();
        }
    }
    function next(err, data, number) {
        if (destroyed)
            return;
        in_next = true;
        if (!err || options.failures)
            queue_data(data, number);
        if (err)
            stream.emit(error_event_name, err);
        in_next = false;
    }
    /** Wrap the mapper function by calling its callback with the order number of the item in the stream. */
    function wrapped_mapper(input, number, callback) {
        return mapper.call(null, input, function (err, data) {
            callback(err, data, number);
        });
    }
    function _end(data) {
        // if end was called with args, write it, 
        ended = true; // write will emit 'end' if ended is true
        stream.writable = false;
        if (data !== undefined)
            return queue_data(data, inputs);
        else if (inputs === outputs) { // wait for processing 
            stream.readable = false;
            stream.emit('end');
            stream.destroy();
        }
    }
    return stream;
}
export async function* stream_to_lines(stream) {
    let buf = '';
    for await (const chunk of stream) {
        let j = 0;
        for (let i = 0; (i = chunk.indexOf('\n', j)) >= 0;) {
            let line = chunk.slice(j, i);
            if (buf) {
                line = buf + line;
                buf = '';
            }
            j = i + 1;
            yield line;
        }
        buf = chunk.slice(j);
    }
}
export class WritableMemoryStream extends Writable {
    chunks = [];
    pbuffer = defer();
    constructor() {
        super({ highWaterMark: 2 ** 30, decodeStrings: false });
    }
    _write(chunk, encoding, callback) {
        this.chunks.push(chunk);
        callback();
    }
    _writev(chunks, callback) {
        for (const { chunk } of chunks)
            this.chunks.push(chunk);
        callback();
    }
    _final(callback) {
        this.pbuffer.resolve(Buffer.concat(this.chunks));
        this.chunks = null;
        callback();
    }
}
/** 用 TextDecoder 通过 .pipe 方法持续的解码 process.stdout 等流，
    主要在 process.ts#start 中使用
    https://nodejs.org/api/stream.html#implementing-a-transform-stream */
export class DecoderStream extends Transform {
    decoder;
    constructor(encoding) {
        super({ encoding: 'utf-8' });
        this.decoder = new TextDecoder(encoding);
    }
    _transform(chunk, encoding, callback) {
        assert(chunk instanceof Uint8Array);
        const str = this.decoder.decode(chunk, { stream: true });
        if (str.length)
            this.push(str, 'utf-8');
        callback();
    }
    _flush(callback) {
        const str = this.decoder.decode();
        if (str.length)
            this.push(str, 'utf-8');
        callback();
    }
}
/** 消费一个可读流 */
export async function consume_stream(stream, ignore_error = false) {
    try {
        for await (const chunk of stream)
            ;
    }
    catch (error) {
        if (!ignore_error)
            throw error;
    }
}
/** 根据 range 生成整数序列 (iterable)
    - range: 取值为逗号分割的多个可用值或值区间 (不能含有空格)，比如：`8321,8322,8300-8310,11000-11999`
    - reverse?: `false` 在 range 内从后往前生成 */
export function* range_to_numbers(range, reverse = false) {
    let parts = range.split(',');
    if (reverse)
        parts.reverse();
    for (const part of parts) {
        const [left, right] = part.split('-').map(n => Number(n));
        if (!right)
            yield left;
        if (reverse)
            for (let i = right; i >= left; i--)
                yield i;
        else
            for (let i = left; i <= right; i++)
                yield i;
    }
}
export function decode_buffer_hex_string(str) {
    return decode(Buffer.from(str.split(' ')
        .map(x => parseInt(x, 16))));
}
//# sourceMappingURL=utils.js.map