const std = @import("std");
const bun = @import("bun");
const Allocator = std.mem.Allocator;
const JSC = bun.JSC;
const string = bun.string;
const ZigString = JSC.ZigString;
const validators = @import("./util/validators.zig");
const envloader = @import("./../../env_loader.zig");

pub fn internalErrorName(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
    const arguments = callframe.arguments_old(1).slice();
    if (arguments.len < 1) {
        return globalThis.throwNotEnoughArguments("internalErrorName", 1, arguments.len);
    }

    const err_value = arguments[0];
    const err_int = err_value.toInt32();

    if (err_int == -4095) return bun.String.static("EOF").toJS(globalThis);
    if (err_int == -4094) return bun.String.static("UNKNOWN").toJS(globalThis);
    if (err_int == -3000) return bun.String.static("EAI_ADDRFAMILY").toJS(globalThis);
    if (err_int == -3001) return bun.String.static("EAI_AGAIN").toJS(globalThis);
    if (err_int == -3002) return bun.String.static("EAI_BADFLAGS").toJS(globalThis);
    if (err_int == -3003) return bun.String.static("EAI_CANCELED").toJS(globalThis);
    if (err_int == -3004) return bun.String.static("EAI_FAIL").toJS(globalThis);
    if (err_int == -3005) return bun.String.static("EAI_FAMILY").toJS(globalThis);
    if (err_int == -3006) return bun.String.static("EAI_MEMORY").toJS(globalThis);
    if (err_int == -3007) return bun.String.static("EAI_NODATA").toJS(globalThis);
    if (err_int == -3008) return bun.String.static("EAI_NONAME").toJS(globalThis);
    if (err_int == -3009) return bun.String.static("EAI_OVERFLOW").toJS(globalThis);
    if (err_int == -3010) return bun.String.static("EAI_SERVICE").toJS(globalThis);
    if (err_int == -3011) return bun.String.static("EAI_SOCKTYPE").toJS(globalThis);
    if (err_int == -3013) return bun.String.static("EAI_BADHINTS").toJS(globalThis);
    if (err_int == -3014) return bun.String.static("EAI_PROTOCOL").toJS(globalThis);

    if (err_int == -bun.sys.UV_E.@"2BIG") return bun.String.static("E2BIG").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ACCES) return bun.String.static("EACCES").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ADDRINUSE) return bun.String.static("EADDRINUSE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ADDRNOTAVAIL) return bun.String.static("EADDRNOTAVAIL").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.AFNOSUPPORT) return bun.String.static("EAFNOSUPPORT").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.AGAIN) return bun.String.static("EAGAIN").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ALREADY) return bun.String.static("EALREADY").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.BADF) return bun.String.static("EBADF").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.BUSY) return bun.String.static("EBUSY").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.CANCELED) return bun.String.static("ECANCELED").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.CHARSET) return bun.String.static("ECHARSET").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.CONNABORTED) return bun.String.static("ECONNABORTED").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.CONNREFUSED) return bun.String.static("ECONNREFUSED").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.CONNRESET) return bun.String.static("ECONNRESET").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.DESTADDRREQ) return bun.String.static("EDESTADDRREQ").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.EXIST) return bun.String.static("EEXIST").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.FAULT) return bun.String.static("EFAULT").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.HOSTUNREACH) return bun.String.static("EHOSTUNREACH").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.INTR) return bun.String.static("EINTR").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.INVAL) return bun.String.static("EINVAL").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.IO) return bun.String.static("EIO").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ISCONN) return bun.String.static("EISCONN").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ISDIR) return bun.String.static("EISDIR").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.LOOP) return bun.String.static("ELOOP").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.MFILE) return bun.String.static("EMFILE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.MSGSIZE) return bun.String.static("EMSGSIZE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NAMETOOLONG) return bun.String.static("ENAMETOOLONG").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NETDOWN) return bun.String.static("ENETDOWN").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NETUNREACH) return bun.String.static("ENETUNREACH").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NFILE) return bun.String.static("ENFILE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOBUFS) return bun.String.static("ENOBUFS").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NODEV) return bun.String.static("ENODEV").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOENT) return bun.String.static("ENOENT").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOMEM) return bun.String.static("ENOMEM").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NONET) return bun.String.static("ENONET").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOSPC) return bun.String.static("ENOSPC").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOSYS) return bun.String.static("ENOSYS").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOTCONN) return bun.String.static("ENOTCONN").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOTDIR) return bun.String.static("ENOTDIR").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOTEMPTY) return bun.String.static("ENOTEMPTY").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOTSOCK) return bun.String.static("ENOTSOCK").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOTSUP) return bun.String.static("ENOTSUP").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.PERM) return bun.String.static("EPERM").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.PIPE) return bun.String.static("EPIPE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.PROTO) return bun.String.static("EPROTO").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.PROTONOSUPPORT) return bun.String.static("EPROTONOSUPPORT").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.PROTOTYPE) return bun.String.static("EPROTOTYPE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ROFS) return bun.String.static("EROFS").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.SHUTDOWN) return bun.String.static("ESHUTDOWN").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.SPIPE) return bun.String.static("ESPIPE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.SRCH) return bun.String.static("ESRCH").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.TIMEDOUT) return bun.String.static("ETIMEDOUT").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.TXTBSY) return bun.String.static("ETXTBSY").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.XDEV) return bun.String.static("EXDEV").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.FBIG) return bun.String.static("EFBIG").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOPROTOOPT) return bun.String.static("ENOPROTOOPT").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.RANGE) return bun.String.static("ERANGE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NXIO) return bun.String.static("ENXIO").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.MLINK) return bun.String.static("EMLINK").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.HOSTDOWN) return bun.String.static("EHOSTDOWN").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.REMOTEIO) return bun.String.static("EREMOTEIO").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NOTTY) return bun.String.static("ENOTTY").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.FTYPE) return bun.String.static("EFTYPE").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.ILSEQ) return bun.String.static("EILSEQ").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.OVERFLOW) return bun.String.static("EOVERFLOW").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.SOCKTNOSUPPORT) return bun.String.static("ESOCKTNOSUPPORT").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.NODATA) return bun.String.static("ENODATA").toJS(globalThis);
    if (err_int == -bun.sys.UV_E.UNATCH) return bun.String.static("EUNATCH").toJS(globalThis);

    var fmtstring = bun.String.createFormat("Unknown system error {d}", .{err_int}) catch bun.outOfMemory();
    return fmtstring.transferToJS(globalThis);
}

pub fn etimedoutErrorCode(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
    return JSC.JSValue.jsNumberFromInt32(-bun.sys.UV_E.TIMEDOUT);
}

pub fn enobufsErrorCode(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
    return JSC.JSValue.jsNumberFromInt32(-bun.sys.UV_E.NOBUFS);
}

/// `extractedSplitNewLines` for ASCII/Latin1 strings. Panics if passed a non-string.
/// Returns `undefined` if param is utf8 or utf16 and not fully ascii.
///
/// ```js
/// // util.js
/// const extractedNewLineRe = new RegExp("(?<=\\n)");
/// extractedSplitNewLines = value => RegExpPrototypeSymbolSplit(extractedNewLineRe, value);
/// ```
pub fn extractedSplitNewLinesFastPathStringsOnly(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
    bun.assert(callframe.argumentsCount() == 1);
    const value = callframe.argument(0);
    bun.assert(value.isString());

    const str = try value.toBunString(globalThis);
    defer str.deref();

    return switch (str.encoding()) {
        inline .utf16, .latin1 => |encoding| split(encoding, globalThis, bun.default_allocator, &str),
        .utf8 => if (bun.strings.isAllASCII(str.byteSlice()))
            return split(.utf8, globalThis, bun.default_allocator, &str)
        else
            return JSC.JSValue.jsUndefined(),
    };
}

fn split(
    comptime encoding: bun.strings.EncodingNonAscii,
    globalThis: *JSC.JSGlobalObject,
    allocator: Allocator,
    str: *const bun.String,
) bun.JSError!JSC.JSValue {
    var fallback = std.heap.stackFallback(1024, allocator);
    const alloc = fallback.get();
    const Char = switch (encoding) {
        .utf8, .latin1 => u8,
        .utf16 => u16,
    };

    var lines: std.ArrayListUnmanaged(bun.String) = .{};
    defer {
        for (lines.items) |out| {
            out.deref();
        }
        lines.deinit(alloc);
    }

    const buffer: []const Char = if (encoding == .utf16)
        str.utf16()
    else
        str.byteSlice();
    var it: SplitNewlineIterator(Char) = .{ .buffer = buffer, .index = 0 };
    while (it.next()) |line| {
        const encoded_line = switch (encoding) {
            inline .utf8 => bun.String.fromUTF8(line),
            inline .latin1 => bun.String.createLatin1(line),
            inline .utf16 => bun.String.fromUTF16(line),
        };
        errdefer encoded_line.deref();
        try lines.append(alloc, encoded_line);
    }

    return bun.String.toJSArray(globalThis, lines.items);
}

pub fn SplitNewlineIterator(comptime T: type) type {
    return struct {
        buffer: []const T,
        index: ?usize,

        const Self = @This();

        /// Returns a slice of the next field, or null if splitting is complete.
        pub fn next(self: *Self) ?[]const T {
            const start = self.index orelse return null;

            if (std.mem.indexOfScalarPos(T, self.buffer, start, '\n')) |delim_start| {
                const end = delim_start + 1;
                const slice = self.buffer[start..end];
                self.index = end;
                return slice;
            } else {
                self.index = null;
                return self.buffer[start..];
            }
        }
    };
}

pub fn normalizeEncoding(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
    const input = callframe.argument(0);
    const str = try bun.String.fromJS(input, globalThis);
    bun.assert(str.tag != .Dead);
    defer str.deref();
    if (str.length() == 0) return JSC.Node.Encoding.utf8.toJS(globalThis);
    if (str.inMapCaseInsensitive(JSC.Node.Encoding.map)) |enc| return enc.toJS(globalThis);
    return JSC.JSValue.jsUndefined();
}

pub fn parseEnv(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
    const content = callframe.argument(0);
    try validators.validateString(globalThis, content, "content", .{});

    var arena = std.heap.ArenaAllocator.init(bun.default_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    const str = content.asString().toSlice(globalThis, allocator);

    var map = envloader.Map.init(allocator);
    var p = envloader.Loader.init(&map, allocator);
    p.loadFromString(str.slice(), true, false);

    var obj = JSC.JSValue.createEmptyObject(globalThis, map.map.count());
    for (map.map.keys(), map.map.values()) |k, v| {
        obj.put(globalThis, JSC.ZigString.initUTF8(k), bun.String.createUTF8ForJS(globalThis, v.value));
    }
    return obj;
}
