// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2025 Niels Martignène <niels.martignene@protonmail.com>

#include "base.hh"
#include "crc.inc"
#include "unicode.inc"

#if __has_include("vendor/dragonbox/include/dragonbox/dragonbox.h")
    #include "vendor/dragonbox/include/dragonbox/dragonbox.h"
#endif

#if defined(_WIN32)
    #if !defined(NOMINMAX)
        #define NOMINMAX
    #endif
    #if !defined(WIN32_LEAN_AND_MEAN)
        #define WIN32_LEAN_AND_MEAN
    #endif
    #include <ws2tcpip.h>
    #include <windows.h>
    #include <fcntl.h>
    #include <io.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <direct.h>
    #include <shlobj.h>
    #if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
        #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
    #endif

    #if !defined(UNIX_PATH_MAX)
        #define UNIX_PATH_MAX 108
    #endif
    typedef struct sockaddr_un {
        ADDRESS_FAMILY sun_family;
        char sun_path[UNIX_PATH_MAX];
    } SOCKADDR_UN, *PSOCKADDR_UN;

    #if defined(__MINGW32__)
        // Some MinGW distributions set it to 0 by default
        int _CRT_glob = 1;
    #endif

    #define RtlGenRandom SystemFunction036
    extern "C" BOOLEAN NTAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength);

    typedef struct _IO_STATUS_BLOCK {
        union {
            LONG Status;
            PVOID Pointer;
        };
        ULONG_PTR Information;
    } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

    typedef LONG NTAPI NtCopyFileChunkFunc(HANDLE SourceHandle, HANDLE DestHandle, HANDLE Event,
                                           PIO_STATUS_BLOCK IoStatusBlock, ULONG Length,
                                           PLARGE_INTEGER SourceOffset, PLARGE_INTEGER DestOffset,
                                           PULONG SourceKey, PULONG DestKey, ULONG Flags);
    typedef ULONG RtlNtStatusToDosErrorFunc(LONG Status);
#elif defined(__wasi__)
    #include <dirent.h>
    #include <fcntl.h>
    #include <fnmatch.h>
    #include <poll.h>
    #include <sys/ioctl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <time.h>

    extern char **environ;
#else
    #include <dlfcn.h>
    #include <dirent.h>
    #include <fcntl.h>
    #include <fnmatch.h>
    #include <grp.h>
    #include <poll.h>
    #include <signal.h>
    #include <spawn.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <sys/resource.h>
    #include <sys/stat.h>
    #include <sys/statvfs.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <sys/wait.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <termios.h>
    #include <time.h>

    extern char **environ;
#endif
#if defined(__linux__)
    #include <sys/syscall.h>
    #include <sys/sendfile.h>
    #include <sys/eventfd.h>
#endif
#if defined(__APPLE__)
    #include <sys/random.h>
    #include <mach-o/dyld.h>
    #include <copyfile.h>
#endif
#if defined(__OpenBSD__) || defined(__FreeBSD__)
    #include <pthread_np.h>
    #include <sys/param.h>
    #include <sys/sysctl.h>
#endif
#if defined(__EMSCRIPTEN__)
    #include <emscripten.h>
#endif
#include <chrono>
#include <random>
#include <thread>

namespace K {

// ------------------------------------------------------------------------
// Utility
// ------------------------------------------------------------------------

#if !defined(FELIX)
    #if defined(FELIX_TARGET)
        const char *FelixTarget = K_STRINGIFY(FELIX_TARGET);
    #else
        const char *FelixTarget = "????";
    #endif
    const char *FelixVersion = "(unknown version)";
    const char *FelixCompiler = "????";
#endif

extern "C" void AssertMessage(const char *filename, int line, const char *cond)
{
    Print(StdErr, "%1:%2: Assertion '%3' failed\n", filename, line, cond);
}

#if defined(_WIN32)

void *MemMem(const void *src, Size src_len, const void *needle, Size needle_len)
{
    K_ASSERT(src_len >= 0);
    K_ASSERT(needle_len > 0);

    src_len -= needle_len - 1;

    int needle0 = *(const uint8_t *)needle;
    Size offset = 0;

    while (offset < src_len) {
        uint8_t *next = (uint8_t *)memchr((uint8_t *)src + offset, needle0, (size_t)(src_len - offset));

        if (!next)
            return nullptr;
        if (!memcmp(next, needle, (size_t)needle_len))
            return next;

        offset = next - (const uint8_t *)src + 1;
    }

    return nullptr;
}

#endif

// ------------------------------------------------------------------------
// Memory / Allocator
// ------------------------------------------------------------------------

// This Allocator design should allow efficient and mostly-transparent use of memory
// arenas and simple pointer-bumping allocator. This will be implemented later, for
// now it's just a doubly linked list of malloc() memory blocks.

class MallocAllocator: public Allocator {
protected:
    void *Allocate(Size size, unsigned int flags) override
    {
        void *ptr = malloc((size_t)size);
        K_CRITICAL(ptr, "Failed to allocate %1 of memory", FmtMemSize(size));

        if (flags & (int)AllocFlag::Zero) {
            MemSet(ptr, 0, size);
        }

        return ptr;
    }

    void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags) override
    {
        if (!new_size) {
            Release(ptr, old_size);
            ptr = nullptr;
        } else {
            void *new_ptr = realloc(ptr, (size_t)new_size);
            K_CRITICAL(new_ptr || !new_size, "Failed to resize %1 memory block to %2",
                                              FmtMemSize(old_size), FmtMemSize(new_size));

            if ((flags & (int)AllocFlag::Zero) && new_size > old_size) {
                MemSet((uint8_t *)new_ptr + old_size, 0, new_size - old_size);
            }

            ptr = new_ptr;
        }

        return ptr;
    }

    void Release(const void *ptr, Size) override
    {
        free((void *)ptr);
    }
};

class NullAllocator: public Allocator {
protected:
    void *Allocate(Size, unsigned int) override { K_UNREACHABLE(); }
    void *Resize(void *, Size, Size, unsigned int) override { K_UNREACHABLE(); }
    void Release(const void *, Size) override {}
};

Allocator *GetDefaultAllocator()
{
    static Allocator *default_allocator = new K_DEFAULT_ALLOCATOR;
    return default_allocator;
}

Allocator *GetNullAllocator()
{
    static Allocator *null_allocator = new NullAllocator;
    return null_allocator;
}

LinkedAllocator& LinkedAllocator::operator=(LinkedAllocator &&other)
{
    ReleaseAll();
    list = other.list;
    other.list = nullptr;

    return *this;
}

void LinkedAllocator::ReleaseAll()
{
    if (!list)
        return;

    Bucket *bucket = list;

    do {
        Bucket *next = bucket->next;
        ReleaseRaw(allocator, bucket, -1);
        bucket = next;
    } while (bucket != list);

    list = nullptr;
}

void LinkedAllocator::ReleaseAllExcept(void *ptr)
{
    K_ASSERT(ptr);

    Bucket *keep = PointerToBucket(ptr);
    Bucket *bucket = keep->next;

    while (bucket != keep) {
        Bucket *next = bucket->next;
        ReleaseRaw(allocator, bucket, -1);
        bucket = next;
    }

    list = keep;

    keep->prev = keep;
    keep->next = keep;
}

void *LinkedAllocator::Allocate(Size size, unsigned int flags)
{
    Bucket *bucket = (Bucket *)AllocateRaw(allocator, K_SIZE(Bucket) + size, flags);

    bucket->prev = bucket;
    bucket->next = bucket;

    list = list ? list : bucket;

    bucket->prev = list;
    bucket->next = list->next;
    list->next->prev = bucket;
    list->next = bucket;

    return (void *)bucket->data;
}

void *LinkedAllocator::Resize(void *ptr, Size old_size, Size new_size, unsigned int flags)
{
    if (!ptr) {
        ptr = Allocate(new_size, flags);
    } else if (!new_size) {
        Release(ptr, old_size);
        ptr = nullptr;
    } else {
        Bucket *bucket = PointerToBucket(ptr);
        bool single = (bucket->next == bucket);

        bucket = (Bucket *)ResizeRaw(allocator, bucket, K_SIZE(Bucket) + old_size,
                                                        K_SIZE(Bucket) + new_size, flags);

        list = bucket;

        if (single) {
            bucket->prev = bucket;
            bucket->next = bucket;
        } else {
            bucket->prev->next = bucket;
            bucket->next->prev = bucket;
        }

        ptr = (void *)bucket->data;
    }

    return ptr;
}

void LinkedAllocator::Release(const void *ptr, Size size)
{
    if (!ptr)
        return;

    Bucket *bucket = PointerToBucket((void *)ptr);
    bool single = (bucket->next == bucket);

    list = single ? nullptr : bucket->next;

    bucket->prev->next = bucket->next;
    bucket->next->prev = bucket->prev;

    ReleaseRaw(allocator, bucket, K_SIZE(Bucket) + size);
}

void LinkedAllocator::GiveTo(LinkedAllocator *alloc)
{
    Bucket *other = alloc->list;

    if (other && list) {
        other->prev->next = list;
        list->prev = other->prev;
        list->next = other;
        other->prev = list;
    } else if (list) {
        K_ASSERT(!alloc->list);
        alloc->list = list;
    }

    list = nullptr;
}

LinkedAllocator::Bucket *LinkedAllocator::PointerToBucket(void *ptr)
{
    uint8_t *data = (uint8_t *)ptr;
    return (Bucket *)(data - offsetof(Bucket, data));
}

BlockAllocator& BlockAllocator::operator=(BlockAllocator &&other)
{
    allocator.operator=(std::move(other.allocator));

    block_size = other.block_size;
    current_bucket = other.current_bucket;
    last_alloc = other.last_alloc;

    other.current_bucket = nullptr;
    other.last_alloc = nullptr;

    return *this;
}

void BlockAllocator::Reset()
{
    last_alloc = nullptr;

    if (current_bucket) {
        current_bucket->used = 0;
        allocator.ReleaseAllExcept(current_bucket);
    } else {
        allocator.ReleaseAll();
    }
}

void BlockAllocator::ReleaseAll()
{
    current_bucket = nullptr;
    last_alloc = nullptr;

    allocator.ReleaseAll();
}

void *BlockAllocator::Allocate(Size size, unsigned int flags)
{
    K_ASSERT(size >= 0);

    // Keep alignement requirements
    Size aligned_size = AlignLen(size, 8);

    if (AllocateSeparately(aligned_size)) {
        uint8_t *ptr = (uint8_t *)AllocateRaw(&allocator, size, flags);
        return ptr;
    } else {
        if (!current_bucket || (current_bucket->used + aligned_size) > block_size) {
            current_bucket = (Bucket *)AllocateRaw(&allocator, K_SIZE(Bucket) + block_size,
                                                   flags & ~(int)AllocFlag::Zero);
            current_bucket->used = 0;
        }

        uint8_t *ptr = current_bucket->data + current_bucket->used;
        current_bucket->used += aligned_size;

        if (flags & (int)AllocFlag::Zero) {
            MemSet(ptr, 0, size);
        }

        last_alloc = ptr;
        return ptr;
    }
}

void *BlockAllocator::Resize(void *ptr, Size old_size, Size new_size, unsigned int flags)
{
    K_ASSERT(old_size >= 0);
    K_ASSERT(new_size >= 0);

    if (!new_size) {
        Release(ptr, old_size);
        ptr = nullptr;
    } else {
        if (!ptr) {
            old_size = 0;
        }

        Size aligned_old_size = AlignLen(old_size, 8);
        Size aligned_new_size = AlignLen(new_size, 8);
        Size aligned_delta = aligned_new_size - aligned_old_size;

        // Try fast path
        if (ptr && ptr == last_alloc &&
                (current_bucket->used + aligned_delta) <= block_size &&
                !AllocateSeparately(aligned_new_size)) {
            current_bucket->used += aligned_delta;

            if ((flags & (int)AllocFlag::Zero) && new_size > old_size) {
                MemSet((uint8_t *)ptr + old_size, 0, new_size - old_size);
            }
        } else if (AllocateSeparately(aligned_old_size)) {
            ptr = ResizeRaw(&allocator, ptr, old_size, new_size, flags);
        } else {
            void *new_ptr = Allocate(new_size, flags & ~(int)AllocFlag::Zero);

            if (new_size > old_size) {
                MemCpy(new_ptr, ptr, old_size);

                if (flags & (int)AllocFlag::Zero) {
                    MemSet((uint8_t *)ptr + old_size, 0, new_size - old_size);
                }
            } else {
                MemCpy(new_ptr, ptr, new_size);
            }

            ptr = new_ptr;
        }
    }

    return ptr;
}

void BlockAllocator::Release(const void *ptr, Size size)
{
    K_ASSERT(size >= 0);

    if (ptr) {
        Size aligned_size = AlignLen(size, 8);

        if (ptr == last_alloc) {
            current_bucket->used -= aligned_size;

            if (!current_bucket->used) {
                ReleaseRaw(&allocator, current_bucket, K_SIZE(Bucket) + block_size);
                current_bucket = nullptr;
            }

            last_alloc = nullptr;
        } else if (AllocateSeparately(aligned_size)) {
            ReleaseRaw(&allocator, ptr, size);
        }
    }
}

void BlockAllocator::GiveTo(LinkedAllocator *alloc)
{
    current_bucket = nullptr;
    last_alloc = nullptr;

    allocator.GiveTo(alloc);
}

#if defined(_WIN32)

void *AllocateSafe(Size len)
{
    void *ptr = VirtualAlloc(nullptr, (SIZE_T)len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!ptr) {
        LogError("Failed to allocate %1 of memory: %2", FmtMemSize(len), GetWin32ErrorString());
        abort();
    }

    if (!VirtualLock(ptr, (SIZE_T)len)) {
        LogError("Failed to lock memory (%1): %2", FmtMemSize(len), GetWin32ErrorString());
        abort();
    }

    ZeroSafe(ptr, len);

    return ptr;
}

void ReleaseSafe(void *ptr, Size len)
{
    if (!ptr)
        return;

    ZeroSafe(ptr, len);
    VirtualFree(ptr, 0, MEM_RELEASE);
}

void ZeroSafe(void *ptr, Size len)
{
    SecureZeroMemory(ptr, (SIZE_T)len);
}

#elif !defined(__wasi__)

static int GetPageSize()
{
    static Size pagesize = sysconf(_SC_PAGESIZE);
    return pagesize;
}

void *AllocateSafe(Size len)
{
    Size aligned = AlignLen(len, GetPageSize());
    int flags = MAP_PRIVATE | MAP_ANONYMOUS;

#if defined(MAP_CONCEAL)
    flags |= MAP_CONCEAL;
#endif

    void *ptr = mmap(nullptr, (size_t)aligned, PROT_READ | PROT_WRITE, flags, -1, 0);
    if (ptr == MAP_FAILED) {
        LogError("Failed to allocate %1 of memory: %2", FmtMemSize(len), strerror(errno));
        abort();
    }

    if (mlock(ptr, (size_t)aligned) < 0) {
        LogError("Failed to lock memory (%1): %2", FmtMemSize(len), strerror(errno));
        abort();
    }

#if defined(MADV_DONTDUMP)
    (void)madvise(ptr, (size_t)aligned, MADV_DONTDUMP);
#elif defined(MADV_NOCORE)
    (void)madvise(ptr, (size_t)aligned, MADV_NOCORE);
#endif

    ZeroSafe(ptr, len);

    return ptr;
}

void ReleaseSafe(void *ptr, Size len)
{
    if (!ptr)
        return;

    ZeroSafe(ptr, len);

    Size aligned = AlignLen(len, GetPageSize());
    munmap(ptr, aligned);
}

void ZeroSafe(void *ptr, Size len)
{
    MemSet(ptr, 0, len);
    __asm__ __volatile__("" : : "r"(ptr) : "memory");
}

#endif

// ------------------------------------------------------------------------
// Date
// ------------------------------------------------------------------------

LocalDate LocalDate::FromJulianDays(int days)
{
    K_ASSERT(days >= 0);

    // Algorithm from Richards, copied from Wikipedia:
    // https://en.wikipedia.org/w/index.php?title=Julian_day&oldid=792497863

    LocalDate date;
    {
        int f = days + 1401 + (((4 * days + 274277) / 146097) * 3) / 4 - 38;
        int e = 4 * f + 3;
        int g = e % 1461 / 4;
        int h = 5 * g + 2;
        date.st.day = (int8_t)(h % 153 / 5 + 1);
        date.st.month = (int8_t)((h / 153 + 2) % 12 + 1);
        date.st.year = (int16_t)((e / 1461) - 4716 + (date.st.month < 3));
    }

    return date;
}

int LocalDate::ToJulianDays() const
{
    K_ASSERT(IsValid());

    // Straight from the Web:
    // http://www.cs.utsa.edu/~cs1063/projects/Spring2011/Project1/jdn-explanation.html

    int julian_days;
    {
        bool adjust = st.month < 3;
        int year = st.year + 4800 - adjust;
        int month = st.month + 12 * adjust - 3;

        julian_days = st.day + (153 * month + 2) / 5 + 365 * year - 32045 +
                      year / 4 - year / 100 + year / 400;
    }

    return julian_days;
}

int LocalDate::GetWeekDay() const
{
    K_ASSERT(IsValid());

    // Zeller's congruence:
    // https://en.wikipedia.org/wiki/Zeller%27s_congruence

    int week_day;
    {
        int year = st.year;
        int month = st.month;
        if (month < 3) {
            year--;
            month += 12;
        }

        int century = year / 100;
        year %= 100;

        week_day = (st.day + (13 * (month + 1) / 5) + year + year / 4 + century / 4 + 5 * century + 5) % 7;
    }

    return week_day;
}

LocalDate &LocalDate::operator++()
{
    K_ASSERT(IsValid());

    if (st.day < DaysInMonth(st.year, st.month)) {
        st.day++;
    } else if (st.month < 12) {
        st.month++;
        st.day = 1;
    } else {
        st.year++;
        st.month = 1;
        st.day = 1;
    }

    return *this;
}

LocalDate &LocalDate::operator--()
{
    K_ASSERT(IsValid());

    if (st.day > 1) {
        st.day--;
    } else if (st.month > 1) {
        st.month--;
        st.day = DaysInMonth(st.year, st.month);
    } else {
        st.year--;
        st.month = 12;
        st.day = DaysInMonth(st.year, st.month);
    }

    return *this;
}

// ------------------------------------------------------------------------
// Time
// ------------------------------------------------------------------------

#if defined(_WIN32)

static int64_t FileTimeToUnixTime(FILETIME ft)
{
    int64_t time = ((int64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
    return time / 10000 - 11644473600000ll;
}

static FILETIME UnixTimeToFileTime(int64_t time)
{
    time = (time + 11644473600000ll) * 10000;

    FILETIME ft;
    ft.dwHighDateTime = (DWORD)(time >> 32);
    ft.dwLowDateTime = (DWORD)time;

    return ft;
}

#endif

int64_t GetUnixTime()
{
#if defined(_WIN32)
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);

    return FileTimeToUnixTime(ft);
#elif defined(__EMSCRIPTEN__)
    return (int64_t)emscripten_get_now();
#elif defined(__linux__)
    struct timespec ts;
    K_CRITICAL(clock_gettime(CLOCK_REALTIME_COARSE, &ts) == 0, "clock_gettime(CLOCK_REALTIME_COARSE) failed: %1", strerror(errno));

    int64_t time = (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
    return time;
#else
    struct timespec ts;
    K_CRITICAL(clock_gettime(CLOCK_REALTIME, &ts) == 0, "clock_gettime(CLOCK_REALTIME) failed: %1", strerror(errno));

    int64_t time = (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
    return time;
#endif
}

TimeSpec DecomposeTimeUTC(int64_t time)
{
    TimeSpec spec = {};

#if defined(_WIN32)
    __time64_t time64 = time / 1000;

    struct tm ti = {};
    _gmtime64_s(&ti, &time64);
#else
    time_t time64 = time / 1000;

    struct tm ti = {};
    gmtime_r(&time64, &ti);
#endif

    spec.year = (int16_t)(1900 + ti.tm_year);
    spec.month = (int8_t)ti.tm_mon + 1; // Whose idea was it to use 0-11? ...
    spec.day = (int8_t)ti.tm_mday;
    spec.week_day = (int8_t)(ti.tm_wday ? (ti.tm_wday + 1) : 7);
    spec.hour = (int8_t)ti.tm_hour;
    spec.min = (int8_t)ti.tm_min;
    spec.sec = (int8_t)ti.tm_sec;
    spec.msec = time % 1000;
    spec.offset = 0;

    return spec;
}

TimeSpec DecomposeTimeLocal(int64_t time)
{
    TimeSpec spec = {};

#if defined(_WIN32)
    __time64_t time64 = time / 1000;

    struct tm ti = {};
    int offset = 0;

    _localtime64_s(&ti, &time64);

    struct tm utc = {};
    _gmtime64_s(&utc, &time64);

    offset = (int)(_mktime64(&ti) - _mktime64(&utc) + (3600 * ti.tm_isdst));
#else
    time_t time64 = time / 1000;

    struct tm ti = {};
    int offset = 0;

    localtime_r(&time64, &ti);
    offset = ti.tm_gmtoff;
#endif

    spec.year = (int16_t)(1900 + ti.tm_year);
    spec.month = (int8_t)ti.tm_mon + 1; // Whose idea was it to use 0-11? ...
    spec.day = (int8_t)ti.tm_mday;
    spec.week_day = (int8_t)(ti.tm_wday ? (ti.tm_wday + 1) : 7);
    spec.hour = (int8_t)ti.tm_hour;
    spec.min = (int8_t)ti.tm_min;
    spec.sec = (int8_t)ti.tm_sec;
    spec.msec = time % 1000;
    spec.offset = (int16_t)(offset / 60);

    return spec;
}

int64_t ComposeTimeUTC(const TimeSpec &spec)
{
    K_ASSERT(!spec.offset);

    struct tm ti = {};

    ti.tm_year = spec.year - 1900;
    ti.tm_mon = spec.month - 1;
    ti.tm_mday = spec.day;
    ti.tm_hour = spec.hour;
    ti.tm_min = spec.min;
    ti.tm_sec = spec.sec;

#if defined(_WIN32)
    int64_t time = (int64_t)_mkgmtime64(&ti);
#else
    int64_t time = (int64_t)timegm(&ti);
#endif

    time *= 1000;
    time += spec.msec;

    return time;
}

// ------------------------------------------------------------------------
// Clock
// ------------------------------------------------------------------------

int64_t GetMonotonicClock()
{
    static std::atomic_int64_t memory;

#if defined(_WIN32)
    int64_t clock = (int64_t)GetTickCount64();
#elif defined(__EMSCRIPTEN__)
    int64_t clock = emscripten_get_now();
#elif defined(CLOCK_MONOTONIC_COARSE)
    struct timespec ts;
    K_CRITICAL(clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) == 0, "clock_gettime(CLOCK_MONOTONIC_COARSE) failed: %1", strerror(errno));

    int64_t clock = (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
#else
    struct timespec ts;
    K_CRITICAL(clock_gettime(CLOCK_MONOTONIC, &ts) == 0, "clock_gettime(CLOCK_MONOTONIC) failed: %1", strerror(errno));

    int64_t clock = (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
#endif

    // Protect against clock going backwards
    int64_t prev = memory.load(std::memory_order_relaxed);
    if (clock < prev) [[unlikely]]
        return prev;
    memory.compare_exchange_weak(prev, clock, std::memory_order_relaxed, std::memory_order_relaxed);

    return clock;
}

// ------------------------------------------------------------------------
// Strings
// ------------------------------------------------------------------------

bool CopyString(const char *str, Span<char> buf)
{
#if defined(K_DEBUG)
    K_ASSERT(buf.len > 0);
#else
    if (!buf.len) [[unlikely]]
        return false;
#endif

    Size i = 0;
    for (; str[i]; i++) {
        if (i >= buf.len - 1) [[unlikely]] {
            buf[buf.len - 1] = 0;
            return false;
        }
        buf[i] = str[i];
    }
    buf[i] = 0;

    return true;
}

bool CopyString(Span<const char> str, Span<char> buf)
{
#if defined(K_DEBUG)
    K_ASSERT(buf.len > 0);
#else
    if (!buf.len) [[unlikely]]
        return false;
#endif

    Size copy_len = std::min(str.len, buf.len - 1);

    MemCpy(buf.ptr, str.ptr, copy_len);
    buf[copy_len] = 0;

    return (copy_len == str.len);
}

Span<char> DuplicateString(Span<const char> str, Allocator *alloc)
{
    K_ASSERT(alloc);

    char *new_str = (char *)AllocateRaw(alloc, str.len + 1);
    MemCpy(new_str, str.ptr, str.len);
    new_str[str.len] = 0;
    return MakeSpan(new_str, str.len);
}

template <typename CompareFunc>
static inline int NaturalCmp(Span<const char> str1, Span<const char> str2, CompareFunc cmp)
{
    Size i = 0;
    Size j = 0;

    while (i < str1.len && j < str2.len) {
        int delta = cmp(str1[i], str2[j]);

        if (delta) {
            if (IsAsciiDigit(str1[i]) && IsAsciiDigit(str2[i])) {
                while (i < str1.len && str1[i] == '0') {
                    i++;
                }
                while (j < str2.len && str2[j] == '0') {
                    j++;
                }

                bool digit1 = false;
                bool digit2 = false;
                int bias = 0;

                for (;;) {
                    digit1 = (i < str1.len) && IsAsciiDigit(str1[i]);
                    digit2 = (j < str2.len) && IsAsciiDigit(str2[j]);

                    if (!digit1 || !digit2)
                        break;

                    bias = bias ? bias : cmp(str1[i], str2[j]);
                    i++;
                    j++;
                }

                if (!digit1 && !digit2 && bias) {
                    return bias;
                } else if (digit1 || digit2) {
                    return digit1 ? 1 : -1;
                }
            } else {
                return delta;
            }
        } else {
            i++;
            j++;
        }
    }

    if (i == str1.len && j < str2.len) {
        return -1;
    } else if (i < str1.len) {
        return 1;
    } else {
        return 0;
    }
}

int CmpNatural(Span<const char> str1, Span<const char> str2)
{
    auto cmp = [](int a, int b) { return a - b; };
    return NaturalCmp(str1, str2, cmp);
}

int CmpNaturalI(Span<const char> str1, Span<const char> str2)
{
    auto cmp = [](int a, int b) { return LowerAscii(a) - LowerAscii(b); };
    return NaturalCmp(str1, str2, cmp);
}

// ------------------------------------------------------------------------
// Format
// ------------------------------------------------------------------------

static const char DigitPairs[201] = "00010203040506070809101112131415161718192021222324"
                                    "25262728293031323334353637383940414243444546474849"
                                    "50515253545556575859606162636465666768697071727374"
                                    "75767778798081828384858687888990919293949596979899";
static const char BigHexLiterals[] = "0123456789ABCDEF";
static const char SmallHexLiterals[] = "0123456789abcdef";

static Span<char> FormatUnsignedToDecimal(uint64_t value, char out_buf[32])
{
    Size offset = 32;
    {
        int pair_idx;
        do {
            pair_idx = (int)((value % 100) * 2);
            value /= 100;
            offset -= 2;
            MemCpy(out_buf + offset, DigitPairs + pair_idx, 2);
        } while (value);
        offset += (pair_idx < 20);
    }

    return MakeSpan(out_buf + offset, 32 - offset);
}

static Span<char> FormatUnsignedToBinary(uint64_t value, char out_buf[64])
{
    Size msb = 64 - (Size)CountLeadingZeros(value);
    if (!msb) {
        msb = 1;
    }

    for (Size i = 0; i < msb; i++) {
        bool bit = (value >> (msb - i - 1)) & 0x1;
        out_buf[i] = bit ? '1' : '0';
    }

    return MakeSpan(out_buf, msb);
}

static Span<char> FormatUnsignedToOctal(uint64_t value, char out_buf[64])
{
    Size offset = 64;
    do {
        uint64_t digit = value & 0x7;
        value >>= 3;
        out_buf[--offset] = BigHexLiterals[digit];
    } while (value);

    return MakeSpan(out_buf + offset, 64 - offset);
}

static Span<char> FormatUnsignedToBigHex(uint64_t value, char out_buf[32])
{
    Size offset = 32;
    do {
        uint64_t digit = value & 0xF;
        value >>= 4;
        out_buf[--offset] = BigHexLiterals[digit];
    } while (value);

    return MakeSpan(out_buf + offset, 32 - offset);
}

static Span<char> FormatUnsignedToSmallHex(uint64_t value, char out_buf[32])
{
    Size offset = 32;
    do {
        uint64_t digit = value & 0xF;
        value >>= 4;
        out_buf[--offset] = SmallHexLiterals[digit];
    } while (value);

    return MakeSpan(out_buf + offset, 32 - offset);
}

#if defined(JKJ_HEADER_DRAGONBOX)
static Size FakeFloatPrecision(Span<char> buf, int K, int min_prec, int max_prec, int *out_K)
{
    K_ASSERT(min_prec >= 0);

    if (-K < min_prec) {
        int delta = min_prec + K;
        MemSet(buf.end(), '0', delta);

        *out_K -= delta;
        return buf.len + delta;
    } else if (-K > max_prec) {
        if (-K <= buf.len) {
            int offset = (int)buf.len + K;
            int truncate = offset + max_prec;
            int scale = offset + max_prec;

            if (buf[truncate] >= '5') {
                buf[truncate] = '0';

                for (int i = truncate - 1; i >= 0; i--) {
                    if (buf[i] == '9') {
                        buf[i] = '0' + !i;
                        truncate += !i;
                    } else {
                        buf[i]++;
                        break;
                    }
                }
            }

            *out_K -= (int)(scale - buf.len);
            return truncate;
        } else {
            buf[0] = '0' + (-K == buf.len + 1 && buf[0] >= '5');

            if (min_prec) {
                MemSet(buf.ptr + 1, '0', min_prec - 1);
                *out_K = -min_prec;
                return min_prec;
            } else {
                *out_K = 0;
                return 1;
            }
        }
    } else {
        return buf.len;
    }
}

static Span<char> PrettifyFloat(Span<char> buf, int K, int min_prec, int max_prec)
{
    // Apply precision settings after conversion
    buf.len = FakeFloatPrecision(buf, K, min_prec, max_prec, &K);

    int KK = (int)buf.len + K;

    if (K >= 0) {
        // 1234e7 -> 12340000000

        if (!buf.len && !K) {
            K = 1;
        }

        MemSet(buf.end(), '0', K);
        buf.len += K;
    } else if (KK > 0) {
        // 1234e-2 -> 12.34

        MemMove(buf.ptr + KK + 1, buf.ptr + KK, buf.len - KK);
        buf.ptr[KK] = '.';
        buf.len++;
    } else {
        // 1234e-6 -> 0.001234

        int offset = 2 - KK;
        MemMove(buf.ptr + offset, buf.ptr, buf.len);
        MemSet(buf.ptr, '0', offset);
        buf.ptr[1] = '.';
        buf.len += offset;
    }

    return buf;
}

static Span<char> ExponentiateFloat(Span<char> buf, int K, int min_prec, int max_prec)
{
    // Apply precision settings after conversion
    buf.len = FakeFloatPrecision(buf, (int)(1 - buf.len), min_prec, max_prec, &K);

    int exponent = (int)buf.len + K - 1;

    if (buf.len > 1) {
        MemMove(buf.ptr + 2, buf.ptr + 1, buf.len - 1);
        buf.ptr[1] = '.';
        buf.ptr[buf.len + 1] = 'e';
        buf.len += 2;
    } else {
        buf.ptr[1] = 'e';
        buf.len = 2;
    }

    if (exponent > 0) {
        buf.ptr[buf.len++] = '+';
    } else {
        buf.ptr[buf.len++] = '-';
        exponent = -exponent;
    }

    if (exponent >= 100) {
        buf.ptr[buf.len++] = (char)('0' + exponent / 100);
        exponent %= 100;

        int pair_idx = (int)(exponent * 2);
        MemCpy(buf.end(), DigitPairs + pair_idx, 2);
        buf.len += 2;
    } else if (exponent >= 10) {
        int pair_idx = (int)(exponent * 2);
        MemCpy(buf.end(), DigitPairs + pair_idx, 2);
        buf.len += 2;
    } else {
        buf.ptr[buf.len++] = (char)('0' + exponent);
    }

    return buf;
}
#endif

// NaN and Inf are handled by caller
template <typename T>
Span<const char> FormatFloatingPoint(T value, bool non_zero, int min_prec, int max_prec, char out_buf[128])
{
#if defined(JKJ_HEADER_DRAGONBOX)
    if (non_zero) {
        auto v = jkj::dragonbox::to_decimal(value, jkj::dragonbox::policy::sign::ignore);

        Span<char> buf = FormatUnsignedToDecimal(v.significand, out_buf);
        int KK = (int)buf.len + v.exponent;

        if (KK > -6 && KK <= 21) {
            return PrettifyFloat(buf, v.exponent, min_prec, max_prec);
        } else {
            return ExponentiateFloat(buf, v.exponent, min_prec, max_prec);
        }
    } else {
        Span<char> buf = MakeSpan(out_buf, 128);

        buf[0] = '0';
        if (min_prec) {
            buf.ptr[1] = '.';
            MemSet(buf.ptr + 2, '0', min_prec);
            buf.len = 2 + min_prec;
        } else {
            buf.len = 1;
        }

        return buf;
    }
#else
    #if defined(_MSC_VER)
        #pragma message("Cannot format floating point values correctly without Dragonbox")
    #else
        #warning Cannot format floating point values correctly without Dragonbox
    #endif

    int ret = snprintf(out_buf, 128, "%g", value);
    return MakeSpan(out_buf, std::min(ret, 128));
#endif
}

template <typename AppendFunc>
static inline void AppendPad(Size pad, char padding, AppendFunc append)
{
    for (Size i = 0; i < pad; i++) {
        append(padding);
    }
}

template <typename AppendFunc>
static inline void AppendSafe(char c, AppendFunc append)
{
    if (IsAsciiControl(c))
        return;

    append(c);
}

template <typename AppendFunc>
static inline void ProcessArg(const FmtArg &arg, AppendFunc append)
{
    switch (arg.type) {
        case FmtType::Str: { append(arg.u.str); } break;

        case FmtType::PadStr: {
            append(arg.u.str);
            AppendPad(arg.pad - arg.u.str.len, arg.padding, append);
        } break;
        case FmtType::RepeatStr: {
            Span<const char> str = arg.u.repeat.str;

            for (int i = 0; i < arg.u.repeat.count; i++) {
                append(str);
            }
        } break;

        case FmtType::Char: { append(MakeSpan(&arg.u.ch, 1)); } break;
        case FmtType::Buffer: {
            Span<const char> str = arg.u.buf;
            append(str);
        } break;
        case FmtType::Custom: { arg.u.custom.Format(append); } break;

        case FmtType::Bool: { append(arg.u.b ? "true" : "false"); } break;

        case FmtType::Integer: {
            if (arg.u.i < 0) {
                char buf[128];
                Span<const char> str = FormatUnsignedToDecimal((uint64_t)-arg.u.i, buf);

                if (arg.pad) {
                    if (arg.padding == '0') {
                        append('-');
                        AppendPad((Size)arg.pad - str.len - 1, arg.padding, append);
                    } else {
                        AppendPad((Size)arg.pad - str.len - 1, arg.padding, append);
                        append('-');
                    }
                } else {
                    append('-');
                }

                append(str);
            } else {
                char buf[128];
                Span<const char> str = FormatUnsignedToDecimal((uint64_t)arg.u.i, buf);

                AppendPad((Size)arg.pad - str.len, arg.padding, append);
                append(str);
            }
        } break;
        case FmtType::Unsigned: {
            char buf[128];
            Span<const char> str = FormatUnsignedToDecimal(arg.u.u, buf);

            AppendPad((Size)arg.pad - str.len, arg.padding, append);
            append(str);
        } break;

        case FmtType::Float: {
            static const uint32_t ExponentMask = 0x7f800000u;
            static const uint32_t MantissaMask = 0x007fffffu;
            static const uint32_t SignMask = 0x80000000u;

            union { float f; uint32_t u32; } u;
            u.f = arg.u.f.value;

            if ((u.u32 & ExponentMask) == ExponentMask) {
                uint32_t mantissa = u.u32 & MantissaMask;

                if (mantissa) {
                    append("NaN");
                } else {
                    append((u.u32 & SignMask) ? "-Inf" : "Inf");
                }
            } else {
                char buf[128];

                if (u.u32 & SignMask) {
                    append('-');
                    append(FormatFloatingPoint(-u.f, true, arg.u.f.min_prec, arg.u.f.max_prec, buf));
                } else {
                    append(FormatFloatingPoint(u.f, u.u32, arg.u.f.min_prec, arg.u.f.max_prec, buf));
                }
            }
        } break;
        case FmtType::Double: {
            static const uint64_t ExponentMask = 0x7FF0000000000000ull;
            static const uint64_t MantissaMask = 0x000FFFFFFFFFFFFFull;
            static const uint64_t SignMask = 0x8000000000000000ull;

            union { double d; uint64_t u64; } u;
            u.d = arg.u.d.value;

            if ((u.u64 & ExponentMask) == ExponentMask) {
                uint64_t mantissa = u.u64 & MantissaMask;

                if (mantissa) {
                    append("NaN");
                } else {
                    append((u.u64 & SignMask) ? "-Inf" : "Inf");
                }
            } else {
                char buf[128];

                if (u.u64 & SignMask) {
                    append('-');
                    append(FormatFloatingPoint(-u.d, true, arg.u.d.min_prec, arg.u.d.max_prec, buf));
                } else {
                    append(FormatFloatingPoint(u.d, u.u64, arg.u.d.min_prec, arg.u.d.max_prec, buf));
                }
            }
        } break;

        case FmtType::Binary: {
            char buf[128];
            Span<const char> str = FormatUnsignedToBinary(arg.u.u, buf);

            AppendPad((Size)arg.pad - str.len, arg.padding, append);
            append(str);
        } break;
        case FmtType::Octal: {
            char buf[128];
            Span<const char> str = FormatUnsignedToOctal(arg.u.u, buf);

            AppendPad((Size)arg.pad - str.len, arg.padding, append);
            append(str);
        } break;
        case FmtType::BigHex: {
            char buf[128];
            Span<const char> str = FormatUnsignedToBigHex(arg.u.u, buf);

            AppendPad((Size)arg.pad - str.len, arg.padding, append);
            append(str);
        } break;
        case FmtType::SmallHex: {
            char buf[128];
            Span<const char> str = FormatUnsignedToSmallHex(arg.u.u, buf);

            AppendPad((Size)arg.pad - str.len, arg.padding, append);
            append(str);
        } break;

        case FmtType::BigBytes: {
            for (uint8_t c: arg.u.hex) {
                char encoded[2];

                encoded[0] = BigHexLiterals[((uint8_t)c >> 4) & 0xF];
                encoded[1] = BigHexLiterals[((uint8_t)c >> 0) & 0xF];

                Span<const char> buf = MakeSpan(encoded, 2);
                append(buf);
            }
        } break;
        case FmtType::SmallBytes: {
            for (uint8_t c: arg.u.hex) {
                char encoded[2];

                encoded[0] = SmallHexLiterals[((uint8_t)c >> 4) & 0xF];
                encoded[1] = SmallHexLiterals[((uint8_t)c >> 0) & 0xF];

                Span<const char> buf = MakeSpan(encoded, 2);
                append(buf);
            }
        } break;

        case FmtType::MemorySize: {
            char buf[128];

            double size;
            if (arg.u.i < 0) {
                append('-');
                size = (double)-arg.u.i;
            } else {
                size = (double)arg.u.i;
            }

            if (size >= 1073688137.0) {
                size /= 1073741824.0;

                int prec = 1 + (size < 9.9995) + (size < 99.995);
                append(FormatFloatingPoint(size, true, prec, prec, buf));
                append(" GiB");
            } else if (size >= 1048524.0) {
                size /= 1048576.0;

                int prec = 1 + (size < 9.9995) + (size < 99.995);
                append(FormatFloatingPoint(size, true, prec, prec, buf));
                append(" MiB");
            } else if (size >= 1023.95) {
                size /= 1024.0;

                int prec = 1 + (size < 9.9995) + (size < 99.995);
                append(FormatFloatingPoint(size, true, prec, prec, buf));
                append(" kiB");
            } else {
                append(FormatFloatingPoint(size, arg.u.i, 0, 0, buf));
                append(" B");
            }
        } break;
        case FmtType::DiskSize: {
            char buf[128];

            double size;
            if (arg.u.i < 0) {
                append('-');
                size = (double)-arg.u.i;
            } else {
                size = (double)arg.u.i;
            }

            if (size >= 999950000.0) {
                size /= 1000000000.0;

                int prec = 1 + (size < 9.9995) + (size < 99.995);
                append(FormatFloatingPoint(size, true, prec, prec, buf));
                append(" GB");
            } else if (size >= 999950.0) {
                size /= 1000000.0;

                int prec = 1 + (size < 9.9995) + (size < 99.995);
                append(FormatFloatingPoint(size, true, prec, prec, buf));
                append(" MB");
            } else if (size >= 999.95) {
                size /= 1000.0;

                int prec = 1 + (size < 9.9995) + (size < 99.995);
                append(FormatFloatingPoint(size, true, prec, prec, buf));
                append(" kB");
            } else {
                append(FormatFloatingPoint(size, arg.u.i, 0, 0, buf));
                append(" B");
            }
        } break;

        case FmtType::Date: {
            K_ASSERT(!arg.u.date.value || arg.u.date.IsValid());

            char buf[128];

            int year = arg.u.date.st.year;
            if (year < 0) {
                append('-');
                year = -year;
            }
            if (year < 10) {
                append("000");
            } else if (year < 100) {
                append("00");
            } else if (year < 1000) {
                append('0');
            }
            append(FormatUnsignedToDecimal((uint64_t)year, buf));
            append('-');
            if (arg.u.date.st.month < 10) {
                append('0');
            }
            append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.month, buf));
            append('-');
            if (arg.u.date.st.day < 10) {
                append('0');
            }
            append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.day, buf));
        } break;

        case FmtType::TimeISO: {
            const TimeSpec &spec = arg.u.time.spec;

            LocalArray<char, 128> buf;

            if (spec.offset && arg.u.time.ms) {
                int offset_h = spec.offset / 60;
                int offset_m = spec.offset % 60;

                buf.len = Fmt(buf.data, "%1%2%3T%4%5%6.%7%8%9%10",
                              FmtInt(spec.year, 2), FmtInt(spec.month, 2),
                              FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
                              FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3),
                              offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
            } else if (spec.offset) {
                int offset_h = spec.offset / 60;
                int offset_m = spec.offset % 60;

                buf.len = Fmt(buf.data, "%1%2%3T%4%5%6%7%8%9",
                              FmtInt(spec.year, 2), FmtInt(spec.month, 2),
                              FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
                              FmtInt(spec.min, 2), FmtInt(spec.sec, 2),
                              offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
            } else if (arg.u.time.ms) {
                buf.len = Fmt(buf.data, "%1%2%3T%4%5%6.%7Z",
                              FmtInt(spec.year, 2), FmtInt(spec.month, 2),
                              FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
                              FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3)).len;
            } else {
                buf.len = Fmt(buf.data, "%1%2%3T%4%5%6Z",
                              FmtInt(spec.year, 2), FmtInt(spec.month, 2),
                              FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
                              FmtInt(spec.min, 2), FmtInt(spec.sec, 2)).len;
            }

            append(buf);
        } break;
        case FmtType::TimeNice: {
            const TimeSpec &spec = arg.u.time.spec;

            LocalArray<char, 128> buf;

            if (arg.u.time.ms) {
                int offset_h = spec.offset / 60;
                int offset_m = spec.offset % 60;

                buf.len = Fmt(buf.data, "%1-%2-%3 %4:%5:%6.%7 %8%9%10",
                              FmtInt(spec.year, 2), FmtInt(spec.month, 2),
                              FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
                              FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3),
                              offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
            } else {
                int offset_h = spec.offset / 60;
                int offset_m = spec.offset % 60;

                buf.len = Fmt(buf.data, "%1-%2-%3 %4:%5:%6 %7%8%9",
                              FmtInt(spec.year, 2), FmtInt(spec.month, 2),
                              FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
                              FmtInt(spec.min, 2), FmtInt(spec.sec, 2),
                              offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
            }

            append(buf);
        } break;

        case FmtType::List: {
            Span<const char> separator = arg.u.list.separator;

            if (arg.u.list.u.names.len) {
                append(arg.u.list.u.names[0]);

                for (Size i = 1; i < arg.u.list.u.names.len; i++) {
                    append(separator);
                    append(arg.u.list.u.names[i]);
                }
            } else {
                append(T("None"));
            }
        } break;
        case FmtType::FlagNames: {
            uint64_t flags = arg.u.list.flags;
            Span<const char> separator = arg.u.list.separator;

            if (flags) {
                for (;;) {
                    int idx = CountTrailingZeros(flags);
                    flags &= ~(1ull << idx);

                    append(arg.u.list.u.names[idx]);
                    if (!flags)
                        break;
                    append(separator);
                }
            } else {
                append(T("None"));
            }
        } break;
        case FmtType::FlagOptions: {
            uint64_t flags = arg.u.list.flags;
            Span<const char> separator = arg.u.list.separator;

            if (arg.u.list.flags) {
                for (;;) {
                    int idx = CountTrailingZeros(flags);
                    flags &= ~(1ull << idx);

                    append(arg.u.list.u.options[idx].name);
                    if (!flags)
                        break;
                    append(separator);
                }
            } else {
                append(T("None"));
            }
        } break;

        case FmtType::Random: {
            LocalArray<char, 512> buf;

            static const char *const DefaultChars = "abcdefghijklmnopqrstuvwxyz0123456789";
            Span<const char> chars = arg.u.random.chars ? arg.u.random.chars : DefaultChars;

            K_ASSERT(arg.u.random.len <= K_SIZE(buf.data));
            buf.len = arg.u.random.len;

            for (Size j = 0; j < arg.u.random.len; j++) {
                int rnd = GetRandomInt(0, (int)chars.len);
                buf[j] = chars[rnd];
            }

            append(buf);
        } break;

        case FmtType::SafeStr: {
            for (char c: arg.u.str) {
                AppendSafe(c, append);
            }
        } break;
        case FmtType::SafeChar: { AppendSafe(arg.u.ch, append); } break;
    }
}

template <typename AppendFunc>
static inline Size ProcessAnsiSpecifier(const char *spec, bool vt100, AppendFunc append)
{
    Size idx = 0;

    LocalArray<char, 32> buf;
    bool valid = true;

    buf.Append("\x1B[");

    // Foreground color
    switch (spec[++idx]) {
        case 'd': { buf.Append("30"); } break;
        case 'r': { buf.Append("31"); } break;
        case 'g': { buf.Append("32"); } break;
        case 'y': { buf.Append("33"); } break;
        case 'b': { buf.Append("34"); } break;
        case 'm': { buf.Append("35"); } break;
        case 'c': { buf.Append("36"); } break;
        case 'w': { buf.Append("37"); } break;
        case 'D': { buf.Append("90"); } break;
        case 'R': { buf.Append("91"); } break;
        case 'G': { buf.Append("92"); } break;
        case 'Y': { buf.Append("93"); } break;
        case 'B': { buf.Append("94"); } break;
        case 'M': { buf.Append("95"); } break;
        case 'C': { buf.Append("96"); } break;
        case 'W': { buf.Append("97"); } break;
        case '.': { buf.Append("39"); } break;
        case '0': {
            buf.Append("0");
            goto end;
        } break;
        case 0: {
            valid = false;
            goto end;
        } break;
        default: { valid = false; } break;
    }

    // Background color
    switch (spec[++idx]) {
        case 'd': { buf.Append(";40"); } break;
        case 'r': { buf.Append(";41"); } break;
        case 'g': { buf.Append(";42"); } break;
        case 'y': { buf.Append(";43"); } break;
        case 'b': { buf.Append(";44"); } break;
        case 'm': { buf.Append(";45"); } break;
        case 'c': { buf.Append(";46"); } break;
        case 'w': { buf.Append(";47"); } break;
        case 'D': { buf.Append(";100"); } break;
        case 'R': { buf.Append(";101"); } break;
        case 'G': { buf.Append(";102"); } break;
        case 'Y': { buf.Append(";103"); } break;
        case 'B': { buf.Append(";104"); } break;
        case 'M': { buf.Append(";105"); } break;
        case 'C': { buf.Append(";106"); } break;
        case 'W': { buf.Append(";107"); } break;
        case '.': { buf.Append(";49"); } break;
        case 0: {
            valid = false;
            goto end;
        } break;
        default: { valid = false; } break;
    }

    // Bold/dim/underline/invert
    switch (spec[++idx]) {
        case '+': { buf.Append(";1"); } break;
        case '-': { buf.Append(";2"); } break;
        case '_': { buf.Append(";4"); } break;
        case '^': { buf.Append(";7"); } break;
        case '.': {} break;
        case 0: {
            valid = false;
            goto end;
        } break;
        default: { valid = false; } break;
    }

end:
    if (!valid) {
#if defined(K_DEBUG)
        LogDebug("Format string contains invalid ANSI specifier");
#endif
        return idx;
    }

    if (vt100) {
        buf.Append("m");
        append(buf);
    }

    return idx;
}

template <typename AppendFunc>
static inline void DoFormat(const char *fmt, Span<const FmtArg> args, bool vt100, AppendFunc append)
{
#if defined(K_DEBUG)
    bool invalid_marker = false;
    uint32_t unused_arguments = ((uint32_t)1 << args.len) - 1;
#endif

    const char *fmt_ptr = fmt;
    for (;;) {
        // Find the next marker (or the end of string) and write everything before it
        const char *marker_ptr = fmt_ptr;
        while (marker_ptr[0] && marker_ptr[0] != '%') {
            marker_ptr++;
        }
        append(MakeSpan(fmt_ptr, (Size)(marker_ptr - fmt_ptr)));
        if (!marker_ptr[0])
            break;

        // Try to interpret this marker as a number
        Size idx = 0;
        Size idx_end = 1;
        for (;;) {
            // Unsigned cast makes the test below quicker, don't remove it or it'll break
            unsigned int digit = (unsigned int)marker_ptr[idx_end] - '0';
            if (digit > 9)
                break;
            idx = (Size)(idx * 10) + (Size)digit;
            idx_end++;
        }

        // That was indeed a number
        if (idx_end > 1) {
            idx--;
            if (idx < args.len) {
                ProcessArg<AppendFunc>(args[idx], append);
#if defined(K_DEBUG)
                unused_arguments &= ~((uint32_t)1 << idx);
            } else {
                invalid_marker = true;
#endif
            }
            fmt_ptr = marker_ptr + idx_end;
        } else if (marker_ptr[1] == '%') {
            append('%');
            fmt_ptr = marker_ptr + 2;
        } else if (marker_ptr[1] == '/') {
            append(*K_PATH_SEPARATORS);
            fmt_ptr = marker_ptr + 2;
        } else if (marker_ptr[1] == '!') {
            fmt_ptr = marker_ptr + 2 + ProcessAnsiSpecifier(marker_ptr + 1, vt100, append);
        } else if (marker_ptr[1]) {
            append(marker_ptr[0]);
            fmt_ptr = marker_ptr + 1;
#if defined(K_DEBUG)
            invalid_marker = true;
#endif
        } else {
#if defined(K_DEBUG)
            invalid_marker = true;
#endif
            break;
        }
    }

#if defined(K_DEBUG)
    if (invalid_marker && unused_arguments) {
        PrintLn(StdErr, "\nLog format string '%1' has invalid markers and unused arguments", fmt);
    } else if (unused_arguments) {
        PrintLn(StdErr, "\nLog format string '%1' has unused arguments", fmt);
    } else if (invalid_marker) {
        PrintLn(StdErr, "\nLog format string '%1' has invalid markers", fmt);
    }
#endif
}

Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, Span<char> out_buf)
{
    K_ASSERT(out_buf.len >= 0);

    if (!out_buf.len)
        return {};
    out_buf.len--;

    Size available_len = out_buf.len;

    DoFormat(fmt, args, vt100, [&](Span<const char> frag) {
        Size copy_len = std::min(frag.len, available_len);

        MemCpy(out_buf.end() - available_len, frag.ptr, copy_len);
        available_len -= copy_len;
    });

    out_buf.len -= available_len;
    out_buf.ptr[out_buf.len] = 0;

    return out_buf;
}

Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, HeapArray<char> *out_buf)
{
    Size start_len = out_buf->len;

    out_buf->Grow(K_FMT_STRING_BASE_CAPACITY);
    DoFormat(fmt, args, vt100, [&](Span<const char> frag) {
        out_buf->Grow(frag.len + 1);
        MemCpy(out_buf->end(), frag.ptr, frag.len);
        out_buf->len += frag.len;
    });
    out_buf->ptr[out_buf->len] = 0;

    return out_buf->Take(start_len, out_buf->len - start_len);
}

Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, Allocator *alloc)
{
    K_ASSERT(alloc);

    HeapArray<char> buf(alloc);
    FmtFmt(fmt, args, vt100, &buf);

    return buf.TrimAndLeak(1);
}

void FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, FunctionRef<void(Span<const char>)> append)
{
    // This one dos not null terminate! Be careful!
    DoFormat(fmt, args, vt100, append);
}

void PrintFmt(const char *fmt, Span<const FmtArg> args, StreamWriter *st)
{
    LocalArray<char, K_FMT_STRING_PRINT_BUFFER_SIZE> buf;
    DoFormat(fmt, args, st->IsVt100(), [&](Span<const char> frag) {
        if (frag.len > K_LEN(buf.data) - buf.len) {
            st->Write(buf);
            buf.len = 0;
        }
        if (frag.len >= K_LEN(buf.data)) {
            st->Write(frag);
        } else {
            MemCpy(buf.data + buf.len, frag.ptr, frag.len);
            buf.len += frag.len;
        }
    });
    st->Write(buf);
}

void PrintLnFmt(const char *fmt, Span<const FmtArg> args, StreamWriter *st)
{
    PrintFmt(fmt, args, st);
    st->Write('\n');
}

// PrintLn variants without format strings
void PrintLn(StreamWriter *out_st)
{
    out_st->Write('\n');
}
void PrintLn()
{
    StdOut->Write('\n');
}

void FmtUpperAscii::Format(FunctionRef<void(Span<const char>)> append) const
{
    for (char c: str) {
        c = UpperAscii(c);
        append((char)c);
    }
}

void FmtLowerAscii::Format(FunctionRef<void(Span<const char>)> append) const
{
    for (char c: str) {
        c = LowerAscii(c);
        append((char)c);
    }
}

void FmtUrlSafe::Format(FunctionRef<void(Span<const char>)> append) const
{
    for (char c: str) {
        if (IsAsciiAlphaOrDigit(c) || strchr(passthrough, c)) {
            append((char)c);
        } else {
            char encoded[3];

            encoded[0] = '%';
            encoded[1] = BigHexLiterals[((uint8_t)c >> 4) & 0xF];
            encoded[2] = BigHexLiterals[((uint8_t)c >> 0) & 0xF];

            Span<const char> buf = MakeSpan(encoded, 3);
            append(buf);
        }
    }
}

void FmtHtmlSafe::Format(FunctionRef<void(Span<const char>)> append) const
{
    for (char c: str) {
        switch (c) {
            case '<': { append("&lt;"); } break;
            case '>':  { append("&gt;"); } break;
            case '"':  { append("&quot;"); } break;
            case '\'': { append("&apos;"); } break;
            case '&':  { append("&amp;"); } break;

            default: { append(c); } break;
        }
    }
}

void FmtEscape::Format(FunctionRef<void(Span<const char>)> append) const
{
    for (char c: str) {
        if (c == '\r') {
            append("\\r");
        } else if (c == '\n') {
            append("\\n");
        } else if (c == '\\') {
            append("\\\\");
        } else if ((unsigned int)c < 32) {
            char encoded[4];

            encoded[0] = '\\';
            encoded[1] = '0' + (((uint8_t)c >> 6) & 7);
            encoded[2] = '0' + (((uint8_t)c >> 3) & 7);
            encoded[3] = '0' + (((uint8_t)c >> 0) & 7);

            Span<const char> buf = MakeSpan(encoded, 4);
            append(buf);
        } else if (c == quote) {
            append('\\');
            append(quote);
        } else {
            append(c);
        }
    }
}

FmtArg FmtVersion(int64_t version, int parts, int by)
{
    K_ASSERT(version >= 0);
    K_ASSERT(parts > 0);

    FmtArg arg = {};
    arg.type = FmtType::Buffer;

    Span<char> buf = arg.u.buf;
    int64_t divisor = 1;

    for (int i = 1; i < parts; i++) {
        divisor *= by;
    }

    for (int i = 0; i < parts; i++) {
        int64_t component = (version / divisor) % by;
        Size len = Fmt(buf, "%1.", component).len;

        buf.ptr += len;
        buf.len -= len;

        divisor /= by;
    }

    // Remove trailing dot
    buf.ptr[-1] = 0;

    return arg;
}

// ------------------------------------------------------------------------
// Debug and errors
// ------------------------------------------------------------------------

static int64_t start_clock = GetMonotonicClock();

static std::function<LogFunc> log_handler = DefaultLogHandler;
static bool log_vt100 = FileIsVt100(STDERR_FILENO);

// thread_local is broken on MinGW when destructors are involved.
// So heap allocation it is, at least for now.
static thread_local std::function<LogFilterFunc> *log_filters[16];
static thread_local Size log_filters_len;

const char *GetEnv(const char *name)
{
#if defined(__EMSCRIPTEN__)
    // Each accessed environment variable is kept in memory and thus leaked once
    static HashMap<const char *, const char *> values;

    bool inserted;
    auto bucket = values.InsertOrGetDefault(name, &inserted);

    if (inserted) {
        const char *str = (const char *)EM_ASM_INT({
            try {
                var name = UTF8ToString($0);
                var str = process.env[name];

                if (str == null)
                    return 0;

                var bytes = lengthBytesUTF8(str) + 1;
                var utf8 = _malloc(bytes);
                stringToUTF8(str, utf8, bytes);

                return utf8;
            } catch (error) {
                return 0;
            }
        }, name);

        bucket->key = DuplicateString(name, GetDefaultAllocator()).ptr;
        bucket->value = str;
    }

    return bucket->value;
#else
    return getenv(name);
#endif
}

bool GetDebugFlag(const char *name)
{
    const char *debug = GetEnv(name);

    if (debug) {
        bool ret = false;
        if (!ParseBool(debug, &ret, K_DEFAULT_PARSE_FLAGS & ~(int)ParseFlag::Log)) {
            LogError("Environment variable '%1' is not a boolean", name);
        }
        return ret;
    } else {
        return false;
    }
}

static void RunLogFilter(Size idx, LogLevel level, const char *ctx, const char *msg)
{
    const std::function<LogFilterFunc> &func = *log_filters[idx];

    func(level, ctx, msg, [&](LogLevel level, const char *ctx, const char *msg) {
        if (idx > 0) {
            RunLogFilter(idx - 1, level, ctx, msg);
        } else {
            log_handler(level, ctx, msg);
        }
    });
}

void LogFmt(LogLevel level, const char *ctx, const char *fmt, Span<const FmtArg> args)
{
    static thread_local bool skip = false;

    static bool init = false;
    static bool log_times;

    // Avoid deadlock if a log filter or the handler tries to log something while handling a previous call
    if (skip)
        return;
    skip = true;
    K_DEFER { skip = false; };

    if (!init) {
        // Do this first... GetDebugFlag() might log an error or something, in which
        // case we don't want to recurse forever and crash!
        init = true;

        log_times = GetDebugFlag("LOG_TIMES");
    }

    char ctx_buf[512];
    if (log_times) {
        double time = (double)(GetMonotonicClock() - start_clock) / 1000;
        Fmt(ctx_buf, "[%1] %2", FmtDouble(time, 3, 8), ctx ? ctx : "");

        ctx = ctx_buf;
    }

    char msg_buf[2048];
    {
        Size len = FmtFmt(T(fmt), args, log_vt100, msg_buf).len;

        if (len == K_SIZE(msg_buf) - 1) {
            strncpy(msg_buf + K_SIZE(msg_buf) - 32, "... [truncated]", 32);
            msg_buf[K_SIZE(msg_buf) - 1] = 0;
        }
    }

    if (log_filters_len) {
        RunLogFilter(log_filters_len - 1, level, ctx, msg_buf);
    } else {
        log_handler(level, ctx, msg_buf);
    }
}

void SetLogHandler(const std::function<LogFunc> &func, bool vt100)
{
    log_handler = func;
    log_vt100 = vt100;
}

void DefaultLogHandler(LogLevel level, const char *ctx, const char *msg)
{
    switch (level)  {
        case LogLevel::Debug:
        case LogLevel::Info: { Print(StdErr, "%!D..%1%!0%2\n", ctx ? ctx : "", msg); } break;
        case LogLevel::Warning: { Print(StdErr, "%!M..%1%!0%2\n", ctx ? ctx : "", msg); } break;
        case LogLevel::Error: { Print(StdErr, "%!R..%1%!0%2\n", ctx ? ctx : "", msg); } break;
    }
}

void PushLogFilter(const std::function<LogFilterFunc> &func)
{
    K_ASSERT(log_filters_len < K_LEN(log_filters));
    log_filters[log_filters_len++] = new std::function<LogFilterFunc>(func);
}

void PopLogFilter()
{
    K_ASSERT(log_filters_len > 0);
    delete log_filters[--log_filters_len];
}

#if defined(_WIN32)
bool RedirectLogToWindowsEvents(const char *name)
{
    static HANDLE log = nullptr;
    K_ASSERT(!log);

    log = OpenEventLogA(nullptr, name);
    if (!log) {
        LogError("Failed to register event provider: %1", GetWin32ErrorString());
        return false;
    }
    atexit([]() { CloseEventLog(log); });

    SetLogHandler([](LogLevel level, const char *ctx, const char *msg) {
        WORD type = 0;
        LocalArray<wchar_t, 8192> buf_w;

        switch (level)  {
            case LogLevel::Debug:
            case LogLevel::Info: { type = EVENTLOG_INFORMATION_TYPE; } break;
            case LogLevel::Warning: { type = EVENTLOG_WARNING_TYPE; } break;
            case LogLevel::Error: { type = EVENTLOG_ERROR_TYPE; } break;
        }

        // Append context
        if (ctx) {
            Size len = ConvertUtf8ToWin32Wide(ctx, buf_w.TakeAvailable());
            if (len < 0)
                return;
            buf_w.len += len;
        }

        // Append message
        {
            Size len = ConvertUtf8ToWin32Wide(msg, buf_w.TakeAvailable());
            if (len < 0)
                return;
            buf_w.len += len;
        }

        const wchar_t *ptr = buf_w.data;
        ReportEventW(log, type, 0, 0, nullptr, 1, 0, &ptr, nullptr);
    }, false);

    return true;
}
#endif

// ------------------------------------------------------------------------
// Progress
// ------------------------------------------------------------------------

#if !defined(__wasi__)

struct ProgressState {
    char text[K_PROGRESS_TEXT_SIZE];

    int64_t value;
    int64_t min;
    int64_t max;

    bool determinate;
    bool valid;
};

struct ProgressNode {
    std::atomic_bool used;

    std::mutex mutex;
    ProgressState front;
    ProgressState back;
};

static std::function<ProgressFunc> pg_handler = DefaultProgressHandler;

static std::atomic_int pg_count;
static ProgressNode pg_nodes[K_PROGRESS_MAX_NODES];

static std::mutex pg_mutex;
static bool pg_run = false;

static void RunProgressThread()
{
    // Reuse for performance
    HeapArray<ProgressInfo> bars;

    int delay = StdErr->IsVt100() ? 400 : 4000;

    for (;;) {
        // Need to run still?
        {
            std::lock_guard<std::mutex> lock(pg_mutex);

            if (!pg_count) {
                pg_run = false;
                break;
            }
        }

        bars.RemoveFrom(0);

        for (ProgressNode &node: pg_nodes) {
            ProgressInfo bar = {};

            // Copy state atomically or bail
            {
                std::unique_lock<std::mutex> lock(node.mutex, std::try_to_lock);

                if (lock.owns_lock()) {
                    node.back = node.front;
                    lock.unlock();
                }

                if (!node.back.valid)
                    continue;
            }

            bar.text = node.back.text;
            bar.value = node.back.value;
            bar.min = node.back.min;
            bar.max = node.back.max;
            bar.determinate = node.back.determinate;

            bars.Append(bar);
        }

        pg_handler(bars);

        WaitDelay(delay);
    }
}

ProgressHandle::~ProgressHandle()
{
    ProgressNode *node = this->node.load();

    if (node) {
        std::lock_guard<std::mutex> lock(node->mutex);

        node->front.valid = false;
        node->used = false;

        if (!--pg_count) {
            StdErr->Flush();
        }
    }
}

void ProgressHandle::Set(int64_t value, int64_t min, int64_t max)
{
    ProgressNode *node = AcquireNode();

    if (!node) [[unlikely]]
        return;

    std::unique_lock<std::mutex> lock(node->mutex, std::try_to_lock);

    if (!lock.owns_lock())
        return;

    node->front.value = value;
    node->front.min = min;
    node->front.max = max;
    node->front.determinate = (max > min);
    node->front.valid = true;
}

void ProgressHandle::Set(int64_t value, int64_t min, int64_t max, Span<const char> text)
{
    ProgressNode *node = AcquireNode();

    if (!node) [[unlikely]]
        return;

    std::unique_lock<std::mutex> lock(node->mutex, std::try_to_lock);

    if (!lock.owns_lock())
        return;

    CopyText(text, node->front.text);
    node->front.value = value;
    node->front.min = min;
    node->front.max = max;
    node->front.determinate = (max > min);
    node->front.valid = true;
}

ProgressNode *ProgressHandle::AcquireNode()
{
    // Fast path
    {
        ProgressNode *node = this->node.load(std::memory_order_relaxed);

        if (node)
            return node;
    }

    int count = pg_count++;

    if (!count) {
        std::lock_guard lock(pg_mutex);

        if (!pg_run) {
            std::thread thread(RunProgressThread);
            thread.detach();

            pg_run = true;
        }
    } else if (count > K_PROGRESS_USED_NODES) {
        pg_count--;
        return nullptr;
    }

    int base = GetRandomInt(0, K_LEN(pg_nodes));

    for (int i = 0; i < K_LEN(pg_nodes); i++) {
        int idx = (base + i) % K_LEN(pg_nodes);

        ProgressNode *node = &pg_nodes[idx];
        bool used = node->used.exchange(true);

        if (!used) {
            static_assert(K_SIZE(text) == K_SIZE(node->front.text));
            MemCpy(node->front.text, text, K_SIZE(text));

            ProgressNode *prev = nullptr;
            bool set = this->node.compare_exchange_strong(prev, node);

            if (set) {
                return node;
            } else {
                node->used = false;
                pg_count--;

                return prev;
            }
        }
    }

    return nullptr;
}

void ProgressHandle::CopyText(Span<const char> text, char out[K_PROGRESS_TEXT_SIZE])
{
    Span<char> buf = MakeSpan(out, K_PROGRESS_TEXT_SIZE);
    bool complete = CopyString(text, buf);

    if (!complete) [[unlikely]] {
        out[K_PROGRESS_TEXT_SIZE - 4] = '.';
        out[K_PROGRESS_TEXT_SIZE - 3] = '.';
        out[K_PROGRESS_TEXT_SIZE - 2] = '.';
        out[K_PROGRESS_TEXT_SIZE - 1] = 0;
    }
}

void SetProgressHandler(const std::function<ProgressFunc> &func)
{
    pg_handler = func;
}

void DefaultProgressHandler(Span<const ProgressInfo> bars)
{
    static uint64_t frame = 0;

    if (!bars.len) {
        StdErr->Flush();
        return;
    }

    Size count = bars.len;
    Size rows = std::min((Size)20, bars.len);

    bars = bars.Take(0, rows);

    if (StdErr->IsVt100()) {
        // Don't blow up stack size
        static LocalArray<char, 65536> buf;
        buf.Clear();

        for (const ProgressInfo &bar: bars) {
            if (bar.determinate) {
                int64_t range = bar.max - bar.min;
                int64_t delta = bar.value - bar.min;

                int progress = (int)(100 * delta / range);
                int size = progress / 4;

                buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1%2]%!0  %3\n", FmtRepeat("=", size), FmtRepeat(" ", 25 - size), bar.text).len;
            } else {
                int progress = (int)(frame % 44);
                int before = (progress > 22) ? (44 - progress) : progress;
                int after = std::max(22 - before, 0);

                buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1===%2]%!0  %3\n", FmtRepeat(" ", before), FmtRepeat(" ", after), bar.text).len;
            }
        }

        if (count > bars.len) {
            buf.len += Fmt(buf.TakeAvailable(), true, "%!D..... and %1 more tasks%!0\n", count - bars.len).len;
            rows++;
        }
        buf.len--;

        StdErr->Write(buf);
        StdErr->Flush();

        if (rows > 1) {
            Print(StdErr, "\r\x1B[%1F\x1B[%2M", rows - 1, rows);
        } else {
            Print(StdErr, "\r\x1B[%1M", rows);
        }
    } else {
        for (const ProgressInfo &bar: bars) {
            PrintLn(StdErr, "%1", bar.text);
        }
    }

    frame++;
}

#endif

// ------------------------------------------------------------------------
// System
// ------------------------------------------------------------------------

#if defined(_WIN32)

static bool win32_utf8 = (GetACP() == CP_UTF8);

bool IsWin32Utf8()
{
    return win32_utf8;
}

Size ConvertUtf8ToWin32Wide(Span<const char> str, Span<wchar_t> out_str_w)
{
    if (!out_str_w.len) {
        LogError("Output buffer is too small");
        return -1;
    }

    if (!str.len) {
        out_str_w[0] = 0;
        return 0;
    } else if (out_str_w.len == 1) {
        LogError("Output buffer is too small");
        return -1;
    }

    int len = MultiByteToWideChar(CP_UTF8, 0, str.ptr, (int)str.len, out_str_w.ptr, (int)out_str_w.len - 1);
    if (!len) {
        switch (GetLastError()) {
            case ERROR_INSUFFICIENT_BUFFER: { LogError("String '%1' is too large", str); } break;
            case ERROR_NO_UNICODE_TRANSLATION: { LogError("String '%1' is not valid UTF-8", str); } break;
            default: { LogError("MultiByteToWideChar() failed: %1", GetWin32ErrorString()); } break;
        }
        return -1;
    }

    // MultiByteToWideChar() does not NUL terminate when passed in explicit string length
    out_str_w.ptr[len] = 0;

    return (Size)len;
}

Size ConvertWin32WideToUtf8(LPCWSTR str_w, Span<char> out_str)
{
    if (!out_str.len) {
        LogError("Output buffer is too small");
        return -1;
    }

    int len = WideCharToMultiByte(CP_UTF8, 0, str_w, -1, out_str.ptr, (int)out_str.len - 1, nullptr, nullptr);
    if (!len) {
        switch (GetLastError()) {
            case ERROR_INSUFFICIENT_BUFFER: { LogError("Cannot convert UTF-16 string to UTF-8: too large"); } break;
            case ERROR_NO_UNICODE_TRANSLATION: { LogError("Cannot convert invalid UTF-16 string to UTF-8"); } break;
            default: { LogError("WideCharToMultiByte() failed: %1", GetWin32ErrorString()); } break;
        }
        return -1;
    }

    return (Size)len - 1;
}

char *GetWin32ErrorString(uint32_t error_code)
{
    static thread_local char str_buf[512];

    if (error_code == UINT32_MAX) {
        error_code = GetLastError();
    }

    if (win32_utf8) {
        if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                            nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                            str_buf, K_SIZE(str_buf), nullptr))
            goto fail;
    } else {
        wchar_t buf_w[256];
        if (!FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                            nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                            buf_w, K_SIZE(buf_w), nullptr))
            goto fail;

        if (!WideCharToMultiByte(CP_UTF8, 0, buf_w, -1, str_buf, K_SIZE(str_buf), nullptr, nullptr))
            goto fail;
    }

    // Truncate newlines
    {
        char *str_end = str_buf + strlen(str_buf);
        while (str_end > str_buf && (str_end[-1] == '\n' || str_end[-1] == '\r'))
            str_end--;
        *str_end = 0;
    }

    return str_buf;

fail:
    sprintf(str_buf, "Win32 error 0x%x", error_code);
    return str_buf;
}

static inline FileType FileAttributesToType(uint32_t attr)
{
    if (attr & FILE_ATTRIBUTE_DIRECTORY) {
        return FileType::Directory;
    } else if (attr & FILE_ATTRIBUTE_DEVICE) {
        return FileType::Device;
    } else {
        return FileType::File;
    }
}

static StatResult StatHandle(HANDLE h, const char *filename, FileInfo *out_info)
{
    BY_HANDLE_FILE_INFORMATION attr;
    if (!GetFileInformationByHandle(h, &attr)) {
        LogError("Cannot stat file '%1': %2", filename, GetWin32ErrorString());
        return StatResult::OtherError;
    }

    out_info->type = FileAttributesToType(attr.dwFileAttributes);
    out_info->size = ((uint64_t)attr.nFileSizeHigh << 32) | attr.nFileSizeLow;
    out_info->mtime = FileTimeToUnixTime(attr.ftLastWriteTime);
    out_info->ctime = FileTimeToUnixTime(attr.ftCreationTime);
    out_info->atime = FileTimeToUnixTime(attr.ftLastAccessTime);
    out_info->btime = out_info->ctime;
    out_info->mode = (out_info->type == FileType::Directory) ? 0755 : 0644;
    out_info->uid = 0;
    out_info->gid = 0;

    return StatResult::Success;
}

StatResult StatFile(int fd, const char *filename, unsigned int flags, FileInfo *out_info)
{
    // We don't detect symbolic links, but since they are much less of a hazard
    // than on POSIX systems we care a lot less about them.

    if (fd < 0) {
        HANDLE h;
        if (win32_utf8) {
            h = CreateFileA(filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                            nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
        } else {
            wchar_t filename_w[4096];
            if (ConvertUtf8ToWin32Wide(filename, filename_w) < 0)
                return StatResult::OtherError;

            h = CreateFileW(filename_w, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                            nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
        }
        if (h == INVALID_HANDLE_VALUE) {
            DWORD err = GetLastError();

            switch (err) {
                case ERROR_FILE_NOT_FOUND:
                case ERROR_PATH_NOT_FOUND: {
                    if (!(flags & (int)StatFlag::SilentMissing)) {
                        LogError("Cannot stat file '%1': %2", filename, GetWin32ErrorString(err));
                    }
                    return StatResult::MissingPath;
                } break;
                case ERROR_ACCESS_DENIED: {
                    LogError("Cannot stat file '%1': %2", filename, GetWin32ErrorString(err));
                    return StatResult::AccessDenied;
                }
                default: {
                    LogError("Cannot stat file '%1': %2", filename, GetWin32ErrorString(err));
                    return StatResult::OtherError;
                } break;
            }
        }
        K_DEFER { CloseHandle(h); };

        return StatHandle(h, filename, out_info);
    } else {
        HANDLE h = (HANDLE)_get_osfhandle(fd);
        return StatHandle(h, filename, out_info);
    }
}

RenameResult RenameFile(const char *src_filename, const char *dest_filename, unsigned int silent, unsigned int flags)
{
    K_ASSERT(!(silent & ((int)RenameResult::Success | (int)RenameResult::OtherError)));

    DWORD move_flags = (flags & (int)RenameFlag::Overwrite) ? MOVEFILE_REPLACE_EXISTING : 0;
    DWORD err = ERROR_SUCCESS;

    for (int i = 0; i < 10; i++) {
        if (win32_utf8) {
            if (MoveFileExA(src_filename, dest_filename, move_flags))
                return RenameResult::Success;
        } else {
            wchar_t src_filename_w[4096];
            wchar_t dest_filename_w[4096];
            if (ConvertUtf8ToWin32Wide(src_filename, src_filename_w) < 0)
                return RenameResult::OtherError;
            if (ConvertUtf8ToWin32Wide(dest_filename, dest_filename_w) < 0)
                return RenameResult::OtherError;

            if (MoveFileExW(src_filename_w, dest_filename_w, move_flags))
                return RenameResult::Success;
        }

        err = GetLastError();

        // If two threads are trying to rename to the same destination or the FS is
        // very busy, we get spurious ERROR_ACCESS_DENIED errors. Wait a bit and retry :)
        if (err != ERROR_ACCESS_DENIED)
            break;

        Sleep(1);
    }

    if (err == ERROR_ALREADY_EXISTS) {
        if (!(silent & (int)RenameResult::AlreadyExists)) {
            LogError("Failed to rename '%1' to '%2': file already exists", src_filename, dest_filename);
        }
        return RenameResult::AlreadyExists;
    } else {
        LogError("Failed to rename '%1' to '%2': %3", src_filename, dest_filename, GetWin32ErrorString(err));
        return RenameResult::OtherError;
    }
}

bool ResizeFile(int fd, const char *filename, int64_t len)
{
    HANDLE h = (HANDLE)_get_osfhandle(fd);

    LARGE_INTEGER prev_pos = {};
    if (!SetFilePointerEx(h, prev_pos, &prev_pos, FILE_CURRENT)) {
        LogError("Failed to resize file '%1': %2", filename, GetWin32ErrorString());
        return false;
    }
    K_DEFER { SetFilePointerEx(h, prev_pos, nullptr, FILE_BEGIN); };

    if (!SetFilePointerEx(h, { .QuadPart = len }, nullptr, FILE_BEGIN)) {
        LogError("Failed to resize file '%1': %2", filename, GetWin32ErrorString());
        return false;
    }
    if (!SetEndOfFile(h)) {
        LogError("Failed to resize file '%1': %2", filename, GetWin32ErrorString());
        return false;
    }

    return true;
}

bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t btime)
{
    HANDLE h = (HANDLE)_get_osfhandle(fd);

    FILETIME mft = UnixTimeToFileTime(mtime);
    FILETIME bft = UnixTimeToFileTime(btime);

    if (!SetFileTime(h, &bft, nullptr, &mft)) {
        LogError("Failed to set modification time of '%1': %2", filename, GetWin32ErrorString());
        return false;
    }

    return true;
}

bool GetVolumeInfo(const char *dirname, VolumeInfo *out_volume)
{
    ULARGE_INTEGER available;
    ULARGE_INTEGER total;

    if (win32_utf8) {
        if (!GetDiskFreeSpaceExA(dirname, &available, &total, nullptr)) {
            LogError("Cannot get volume information for '%1': %2", dirname, GetWin32ErrorString());
            return false;
        }
    } else {
        wchar_t dirname_w[4096];
        if (ConvertUtf8ToWin32Wide(dirname, dirname_w) < 0)
            return false;

        if (!GetDiskFreeSpaceExW(dirname_w, &available, &total, nullptr)) {
            LogError("Cannot get volume information for '%1': %2", dirname, GetWin32ErrorString());
            return false;
        }
    }

    out_volume->total = (int64_t)total.QuadPart;
    out_volume->available = (int64_t)available.QuadPart;

    return true;
}

EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
                              FunctionRef<bool(const char *, FileType)> func)
{
    EnumResult ret = EnumerateDirectory(dirname, filter, max_files,
                                        [&](const char *basename, const FileInfo &file_info) {
        return func(basename, file_info.type);
    });

    return ret;
}

EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
                              FunctionRef<bool(const char *, const FileInfo &)> func)
{
    if (filter) {
        K_ASSERT(!strpbrk(filter, K_PATH_SEPARATORS));
    } else {
        filter = "*";
    }

    wchar_t find_filter_w[4096];
    {
        char find_filter[4096];
        if (snprintf(find_filter, K_SIZE(find_filter), "%s\\%s", dirname, filter) >= K_SIZE(find_filter)) {
            LogError("Cannot enumerate directory '%1': Path too long", dirname);
            return EnumResult::OtherError;
        }

        if (ConvertUtf8ToWin32Wide(find_filter, find_filter_w) < 0)
            return EnumResult::OtherError;
    }

    WIN32_FIND_DATAW attr;
    HANDLE handle = FindFirstFileExW(find_filter_w, FindExInfoBasic, &attr,
                                     FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH);
    if (handle == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();

        if (err == ERROR_FILE_NOT_FOUND) {
            // Erase the filter part from the buffer, we are about to exit anyway.
            // And no, I don't want to include wchar.h
            Size len = 0;
            while (find_filter_w[len++]);
            while (len > 0 && find_filter_w[--len] != L'\\');
            find_filter_w[len] = 0;

            DWORD attrib = GetFileAttributesW(find_filter_w);
            if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY))
                return EnumResult::Success;
        }

        LogError("Cannot enumerate directory '%1': %2", dirname, GetWin32ErrorString());

        switch (err) {
            case ERROR_FILE_NOT_FOUND:
            case ERROR_PATH_NOT_FOUND: return EnumResult::MissingPath;
            case ERROR_ACCESS_DENIED: return EnumResult::AccessDenied;
            default: return EnumResult::OtherError;
        }
    }
    K_DEFER { FindClose(handle); };

    Size count = 0;
    do {
        if ((attr.cFileName[0] == '.' && !attr.cFileName[1]) ||
                (attr.cFileName[0] == '.' && attr.cFileName[1] == '.' && !attr.cFileName[2]))
            continue;

        if (count++ >= max_files && max_files >= 0) [[unlikely]] {
            LogError("Partial enumation of directory '%1'", dirname);
            return EnumResult::PartialEnum;
        }

        char filename[512];
        if (ConvertWin32WideToUtf8(attr.cFileName, filename) < 0)
            return EnumResult::OtherError;

        FileInfo file_info = {};

        file_info.type = FileAttributesToType(attr.dwFileAttributes);
        file_info.size = ((uint64_t)attr.nFileSizeHigh << 32) | attr.nFileSizeLow;
        file_info.mtime = FileTimeToUnixTime(attr.ftLastWriteTime);
        file_info.btime = FileTimeToUnixTime(attr.ftCreationTime);
        file_info.mode = (file_info.type == FileType::Directory) ? 0755 : 0644;
        file_info.uid = 0;
        file_info.gid = 0;

        if (!func(filename, file_info))
            return EnumResult::CallbackFail;
    } while (FindNextFileW(handle, &attr));

    if (GetLastError() != ERROR_NO_MORE_FILES) {
        LogError("Error while enumerating directory '%1': %2", dirname,
                 GetWin32ErrorString());
        return EnumResult::OtherError;
    }

    return EnumResult::Success;
}

#else

static FileType FileModeToType(mode_t mode)
{
    if (S_ISDIR(mode)) {
        return FileType::Directory;
    } else if (S_ISREG(mode)) {
        return FileType::File;
    } else if (S_ISBLK(mode) || S_ISCHR(mode)) {
        return FileType::Device;
    } else if (S_ISLNK(mode)) {
        return FileType::Link;
    } else if (S_ISFIFO(mode)) {
        return FileType::Pipe;
    } else if (S_ISSOCK(mode)) {
        return FileType::Socket;
    } else {
        // This... should not happen. But who knows?
        return FileType::File;
    }
}

static StatResult StatAt(int fd, bool fd_is_directory, const char *filename, unsigned int flags, FileInfo *out_info)
{
#if defined(__linux__) && defined(STATX_TYPE) && !defined(CORE_NO_STATX)
    const char *pathname = filename;
    int stat_flags = (flags & (int)StatFlag::FollowSymlink) ? 0 : AT_SYMLINK_NOFOLLOW;
    int stat_mask = STATX_TYPE | STATX_MODE | STATX_MTIME | STATX_BTIME | STATX_SIZE;

    if (!fd_is_directory) {
        if (fd >= 0) {
            pathname = "";
            stat_flags |= AT_EMPTY_PATH;
        } else {
            fd = AT_FDCWD;
        }
    }

    struct statx sxb;
    if (statx(fd, pathname, stat_flags, stat_mask, &sxb) < 0) {
        switch (errno) {
            case ENOENT: {
                if (!(flags & (int)StatFlag::SilentMissing)) {
                    LogError("Cannot stat '%1': %2", filename, strerror(errno));
                }
                return StatResult::MissingPath;
            } break;
            case EACCES: {
                LogError("Cannot stat '%1': %2", filename, strerror(errno));
                return StatResult::AccessDenied;
            } break;
            case ENOTDIR: {
                LogError("Cannot stat '%1': Component is not a directory", filename);
                return StatResult::OtherError;
            } break;
            default: {
                LogError("Cannot stat '%1': %2", filename, strerror(errno));
                return StatResult::OtherError;
            } break;
        }
    }

    out_info->type = FileModeToType(sxb.stx_mode);
    out_info->size = (int64_t)sxb.stx_size;
    out_info->mtime = (int64_t)sxb.stx_mtime.tv_sec * 1000 +
                      (int64_t)sxb.stx_mtime.tv_nsec / 1000000;
    out_info->ctime = (int64_t)sxb.stx_ctime.tv_sec * 1000 +
                      (int64_t)sxb.stx_ctime.tv_nsec / 1000000;
    out_info->atime = (int64_t)sxb.stx_atime.tv_sec * 1000 +
                      (int64_t)sxb.stx_atime.tv_nsec / 1000000;
    if (sxb.stx_mask & STATX_BTIME) {
        out_info->btime = (int64_t)sxb.stx_btime.tv_sec * 1000 +
                          (int64_t)sxb.stx_btime.tv_nsec / 1000000;
    } else {
        out_info->btime = out_info->mtime;
    }
    out_info->mode = (unsigned int)sxb.stx_mode & ~S_IFMT;
    out_info->uid = sxb.stx_uid;
    out_info->gid = sxb.stx_gid;
#else
    if (fd < 0) {
        fd_is_directory = true;
        fd = AT_FDCWD;
    }

    struct stat sb;
    int ret = 0;

    if (fd_is_directory) {
        int stat_flags = (flags & (int)StatFlag::FollowSymlink) ? 0 : AT_SYMLINK_NOFOLLOW;
        ret = fstatat(fd, filename, &sb, stat_flags);
    } else {
        ret = fstat(fd, &sb);
    }

    if (ret < 0) {
        switch (errno) {
            case ENOENT: {
                if (!(flags & (int)StatFlag::SilentMissing)) {
                    LogError("Cannot stat '%1': %2", filename, strerror(errno));
                }
                return StatResult::MissingPath;
            } break;
            case EACCES: {
                LogError("Cannot stat '%1': %2", filename, strerror(errno));
                return StatResult::AccessDenied;
            } break;
            case ENOTDIR: {
                LogError("Cannot stat '%1': Component is not a directory", filename);
                return StatResult::OtherError;
            } break;
            default: {
                LogError("Cannot stat '%1': %2", filename, strerror(errno));
                return StatResult::OtherError;
            } break;
        }
    }

    out_info->type = FileModeToType(sb.st_mode);
    out_info->size = (int64_t)sb.st_size;
#if defined(__linux__)
    out_info->mtime = (int64_t)sb.st_mtim.tv_sec * 1000 +
                      (int64_t)sb.st_mtim.tv_nsec / 1000000;
    out_info->ctime = (int64_t)sb.st_ctim.tv_sec * 1000 +
                      (int64_t)sb.st_ctim.tv_nsec / 1000000;
    out_info->atime = (int64_t)sb.st_atim.tv_sec * 1000 +
                      (int64_t)sb.st_atim.tv_nsec / 1000000;
    out_info->btime = out_info->mtime;
#elif defined(__APPLE__)
    out_info->mtime = (int64_t)sb.st_mtimespec.tv_sec * 1000 +
                      (int64_t)sb.st_mtimespec.tv_nsec / 1000000;
    out_info->ctime = (int64_t)sb.st_ctimespec.tv_sec * 1000 +
                      (int64_t)sb.st_ctimespec.tv_nsec / 1000000;
    out_info->atime = (int64_t)sb.st_atimespec.tv_sec * 1000 +
                      (int64_t)sb.st_atimespec.tv_nsec / 1000000;
    out_info->btime = (int64_t)sb.st_birthtimespec.tv_sec * 1000 +
                      (int64_t)sb.st_birthtimespec.tv_nsec / 1000000;
#elif defined(__OpenBSD__)
    out_info->mtime = (int64_t)sb.st_mtim.tv_sec * 1000 +
                      (int64_t)sb.st_mtim.tv_nsec / 1000000;
    out_info->ctime = (int64_t)sb.st_ctim.tv_sec * 1000 +
                      (int64_t)sb.st_ctim.tv_nsec / 1000000;
    out_info->atime = (int64_t)sb.st_atim.tv_sec * 1000 +
                      (int64_t)sb.st_atim.tv_nsec / 1000000;
    out_info->btime = (int64_t)sb.__st_birthtim.tv_sec * 1000 +
                      (int64_t)sb.__st_birthtim.tv_nsec / 1000000;
#elif defined(__FreeBSD__)
    out_info->mtime = (int64_t)sb.st_mtim.tv_sec * 1000 +
                      (int64_t)sb.st_mtim.tv_nsec / 1000000;
    out_info->ctime = (int64_t)sb.st_ctim.tv_sec * 1000 +
                      (int64_t)sb.st_ctim.tv_nsec / 1000000;
    out_info->atime = (int64_t)sb.st_atim.tv_sec * 1000 +
                      (int64_t)sb.st_atim.tv_nsec / 1000000;
    out_info->btime = (int64_t)sb.st_birthtim.tv_sec * 1000 +
                      (int64_t)sb.st_birthtim.tv_nsec / 1000000;
#else
    out_info->mtime = (int64_t)sb.st_mtim.tv_sec * 1000 +
                      (int64_t)sb.st_mtim.tv_nsec / 1000000;
    out_info->ctime = (int64_t)sb.st_ctim.tv_sec * 1000 +
                      (int64_t)sb.st_ctim.tv_nsec / 1000000;
    out_info->atime = (int64_t)sb.st_atim.tv_sec * 1000 +
                      (int64_t)sb.st_atim.tv_nsec / 1000000;
    out_info->btime = out_info->mtime;
#endif
    out_info->mode = (unsigned int)sb.st_mode;
    out_info->uid = (uint32_t)sb.st_uid;
    out_info->gid = (uint32_t)sb.st_gid;
#endif

    return StatResult::Success;
}

StatResult StatFile(int fd, const char *path, unsigned int flags, FileInfo *out_info)
{
    return StatAt(fd, false, path, flags, out_info);
}

static bool SyncDirectory(Span<const char> directory)
{
    char directory0[4096];
    if (directory.len >= K_SIZE(directory0)) {
        LogError("Failed to sync directory '%1': path too long", directory);
        return false;
    }
    MemCpy(directory0, directory.ptr, directory.len);
    directory0[directory.len] = 0;

    int dirfd = K_RESTART_EINTR(open(directory0, O_RDONLY | O_CLOEXEC), < 0);
    if (dirfd < 0) {
        LogError("Failed to sync directory '%1': %2", directory, strerror(errno));
        return false;
    }
    K_DEFER { CloseDescriptor(dirfd); };

    if (fsync(dirfd) < 0) {
        LogError("Failed to sync directory '%1': %2", directory, strerror(errno));
        return false;
    }

    return true;
}

static inline bool IsErrnoNotSupported(int err)
{
    bool unsupported = (err == ENOSYS || err == ENOTSUP || err == EOPNOTSUPP);
    return unsupported;
}

RenameResult RenameFile(const char *src_filename, const char *dest_filename, unsigned int silent, unsigned int flags)
{
    K_ASSERT(!(silent & ((int)RenameResult::Success | (int)RenameResult::OtherError)));

    if (flags & (int)RenameFlag::Overwrite) {
        if (rename(src_filename, dest_filename) < 0)
            goto error;
    } else {
#if defined(SYS_renameat2)
        {
            int dirfd = AT_FDCWD;
            int rflags = 1; // RENAME_NOREPLACE

            if (!syscall(SYS_renameat2, dirfd, src_filename, dirfd, dest_filename, rflags))
                goto sync;
            if (!IsErrnoNotSupported(errno) && errno != EINVAL)
                goto error;
        }
#elif defined(RENAME_EXCL)
        if (!renamex_np(src_filename, dest_filename, RENAME_EXCL))
            goto sync;
        if (!IsErrnoNotSupported(errno) && errno != EINVAL)
            goto error;
#endif

        // Not atomic, but not racy
        if (!link(src_filename, dest_filename)) {
            if (unlink(src_filename) < 0) {
                unlink(dest_filename);
                goto error;
            }
            goto sync;
        }
#if defined(__linux__)
        if (!IsErrnoNotSupported(errno) && errno != EINVAL && errno != EPERM)
            goto error;
#else
        if (!IsErrnoNotSupported(errno) && errno != EINVAL)
            goto error;
#endif

        // Fall back to racy way...
        if (!faccessat(AT_FDCWD, dest_filename, F_OK, AT_SYMLINK_NOFOLLOW)) {
            errno = EEXIST;
            goto error;
        }
        if (errno != ENOENT)
            goto error;
        if (rename(src_filename, dest_filename) < 0)
            goto error;
    }

sync:
    if (flags & (int)RenameFlag::Sync) {
        Span<const char> src_directory = GetPathDirectory(src_filename);
        Span<const char> dest_directory = GetPathDirectory(dest_filename);

        // Not much we can do if fsync fails (I think), so ignore errors.
        // Hope for the best: that's the spirit behind the POSIX filesystem API ;)
        SyncDirectory(src_directory);
        if (dest_directory != src_directory) {
            SyncDirectory(dest_directory);
        }
    }

    return RenameResult::Success;

error:
    if (errno == EEXIST) {
        if (!(silent & (int)RenameResult::AlreadyExists)) {
            LogError("Failed to rename '%1' to '%2': file already exists", src_filename, dest_filename);
        }
        return RenameResult::AlreadyExists;
    }

    LogError("Failed to rename '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
    return RenameResult::OtherError;
}

bool ResizeFile(int fd, const char *filename, int64_t len)
{
    if (ftruncate(fd, len) < 0) {
        if (errno == EINVAL) {
            // Only write() calls seem to return ENOSPC, ftruncate() seems to fail with EINVAL
            LogError("Failed to reserve file '%1': not enough space", filename);
        } else {
            LogError("Failed to reserve file '%1': %2", filename, strerror(errno));
        }
        return false;
    }

    return true;
}

bool SetFileMode(int fd, const char *filename, uint32_t mode)
{
    if (fd >= 0) {
        if (fchmod(fd, (mode_t)mode) < 0) {
            LogError("Failed to set permissions of '%1': %2", filename, strerror(errno));
            return false;
        }
    } else {
        if (fchmodat(AT_FDCWD, filename, (mode_t)mode, AT_SYMLINK_NOFOLLOW) < 0) {
            LogError("Failed to set permissions of '%1': %2", filename, strerror(errno));
            return false;
        }
    }

    return true;
}

bool SetFileOwner(int fd, const char *filename, uint32_t uid, uint32_t gid)
{
    if (fd >= 0) {
        if (fchown(fd, (uid_t)uid, (gid_t)gid) < 0) {
            LogError("Failed to change owner of '%1': %2", filename, strerror(errno));
            return false;
        }
    } else {
        if (lchown(filename, (uid_t)uid, (gid_t)gid) < 0) {
            LogError("Failed to change owner of '%1': %2", filename, strerror(errno));
            return false;
        }
    }

    return true;
}

bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t)
{
    struct timespec times[2] = {};

    times[0].tv_nsec = UTIME_OMIT;
    times[1].tv_sec = mtime / 1000;
    times[1].tv_nsec = (mtime % 1000) * 1000000;

    if (fd >= 0) {
        if (futimens(fd, times) < 0) {
            LogError("Failed to set modification time of '%1': %2", filename, strerror(errno));
            return false;
        }
    } else {
        if (utimensat(AT_FDCWD, filename, times, AT_SYMLINK_NOFOLLOW) < 0) {
            LogError("Failed to set modification time of '%1': %2", filename, strerror(errno));
            return false;
        }
    }

    return true;
}

#if !defined(__wasm__)

bool GetVolumeInfo(const char *dirname, VolumeInfo *out_volume)
{
    struct statvfs vfs;
    if (statvfs(dirname, &vfs) < 0) {
        LogError("Cannot get volume information for '%1': %2", dirname, strerror(errno));
        return false;
    }

    out_volume->total = (int64_t)vfs.f_blocks * vfs.f_frsize;
    out_volume->available = (int64_t)vfs.f_bavail * vfs.f_frsize;

    return true;
}

#endif

static EnumResult ReadDirectory(DIR *dirp, const char *dirname, const char *filter, Size max_files,
                                FunctionRef<bool(const char *, FileType)> func)
{
    // Avoid random failure in empty directories
    errno = 0;

    Size count = 0;
    dirent *dent;
    while ((dent = readdir(dirp))) {
        if ((dent->d_name[0] == '.' && !dent->d_name[1]) ||
                (dent->d_name[0] == '.' && dent->d_name[1] == '.' && !dent->d_name[2]))
            continue;

        if (!filter || !fnmatch(filter, dent->d_name, FNM_PERIOD)) {
            if (count++ >= max_files && max_files >= 0) [[unlikely]] {
                LogError("Partial enumation of directory '%1'", dirname);
                return EnumResult::PartialEnum;
            }

            FileType file_type;
#if defined(_DIRENT_HAVE_D_TYPE)
            if (dent->d_type != DT_UNKNOWN) {
                switch (dent->d_type) {
                    case DT_DIR: { file_type = FileType::Directory; } break;
                    case DT_REG: { file_type = FileType::File; } break;
                    case DT_LNK: { file_type = FileType::Link; } break;
                    case DT_BLK:
                    case DT_CHR: { file_type = FileType::Device; } break;
                    case DT_FIFO: { file_type = FileType::Pipe; } break;
#if !defined(__wasi__)
                    case DT_SOCK: { file_type = FileType::Socket; } break;
#endif

                    default: {
                        // This... should not happen. But who knows?
                        file_type = FileType::File;
                    } break;
                }
            } else
#endif
            {
                struct stat sb;
                if (fstatat(dirfd(dirp), dent->d_name, &sb, AT_SYMLINK_NOFOLLOW) < 0) {
                    LogError("Ignoring file '%1' in '%2' (stat failed)", dent->d_name, dirname);
                    continue;
                }

                file_type = FileModeToType(sb.st_mode);
            }

            if (!func(dent->d_name, file_type))
                return EnumResult::CallbackFail;
        }

        errno = 0;
    }

    if (errno) {
        LogError("Error while enumerating directory '%1': %2", dirname, strerror(errno));
        return EnumResult::OtherError;
    }

    return EnumResult::Success;
}

static EnumResult ReadDirectory(DIR *dirp, const char *dirname, const char *filter, Size max_files,
                                FunctionRef<bool(const char *, const FileInfo &)> func)
{
    // Avoid random failure in empty directories
    errno = 0;

    Size count = 0;
    dirent *dent;
    while ((dent = readdir(dirp))) {
        if ((dent->d_name[0] == '.' && !dent->d_name[1]) ||
                (dent->d_name[0] == '.' && dent->d_name[1] == '.' && !dent->d_name[2]))
            continue;

        if (!filter || !fnmatch(filter, dent->d_name, FNM_PERIOD)) {
            if (count++ >= max_files && max_files >= 0) [[unlikely]] {
                LogError("Partial enumation of directory '%1'", dirname);
                return EnumResult::PartialEnum;
            }

            FileInfo file_info;
            StatResult ret = StatAt(dirfd(dirp), true, dent->d_name, (int)StatFlag::SilentMissing, &file_info);

            if (ret == StatResult::Success && !func(dent->d_name, file_info))
                return EnumResult::CallbackFail;
        }

        errno = 0;
    }

    if (errno) {
        LogError("Error while enumerating directory '%1': %2", dirname, strerror(errno));
        return EnumResult::OtherError;
    }

    return EnumResult::Success;
}

EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
                              FunctionRef<bool(const char *, FileType)> func)
{
    DIR *dirp = K_RESTART_EINTR(opendir(dirname), == nullptr);
    if (!dirp) {
        LogError("Cannot enumerate directory '%1': %2", dirname, strerror(errno));

        switch (errno) {
            case ENOENT: return EnumResult::MissingPath;
            case EACCES: return EnumResult::AccessDenied;
            default: return EnumResult::OtherError;
        }
    }
    K_DEFER { closedir(dirp); };

    return ReadDirectory(dirp, dirname, filter, max_files, func);
}

EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
                              FunctionRef<bool(const char *, const FileInfo &)> func)
{
    DIR *dirp = K_RESTART_EINTR(opendir(dirname), == nullptr);
    if (!dirp) {
        LogError("Cannot enumerate directory '%1': %2", dirname, strerror(errno));

        switch (errno) {
            case ENOENT: return EnumResult::MissingPath;
            case EACCES: return EnumResult::AccessDenied;
            default: return EnumResult::OtherError;
        }
    }
    K_DEFER { closedir(dirp); };

    return ReadDirectory(dirp, dirname, filter, max_files, func);
}

#if !defined(__APPLE__)

EnumResult EnumerateDirectory(int fd, const char *dirname, const char *filter, Size max_files,
                              FunctionRef<bool(const char *, FileType)> func)
{
    DIR *dirp = fdopendir(fd);
    if (!dirp) {
        CloseDescriptor(fd);

        LogError("Cannot enumerate directory '%1': %2", dirname, strerror(errno));
        return EnumResult::OtherError;
    }
    K_DEFER { closedir(dirp); };

    return ReadDirectory(dirp, dirname, filter, max_files, func);
}

EnumResult EnumerateDirectory(int fd, const char *dirname, const char *filter, Size max_files,
                              FunctionRef<bool(const char *, const FileInfo &)> func)
{
    DIR *dirp = fdopendir(fd);
    if (!dirp) {
        CloseDescriptor(fd);

        LogError("Cannot enumerate directory '%1': %2", dirname, strerror(errno));
        return EnumResult::OtherError;
    }
    K_DEFER { closedir(dirp); };

    return ReadDirectory(dirp, dirname, filter, max_files, func);
}

#endif

#endif

bool EnumerateFiles(const char *dirname, const char *filter, Size max_depth, Size max_files,
                    Allocator *str_alloc, HeapArray<const char *> *out_files)
{
    K_DEFER_NC(out_guard, len = out_files->len) { out_files->RemoveFrom(len); };

    EnumResult ret = EnumerateDirectory(dirname, nullptr, max_files,
                                        [&](const char *basename, FileType file_type) {
        switch (file_type) {
            case FileType::Directory: {
                if (max_depth) {
                    const char *sub_directory = Fmt(str_alloc, "%1%/%2", dirname, basename).ptr;
                    return EnumerateFiles(sub_directory, filter, std::max((Size)-1, max_depth - 1),
                                          max_files, str_alloc, out_files);
                }
            } break;

            case FileType::File:
            case FileType::Link: {
                if (!filter || MatchPathName(basename, filter)) {
                    const char *filename = Fmt(str_alloc, "%1%/%2", dirname, basename).ptr;
                    out_files->Append(filename);
                }
            } break;

            case FileType::Device:
            case FileType::Pipe:
            case FileType::Socket: {} break;
        }

        return true;
    });
    if (ret != EnumResult::Success && ret != EnumResult::PartialEnum)
        return false;

    out_guard.Disable();
    return true;
}

bool IsDirectoryEmpty(const char *dirname)
{
    EnumResult ret = EnumerateDirectory(dirname, nullptr, -1, [](const char *, FileType) { return false; });

    bool empty = (ret == EnumResult::Success);
    return empty;
}

bool TestFile(const char *filename)
{
    FileInfo file_info;
    StatResult ret = StatFile(filename, (int)StatFlag::SilentMissing, &file_info);

    bool exists = (ret == StatResult::Success);
    return exists;
}

bool TestFile(const char *filename, FileType type)
{
    FileInfo file_info;
    if (StatFile(filename, (int)StatFlag::SilentMissing, &file_info) != StatResult::Success)
        return false;

    // Don't follow, but don't warn if we just wanted a file
    if (type != FileType::Link && file_info.type == FileType::Link) {
        file_info.type = FileType::File;
    }

    if (type != file_info.type) {
        switch (type) {
            case FileType::Directory: { LogError("Path '%1' is not a directory", filename); } break;
            case FileType::File: { LogError("Path '%1' is not a file", filename); } break;
            case FileType::Device: { LogError("Path '%1' is not a device", filename); } break;
            case FileType::Pipe: { LogError("Path '%1' is not a pipe", filename); } break;
            case FileType::Socket: { LogError("Path '%1' is not a socket", filename); } break;

            case FileType::Link: { K_UNREACHABLE(); } break;
        }

        return false;
    }

    return true;
}

bool IsDirectory(const char *filename)
{
    FileInfo file_info;
    if (StatFile(filename, (int)StatFlag::SilentMissing, &file_info) != StatResult::Success)
        return false;
    return file_info.type == FileType::Directory;
}

static Size MatchPathItem(const char *path, const char *spec)
{
    Size i = 0;

    while (spec[i] && spec[i] != '*') {
        switch (spec[i]) {
            case '?': {
                if (!path[i] || IsPathSeparator(path[i]))
                    return -1;
            } break;

#if defined(_WIN32)
            case '\\':
#endif
            case '/': {
                if (!IsPathSeparator(path[i]))
                    return -1;
            } break;

            default: {
                if (path[i] != spec[i])
                    return -1;
            } break;
        }

        i++;
    }

    return i;
}

static Size MatchPathItemI(const char *path, const char *spec)
{
    Size i = 0;

    while (spec[i] && spec[i] != '*') {
        switch (spec[i]) {
            case '?': {
                if (!path[i] || IsPathSeparator(path[i]))
                    return -1;
            } break;

#if defined(_WIN32)
            case '\\':
#endif
            case '/': {
                if (!IsPathSeparator(path[i]))
                    return -1;
            } break;

            default: {
                // XXX: Use proper Unicode/locale case-folding? Or is this enough?

                if (LowerAscii(path[i]) != LowerAscii(spec[i]))
                    return -1;
            } break;
        }

        i++;
    }

    return i;
}

bool MatchPathName(const char *path, const char *spec, bool case_sensitive)
{
    auto match = case_sensitive ? MatchPathItem : MatchPathItemI;

    // Match head
    {
        Size match_len = match(path, spec);

        if (match_len < 0) {
            return false;
        } else {
            // Fast path (no wildcard)
            if (!spec[match_len])
                return !path[match_len];

            path += match_len;
            spec += match_len;
        }
    }

    // Find tail
    const char *tail = strrchr(spec, '*') + 1;

    // Match remaining items
    while (spec[0] == '*') {
        bool superstar = (spec[1] == '*');
        while (spec[0] == '*') {
            spec++;
        }

        for (;;) {
            Size match_len = match(path, spec);

            // We need to be greedy for the last wildcard, or we may not reach the tail
            if (match_len < 0 || (spec == tail && path[match_len])) {
                if (!path[0])
                    return false;
                if (!superstar && IsPathSeparator(path[0]))
                    return false;
                path++;
            } else {
                path += match_len;
                spec += match_len;

                break;
            }
        }
    }

    return true;
}

bool MatchPathSpec(const char *path, const char *spec, bool case_sensitive)
{
    Span<const char> path2 = path;

    do {
        const char *it = SplitStrReverseAny(path2, K_PATH_SEPARATORS, &path2).ptr;

        if (MatchPathName(it, spec, case_sensitive))
            return true;
    } while (path2.len);

    return false;
}

bool FindExecutableInPath(Span<const char> paths, const char *name, Allocator *alloc, const char **out_path)
{
    K_ASSERT(alloc || !out_path);

    // Fast path
    if (strpbrk(name, K_PATH_SEPARATORS)) {
        if (!TestFile(name, FileType::File))
            return false;

        if (out_path) {
            *out_path = DuplicateString(name, alloc).ptr;
        }
        return true;
    }

    while (paths.len) {
        Span<const char> path = SplitStr(paths, K_PATH_DELIMITER, &paths);

        LocalArray<char, 4096> buf;
        buf.len = Fmt(buf.data, "%1%/%2", path, name).len;

#if defined(_WIN32)
        static const Span<const char> extensions[] = { ".com", ".exe", ".bat", ".cmd" };

        for (Span<const char> ext: extensions) {
            if (ext.len < buf.Available() - 1) [[likely]] {
                MemCpy(buf.end(), ext.ptr, ext.len + 1);

                if (TestFile(buf.data)) {
                    if (out_path) {
                        *out_path = DuplicateString(buf.data, alloc).ptr;
                    }
                    return true;
                }
            }
        }
#else
        if (buf.len < K_SIZE(buf.data) - 1 && TestFile(buf.data)) {
            if (out_path) {
                *out_path = DuplicateString(buf.data, alloc).ptr;
            }
            return true;
        }
#endif
    }

    return false;
}

bool FindExecutableInPath(const char *name, Allocator *alloc, const char **out_path)
{
    K_ASSERT(alloc || !out_path);

    // Fast path
    if (strpbrk(name, K_PATH_SEPARATORS)) {
        if (!TestFile(name, FileType::File))
            return false;

        if (out_path) {
            *out_path = DuplicateString(name, alloc).ptr;
        }
        return true;
    }

#if defined(_WIN32)
    LocalArray<char, 16384> env_buf;
    Span<const char> paths;
    if (win32_utf8) {
        paths = GetEnv("PATH");
    } else {
        wchar_t buf_w[K_SIZE(env_buf.data)];
        DWORD len = GetEnvironmentVariableW(L"PATH", buf_w, K_LEN(buf_w));

        if (!len && GetLastError() != ERROR_ENVVAR_NOT_FOUND) {
            LogError("Failed to get PATH environment variable: %1", GetWin32ErrorString());
            return false;
        } else if (len >= K_LEN(buf_w)) {
            LogError("Failed to get PATH environment variable: buffer to small");
            return false;
        }
        buf_w[len] = 0;

        env_buf.len = ConvertWin32WideToUtf8(buf_w, env_buf.data);
        if (env_buf.len < 0)
            return false;

        paths = env_buf;
    }
#else
    Span<const char> paths = GetEnv("PATH");
#endif

    return FindExecutableInPath(paths, name, alloc, out_path);
}

bool SetWorkingDirectory(const char *directory)
{
#if defined(_WIN32)
    if (!win32_utf8) {
        wchar_t directory_w[4096];
        if (ConvertUtf8ToWin32Wide(directory, directory_w) < 0)
            return false;

        if (!SetCurrentDirectoryW(directory_w)) {
            LogError("Failed to set current directory to '%1': %2", directory, GetWin32ErrorString());
            return false;
        }

        return true;
    }
#endif

    if (chdir(directory) < 0) {
        LogError("Failed to set current directory to '%1': %2", directory, strerror(errno));
        return false;
    }

    return true;
}

const char *GetWorkingDirectory()
{
    static thread_local char buf[4096];

#if defined(_WIN32)
    if (!win32_utf8) {
        wchar_t buf_w[K_SIZE(buf)];
        DWORD ret = GetCurrentDirectoryW(K_SIZE(buf_w), buf_w);
        K_ASSERT(ret && ret <= K_SIZE(buf_w));

        Size str_len = ConvertWin32WideToUtf8(buf_w, buf);
        K_ASSERT(str_len >= 0);

        return buf;
    }
#endif

    const char *ptr = getcwd(buf, K_SIZE(buf));
    K_ASSERT(ptr);

    return buf;
}

const char *GetApplicationExecutable()
{
#if defined(_WIN32)
    static char executable_path[4096];

    if (!executable_path[0]) {
        if (win32_utf8) {
            Size path_len = (Size)GetModuleFileNameA(nullptr, executable_path, K_SIZE(executable_path));
            K_ASSERT(path_len && path_len < K_SIZE(executable_path));
        } else {
            wchar_t path_w[K_SIZE(executable_path)];
            Size path_len = (Size)GetModuleFileNameW(nullptr, path_w, K_SIZE(path_w));
            K_ASSERT(path_len && path_len < K_LEN(path_w));

            Size str_len = ConvertWin32WideToUtf8(path_w, executable_path);
            K_ASSERT(str_len >= 0);
        }
    }

    return executable_path;
#elif defined(__APPLE__)
    static char executable_path[4096];

    if (!executable_path[0]) {
        uint32_t buffer_size = K_SIZE(executable_path);
        int ret = _NSGetExecutablePath(executable_path, &buffer_size);
        K_ASSERT(!ret);

        char *path_buf = realpath(executable_path, nullptr);
        K_ASSERT(path_buf);
        K_ASSERT(strlen(path_buf) < K_SIZE(executable_path));

        CopyString(path_buf, executable_path);
        free(path_buf);
    }

    return executable_path;
#elif defined(__linux__)
    static char executable_path[4096];

    if (!executable_path[0]) {
        ssize_t ret = readlink("/proc/self/exe", executable_path, K_SIZE(executable_path));
        K_ASSERT(ret > 0 && ret < K_SIZE(executable_path));
    }

    return executable_path;
#elif defined(__OpenBSD__)
    static char executable_path[4096];

    if (!executable_path[0]) {
        int name[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };

        size_t argc;
        {
            int ret = sysctl(name, K_LEN(name), nullptr, &argc, nullptr, 0);
            K_ASSERT(ret >= 0);
            K_ASSERT(argc >= 1);
        }

        HeapArray<char *> argv;
        {
            argv.AppendDefault(argc);
            int ret = sysctl(name, K_LEN(name), argv.ptr, &argc, nullptr, 0);
            K_ASSERT(ret >= 0);
        }

        if (PathIsAbsolute(argv[0])) {
            K_ASSERT(strlen(argv[0]) < K_SIZE(executable_path));

            CopyString(argv[0], executable_path);
        } else {
            const char *path;
            bool success = FindExecutableInPath(argv[0], GetDefaultAllocator(), &path);
            K_ASSERT(success);
            K_ASSERT(strlen(path) < K_SIZE(executable_path));

            CopyString(path, executable_path);
            ReleaseRaw(nullptr, (void *)path, -1);
        }
    }

    return executable_path;
#elif defined(__FreeBSD__)
    static char executable_path[4096];

    if (!executable_path[0]) {
        int name[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
        size_t len = sizeof(executable_path);

        int ret = sysctl(name, K_LEN(name), executable_path, &len, nullptr, 0);
        K_ASSERT(ret >= 0);
        K_ASSERT(len < K_SIZE(executable_path));
    }

    return executable_path;
#elif defined(__wasm__)
    return nullptr;
#else
    #error GetApplicationExecutable() not implemented for this platform
#endif
}

const char *GetApplicationDirectory()
{
    static char executable_dir[4096];

    if (!executable_dir[0]) {
        const char *executable_path = GetApplicationExecutable();
        Size dir_len = (Size)strlen(executable_path);
        while (dir_len && !IsPathSeparator(executable_path[--dir_len]));
        MemCpy(executable_dir, executable_path, dir_len);
        executable_dir[dir_len] = 0;
    }

    return executable_dir;
}

Span<const char> GetPathDirectory(Span<const char> filename)
{
    Span<const char> directory;
    SplitStrReverseAny(filename, K_PATH_SEPARATORS, &directory);

    return directory.len ? directory : ".";
}

// Names starting with a dot are not considered to be an extension (POSIX hidden files)
Span<const char> GetPathExtension(Span<const char> filename, CompressionType *out_compression_type)
{
    filename = SplitStrReverseAny(filename, K_PATH_SEPARATORS);

    Span<const char> extension = {};
    const auto consume_next_extension = [&]() {
        Span<const char> part = SplitStrReverse(filename, '.', &filename);

        if (part.ptr > filename.ptr) {
            extension = MakeSpan(part.ptr - 1, part.len + 1);
        } else {
            extension = MakeSpan(part.end(), 0);
        }
    };

    consume_next_extension();

    if (out_compression_type) {
        const char *const *it = std::find_if(std::begin(CompressionTypeExtensions), std::end(CompressionTypeExtensions),
                                             [&](const char *it) { return it && TestStr(it, extension); });

        if (it != std::end(CompressionTypeExtensions)) {
            *out_compression_type = (CompressionType)(it - CompressionTypeExtensions);
            consume_next_extension();
        } else {
            *out_compression_type = CompressionType::None;
        }
    }

    return extension;
}

Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory, unsigned int flags, Allocator *alloc)
{
    K_ASSERT(alloc);

    if (!path.len && !root_directory.len)
        return Fmt(alloc, "");

#if !defined(_WIN32)
    if (!(flags & (int)NormalizeFlag::NoExpansion)) {
        Span<const char> prefix = SplitStrAny(path, K_PATH_SEPARATORS);

        if (prefix == "~") {
            const char *home = GetEnv("HOME");

            if (home) {
                root_directory = home;
                path = TrimStrLeft(path.Take(1, path.len - 1), K_PATH_SEPARATORS);
            }
        }
    }
#endif

    HeapArray<char> buf(alloc);
    Size parts_count = 0;

    char separator = (flags & (int)NormalizeFlag::ForceSlash) ? '/' : *K_PATH_SEPARATORS;

    const auto append_normalized_path = [&](Span<const char> path) {
        if (!buf.len && PathIsAbsolute(path)) {
            Span<const char> prefix = SplitStrAny(path, K_PATH_SEPARATORS, &path);
            buf.Append(prefix);
            buf.Append(separator);
        }

        while (path.len) {
            Span<const char> part = SplitStrAny(path, K_PATH_SEPARATORS, &path);

            if (part == "..") {
                if (parts_count) {
                    while (--buf.len && !IsPathSeparator(buf.ptr[buf.len - 1]));
                    parts_count--;
                } else {
                    buf.Append("..");
                    buf.Append(separator);
                }
            } else if (part == ".") {
                // Skip
            } else if (part.len) {
                buf.Append(part);
                buf.Append(separator);
                parts_count++;
            }
        }
    };

    if (root_directory.len && !PathIsAbsolute(path)) {
        append_normalized_path(root_directory);
    }
    append_normalized_path(path);

    if (!buf.len) {
        buf.Append('.');

        if (flags & (int)NormalizeFlag::EndWithSeparator) {
            buf.Append(separator);
        }
    } else if (buf.len == 1 && IsPathSeparator(buf[0])) {
        // Root '/', keep as-is or almost
        buf[0] = separator;
    } else if (!(flags & (int)NormalizeFlag::EndWithSeparator)) {
        // Strip last separator
        buf.len--;
    }

#if defined(_WIN32)
    if (buf.len >= 2 && IsAsciiAlpha(buf[0]) && buf[1] == ':') {
        buf[0] = UpperAscii(buf[0]);
    }
#endif

    // NUL terminator
    buf.Trim(1);
    buf.ptr[buf.len] = 0;

    return buf.Leak();
}

bool PathIsAbsolute(const char *path)
{
#if defined(_WIN32)
    if (IsAsciiAlpha(path[0]) && path[1] == ':')
        return true;
#endif

    return IsPathSeparator(path[0]);
}
bool PathIsAbsolute(Span<const char> path)
{
#if defined(_WIN32)
    if (path.len >= 2 && IsAsciiAlpha(path[0]) && path[1] == ':')
        return true;
#endif

    return path.len && IsPathSeparator(path[0]);
}

bool PathContainsDotDot(const char *path)
{
    const char *ptr = path;

    while ((ptr = strstr(ptr, ".."))) {
        if ((ptr == path || IsPathSeparator(ptr[-1])) && (IsPathSeparator(ptr[2]) || !ptr[2]))
            return true;
        ptr += 2;
    }

    return false;
}

bool PathContainsDotDot(Span<const char> path)
{
    const char *ptr = path.ptr;
    const char *end = path.end();

    while ((ptr = (const char *)MemMem(ptr, end - ptr, "..", 2))) {
        if ((ptr == path.ptr || IsPathSeparator(ptr[-1])) && (ptr + 2 == end || IsPathSeparator(ptr[2])))
            return true;
        ptr += 2;
    }

    return false;
}

static bool CheckForDumbTerm()
{
    static bool dumb = ([]() {
        const char *term = GetEnv("TERM");

        if (term && TestStr(term, "dumb"))
            return true;
        if (GetEnv("NO_COLOR"))
            return true;

        return false;
    })();

    return dumb;
}

#if defined(_WIN32)

OpenResult OpenFile(const char *filename, unsigned int flags, unsigned int silent, int *out_fd)
{
    K_ASSERT(!(silent & ((int)OpenResult::Success | (int)OpenResult::OtherError)));

    DWORD access = 0;
    DWORD share = 0;
    DWORD creation = 0;
    DWORD attributes = 0;
    int oflags = -1;
    switch (flags & ((int)OpenFlag::Read |
                     (int)OpenFlag::Write |
                     (int)OpenFlag::Append)) {
        case (int)OpenFlag::Read: {
            access = GENERIC_READ;
            share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
            creation = OPEN_EXISTING;
            attributes = FILE_ATTRIBUTE_NORMAL;
            oflags = _O_RDONLY | _O_BINARY | _O_NOINHERIT;
        } break;
        case (int)OpenFlag::Write: {
            access = GENERIC_WRITE;
            share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
            creation = CREATE_ALWAYS;
            attributes = FILE_ATTRIBUTE_NORMAL;
            oflags = _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY | _O_NOINHERIT;
        } break;
        case (int)OpenFlag::Read | (int)OpenFlag::Write: {
            access = GENERIC_READ | GENERIC_WRITE;
            share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
            creation = CREATE_ALWAYS;
            attributes = FILE_ATTRIBUTE_NORMAL;
            oflags = _O_RDWR | _O_CREAT | _O_TRUNC | _O_BINARY | _O_NOINHERIT;
        } break;
        case (int)OpenFlag::Append: {
            access = GENERIC_WRITE;
            share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
            creation = OPEN_ALWAYS;
            attributes = FILE_ATTRIBUTE_NORMAL;
            oflags = _O_WRONLY | _O_CREAT | _O_APPEND | _O_BINARY | _O_NOINHERIT;
        } break;
    }
    K_ASSERT(oflags >= 0);

    if (flags & (int)OpenFlag::Keep) {
        if (creation == CREATE_ALWAYS) {
            creation = OPEN_ALWAYS;
        }
        oflags &= ~_O_TRUNC;
    }
    if (flags & (int)OpenFlag::Directory) {
        K_ASSERT(!(flags & (int)OpenFlag::Exclusive));
        K_ASSERT(!(flags & (int)OpenFlag::Append));

        creation = OPEN_EXISTING;
        attributes = FILE_FLAG_BACKUP_SEMANTICS;
        oflags &= ~(_O_CREAT | _O_TRUNC | _O_BINARY);
    }
    if (flags & (int)OpenFlag::Exists) {
        K_ASSERT(!(flags & (int)OpenFlag::Exclusive));

        creation = OPEN_EXISTING;
        oflags &= ~_O_CREAT;
    } else if (flags & (int)OpenFlag::Exclusive) {
        K_ASSERT(creation == CREATE_ALWAYS);

        creation = CREATE_NEW;
        oflags |= (int)_O_EXCL;
    }

    HANDLE h = nullptr;
    int fd = -1;
    K_DEFER_N(err_guard) {
        CloseDescriptor(fd);
        if (h) {
            CloseHandle(h);
        }
    };

    if (win32_utf8) {
        h = CreateFileA(filename, access, share, nullptr, creation, attributes, nullptr);
    } else {
        wchar_t filename_w[4096];
        if (ConvertUtf8ToWin32Wide(filename, filename_w) < 0)
            return OpenResult::OtherError;

        h = CreateFileW(filename_w, access, share, nullptr, creation, attributes, nullptr);
    }
    if (h == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();

        OpenResult ret;
        switch (err) {
            case ERROR_FILE_NOT_FOUND:
            case ERROR_PATH_NOT_FOUND: { ret = OpenResult::MissingPath; } break;
            case ERROR_FILE_EXISTS: { ret = OpenResult::FileExists; } break;
            case ERROR_ACCESS_DENIED: { ret = OpenResult::AccessDenied; } break;
            default: { ret = OpenResult::OtherError; } break;
        }

        if (!(silent & (int)ret)) {
            LogError("Cannot open '%1': %2", filename, GetWin32ErrorString(err));
        }
        return ret;
    }

    fd = _open_osfhandle((intptr_t)h, oflags);
    if (fd < 0) {
        LogError("Cannot open '%1': %2", filename, strerror(errno));
        return OpenResult::OtherError;
    }

    if ((flags & (int)OpenFlag::Append) && _lseeki64(fd, 0, SEEK_END) < 0) {
        LogError("Cannot move file pointer: %1", strerror(errno));
        return OpenResult::OtherError;
    }

    err_guard.Disable();
    *out_fd = fd;

    return OpenResult::Success;
}

void CloseDescriptor(int fd)
{
    if (fd < 0)
        return;

    _close(fd);
}

bool FlushFile(int fd, const char *filename)
{
    K_ASSERT(filename);

    HANDLE h = (HANDLE)_get_osfhandle(fd);

    if (!FlushFileBuffers(h)) {
        DWORD err = GetLastError();

        if (err != ERROR_INVALID_HANDLE) {
            LogError("Failed to sync '%1': %2", filename, GetWin32ErrorString(err));
            return false;
        }
    }

    return true;
}

bool SpliceFile(int src_fd, const char *src_filename, int64_t src_offset,
                int dest_fd, const char *dest_filename, int64_t dest_offset, int64_t size,
                FunctionRef<void(int64_t, int64_t)> progress)
{
    static NtCopyFileChunkFunc *NtCopyFileChunk =
        (NtCopyFileChunkFunc *)(void *)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCopyFileChunk");

    int64_t max = size;
    progress(0, max);

    // Try fast kernel-mode copy introduced in Windows 11
    if (NtCopyFileChunk) {
        HANDLE h1 = (HANDLE)_get_osfhandle(src_fd);
        HANDLE h2 = (HANDLE)_get_osfhandle(dest_fd);

        LARGE_INTEGER offset0 = {};
        LARGE_INTEGER offset1 = {};

        offset0.QuadPart = src_offset;
        offset1.QuadPart = dest_offset;

        while (size) {
            unsigned long count = (unsigned long)std::min(size, (int64_t)Mebibytes(64));

            IO_STATUS_BLOCK iob;
            LONG status = NtCopyFileChunk(h1, h2, nullptr, &iob, count, &offset0, &offset1, nullptr, nullptr, 0);

            if (status) {
                static RtlNtStatusToDosErrorFunc *RtlNtStatusToDosError =
                    (RtlNtStatusToDosErrorFunc *)(void *)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlNtStatusToDosError");

                unsigned long err = RtlNtStatusToDosError(status);
                LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, GetWin32ErrorString(err));

                return false;
            }
            if (!iob.Information) {
                LogError("Failed to copy '%1' to '%2': Truncated file", src_filename, dest_filename);
                return false;
            }

            offset0.QuadPart += iob.Information;
            offset1.QuadPart += iob.Information;
            size -= iob.Information;

            progress(max - size, max);
        }

        return true;
    }

    // User-mode fallback method
    {
        if (_lseeki64(src_fd, src_offset, SEEK_SET) < 0) {
            LogError("Failed to seek to start of '%1': %2", src_filename, strerror(errno));
            return false;
        }
        if (_lseeki64(dest_fd, dest_offset, SEEK_SET) < 0) {
            LogError("Failed to seek to start of '%1': %2", dest_filename, strerror(errno));
            return false;
        }

        while (size) {
            LocalArray<uint8_t, 655536> buf;
            unsigned long count = (unsigned long)std::min(size, (int64_t)K_SIZE(buf.data));

            buf.len = _read(src_fd, buf.data, count);

            if (buf.len < 0) {
                if (errno == EINTR)
                    continue;

                LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
                return false;
            }
            if (!buf.len) {
                LogError("Failed to copy '%1' to '%2': Truncated file", src_filename, dest_filename);
                return false;
            }

            Span<const uint8_t> remain = buf;

            do {
                int written = _write(dest_fd, remain.ptr, (unsigned int)remain.len);

                if (written < 0) {
                   LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
                    return false;
                }
                if (!written) {
                    LogError("Failed to copy '%1' to '%2': Truncated file", src_filename, dest_filename);
                    return false;
                }

                remain.ptr += written;
                remain.len -= written;
            } while (remain.len);

            size -= buf.len;

            progress(max - size, max);
        }

        return true;
    }

    K_UNREACHABLE();
}

bool FileIsVt100(int fd)
{
    static thread_local int cache_fd = -1;
    static thread_local bool cache_vt100;

    if (CheckForDumbTerm())
        return false;

    // Fast path, for repeated calls (such as Print in a loop)
    if (fd == cache_fd)
        return cache_vt100;

    if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
        HANDLE h = (HANDLE)_get_osfhandle(fd);

        DWORD console_mode;
        if (GetConsoleMode(h, &console_mode)) {
            static bool enable_emulation = [&]() {
                bool emulation = console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING;

                if (!emulation) {
                    // Enable VT100 escape sequences, introduced in Windows 10
                    DWORD new_mode = console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
                    emulation = SetConsoleMode(h, new_mode);

                    if (emulation) {
                        static HANDLE exit_handle = h;
                        static DWORD exit_mode = console_mode;

                        atexit([]() { SetConsoleMode(exit_handle, exit_mode); });
                    } else {
                        // Try ConEmu ANSI support for Windows < 10
                        const char *conemuansi_str = GetEnv("ConEmuANSI");
                        emulation = conemuansi_str && TestStr(conemuansi_str, "ON");
                    }
                }

                return emulation;
            }();

            cache_vt100 = enable_emulation;
        } else {
            cache_vt100 = false;
        }
    } else {
        cache_vt100 = false;
    }

    cache_fd = fd;
    return cache_vt100;
}

bool MakeDirectory(const char *directory, bool error_if_exists)
{
    if (win32_utf8) {
        if (!CreateDirectoryA(directory, nullptr))
            goto error;
    } else {
        wchar_t directory_w[4096];
        if (ConvertUtf8ToWin32Wide(directory, directory_w) < 0)
            return false;

        if (!CreateDirectoryW(directory_w, nullptr))
            goto error;
    }

    return true;

error:
    DWORD err = GetLastError();

    if (err != ERROR_ALREADY_EXISTS || error_if_exists) {
        LogError("Cannot create directory '%1': %2", directory, GetWin32ErrorString(err));
        return false;
    } else {
        return true;
    }
}

bool MakeDirectoryRec(Span<const char> directory)
{
    LocalArray<wchar_t, 4096> buf_w;
    buf_w.len = ConvertUtf8ToWin32Wide(directory, buf_w.data);
    if (buf_w.len < 0)
        return false;

    // Simple case: directory already exists or only last level was missing
    if (!CreateDirectoryW(buf_w.data, nullptr)) {
        DWORD err = GetLastError();

        if (err == ERROR_ALREADY_EXISTS) {
            return true;
        } else if (err != ERROR_PATH_NOT_FOUND) {
            LogError("Cannot create directory '%1': %2", directory, strerror(errno));
            return false;
        }
    }

    for (Size offset = 1, parts = 0; offset <= buf_w.len; offset++) {
        if (!buf_w.data[offset] || buf_w[offset] == L'\\' || buf_w[offset] == L'/') {
            buf_w.data[offset] = 0;
            parts++;

            if (!CreateDirectoryW(buf_w.data, nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) {
                Size offset8 = 0;
                while (offset8 < directory.len) {
                    parts -= IsPathSeparator(directory[offset8]);
                    if (!parts)
                        break;
                    offset8++;
                }

                LogError("Cannot create directory '%1': %2",
                         directory.Take(0, offset8), GetWin32ErrorString());
                return false;
            }

            buf_w.data[offset] = L'\\';
        }
    }

    return true;
}

bool UnlinkDirectory(const char *directory, bool error_if_missing)
{
    if (win32_utf8) {
        if (!RemoveDirectoryA(directory))
            goto error;
    } else {
        wchar_t directory_w[4096];
        if (ConvertUtf8ToWin32Wide(directory, directory_w) < 0)
            return false;

        if (!RemoveDirectoryW(directory_w))
            goto error;
    }

    return true;

error:
    DWORD err = GetLastError();

    if (err != ERROR_FILE_NOT_FOUND || error_if_missing) {
        LogError("Failed to remove directory '%1': %2", directory, GetWin32ErrorString(err));
        return false;
    } else {
        return true;
    }
}

bool UnlinkFile(const char *filename, bool error_if_missing)
{
    if (win32_utf8) {
        if (!DeleteFileA(filename))
            goto error;
    } else {
        wchar_t filename_w[4096];
        if (ConvertUtf8ToWin32Wide(filename, filename_w) < 0)
            return false;

        if (!DeleteFileW(filename_w))
            goto error;
    }

    return true;

error:
    DWORD err = GetLastError();

    if (err != ERROR_FILE_NOT_FOUND || error_if_missing) {
        LogError("Failed to remove file '%1': %2", filename, GetWin32ErrorString());
        return false;
    } else {
        return true;
    }
}

#else

OpenResult OpenFile(const char *filename, unsigned int flags, unsigned int silent, int *out_fd)
{
    K_ASSERT(!(silent & ((int)OpenResult::Success | (int)OpenResult::OtherError)));

    int oflags = -1;
    switch (flags & ((int)OpenFlag::Read |
                     (int)OpenFlag::Write |
                     (int)OpenFlag::Append)) {
        case (int)OpenFlag::Read: { oflags = O_RDONLY | O_CLOEXEC; } break;
        case (int)OpenFlag::Write: { oflags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; } break;
        case (int)OpenFlag::Read | (int)OpenFlag::Write: { oflags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC; } break;
        case (int)OpenFlag::Append: { oflags = O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC; } break;
    }
    K_ASSERT(oflags >= 0);

    if (flags & (int)OpenFlag::Keep) {
        oflags &= ~O_TRUNC;
    }
    if (flags & (int)OpenFlag::Directory) {
        K_ASSERT(!(flags & (int)OpenFlag::Exclusive));
        K_ASSERT(!(flags & (int)OpenFlag::Append));

        oflags &= ~(O_CREAT | O_WRONLY | O_RDWR | O_TRUNC);
    }
    if (flags & (int)OpenFlag::Exists) {
        K_ASSERT(!(flags & (int)OpenFlag::Exclusive));
        oflags &= ~O_CREAT;
    } else if (flags & (int)OpenFlag::Exclusive) {
        K_ASSERT(oflags & O_CREAT);
        oflags |= O_EXCL;
    }
    if (flags & (int)OpenFlag::NoFollow) {
        oflags |= O_NOFOLLOW;
    }

    int fd = K_RESTART_EINTR(open(filename, oflags, 0644), < 0);
    if (fd < 0) {
        OpenResult ret;
        switch (errno) {
            case ENOENT: { ret = OpenResult::MissingPath; } break;
            case EEXIST: { ret = OpenResult::FileExists; } break;
            case EACCES: { ret = OpenResult::AccessDenied; } break;
            default: { ret = OpenResult::OtherError; } break;
        }

        if (!(silent & (int)ret)) {
            LogError("Cannot open '%1': %2", filename, strerror(errno));
        }
        return ret;
    }

    *out_fd = fd;
    return OpenResult::Success;
}

void CloseDescriptor(int fd)
{
    // We could call close() anyway, it will fail with EINVAL,
    // but that leads to debugger or valgrind noise.
    if (fd < 0)
        return;

    close(fd);
}

bool FlushFile(int fd, const char *filename)
{
    K_ASSERT(filename);

#if defined(__APPLE__)
    if (fsync(fd) < 0 && errno != EINVAL && errno != ENOTSUP) {
#else
    if (fsync(fd) < 0 && errno != EINVAL) {
#endif
        LogError("Failed to sync '%1': %2", filename, strerror(errno));
        return false;
    }

    return true;
}

bool SpliceFile(int src_fd, const char *src_filename, int64_t src_offset,
                int dest_fd, const char *dest_filename, int64_t dest_offset, int64_t size,
                FunctionRef<void(int64_t, int64_t)> progress)
{
    static_assert(sizeof(off_t) == 8, "This code base requires large file offsets");

    int64_t max = size;
    progress(0, max);

    // Try copy_file_range() if available
#if defined(SYS_copy_file_range)
    {
        bool first = true;

        while (size) {
            // glibc < 2.27 doesn't define copy_file_range

            size_t count = (size_t)std::min(size, (int64_t)Mebibytes(64));
            ssize_t ret = syscall(SYS_copy_file_range, src_fd, (off_t *)&src_offset, dest_fd, (off_t *)&dest_offset, count, 0u);

            if (ret < 0) {
                if (first && errno == EXDEV)
                    goto xdev;
                if (errno == EINTR)
                    continue;

                LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
                return false;
            }

            first = false;
            size -= ret;

            progress(max - size, max);
        }

        return true;
    }

xdev:
#elif defined(__FreeBSD__)
    {
        bool first = true;

        while (size) {
            size_t count = (size_t)std::min(size, (int64_t)Mebibytes(64));
            ssize_t ret = copy_file_range(src_fd, (off_t *)&src_offset, dest_fd, (off_t *)&dest_offset, count, 0u);

            if (ret < 0) {
                if (first && errno == EXDEV)
                    goto xdev;
                if (errno == EINTR)
                    continue;

                LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
                return false;
            }

            first = false;
            size -= ret;

            progress(max - size, max);
        }

        return true;
    }

xdev:
#endif

#if defined(__linux__)
    // Try sendfile() on Linux
    {
        bool first = true;

        if (lseek(dest_fd, dest_offset, SEEK_SET) < 0) {
            LogError("Failed to seek to start of '%1': %2", dest_filename, strerror(errno));
            return false;
        }

        while (size) {
            size_t count = (size_t)std::min(size, (int64_t)Mebibytes(64));
            ssize_t ret = sendfile(dest_fd, src_fd, (off_t *)&src_offset, count);

            if (ret < 0) {
                if (first && errno == EINVAL)
                    goto unsupported;
                if (errno == EINTR)
                    continue;

                LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
                return false;
            }

            first = false;
            size -= ret;

            progress(max - size, max);
        }

        return true;
    }

unsupported:
#endif

    // User-mode fallback method
    {
        if (lseek(src_fd, src_offset, SEEK_SET) < 0) {
            LogError("Failed to seek to start of '%1': %2", src_filename, strerror(errno));
            return false;
        }
        if (lseek(dest_fd, dest_offset, SEEK_SET) < 0) {
            LogError("Failed to seek to start of '%1': %2", dest_filename, strerror(errno));
            return false;
        }

        while (size) {
            LocalArray<uint8_t, 655536> buf;
            Size count = (Size)std::min(size, (int64_t)K_SIZE(buf.data));

            buf.len = read(src_fd, buf.data, (size_t)count);

            if (buf.len < 0) {
                if (errno == EINTR)
                    continue;

                LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
                return false;
            }
            if (!buf.len) {
                LogError("Failed to copy '%1' to '%2': Truncated file");
                return false;
            }

            Span<const uint8_t> remain = buf;

            do {
                ssize_t written = write(dest_fd, remain.ptr, (size_t)remain.len);

                if (written < 0) {
                   LogError("Failed to copy '%1' to '%2': %3", src_filename, dest_filename, strerror(errno));
                    return false;
                }
                if (!written) {
                    LogError("Failed to copy '%1' to '%2': Truncated file", src_filename, dest_filename);
                    return false;
                }

                remain.ptr += written;
                remain.len -= written;
            } while (remain.len);

            size -= buf.len;

            progress(max - size, max);
        }

        return true;
    }

    K_UNREACHABLE();
}

bool FileIsVt100(int fd)
{
    static thread_local int cache_fd = -1;
    static thread_local bool cache_vt100;

    if (CheckForDumbTerm())
        return false;

#if defined(__EMSCRIPTEN__)
    static bool win32 = ([]() {
        int win32 = EM_ASM_INT({
            try {
                const os = require('os');

                var win32 = (os.platform() === 'win32');
                return win32 ? 1 : 0;
            } catch (err) {
                return 0;
            }
        });

        return (bool)win32;
    })();

    if (win32)
        return false;
#endif

    // Fast path, for repeated calls (such as Print in a loop)
    if (fd == cache_fd)
        return cache_vt100;

    cache_fd = fd;
    cache_vt100 = isatty(fd);

    return cache_vt100;
}

bool MakeDirectory(const char *directory, bool error_if_exists)
{
    if (mkdir(directory, 0755) < 0 && (errno != EEXIST || error_if_exists)) {
        LogError("Cannot create directory '%1': %2", directory, strerror(errno));
        return false;
    }

    return true;
}

bool MakeDirectoryRec(Span<const char> directory)
{
    char buf[4096];
    if (directory.len >= K_SIZE(buf)) [[unlikely]] {
        LogError("Path '%1' is too large", directory);
        return false;
    }
    MemCpy(buf, directory.ptr, directory.len);
    buf[directory.len] = 0;

    // Simple case: directory already exists or only last level was missing
    if (mkdir(buf, 0755) < 0) {
        if (errno == EEXIST) {
            return true;
        } else if (errno != ENOENT) {
            LogError("Cannot create directory '%1': %2", buf, strerror(errno));
            return false;
        }
    }

    for (Size offset = 1; offset <= directory.len; offset++) {
        if (!buf[offset] || IsPathSeparator(buf[offset])) {
            buf[offset] = 0;

            if (mkdir(buf, 0755) < 0 && errno != EEXIST) {
                LogError("Cannot create directory '%1': %2", buf, strerror(errno));
                return false;
            }

            buf[offset] = *K_PATH_SEPARATORS;
        }
    }

    return true;
}

bool UnlinkDirectory(const char *directory, bool error_if_missing)
{
    if (rmdir(directory) < 0 && (errno != ENOENT || error_if_missing)) {
        LogError("Failed to remove directory '%1': %2", directory, strerror(errno));
        return false;
    }

    return true;
}

bool UnlinkFile(const char *filename, bool error_if_missing)
{
    if (unlink(filename) < 0 && (errno != ENOENT || error_if_missing)) {
        LogError("Failed to remove file '%1': %2", filename, strerror(errno));
        return false;
    }

    return true;
}

#endif

bool EnsureDirectoryExists(const char *filename)
{
    Span<const char> directory = GetPathDirectory(filename);
    return MakeDirectoryRec(directory);
}

#if !defined(__wasi__)

#if defined(_WIN32)

static const DWORD main_thread = GetCurrentThreadId();
static HANDLE console_ctrl_event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
static bool ignore_ctrl_event = false;

static BOOL CALLBACK ConsoleCtrlHandler(DWORD)
{
    SetEvent(console_ctrl_event);
    return (BOOL)ignore_ctrl_event;
}

static bool InitConsoleCtrlHandler()
{
    static std::once_flag flag;

    static bool success;
    std::call_once(flag, []() { success = SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); });

    if (!success) {
        LogError("SetConsoleCtrlHandler() failed: %1", GetWin32ErrorString());
    }
    return success;
}

bool CreateOverlappedPipe(bool overlap0, bool overlap1, PipeMode mode, HANDLE out_handles[2])
{
    static LONG pipe_idx;

    HANDLE handles[2] = {};
    K_DEFER_N(handle_guard) {
        CloseHandleSafe(&handles[0]);
        CloseHandleSafe(&handles[1]);
    };

    char pipe_name[128];
    do {
        Fmt(pipe_name, "\\\\.\\pipe\\kcc.%1.%2",
            GetCurrentProcessId(), InterlockedIncrement(&pipe_idx));

        DWORD open_mode = PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | (overlap0 ? FILE_FLAG_OVERLAPPED : 0);
        DWORD pipe_mode = PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
        switch (mode) {
            case PipeMode::Byte: { pipe_mode |= PIPE_TYPE_BYTE; } break;
            case PipeMode::Message: { pipe_mode |= PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE; } break;
        }

        handles[0] = CreateNamedPipeA(pipe_name, open_mode, pipe_mode, 1, 8192, 8192, 0, nullptr);
        if (!handles[0] && GetLastError() != ERROR_ACCESS_DENIED) {
            LogError("Failed to create pipe: %1", GetWin32ErrorString());
            return false;
        }
    } while (!handles[0]);

    handles[1] = CreateFileA(pipe_name, GENERIC_WRITE, 0, nullptr, OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL | (overlap1 ? FILE_FLAG_OVERLAPPED : 0), nullptr);
    if (handles[1] == INVALID_HANDLE_VALUE) {
        LogError("Failed to create pipe: %1", GetWin32ErrorString());
        return false;
    }

    if (mode == PipeMode::Message) {
        DWORD value = PIPE_READMODE_MESSAGE;
        if (!SetNamedPipeHandleState(handles[1], &value, nullptr, nullptr)) {
            LogError("Failed to switch pipe to message mode: %1", GetWin32ErrorString());
            return false;
        }
    }

    handle_guard.Disable();
    out_handles[0] = handles[0];
    out_handles[1] = handles[1];
    return true;
}

void CloseHandleSafe(HANDLE *handle_ptr)
{
    HANDLE h = *handle_ptr;

    if (h && h != INVALID_HANDLE_VALUE) {
        CancelIo(h);
        CloseHandle(h);
    }

    *handle_ptr = nullptr;
}

struct PendingIO {
    OVERLAPPED ov = {}; // Keep first

    bool pending = false;
    DWORD err = 0;
    Size len = -1;

    static void CALLBACK CompletionHandler(DWORD err, DWORD len, OVERLAPPED *ov)
    {
        PendingIO *self = (PendingIO *)ov;

        self->pending = false;
        self->err = err;
        self->len = err ? -1 : len;
    }
};

bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
                        FunctionRef<Span<const uint8_t>()> in_func,
                        FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code)
{
    STARTUPINFOW si = {};

    BlockAllocator temp_alloc;

    // Convert command line
    Span<wchar_t> cmd_line_w = AllocateSpan<wchar_t>(&temp_alloc, 2 * strlen(cmd_line) + 1);
    if (ConvertUtf8ToWin32Wide(cmd_line, cmd_line_w) < 0)
        return false;

    // Convert work directory
    Span<wchar_t> work_dir_w;
    if (info.work_dir) {
        work_dir_w = AllocateSpan<wchar_t>(&temp_alloc, 2 * strlen(info.work_dir) + 1);
        if (ConvertUtf8ToWin32Wide(info.work_dir, work_dir_w) < 0)
            return false;
    } else {
        work_dir_w = {};
    }

    // Detect CTRL+C and CTRL+BREAK events
    if (!InitConsoleCtrlHandler())
        return false;

    // Neither GenerateConsoleCtrlEvent() or TerminateProcess() manage to fully kill Clang on
    // Windows (in highly-parallel builds) after CTRL-C, with occasionnal suspended child
    // processes remaining alive. Furthermore, processes killed by GenerateConsoleCtrlEvent()
    // can trigger "MessageBox" errors, unless SetErrorMode() is used.
    //
    // TerminateJobObject() is a bit brutal, but it takes care of these issues.
    HANDLE job_handle = CreateJobObject(nullptr, nullptr);
    if (!job_handle) {
        LogError("Failed to create job object: %1", GetWin32ErrorString());
        return false;
    }
    K_DEFER { CloseHandleSafe(&job_handle); };

    // If I die, everyone dies!
    {
        JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {};
        limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

        if (!SetInformationJobObject(job_handle, JobObjectExtendedLimitInformation, &limits, K_SIZE(limits))) {
            LogError("SetInformationJobObject() failed: %1", GetWin32ErrorString());
            return false;
        }
    }

    // Create read pipes
    HANDLE in_pipe[2] = {};
    K_DEFER {
        CloseHandleSafe(&in_pipe[0]);
        CloseHandleSafe(&in_pipe[1]);
    };
    if (in_func.IsValid() && !CreateOverlappedPipe(false, true, PipeMode::Byte, in_pipe))
        return false;

    // Create write pipes
    HANDLE out_pipe[2] = {};
    K_DEFER {
        CloseHandleSafe(&out_pipe[0]);
        CloseHandleSafe(&out_pipe[1]);
    };
    if (out_func.IsValid() && !CreateOverlappedPipe(true, false, PipeMode::Byte, out_pipe))
        return false;

    // Prepare environment (if needed)
    HeapArray<wchar_t> new_env_w;
    if (info.reset_env || info.env_variables.len) {
        if (!info.reset_env) {
            Span<wchar_t> current_env = MakeSpan(GetEnvironmentStringsW(), 0);

            do {
                Size len = (Size)wcslen(current_env.end());
                current_env.len += len + 1;
            } while (current_env.ptr[current_env.len]);

            new_env_w.Append(current_env);
        }

        for (const ExecuteInfo::KeyValue &kv: info.env_variables) {
            Span<const char> key = kv.key;
            Span<const char> value = kv.value;

            Size len = 2 * (key.len + value.len + 1) + 1;
            new_env_w.Grow(len);

            len = ConvertUtf8ToWin32Wide(key, new_env_w.TakeAvailable());
            if (len < 0) [[unlikely]]
                return false;
            new_env_w.len += len;

            new_env_w.Append(L'=');

            len = ConvertUtf8ToWin32Wide(value, new_env_w.TakeAvailable());
            if (len < 0) [[unlikely]]
                return false;
            new_env_w.len += len;

            new_env_w.Append(0);
        }

        new_env_w.Append(0);
    }

    // Start process
    HANDLE process_handle;
    {
        K_DEFER {
            CloseHandleSafe(&si.hStdInput);
            CloseHandleSafe(&si.hStdOutput);
            CloseHandleSafe(&si.hStdError);
        };

        if (in_func.IsValid() || out_func.IsValid()) {
            if (!DuplicateHandle(GetCurrentProcess(), in_func.IsValid() ? in_pipe[0] : GetStdHandle(STD_INPUT_HANDLE),
                                 GetCurrentProcess(), &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
                LogError("Failed to duplicate handle: %1", GetWin32ErrorString());
                return false;
            }
            if (!DuplicateHandle(GetCurrentProcess(), out_func.IsValid() ? out_pipe[1] : GetStdHandle(STD_OUTPUT_HANDLE),
                                 GetCurrentProcess(), &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS) ||
                !DuplicateHandle(GetCurrentProcess(), out_func.IsValid() ? out_pipe[1] : GetStdHandle(STD_ERROR_HANDLE),
                                 GetCurrentProcess(), &si.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
                LogError("Failed to duplicate handle: %1", GetWin32ErrorString());
                return false;
            }
            si.dwFlags |= STARTF_USESTDHANDLES;
        }

        int flags = CREATE_NEW_PROCESS_GROUP | CREATE_UNICODE_ENVIRONMENT;

        PROCESS_INFORMATION pi = {};
        if (!CreateProcessW(nullptr, cmd_line_w.ptr, nullptr, nullptr, TRUE, flags,
                            new_env_w.ptr, work_dir_w.ptr, &si, &pi)) {
            LogError("Failed to start process: %1", GetWin32ErrorString());
            return false;
        }
        if (!AssignProcessToJobObject(job_handle, pi.hProcess)) {
            CloseHandleSafe(&job_handle);
        }

        process_handle = pi.hProcess;
        CloseHandle(pi.hThread);

        CloseHandleSafe(&in_pipe[0]);
        CloseHandleSafe(&out_pipe[1]);
    }
    K_DEFER { CloseHandleSafe(&process_handle); };

    // Read and write standard process streams
    {
        bool running = true;

        PendingIO proc_in;
        Span<const uint8_t> write_buf = {};
        PendingIO proc_out;
        uint8_t read_buf[4096];

        while (running) {
            // Try to write
            if (in_func.IsValid() && !proc_in.pending) {
                if (!proc_in.err) {
                    if (proc_in.len >= 0) {
                        write_buf.ptr += proc_in.len;
                        write_buf.len -= proc_in.len;
                    }

                    if (!write_buf.len) {
                        write_buf = in_func();
                        K_ASSERT(write_buf.len >= 0);
                    }

                    if (write_buf.len) {
                        K_ASSERT(write_buf.len < UINT_MAX);

                        if (!WriteFileEx(in_pipe[1], write_buf.ptr, (DWORD)write_buf.len,
                                         &proc_in.ov, PendingIO::CompletionHandler)) {
                            proc_in.err = GetLastError();
                        }
                    } else {
                        CloseHandleSafe(&in_pipe[1]);
                    }
                }

                if (proc_in.err && proc_in.err != ERROR_BROKEN_PIPE && proc_in.err != ERROR_NO_DATA) {
                    LogError("Failed to write to process: %1", GetWin32ErrorString(proc_in.err));
                }

                proc_in.pending = true;
            }

            // Try to read
            if (out_func.IsValid() && !proc_out.pending) {
                if (!proc_out.err) {
                    if (proc_out.len >= 0) {
                        out_func(MakeSpan(read_buf, proc_out.len));
                        proc_out.len = -1;
                    }

                    if (proc_out.len && !ReadFileEx(out_pipe[0], read_buf, K_SIZE(read_buf), &proc_out.ov, PendingIO::CompletionHandler)) {
                        proc_out.err = GetLastError();
                    }
                }

                if (proc_out.err) {
                    if (proc_out.err != ERROR_BROKEN_PIPE && proc_out.err != ERROR_NO_DATA) {
                        LogError("Failed to read process output: %1", GetWin32ErrorString(proc_out.err));
                    }
                    break;
                }

                proc_out.pending = true;
            }

            running = (WaitForSingleObjectEx(console_ctrl_event, INFINITE, TRUE) != WAIT_OBJECT_0);
        }
    }

    // Terminate any remaining I/O
    CloseHandleSafe(&in_pipe[1]);
    CloseHandleSafe(&out_pipe[0]);

    // Wait for process exit
    {
        HANDLE events[2] = {
            process_handle,
            console_ctrl_event
        };

        if (WaitForMultipleObjects(K_LEN(events), events, FALSE, INFINITE) == WAIT_FAILED) {
            LogError("WaitForMultipleObjects() failed: %1", GetWin32ErrorString());
            return false;
        }
    }

    // Get exit code
    DWORD exit_code;
    if (WaitForSingleObject(console_ctrl_event, 0) == WAIT_OBJECT_0) {
        TerminateJobObject(job_handle, STATUS_CONTROL_C_EXIT);
        exit_code = STATUS_CONTROL_C_EXIT;
    } else if (!GetExitCodeProcess(process_handle, &exit_code)) {
        LogError("GetExitCodeProcess() failed: %1", GetWin32ErrorString());
        return false;
    }

    // Mimic POSIX SIGINT
    if (exit_code == STATUS_CONTROL_C_EXIT) {
        exit_code = 130;
    }

    *out_code = (int)exit_code;
    return true;
}

#else

static const pthread_t main_thread = pthread_self();

static std::atomic_bool flag_signal { false };
static std::atomic_int explicit_signal { 0 };
static std::atomic_int interrupt_pfd[2] { -1, -1 };

void SetSignalHandler(int signal, void (*func)(int), struct sigaction *prev)
{
    struct sigaction action = {};

    action.sa_handler = func;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;

    sigaction(signal, &action, prev);
}

static void DefaultSignalHandler(int signal)
{
    if (pthread_self() != main_thread) {
        pthread_kill(main_thread, signal);
        return;
    }

    pid_t pid = getpid();
    K_ASSERT(pid > 1);

    if (int fd = interrupt_pfd[1].load(); fd >= 0) {
        char dummy = 0;
        K_IGNORE write(fd, &dummy, 1);
    }

    if (flag_signal) {
        explicit_signal = signal;
    } else {
        int code = (signal == SIGINT) ? 130 : 1;
        exit(code);
    }
}

bool CreatePipe(bool block, int out_pfd[2])
{
#if defined(__APPLE__)
    if (pipe(out_pfd) < 0) {
        LogError("Failed to create pipe: %1", strerror(errno));
        return false;
    }

    if (fcntl(out_pfd[0], F_SETFD, FD_CLOEXEC) < 0 || fcntl(out_pfd[1], F_SETFD, FD_CLOEXEC) < 0) {
        LogError("Failed to set FD_CLOEXEC on pipe: %1", strerror(errno));
        return false;
    }
    if (!block) {
        if (fcntl(out_pfd[0], F_SETFL, O_NONBLOCK) < 0 || fcntl(out_pfd[1], F_SETFL, O_NONBLOCK) < 0) {
            LogError("Failed to set O_NONBLOCK on pipe: %1", strerror(errno));
            return false;
        }
    }

    return true;
#else
    int flags = O_CLOEXEC | (block ? 0 : O_NONBLOCK);

    if (pipe2(out_pfd, flags) < 0)  {
        LogError("Failed to create pipe: %1", strerror(errno));
        return false;
    }

    return true;
#endif
}

void CloseDescriptorSafe(int *fd_ptr)
{
    if (*fd_ptr >= 0) {
        close(*fd_ptr);
    }

    *fd_ptr = -1;
}

static void InitInterruptPipe()
{
    static bool success = ([]() {
        static int pfd[2];

        if (!CreatePipe(false, pfd))
            return false;

        atexit([]() {
            CloseDescriptor(pfd[0]);
            CloseDescriptor(pfd[1]);
        });

        interrupt_pfd[0] = pfd[0];
        interrupt_pfd[1] = pfd[1];

        return true;
    })();

    K_CRITICAL(success, "Failed to create interrupt pipe");
}

bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
                        FunctionRef<Span<const uint8_t>()> in_func,
                        FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code)
{
    BlockAllocator temp_alloc;

    // Create read pipes
    int in_pfd[2] = {-1, -1};
    K_DEFER {
        CloseDescriptorSafe(&in_pfd[0]);
        CloseDescriptorSafe(&in_pfd[1]);
    };
    if (in_func.IsValid()) {
        if (!CreatePipe(false, in_pfd))
            return false;
    }

    // Create write pipes
    int out_pfd[2] = {-1, -1};
    K_DEFER {
        CloseDescriptorSafe(&out_pfd[0]);
        CloseDescriptorSafe(&out_pfd[1]);
    };
    if (out_func.IsValid()) {
        if (!CreatePipe(false, out_pfd))
            return false;
    }

    InitInterruptPipe();

    // Prepare new environment (if needed)
    HeapArray<char *> new_env;
    if (info.reset_env || info.env_variables.len) {
        if (!info.reset_env) {
            char **ptr = environ;

            while (*ptr) {
                new_env.Append(*ptr);
                ptr++;
            }
        }

        for (const ExecuteInfo::KeyValue &kv: info.env_variables) {
            const char *var = Fmt(&temp_alloc, "%1=%2", kv.key, kv.value).ptr;
            new_env.Append((char *)var);
        }

        new_env.Append(nullptr);
    }

    // Start process
    pid_t pid;
    {
        posix_spawn_file_actions_t file_actions;
        if ((errno = posix_spawn_file_actions_init(&file_actions))) {
            LogError("Failed to set up standard process descriptors: %1", strerror(errno));
            return false;
        }
        K_DEFER { posix_spawn_file_actions_destroy(&file_actions); };

        if (in_func.IsValid() && (errno = posix_spawn_file_actions_adddup2(&file_actions, in_pfd[0], STDIN_FILENO))) {
            LogError("Failed to set up standard process descriptors: %1", strerror(errno));
            return false;
        }
        if (out_func.IsValid() && ((errno = posix_spawn_file_actions_adddup2(&file_actions, out_pfd[1], STDOUT_FILENO)) ||
                                   (errno = posix_spawn_file_actions_adddup2(&file_actions, out_pfd[1], STDERR_FILENO)))) {
            LogError("Failed to set up standard process descriptors: %1", strerror(errno));
            return false;
        }

        if (info.work_dir) {
            const char *argv[] = { "env", "-C", info.work_dir, "sh", "-c", cmd_line, nullptr };
            errno = posix_spawn(&pid, "/bin/env", &file_actions, nullptr, const_cast<char **>(argv), new_env.ptr ? new_env.ptr : environ);
        } else {
            const char *argv[] = { "sh", "-c", cmd_line, nullptr };
            errno = posix_spawn(&pid, "/bin/sh", &file_actions, nullptr, const_cast<char **>(argv), new_env.ptr ? new_env.ptr : environ);
        }
        if (errno) {
            LogError("Failed to start process: %1", strerror(errno));
            return false;
        }

        CloseDescriptorSafe(&in_pfd[0]);
        CloseDescriptorSafe(&out_pfd[1]);
    }

    Span<const uint8_t> write_buf = {};
    bool terminate = false;

    // Read and write standard process streams
    while (in_pfd[1] >= 0 || out_pfd[0] >= 0) {
        LocalArray<struct pollfd, 3> pfds;
        int in_idx = -1, out_idx = -1, term_idx = -1;
        if (in_pfd[1] >= 0) {
            in_idx = pfds.len;
            pfds.Append({ in_pfd[1], POLLOUT, 0 });
        }
        if (out_pfd[0] >= 0) {
            out_idx = pfds.len;
            pfds.Append({ out_pfd[0], POLLIN, 0 });
        }
        if (int fd = interrupt_pfd[0].load(); fd >= 0) {
            term_idx = pfds.len;
            pfds.Append({ fd, POLLIN, 0 });
        }

        if (K_RESTART_EINTR(poll(pfds.data, (nfds_t)pfds.len, -1), < 0) < 0) {
            LogError("Failed to poll process I/O: %1", strerror(errno));
            break;
        }

        unsigned int in_revents = (in_idx >= 0) ? pfds[in_idx].revents : 0;
        unsigned int out_revents = (out_idx >= 0) ? pfds[out_idx].revents : 0;
        unsigned int term_revents = (term_idx >= 0) ? pfds[term_idx].revents : 0;

        // Try to write
        if (in_revents & (POLLHUP | POLLERR)) {
            CloseDescriptorSafe(&in_pfd[1]);
        } else if (in_revents & POLLOUT) {
            K_ASSERT(in_func.IsValid());

            if (!write_buf.len) {
                write_buf = in_func();
                K_ASSERT(write_buf.len >= 0);
            }

            if (write_buf.len) {
                ssize_t write_len = K_RESTART_EINTR(write(in_pfd[1], write_buf.ptr, (size_t)write_buf.len), < 0);

                if (write_len > 0) {
                    write_buf.ptr += write_len;
                    write_buf.len -= (Size)write_len;
                } else if (!write_len) {
                    CloseDescriptorSafe(&in_pfd[1]);
                } else {
                    LogError("Failed to write process input: %1", strerror(errno));
                    CloseDescriptorSafe(&in_pfd[1]);
                }
            } else {
                CloseDescriptorSafe(&in_pfd[1]);
            }
        }

        // Try to read
        if (out_revents & POLLERR) {
            break;
        } else if (out_revents & (POLLIN | POLLHUP)) {
            K_ASSERT(out_func.IsValid());

            uint8_t read_buf[4096];
            ssize_t read_len = K_RESTART_EINTR(read(out_pfd[0], read_buf, K_SIZE(read_buf)), < 0);

            if (read_len > 0) {
                out_func(MakeSpan(read_buf, read_len));
            } else if (!read_len) {
                // EOF
                break;
            } else {
                LogError("Failed to read process output: %1", strerror(errno));
                break;
            }
        }

        if (term_revents) {
            kill(pid, SIGTERM);
            terminate = true;

            break;
        }
    }

    // Done reading and writing
    CloseDescriptorSafe(&in_pfd[1]);
    CloseDescriptorSafe(&out_pfd[0]);

    // Wait for process exit
    int status;
    {
        int64_t start = GetMonotonicClock();

        for (;;) {
            int ret = K_RESTART_EINTR(waitpid(pid, &status, terminate ? WNOHANG : 0), < 0);

            if (ret < 0) {
                LogError("Failed to wait for process exit: %1", strerror(errno));
                return false;
            } else if (!ret) {
                int64_t delay = GetMonotonicClock() - start;

                if (delay < 2000) {
                    // A timeout on waitpid would be better, but... sigh
                    WaitDelay(10);
                } else {
                    kill(pid, SIGKILL);
                    terminate = false;
                }
            } else {
                break;
            }
        }
    }

    if (WIFSIGNALED(status)) {
        *out_code = 128 + WTERMSIG(status);
    } else if (WIFEXITED(status)) {
        *out_code = WEXITSTATUS(status);
    } else {
        *out_code = -1;
    }
    return true;
}

#endif

bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
                        Span<const uint8_t> in_buf, Size max_len,
                        HeapArray<uint8_t> *out_buf, int *out_code)
{
    Size start_len = out_buf->len;
    K_DEFER_N(out_guard) { out_buf->RemoveFrom(start_len); };

    // Check virtual memory limits
    {
        Size memory_max = K_SIZE_MAX - out_buf->len - 1;

        if (memory_max <= 0) [[unlikely]] {
            LogError("Exhausted memory limit");
            return false;
        }

        K_ASSERT(max_len);
        max_len = (max_len >= 0) ? std::min(max_len, memory_max) : memory_max;
    }

    // Don't f*ck up the log
    bool warned = false;

    bool success = ExecuteCommandLine(cmd_line, info, [&]() { return in_buf; },
                                                      [&](Span<uint8_t> buf) {
        if (out_buf->len - start_len <= max_len - buf.len) {
            out_buf->Append(buf);
        } else if (!warned) {
            LogError("Truncated output");
            warned = true;
        }
    }, out_code);
    if (!success)
        return false;

    out_guard.Disable();
    return true;
}

Size ReadCommandOutput(const char *cmd_line, Span<char> out_output)
{
    static ExecuteInfo::KeyValue variables[] = {
        { "LANG", "C" },
        { "LC_ALL", "C" }
    };

    ExecuteInfo info = {};
    info.env_variables = variables;

    Size total_len = 0;
    const auto write = [&](Span<uint8_t> buf) {
        Size copy = std::min(out_output.len - total_len, buf.len);

        MemCpy(out_output.ptr + total_len, buf.ptr, copy);
        total_len += copy;
    };

    int exit_code;
    if (!ExecuteCommandLine(cmd_line, info, MakeSpan((const uint8_t *)nullptr, 0), write, &exit_code))
        return -1;
    if (exit_code) {
        LogDebug("Command '%1 failed (exit code: %2)", cmd_line, exit_code);
        return -1;
    }

    return total_len;
}

bool ReadCommandOutput(const char *cmd_line, HeapArray<char> *out_output)
{
    static ExecuteInfo::KeyValue variables[] = {
        { "LANG", "C" },
        { "LC_ALL", "C" }
    };

    ExecuteInfo info = {};
    info.env_variables = variables;

    int exit_code;
    if (!ExecuteCommandLine(cmd_line, info, {}, Mebibytes(1), out_output, &exit_code))
        return false;
    if (exit_code) {
        LogDebug("Command '%1 failed (exit code: %2)", cmd_line, exit_code);
        return false;
    }

    return true;
}

#endif

#if defined(_WIN32)

static HANDLE wait_msg_event = CreateEvent(nullptr, TRUE, FALSE, nullptr);

void WaitDelay(int64_t delay)
{
    K_ASSERT(delay >= 0);
    K_ASSERT(delay < 1000ll * INT32_MAX);

    while (delay) {
        DWORD delay32 = (DWORD)std::min(delay, (int64_t)UINT32_MAX);
        delay -= delay32;

        Sleep(delay32);
    }
}

WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t *out_ready)
{
    K_ASSERT(sources.len <= 62);

    ignore_ctrl_event = InitConsoleCtrlHandler();
    K_ASSERT(ignore_ctrl_event);

    LocalArray<HANDLE, 64> events;
    DWORD wake = 0;
    DWORD wait_ret = 0;

    events.Append(console_ctrl_event);

    // There is no way to get a waitable HANDLE for the Win32 GUI message pump.
    // Instead, waitable sources (such as the system tray code) return NULL to indicate that
    // they need to wait for messages on the message pump.
    for (const WaitSource &src: sources) {
        if (src.handle) {
            events.Append(src.handle);
        } else {
            wake = QS_ALLINPUT;
        }

        timeout = (int64_t)std::min((uint64_t)timeout, (uint64_t)src.timeout);
    }

    if (main_thread == GetCurrentThreadId()) {
        wait_ret = WAIT_OBJECT_0 + (DWORD)events.len;
        events.Append(wait_msg_event);
    }

    DWORD ret;
    if (timeout >= 0) {
        do {
            DWORD timeout32 = (DWORD)std::min(timeout, (int64_t)UINT32_MAX);
            timeout -= timeout32;

            ret = MsgWaitForMultipleObjects((DWORD)events.len, events.data, FALSE, timeout32, wake);
        } while (ret == WAIT_TIMEOUT && timeout);
    } else {
        ret = MsgWaitForMultipleObjects((DWORD)events.len, events.data, FALSE, INFINITE, wake);
    }

    if (ret == WAIT_TIMEOUT) {
        return WaitResult::Timeout;
    } else if (ret == WAIT_OBJECT_0) {
        return WaitResult::Interrupt;
    } else if (ret == wait_ret) {
        ResetEvent(wait_msg_event);
        return WaitResult::Message;
    } else if (ret == WAIT_OBJECT_0 + events.len) {
        // Mark all sources with an interest in the message pump as ready
        if (out_ready) {
            uint64_t flags = 0;
            for (Size i = 0; i < sources.len; i++) {
                flags |= !sources[i].handle ? (1ull << i) : 0;
            }
            *out_ready = flags;
        }
        return WaitResult::Ready;
    } else {
        Size idx = (Size)ret - WAIT_OBJECT_0 - 1;
        K_ASSERT(idx >= 0 && idx < sources.len);

        if (out_ready) {
            *out_ready |= 1ull << idx;
        }
        return WaitResult::Ready;
    }
}

WaitResult WaitEvents(int64_t timeout)
{
    Span<const WaitSource> sources = {};
    return WaitEvents(sources, timeout);
}

void PostWaitMessage()
{
    SetEvent(wait_msg_event);
}

void PostTerminate()
{
    SetEvent(console_ctrl_event);
}

#else

void WaitDelay(int64_t delay)
{
    K_ASSERT(delay >= 0);
    K_ASSERT(delay < 1000ll * INT32_MAX);

    struct timespec ts;
    ts.tv_sec = (int)(delay / 1000);
    ts.tv_nsec = (int)((delay % 1000) * 1000000);

    struct timespec rem;
    while (nanosleep(&ts, &rem) < 0) {
        K_ASSERT(errno == EINTR);
        ts = rem;
    }
}

#if !defined(__wasi__)

WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t *out_ready)
{
    LocalArray<struct pollfd, 64> pfds;
    K_ASSERT(sources.len <= K_LEN(pfds.data) - 1);

    // Don't exit after SIGINT/SIGTERM, just signal us
    flag_signal = true;

    static std::atomic_bool message { false };
    SetSignalHandler(SIGUSR1, [](int) { message = true; });

    for (const WaitSource &src: sources) {
        short events = src.events ? (short)src.events : POLLIN;
        pfds.Append({ src.fd, events, 0 });

        timeout = (int64_t)std::min((uint64_t)timeout, (uint64_t)src.timeout);
    }

    InitInterruptPipe();
    pfds.Append({ interrupt_pfd[0], POLLIN, 0 });

    int64_t start = (timeout >= 0) ? GetMonotonicClock() : 0;
    int64_t until = start + timeout;
    int timeout32 = (int)std::min(until - start, (int64_t)INT_MAX);

    for (;;) {
        if (explicit_signal == SIGTERM) {
            return WaitResult::Exit;
        } else if (explicit_signal) {
            return WaitResult::Interrupt;
        } else if (message && pthread_self() == main_thread) {
            message = false;
            return WaitResult::Message;
        }

        int ready = poll(pfds.data, (nfds_t)pfds.len, timeout32);

        if (ready < 0) {
            if (errno == EINTR)
                continue;

            LogError("Failed to poll for events: %1", strerror(errno));
            abort();
        } else if (ready > 0) {
            uint64_t flags = 0;
            for (Size i = 0; i < pfds.len - 1; i++) {
                flags |= pfds[i].revents ? (1ull << i) : 0;
            }

            if (flags) {
                if (out_ready) {
                    *out_ready = flags;
                }
                return WaitResult::Ready;
            }
        }

        if (timeout >= 0) {
            int64_t clock = GetMonotonicClock();
            if (clock >= until)
                break;
            timeout32 = (int)std::min(until - clock, (int64_t)INT_MAX);
        }
    }

    return WaitResult::Timeout;
}

WaitResult WaitEvents(int64_t timeout)
{
    Span<const WaitSource> sources = {};
    return WaitEvents(sources, timeout);
}

void PostWaitMessage()
{
    pid_t pid = getpid();
    kill(pid, SIGUSR1);
}

void PostTerminate()
{
    InitInterruptPipe();

    char dummy = 0;
    K_IGNORE write(interrupt_pfd[1], &dummy, 1);
}

#endif

#endif

int GetCoreCount()
{
#if defined(__wasi__)
    return 1;
#else
    static int cores;

    if (!cores) {
        const char *env = GetEnv("OVERRIDE_CORES");

        if (env) {
            char *end_ptr;
            long value = strtol(env, &end_ptr, 10);

            if (end_ptr > env && !end_ptr[0] && value > 0) {
                cores = (int)value;
            } else {
                LogError("OVERRIDE_CORES must be positive number (ignored)");
                cores = (int)std::thread::hardware_concurrency();
            }
        } else {
            cores = (int)std::thread::hardware_concurrency();
        }

        K_ASSERT(cores > 0);
    }

    return cores;
#endif
}

#if !defined(_WIN32) && !defined(__wasi__)

bool RaiseMaximumOpenFiles(int limit)
{
    struct rlimit lim;

    if (getrlimit(RLIMIT_NOFILE, &lim) < 0) {
        LogError("getrlimit(RLIMIT_NOFILE) failed: %1", strerror(errno));
        return false;
    }

    rlim_t target = (limit >= 0) ? (rlim_t)limit : lim.rlim_max;

    if (lim.rlim_cur >= target)
        return true;

    lim.rlim_cur = std::min(target, lim.rlim_max);

    if (setrlimit(RLIMIT_NOFILE, &lim) < 0) {
        LogError("Could not raise RLIMIT_NOFILE: %1", strerror(errno));
        return false;
    }

    if (lim.rlim_cur < target) {
        LogWarning("Maximum number of open descriptors is low: %1 (recommended: %2)", lim.rlim_cur, target);
    }

    return true;
}

bool DropRootIdentity()
{
    uid_t uid = getuid();
    uid_t euid = geteuid();
    gid_t gid = getgid();

    if (!uid) {
        LogError("This program must not be run as root");
        return false;
    }
    if (uid != euid) {
        LogDebug("Dropping SUID privileges...");
    }

    if (!euid && setgroups(1, &gid) < 0)
        goto error;
    if (setregid(gid, gid) < 0)
        goto error;
    if (setreuid(uid, uid) < 0)
        goto error;
    K_CRITICAL(setuid(0) < 0, "Managed to regain root privileges");

    return true;

error:
    LogError("Failed to drop root privilegies: %1", strerror(errno));
    return false;
}

#endif

#if defined(__linux__)

bool NotifySystemd()
{
    const char *addr = GetEnv("NOTIFY_SOCKET");
    if (!addr)
        return true;

    struct sockaddr_un sa;
    if (addr[0] == '@') {
        addr++;

        if (strlen(addr) >= sizeof(sa.sun_path) - 1) {
            LogError("Abstract socket address in NOTIFY_SOCKET is too long");
            return false;
        }

        sa.sun_family = AF_UNIX;
        sa.sun_path[0] = 0;
        CopyString(addr, MakeSpan(sa.sun_path + 1, K_SIZE(sa.sun_path) - 1));
    } else if (addr[0] == '/') {
        if (strlen(addr) >= sizeof(sa.sun_path)) {
            LogError("Socket pathname in NOTIFY_SOCKET is too long");
            return false;
        }

        sa.sun_family = AF_UNIX;
        CopyString(addr, sa.sun_path);
    } else {
        LogError("Invalid socket address in NOTIFY_SOCKET");
        return false;
    }

    int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
    if (fd < 0) {
        LogError("Failed to create UNIX socket: %1", strerror(errno));
        return false;
    }
    K_DEFER { close(fd); };

    struct iovec iov = {};
    struct msghdr msg = {};
    iov.iov_base = (void *)"READY=1";
    iov.iov_len = strlen("READY=1");
    msg.msg_name = &sa;
    msg.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) {
        LogError("Failed to send message to systemd: %1", strerror(errno));
        return false;
    }

    unsetenv("NOTIFY_SOCKET");
    return true;
}

#endif

// ------------------------------------------------------------------------
// Main
// ------------------------------------------------------------------------

static InitHelper *init;
static FinalizeHelper *finalize;

InitHelper::InitHelper(const char *name)
    : name(name)
{
    next = init;
    init = this;
}

FinalizeHelper::FinalizeHelper(const char *name)
    : name(name)
{
    next = finalize;
    finalize = this;
}

void InitApp()
{
#if defined(_WIN32)
    // Use binary standard I/O
    _setmode(STDIN_FILENO, _O_BINARY);
    _setmode(STDOUT_FILENO, _O_BINARY);
    _setmode(STDERR_FILENO, _O_BINARY);

    SetConsoleCP(CP_UTF8);
    SetConsoleOutputCP(CP_UTF8);
#endif

#if !defined(_WIN32) && !defined(__wasi__)
    // Setup default signal handlers
    SetSignalHandler(SIGINT, DefaultSignalHandler);
    SetSignalHandler(SIGTERM, DefaultSignalHandler);
    SetSignalHandler(SIGHUP, DefaultSignalHandler);
    SetSignalHandler(SIGPIPE, [](int) {});

    InitInterruptPipe();

    // Make sure timezone information is in place, which is useful if some kind of sandbox runs later and
    // the timezone information is not available (seccomp, namespace, landlock, whatever).
    tzset();
#endif

#if defined(__OpenBSD__)
    // This can depend on PATH, which could change during execution
    // so we want to cache the result as soon as possible.
    GetApplicationExecutable();
#endif

    // Init libraries
    while (init) {
#if defined(K_DEBUG)
        LogDebug("Init %1 library", init->name);
#endif

        init->Run();
        init = init->next;
    }
}

void ExitApp()
{
    while (finalize) {
#if defined(K_DEBUG)
        LogDebug("Finalize %1 library", finalize->name);
#endif

        finalize->Run();
        finalize = finalize->next;
    }
}

// ------------------------------------------------------------------------
// Standard paths
// ------------------------------------------------------------------------

#if defined(_WIN32)

const char *GetUserConfigPath(const char *name, Allocator *alloc)
{
    K_ASSERT(!strchr(K_PATH_SEPARATORS, name[0]));

    static char cache_dir[4096];
    static std::once_flag flag;

    std::call_once(flag, []() {
        wchar_t *dir = nullptr;
        K_DEFER { CoTaskMemFree(dir); };

        K_CRITICAL(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &dir) == S_OK,
                    "Failed to retrieve path to roaming user AppData");
        K_CRITICAL(ConvertWin32WideToUtf8(dir, cache_dir) >= 0,
                    "Path to roaming AppData is invalid or too big");
    });

    const char *path = Fmt(alloc, "%1%/%2", cache_dir, name).ptr;
    return path;
}

const char *GetUserCachePath(const char *name, Allocator *alloc)
{
    K_ASSERT(!strchr(K_PATH_SEPARATORS, name[0]));

    static char cache_dir[4096];
    static std::once_flag flag;

    std::call_once(flag, []() {
        wchar_t *dir = nullptr;
        K_DEFER { CoTaskMemFree(dir); };

        K_CRITICAL(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &dir) == S_OK,
                    "Failed to retrieve path to local user AppData");
        K_CRITICAL(ConvertWin32WideToUtf8(dir, cache_dir) >= 0,
                    "Path to local AppData is invalid or too big");
    });

    const char *path = Fmt(alloc, "%1%/%2", cache_dir, name).ptr;
    return path;
}

const char *GetTemporaryDirectory()
{
    static char temp_dir[4096];
    static std::once_flag flag;

    std::call_once(flag, []() {
        Size len;
        if (win32_utf8) {
            len = (Size)GetTempPathA(K_SIZE(temp_dir), temp_dir);
            K_CRITICAL(len < K_SIZE(temp_dir), "Temporary directory path is too big");
        } else {
            static wchar_t dir_w[4096];
            Size len_w = (Size)GetTempPathW(K_LEN(dir_w), dir_w);

            K_CRITICAL(len_w < K_LEN(dir_w), "Temporary directory path is too big");

            len = ConvertWin32WideToUtf8(dir_w, temp_dir);
            K_CRITICAL(len >= 0, "Temporary directory path is invalid or too big");
        }

        while (len > 0 && IsPathSeparator(temp_dir[len - 1])) {
            len--;
        }
        temp_dir[len] = 0;
    });

    return temp_dir;
}

#else

const char *GetUserConfigPath(const char *name, Allocator *alloc)
{
    K_ASSERT(!strchr(K_PATH_SEPARATORS, name[0]));

    const char *xdg = GetEnv("XDG_CONFIG_HOME");
    const char *home = GetEnv("HOME");

    const char *path = nullptr;

    if (xdg) {
        path = Fmt(alloc, "%1%/%2", xdg, name).ptr;
    } else if (home) {
        path = Fmt(alloc, "%1%/.config/%2", home, name).ptr;
#if !defined(__wasi__)
    } else if (!getuid()) {
        path = Fmt(alloc, "/root/.config/%1", name).ptr;
#endif
    }

    if (path && !EnsureDirectoryExists(path))
        return nullptr;

    return path;
}

const char *GetUserCachePath(const char *name, Allocator *alloc)
{
    K_ASSERT(!strchr(K_PATH_SEPARATORS, name[0]));

    const char *xdg = GetEnv("XDG_CACHE_HOME");
    const char *home = GetEnv("HOME");

    const char *path = nullptr;

    if (xdg) {
        path = Fmt(alloc, "%1%/%2", xdg, name).ptr;
    } else if (home) {
        path = Fmt(alloc, "%1%/.cache/%2", home, name).ptr;
#if !defined(__wasi__)
    } else if (!getuid()) {
        path = Fmt(alloc, "/root/.cache/%1", name).ptr;
#endif
    }

    if (path && !EnsureDirectoryExists(path))
        return nullptr;

    return path;
}

const char *GetSystemConfigPath(const char *name, Allocator *alloc)
{
    K_ASSERT(!strchr(K_PATH_SEPARATORS, name[0]));

    const char *path = Fmt(alloc, "/etc/%1", name).ptr;
    return path;
}

const char *GetTemporaryDirectory()
{
    static char temp_dir[4096];
    static std::once_flag flag;

    std::call_once(flag, []() {
        Span<const char> env = GetEnv("TMPDIR");

        while (env.len > 0 && IsPathSeparator(env[env.len - 1])) {
            env.len--;
        }

        if (env.len && env.len < K_SIZE(temp_dir)) {
            CopyString(env, temp_dir);
        } else {
            CopyString("/tmp", temp_dir);
        }
    });

    return temp_dir;
}

#endif

const char *FindConfigFile(const char *directory, Span<const char *const> names,
                           Allocator *alloc, HeapArray<const char *> *out_possibilities)
{
    K_ASSERT(!directory || directory[0]);

    decltype(GetUserConfigPath) *funcs[] = {
        GetUserConfigPath,
#if !defined(_WIN32)
        GetSystemConfigPath
#endif
    };

    const char *filename = nullptr;

    // Try application directory
    for (const char *name: names) {
        Span<const char> dir = GetApplicationDirectory();
        const char *path = Fmt(alloc, "%1%/%2", dir, name).ptr;

        if (!filename && TestFile(path, FileType::File)) {
            filename = path;
        }
        if (out_possibilities) {
            out_possibilities->Append(path);
        }
    }

    LocalArray<const char *, 8> tests;
    {
        K_ASSERT(names.len <= tests.Available());

        for (const char *name: names) {
            if (directory) {
                const char *test = Fmt(alloc, "%1%/%2", directory, name).ptr;
                tests.Append(test);
            } else {
                tests.Append(name);
            }
        }
    }

    // Try standard paths
    for (const auto &func: funcs) {
        for (const char *test: tests) {
            const char *path = func(test, alloc);

            if (!path)
                continue;

            if (!filename && TestFile(path, FileType::File)) {
                filename = path;
            }
            if (out_possibilities) {
                out_possibilities->Append(path);
            }
        }
    }

    return filename;
}

static const char *CreateUniquePath(Span<const char> directory, const char *prefix, const char *extension,
                                    Allocator *alloc, FunctionRef<bool(const char *path)> create)
{
    K_ASSERT(alloc);

    HeapArray<char> filename(alloc);
    filename.Append(directory);
    filename.Append(*K_PATH_SEPARATORS);
    if (prefix) {
        filename.Append(prefix);
        filename.Append('.');
    }

    Size change_offset = filename.len;

    PushLogFilter([](LogLevel, const char *, const char *, FunctionRef<LogFunc>) {});
    K_DEFER_N(log_guard) { PopLogFilter(); };

    for (Size i = 0; i < 1000; i++) {
        // We want to show an error on last try
        if (i == 999) [[unlikely]] {
            PopLogFilter();
            log_guard.Disable();
        }

        filename.RemoveFrom(change_offset);
        Fmt(&filename, "%1%2", FmtRandom(24), extension);

        if (create(filename.ptr)) {
            const char *ret = filename.TrimAndLeak(1).ptr;
            return ret;
        }
    }

    return nullptr;
}

const char *CreateUniqueFile(Span<const char> directory, const char *prefix, const char *extension,
                             Allocator *alloc, int *out_fd)
{
    return CreateUniquePath(directory, prefix, extension, alloc, [&](const char *path) {
        int flags = (int)OpenFlag::Read | (int)OpenFlag::Write |
                    (int)OpenFlag::Exclusive;

        int fd = OpenFile(path, flags);

        if (fd >= 0) {
            if (out_fd) {
                *out_fd = fd;
            } else {
                CloseDescriptor(fd);
            }

            return true;
        } else {
            return false;
        }
    });
}

const char *CreateUniqueDirectory(Span<const char> directory, const char *prefix, Allocator *alloc)
{
    return CreateUniquePath(directory, prefix, "", alloc, [&](const char *path) {
        return MakeDirectory(path);
    });
}

// ------------------------------------------------------------------------
// Parsing
// ------------------------------------------------------------------------

bool ParseBool(Span<const char> str, bool *out_value, unsigned int flags, Span<const char> *out_remaining)
{
    union {
        char raw[8];
        uint64_t u;
    } u = {};
    Size end = 0;
    bool value = false;

    switch (str.len) {
        default: { K_ASSERT(str.len >= 0); } [[fallthrough]];

#if defined(K_BIG_ENDIAN)
        case 8: { u.raw[0] = LowerAscii(str[7]); } [[fallthrough]];
        case 7: { u.raw[1] = LowerAscii(str[6]); } [[fallthrough]];
        case 6: { u.raw[2] = LowerAscii(str[5]); } [[fallthrough]];
        case 5: { u.raw[3] = LowerAscii(str[4]); } [[fallthrough]];
        case 4: { u.raw[4] = LowerAscii(str[3]); } [[fallthrough]];
        case 3: { u.raw[5] = LowerAscii(str[2]); } [[fallthrough]];
        case 2: { u.raw[6] = LowerAscii(str[1]); } [[fallthrough]];
        case 1: { u.raw[7] = LowerAscii(str[0]); } [[fallthrough]];
        case 0: {} break;
#else
        case 8: { u.raw[7] = LowerAscii(str[7]); } [[fallthrough]];
        case 7: { u.raw[6] = LowerAscii(str[6]); } [[fallthrough]];
        case 6: { u.raw[5] = LowerAscii(str[5]); } [[fallthrough]];
        case 5: { u.raw[4] = LowerAscii(str[4]); } [[fallthrough]];
        case 4: { u.raw[3] = LowerAscii(str[3]); } [[fallthrough]];
        case 3: { u.raw[2] = LowerAscii(str[2]); } [[fallthrough]];
        case 2: { u.raw[1] = LowerAscii(str[1]); } [[fallthrough]];
        case 1: { u.raw[0] = LowerAscii(str[0]); } [[fallthrough]];
        case 0: {} break;
#endif
    }

#define MATCH(Wanted, Len, Value) \
        if (uint64_t masked = u.u & ((1ull << ((Len) * 8)) - 1); masked == (Wanted)) { \
            end = (Len); \
            value = (Value); \
             \
            break; \
        }

    do {
        MATCH(0x31ull, 1, true);
        MATCH(0x6E6Full, 2, true);
        MATCH(0x736579ull, 3, true);
        MATCH(0x79ull, 1, true);
        MATCH(0x65757274ull, 4, true);
        MATCH(0x30ull, 1, false);
        MATCH(0x66666Full, 3, false);
        MATCH(0x6F6Eull, 2, false);
        MATCH(0x6Eull, 1, false);
        MATCH(0x65736C6166ull, 5, false);

        if (flags & (int)ParseFlag::Log) {
            LogError("Invalid boolean value '%1'", str);
        }
        return false;
    } while (false);

#undef MATCH

    if ((flags & (int)ParseFlag::End) && end < str.len) [[unlikely]] {
        if (flags & (int)ParseFlag::Log) {
            LogError("Malformed boolean '%1'", str);
        }
        return false;
    }

    *out_value = value;
    if (out_remaining) {
        *out_remaining = str.Take(end, str.len - end);
    }
    return true;
}

bool ParseSize(Span<const char> str, int64_t *out_size, unsigned int flags, Span<const char> *out_remaining)
{
    uint64_t size = 0;
    uint64_t multiplier = 1;

    if (!ParseInt(str, &size, flags & ~(int)ParseFlag::End, &str))
        return false;
    if (size > INT64_MAX) [[unlikely]]
        goto overflow;

    if (str.len) {
        int next = 1;

        switch (str[0]) {
            case 'B': { multiplier = 1; } break;
            case 'k': { multiplier = 1000; } break;
            case 'M': { multiplier = 1000000; } break;
            case 'G': { multiplier = 1000000000; } break;
            case 'T': { multiplier = 1000000000000; } break;
            default: { next = 0; } break;
        }

        if ((flags & (int)ParseFlag::End) && str.len > next) [[unlikely]] {
            if (flags & (int)ParseFlag::Log) {
                LogError("Unknown size unit '%1'", str[0]);
            }
            return false;
        }
        str = str.Take(next, str.len - next);
    }

#if defined(__GNUC__) || defined(__clang__)
    if (__builtin_mul_overflow(size, multiplier, &size) || size > INT64_MAX) [[unlikely]]
        goto overflow;
#else
    {
        uint64_t total = size * multiplier;
        if ((size && total / size != multiplier) || total > INT64_MAX) [[unlikely]]
            goto overflow;
        size = (int64_t)total;
    }
#endif

    *out_size = (int64_t)size;
    if (out_remaining) {
        *out_remaining = str;
    }
    return true;

overflow:
    if (flags & (int)ParseFlag::Log) {
        LogError("Size value is too high");
    }
    return false;
}

bool ParseDuration(Span<const char> str, int64_t *out_duration, unsigned int flags, Span<const char> *out_remaining)
{
    int64_t duration = 0;
    int64_t multiplier = 1000;

    if (!ParseInt(str, &duration, flags & ~(int)ParseFlag::End, &str))
        return false;
    if (duration < 0) [[unlikely]] {
        LogError("Duration values must be positive");
        return false;
    }

    if (str.len) {
        int next = 1;

        switch (str[0]) {
            case 's': { multiplier = 1000; } break;
            case 'm': { multiplier = 60000; } break;
            case 'h': { multiplier = 3600000; } break;
            case 'd': { multiplier = 86400000; } break;
            default: { next = 0; } break;
        }

        if ((flags & (int)ParseFlag::End) && str.len > next) [[unlikely]] {
            if (flags & (int)ParseFlag::Log) {
                LogError("Unknown duration unit '%1'", str[0]);
            }
            return false;
        }

        str = str.Take(next, str.len - next);
    }

#if defined(__GNUC__) || defined(__clang__)
    if (__builtin_mul_overflow(duration, multiplier, &duration)) [[unlikely]]
        goto overflow;
#else
    {
        uint64_t total = duration * multiplier;
        if ((duration && total / duration != (uint64_t)multiplier) || total > INT64_MAX) [[unlikely]]
            goto overflow;
        duration = (int64_t)total;
    }
#endif

    *out_duration = duration;
    if (out_remaining) {
        *out_remaining = str;
    }
    return true;

overflow:
    if (flags & (int)ParseFlag::Log) {
        LogError("Duration value is too high");
    }
    return false;
}

// XXX: Rewrite the ugly parsing part
bool ParseDate(Span<const char> date_str, LocalDate *out_date, unsigned int flags, Span<const char> *out_remaining)
{
    LocalDate date;

    int parts[3] = {};
    int lengths[3] = {};
    Size offset = 0;
    for (int i = 0; i < 3; i++) {
        int mult = 1;
        while (offset < date_str.len) {
            char c = date_str[offset];
            int digit = c - '0';
            if ((unsigned int)digit < 10) {
                parts[i] = (parts[i] * 10) + digit;
                if (++lengths[i] > 5) [[unlikely]]
                    goto malformed;
            } else if (!lengths[i] && c == '-' && mult == 1 && i != 1) {
                mult = -1;
            } else if (i == 2 && !(flags & (int)ParseFlag::End) && c != '/' && c != '-') [[unlikely]] {
                break;
            } else if (!lengths[i] || (c != '/' && c != '-')) [[unlikely]] {
                goto malformed;
            } else {
                offset++;
                break;
            }
            offset++;
        }
        parts[i] *= mult;
    }
    if ((flags & (int)ParseFlag::End) && offset < date_str.len)
        goto malformed;

    if ((unsigned int)lengths[1] > 2) [[unlikely]]
        goto malformed;
    if ((lengths[0] > 2) == (lengths[2] > 2)) [[unlikely]] {
        if (flags & (int)ParseFlag::Log) {
            LogError("Ambiguous date string '%1'", date_str);
        }
        return false;
    } else if (lengths[2] > 2) {
        std::swap(parts[0], parts[2]);
    }
    if (parts[0] < -INT16_MAX || parts[0] > INT16_MAX || (unsigned int)parts[2] > 99) [[unlikely]]
        goto malformed;

    date.st.year = (int16_t)parts[0];
    date.st.month = (int8_t)parts[1];
    date.st.day = (int8_t)parts[2];

    if ((flags & (int)ParseFlag::Validate) && !date.IsValid()) {
        if (flags & (int)ParseFlag::Log) {
            LogError("Invalid date string '%1'", date_str);
        }
        return false;
    }

    *out_date = date;
    if (out_remaining) {
        *out_remaining = date_str.Take(offset, date_str.len - offset);
    }
    return true;

malformed:
    if (flags & (int)ParseFlag::Log) {
        LogError("Malformed date string '%1'", date_str);
    }
    return false;
}

bool ParseVersion(Span<const char> str, int parts, int multiplier,
                  int64_t *out_version, unsigned int flags, Span<const char> *out_remaining)
{
    K_ASSERT(parts >= 0 && parts < 6);

    int64_t version = 0;
    Span<const char> remain = str;

    while (remain.len && parts) {
        int component = 0;
        if (!ParseInt(remain, &component, 0, &remain)) {
            if (flags & (int)ParseFlag::Log) {
                LogError("Malformed version string '%1'", str);
            }
            return false;
        }

        version = (version * multiplier) + component;
        parts--;

        if (!remain.len || remain[0] != '.')
            break;
        remain.ptr++;
        remain.len--;
    }

    if (remain.len && (flags & (int)ParseFlag::End)) {
        if (flags & (int)ParseFlag::Log) {
            LogError("Malformed version string '%1'", str);
        }
        return false;
    }

    while (parts) {
        version *= multiplier;
        parts--;
    }

    *out_version = version;
    if (out_remaining) {
        *out_remaining = remain;
    }
    return true;
}

// ------------------------------------------------------------------------
// Random
// ------------------------------------------------------------------------

static thread_local Size rnd_remain;
static thread_local int64_t rnd_clock;
#if !defined(_WIN32)
static thread_local pid_t rnd_pid;
#endif
static thread_local uint32_t rnd_state[16];
static thread_local uint8_t rnd_buf[64];
static thread_local Size rnd_offset;

static thread_local FastRandom rng_fast;

static inline uint32_t ROTL32(uint32_t v, int n)
{
    return (v << n) | (v >> (32 - n));
}

static inline uint64_t ROTL64(uint64_t v, int n)
{
    return (v << n) | (v >> (64 - n));
}

static inline uint32_t LE32(const uint8_t *ptr)
{
    return ((uint32_t)ptr[0] << 0) |
           ((uint32_t)ptr[1] << 8) |
           ((uint32_t)ptr[2] << 16) |
           ((uint32_t)ptr[3] << 24);
}

void InitChaCha20(uint32_t state[16], const uint8_t key[32], const uint8_t iv[8], const uint8_t counter[8])
{
    static uint8_t magic[] = "expand 32-byte k";

    state[0] = LE32(magic + 0);
    state[1] = LE32(magic + 4);
    state[2] = LE32(magic + 8);
    state[3] = LE32(magic + 12);
    state[4] = LE32(key + 0);
    state[5] = LE32(key + 4);
    state[6] = LE32(key + 8);
    state[7] = LE32(key + 12);
    state[8] = LE32(key + 16);
    state[9] = LE32(key + 20);
    state[10] = LE32(key + 24);
    state[11] = LE32(key + 28);
    state[12] = counter ? LE32(counter + 0) : 0;
    state[13] = counter ? LE32(counter + 4) : 0;
    state[14] = LE32(iv + 0);
    state[15] = LE32(iv + 4);
}

void RunChaCha20(uint32_t state[16], uint8_t out_buf[64])
{
    uint32_t *out_buf32 = (uint32_t *)out_buf;

    uint32_t x[16];
    MemCpy(x, state, K_SIZE(x));

    for (Size i = 0; i < 20; i += 2) {
        x[0] += x[4];   x[12] = ROTL32(x[12] ^ x[0], 16);
        x[1] += x[5];   x[13] = ROTL32(x[13] ^ x[1], 16);
        x[2] += x[6];   x[14] = ROTL32(x[14] ^ x[2], 16);
        x[3] += x[7];   x[15] = ROTL32(x[15] ^ x[3], 16);

        x[8]  += x[12]; x[4]  = ROTL32(x[4] ^ x[8],  12);
        x[9]  += x[13]; x[5]  = ROTL32(x[5] ^ x[9],  12);
        x[10] += x[14]; x[6]  = ROTL32(x[6] ^ x[10], 12);
        x[11] += x[15]; x[7]  = ROTL32(x[7] ^ x[11], 12);

        x[0] += x[4];   x[12] = ROTL32(x[12] ^ x[0], 8);
        x[1] += x[5];   x[13] = ROTL32(x[13] ^ x[1], 8);
        x[2] += x[6];   x[14] = ROTL32(x[14] ^ x[2], 8);
        x[3] += x[7];   x[15] = ROTL32(x[15] ^ x[3], 8);

        x[8]  += x[12]; x[4]  = ROTL32(x[4] ^ x[8],  7);
        x[9]  += x[13]; x[5]  = ROTL32(x[5] ^ x[9],  7);
        x[10] += x[14]; x[6]  = ROTL32(x[6] ^ x[10], 7);
        x[11] += x[15]; x[7]  = ROTL32(x[7] ^ x[11], 7);

        x[0] += x[5];   x[15] = ROTL32(x[15] ^ x[0], 16);
        x[1] += x[6];   x[12] = ROTL32(x[12] ^ x[1], 16);
        x[2] += x[7];   x[13] = ROTL32(x[13] ^ x[2], 16);
        x[3] += x[4];   x[14] = ROTL32(x[14] ^ x[3], 16);

        x[10] += x[15]; x[5]  = ROTL32(x[5] ^ x[10], 12);
        x[11] += x[12]; x[6]  = ROTL32(x[6] ^ x[11], 12);
        x[8]  += x[13]; x[7]  = ROTL32(x[7] ^ x[8],  12);
        x[9]  += x[14]; x[4]  = ROTL32(x[4] ^ x[9],  12);

        x[0] += x[5];   x[15] = ROTL32(x[15] ^ x[0], 8);
        x[1] += x[6];   x[12] = ROTL32(x[12] ^ x[1], 8);
        x[2] += x[7];   x[13] = ROTL32(x[13] ^ x[2], 8);
        x[3] += x[4];   x[14] = ROTL32(x[14] ^ x[3], 8);

        x[10] += x[15]; x[5]  = ROTL32(x[5] ^ x[10], 7);
        x[11] += x[12]; x[6]  = ROTL32(x[6] ^ x[11], 7);
        x[8]  += x[13]; x[7]  = ROTL32(x[7] ^ x[8],  7);
        x[9]  += x[14]; x[4]  = ROTL32(x[4] ^ x[9],  7);
    }

    for (Size i = 0; i < K_LEN(x); i++) {
        out_buf32[i] = LittleEndian(x[i] + state[i]);
    }

    state[12]++;
    state[13] += !state[12];
}

void FillRandomSafe(void *out_buf, Size len)
{
    bool reseed = false;

    // Reseed every 4 megabytes, or every hour, or after a fork
    reseed |= (rnd_remain <= 0);
    reseed |= (GetMonotonicClock() - rnd_clock > 3600 * 1000);
#if !defined(_WIN32)
    reseed |= (getpid() != rnd_pid);
#endif

    if (reseed) {
        struct { uint8_t key[32]; uint8_t iv[8]; } buf;

        MemSet(rnd_state, 0, K_SIZE(rnd_state));
#if defined(_WIN32)
        K_CRITICAL(RtlGenRandom(&buf, K_SIZE(buf)), "RtlGenRandom() failed: %1", GetWin32ErrorString());
#elif defined(__linux__)
        {
restart:
            int ret = syscall(SYS_getrandom, &buf, K_SIZE(buf), 0);
            K_CRITICAL(ret >= 0, "getrandom() failed: %1", strerror(errno));

            if (ret < K_SIZE(buf)) [[unlikely]]
                goto restart;
        }
#else
        K_CRITICAL(getentropy(&buf, K_SIZE(buf)) == 0, "getentropy() failed: %1", strerror(errno));
#endif

        InitChaCha20(rnd_state, buf.key, buf.iv);
        ZeroSafe(&buf, K_SIZE(buf));

        rnd_remain = Mebibytes(4);
        rnd_clock = GetMonotonicClock();
#if !defined(_WIN32)
        rnd_pid = getpid();
#endif

        rnd_offset = K_SIZE(rnd_buf);
    }

    Size copy_len = std::min(K_SIZE(rnd_buf) - rnd_offset, len);
    MemCpy(out_buf, rnd_buf + rnd_offset, copy_len);
    ZeroSafe(rnd_buf + rnd_offset, copy_len);
    rnd_offset += copy_len;

    for (Size i = copy_len; i < len; i += K_SIZE(rnd_buf)) {
        RunChaCha20(rnd_state, rnd_buf);

        copy_len = std::min(K_SIZE(rnd_buf), len - i);
        MemCpy((uint8_t *)out_buf + i, rnd_buf, copy_len);
        ZeroSafe(rnd_buf, copy_len);
        rnd_offset = copy_len;
    }

    rnd_remain -= len;
}

FastRandom::FastRandom()
{
    do {
        FillRandomSafe(state, K_SIZE(state));
    } while (std::all_of(std::begin(state), std::end(state), [](uint64_t v) { return !v; }));
}

FastRandom::FastRandom(uint64_t seed)
{
    // splitmix64 generator to seed xoshiro256++, as recommended

    seed += 0x9e3779b97f4a7c15;

    for (int i = 0; i < 4; i++) {
        seed = (seed ^ (seed >> 30)) * 0xbf58476d1ce4e5b9;
        seed = (seed ^ (seed >> 27)) * 0x94d049bb133111eb;
        state[i] = seed ^ (seed >> 31);
    }
}

uint64_t FastRandom::Next()
{
    // xoshiro256++ by David Blackman and Sebastiano Vigna (vigna@acm.org)
    // Hopefully I did not screw it up :)

    uint64_t result = ROTL64(state[0] + state[3], 23) + state[0];
    uint64_t t = state[1] << 17;

    state[2] ^= state[0];
    state[3] ^= state[1];
    state[1] ^= state[2];
    state[0] ^= state[3];
    state[2] ^= t;
    state[3] = ROTL64(state[3], 45);

    return result;
}

void FastRandom::Fill(void *out_buf, Size len)
{
    for (Size i = 0; i < len; i += 8) {
        uint64_t rnd = Next();

        Size copy_len = std::min(K_SIZE(rnd), len - i);
        MemCpy((uint8_t *)out_buf + i, &rnd, copy_len);
    }
}

int FastRandom::GetInt(int min, int max)
{
    int range = max - min;

    if (range < 2) [[unlikely]] {
        K_ASSERT(range >= 1);
        return min;
    }

    unsigned int treshold = (UINT_MAX - UINT_MAX % range);

    unsigned int x;
    do {
        x = (unsigned int)Next();
    } while (x >= treshold);
    x %= range;

    return min + (int)x;
}

int64_t FastRandom::GetInt64(int64_t min, int64_t max)
{
    int64_t range = max - min;

    if (range < 2) [[unlikely]] {
        K_ASSERT(range >= 1);
        return min;
    }

    uint64_t treshold = (UINT64_MAX - UINT64_MAX % range);

    uint64_t x;
    do {
        x = (uint64_t)Next();
    } while (x >= treshold);
    x %= range;

    return min + (int64_t)x;
}

uint64_t GetRandom()
{
    return rng_fast.Next();
}

int GetRandomInt(int min, int max)
{
    return rng_fast.GetInt(min, max);
}

int64_t GetRandomInt64(int64_t min, int64_t max)
{
    return rng_fast.GetInt64(min, max);
}

// ------------------------------------------------------------------------
// Sockets
// ------------------------------------------------------------------------

#if !defined(__wasi__)

#if defined(_WIN32)

bool InitWinsock()
{
    static bool ready = false;
    static std::once_flag flag;

    std::call_once(flag, []() {
        WORD version = MAKEWORD(2, 2);
        WSADATA wsa = {};

        int ret = WSAStartup(version, &wsa);

        if (ret) {
            LogError("Failed to initialize Winsock: %1", GetWin32ErrorString(ret));
            return;
        }

        K_ASSERT(LOBYTE(wsa.wVersion) == 2 && HIBYTE(wsa.wVersion) == 2);
        atexit([]() { WSACleanup(); });

        ready = true;
    });

    return ready;
}

int CreateSocket(SocketType type, int flags)
{
    if (!InitWinsock())
        return -1;

    int family = 0;

    switch (type) {
        case SocketType::Dual:
        case SocketType::IPv6: { family = AF_INET6; } break;
        case SocketType::IPv4: { family = AF_INET; } break;
        case SocketType::Unix: { family = AF_UNIX; } break;
    }

    bool overlapped = (flags & SOCK_OVERLAPPED);
    flags &= ~SOCK_OVERLAPPED;

    SOCKET sock = WSASocketW(family, flags, 0, nullptr, 0, overlapped ? WSA_FLAG_OVERLAPPED : 0);
    if (sock == INVALID_SOCKET) {
        LogError("Failed to create IP socket: %1", GetWin32ErrorString());
        return -1;
    }
    K_DEFER_N(err_guard) { closesocket(sock); };

    int reuse = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));

    if (type == SocketType::Dual || type == SocketType::IPv6) {
        int v6only = (type == SocketType::IPv6);

        if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&v6only, sizeof(v6only)) < 0) {
            LogError("Failed to change dual-stack socket option: %1", GetWin32ErrorString());
            return -1;
        }
    }

    err_guard.Disable();
    return (int)sock;
}

#else

int CreateSocket(SocketType type, int flags)
{
    int family = 0;

    switch (type) {
        case SocketType::Dual:
        case SocketType::IPv6: { family = AF_INET6; } break;
        case SocketType::IPv4: { family = AF_INET; } break;
        case SocketType::Unix: { family = AF_UNIX; } break;
    }

#if defined(SOCK_CLOEXEC)
    flags |= SOCK_CLOEXEC;
#endif

    int sock = socket(family, flags, 0);
    if (sock < 0) {
        LogError("Failed to create IP socket: %1", strerror(errno));
        return -1;
    }
    K_DEFER_N(err_guard) { close(sock); };

#if !defined(SOCK_CLOEXEC)
    fcntl(sock, F_SETFD, FD_CLOEXEC);
#endif

    int reuse = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    if (type == SocketType::Dual || type == SocketType::IPv6) {
        int v6only = (type == SocketType::IPv6);

#if defined(__OpenBSD__)
        if (!v6only) {
            LogError("Dual-stack sockets are not supported on OpenBSD");
            return -1;
        }
#else
        if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) < 0) {
            LogError("Failed to change dual-stack socket option: %1", strerror(errno));
            return -1;
        }
#endif
    }

    err_guard.Disable();
    return (int)sock;
}

#endif

bool BindIPSocket(int sock, SocketType type, const char *addr, int port)
{
    K_ASSERT(type == SocketType::Dual || type == SocketType::IPv4 || type == SocketType::IPv6);

    if (type == SocketType::IPv4) {
        struct sockaddr_in sa = {};

        sa.sin_family = AF_INET;
        sa.sin_port = htons((uint16_t)port);

        if (addr) {
            if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
                LogError("Invalid IPv4 address '%1'", addr);
                return false;
            }
        } else {
            sa.sin_addr.s_addr = htonl(INADDR_ANY);
        }

        if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
#if defined(_WIN32)
            LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, GetWin32ErrorString());
            return false;
#else
            LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, strerror(errno));
            return false;
#endif
        }
    } else {
        struct sockaddr_in6 sa = {};

        sa.sin6_family = AF_INET6;
        sa.sin6_port = htons((uint16_t)port);

        if (addr) {
            if (!strchr(addr, ':')) {
                char buf[512];
                Fmt(buf, "::FFFF:%1", addr);

                if (inet_pton(AF_INET6, buf, &sa.sin6_addr) <= 0) {
                    LogError("Invalid IPv4 or IPv6 address '%1'", addr);
                    return false;
                }
            } else {
                if (inet_pton(AF_INET6, addr, &sa.sin6_addr) <= 0) {
                    LogError("Invalid IPv6 address '%1'", addr);
                    return false;
                }
            }
        } else {
            sa.sin6_addr = IN6ADDR_ANY_INIT;
        }

        if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
#if defined(_WIN32)
            LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, GetWin32ErrorString());
            return false;
#else
            LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, strerror(errno));
            return false;
#endif
        }
    }

    return true;
}

bool BindUnixSocket(int sock, const char *path)
{
    struct sockaddr_un sa = {};

    // Protect against abtract Unix sockets on Linux
    if (!path[0]) {
        LogError("Cannot open empty UNIX socket");
        return false;
    }

    sa.sun_family = AF_UNIX;
    if (!CopyString(path, sa.sun_path)) {
        LogError("Excessive UNIX socket path length");
        return false;
    }

#if !defined(_WIN32)
    // Remove existing socket (if any)
    {
        struct stat sb;
        if (!stat(path, &sb) && S_ISSOCK(sb.st_mode)) {
            LogDebug("Removing existing socket '%1'", path);
            unlink(path);
        }
    }
#endif

    if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
#if defined(_WIN32)
        LogError("Failed to bind socket to '%1': %2", path, GetWin32ErrorString());
        return false;
#else
        LogError("Failed to bind socket to '%1': %2", path, strerror(errno));
        return false;
#endif
    }

#if !defined(_WIN32)
    chmod(path, 0666);
#endif

    return true;
}

bool ConnectIPSocket(int sock, const char *addr, int port)
{
    if (strchr(addr, ':')) {
        struct sockaddr_in6 sa = {};

        sa.sin6_family = AF_INET6;
        sa.sin6_port = htons((unsigned short)port);

        if (inet_pton(AF_INET6, addr, &sa.sin6_addr) <= 0) {
            LogError("Invalid IPv6 address '%1'", addr);
            return false;
        }

        if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
#if defined(_WIN32)
            LogError("Failed to connect to '%1' (%2): %3", addr, port, GetWin32ErrorString());
            return false;
#else
            LogError("Failed to connect to '%1' (%2): %3", addr, port, strerror(errno));
            return false;
#endif
        }
    } else {
        struct sockaddr_in sa = {};

        sa.sin_family = AF_INET;
        sa.sin_port = htons((unsigned short)port);

        if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
            LogError("Invalid IPv4 address '%1'", addr);
            return false;
        }

        if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
#if defined(_WIN32)
            LogError("Failed to connect to '%1' (%2): %3", addr, port, GetWin32ErrorString());
            return false;
#else
            LogError("Failed to connect to '%1' (%2): %3", addr, port, strerror(errno));
            return false;
#endif
        }
    }

    return true;
}

bool ConnectUnixSocket(int sock, const char *path)
{
    struct sockaddr_un sa = {};

    sa.sun_family = AF_UNIX;
    if (!CopyString(path, sa.sun_path)) {
        LogError("Excessive UNIX socket path length");
        return false;
    }

    if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
#if defined(_WIN32)
        LogError("Failed to connect to UNIX socket '%1': %2", path, GetWin32ErrorString());
        return false;
#else
        LogError("Failed to connect to UNIX socket '%1': %2", path, strerror(errno));
        return false;
#endif
    }

    return true;
}

void SetDescriptorNonBlock(int fd, bool enable)
{
#if defined(_WIN32)
    unsigned long mode = enable;
    ioctlsocket((SOCKET)fd, FIONBIO, &mode);
#else
    int flags = fcntl(fd, F_GETFL, 0);
    flags = ApplyMask(flags, O_NONBLOCK, enable);
    fcntl(fd, F_SETFL, flags);
#endif
}

void SetDescriptorRetain(int fd, bool retain)
{
#if defined(TCP_CORK)
    int flag = retain;
    setsockopt(fd, IPPROTO_TCP, TCP_CORK, &flag, sizeof(flag));
#elif defined(TCP_NOPUSH)
    int flag = retain;
    setsockopt(fd, IPPROTO_TCP, TCP_NOPUSH, &flag, sizeof(flag));

#if defined(__APPLE__)
    if (!retain) {
        send(fd, nullptr, 0, MSG_NOSIGNAL);
    }
#endif
#else
    // Nothing to see here

    (void)fd;
    (void)retain;
#endif
}

void CloseSocket(int fd)
{
    if (fd < 0)
        return;

#if defined(_WIN32)
    shutdown((SOCKET)fd, SD_BOTH);
    closesocket((SOCKET)fd);
#else
    shutdown(fd, SHUT_RDWR);
    close(fd);
#endif
}

#endif

// ------------------------------------------------------------------------
// Tasks
// ------------------------------------------------------------------------

#if !defined(__wasi__)

struct Task {
    Async *async;
    std::function<bool()> func;
};

struct WorkerData {
    AsyncPool *pool = nullptr;
    int idx;

    std::mutex queue_mutex;
    BucketArray<Task> tasks;
};

class AsyncPool {
    K_DELETE_COPY(AsyncPool)

    std::mutex pool_mutex;
    std::condition_variable pending_cv;
    std::condition_variable sync_cv;

    // Manipulate with pool_mutex locked
    int refcount = 0;

    int async_count = 0;
    std::atomic_uint next_worker { 0 };
    HeapArray<WorkerData> workers;
    std::atomic_int pending_tasks { 0 };

public:
    AsyncPool(int threads, bool leak);

    int GetWorkerCount() const { return (int)workers.len; }

    void RegisterAsync();
    void UnregisterAsync();

    void AddTask(Async *async, const std::function<bool()> &func);
    void AddTask(Async *async, int worker_idx, const std::function<bool()> &func);

    void RunWorker(int worker_idx);
    void SyncOn(Async *async, bool soon);
    bool WaitOn(Async *async, int timeout);

    void RunTasks(int worker_idx, Async *only);
    void RunTask(Task *task);
};

// thread_local breaks down on MinGW when destructors are involved, work
// around this with heap allocation.
static thread_local AsyncPool *async_default_pool = nullptr;
static thread_local AsyncPool *async_running_pool = nullptr;
static thread_local int async_running_worker_idx;
static thread_local bool async_running_task = false;

Async::Async(int threads)
{
    K_ASSERT(threads);

    if (threads > 0) {
        pool = new AsyncPool(threads, false);
    } else if (async_running_pool) {
        pool = async_running_pool;
    } else {
        if (!async_default_pool) {
            // NOTE: We're leaking one AsyncPool each time a non-worker thread uses Async()
            // for the first time. That's only one leak in most cases, when the main thread
            // is the only non-worker thread using Async, but still. Something to keep in mind.

            threads = GetCoreCount();
            async_default_pool = new AsyncPool(threads, true);
        }

        pool = async_default_pool;
    }

    pool->RegisterAsync();
}

Async::Async(Async *parent)
{
    K_ASSERT(parent);

    pool = parent->pool;
    pool->RegisterAsync();
}

Async::~Async()
{
    success = false;
    Sync();

    pool->UnregisterAsync();
}

void Async::Run(const std::function<bool()> &func)
{
    pool->AddTask(this, func);
}

void Async::Run(int worker, const std::function<bool()> &func)
{
    pool->AddTask(this, worker, func);
}

bool Async::Sync()
{
    pool->SyncOn(this, false);
    return success;
}

bool Async::SyncSoon()
{
    pool->SyncOn(this, true);
    return success;
}

bool Async::Wait(int timeout)
{
    return pool->WaitOn(this, timeout);
}

int Async::GetWorkerCount()
{
    return pool->GetWorkerCount();
}

bool Async::IsTaskRunning()
{
    return async_running_task;
}

int Async::GetWorkerIdx()
{
    return async_running_worker_idx;
}

AsyncPool::AsyncPool(int threads, bool leak)
{
    if (threads > K_ASYNC_MAX_THREADS) {
        LogError("Async cannot use more than %1 threads", K_ASYNC_MAX_THREADS);
        threads = K_ASYNC_MAX_THREADS;
    }

    // The first queue is for the main thread
    workers.AppendDefault(threads);

    refcount = leak;
}

#if defined(_WIN32)

static DWORD WINAPI RunWorkerWin32(void *udata)
{
    WorkerData *worker = (WorkerData *)udata;
    worker->pool->RunWorker(worker->idx);
    return 0;
}

#else

static void *RunWorkerPthread(void *udata)
{
    WorkerData *worker = (WorkerData *)udata;
    worker->pool->RunWorker(worker->idx);
    return nullptr;
}

#endif

void AsyncPool::RegisterAsync()
{
    std::lock_guard<std::mutex> lock_pool(pool_mutex);

    if (!async_count++) {
        for (int i = 1; i < workers.len; i++) {
            WorkerData *worker = &workers[i];

            if (!worker->pool) {
                worker->pool = this;
                worker->idx = i;

#if defined(_WIN32)
                // Our worker threads may exit after main() has returned (or exit has been called),
                // which can trigger crashes in _Cnd_do_broadcast_at_thread_exit() because it
                // tries to dereference destroyed stuff. It turns out that std::thread calls this
                // function, and we don't want that, so avoid std::thread on Windows.
                HANDLE h = CreateThread(nullptr, 0, RunWorkerWin32, worker, 0, nullptr);
                if (!h) [[unlikely]] {
                    LogError("Failed to create worker thread: %1", GetWin32ErrorString());

                    worker->pool = nullptr;
                    return;
                }

                CloseHandle(h);
#else
                pthread_t thread;
                int ret = pthread_create(&thread, nullptr, RunWorkerPthread, worker);
                if (ret) [[unlikely]] {
                    LogError("Failed to create worker thread: %1", strerror(ret));

                    worker->pool = nullptr;
                    return;
                }

                pthread_detach(thread);
#endif

                refcount++;
            }
        }
    }
}

void AsyncPool::UnregisterAsync()
{
    std::lock_guard<std::mutex> lock_pool(pool_mutex);
    async_count--;
}

void AsyncPool::AddTask(Async *async, const std::function<bool()> &func)
{
    if (async_running_pool != this) {
        int worker_idx = (next_worker++ % (int)workers.len);
        AddTask(async, worker_idx, func);
    } else {
        AddTask(async, async_running_worker_idx, func);
    }
}

void AsyncPool::AddTask(Async *async, int worker_idx, const std::function<bool()> &func)
{
    WorkerData *worker = &workers[worker_idx];

    // Add the task damn it
    {
        std::lock_guard<std::mutex> lock_queue(worker->queue_mutex);
        worker->tasks.Append({ async, func });
    }

    async->remaining_tasks++;

    int prev_pending = pending_tasks++;

    if (prev_pending >= K_ASYNC_MAX_PENDING_TASKS) {
        int worker_idx = async_running_worker_idx;

        do {
            RunTasks(worker_idx, nullptr);
        } while (pending_tasks >= K_ASYNC_MAX_PENDING_TASKS);
    } else if (!prev_pending) {
        std::lock_guard<std::mutex> lock_pool(pool_mutex);

        pending_cv.notify_all();
        sync_cv.notify_all();
    }
}

void AsyncPool::RunWorker(int worker_idx)
{
    async_running_pool = this;
    async_running_worker_idx = worker_idx;

    std::unique_lock<std::mutex> lock_pool(pool_mutex);

    while (async_count) {
        lock_pool.unlock();
        RunTasks(worker_idx, nullptr);
        lock_pool.lock();

        std::chrono::duration<int, std::milli> duration(K_ASYNC_MAX_IDLE_TIME); // Thanks C++
        pending_cv.wait_for(lock_pool, duration, [&]() { return !!pending_tasks; });
    }

    workers[worker_idx].pool = nullptr;

    if (!--refcount) {
        lock_pool.unlock();
        delete this;
    }
}

void AsyncPool::SyncOn(Async *async, bool soon)
{
    K_DEFER_C(pool = async_running_pool,
               worker_idx = async_running_worker_idx) {
        async_running_pool = pool;
        async_running_worker_idx = worker_idx;
    };

    async_running_pool = this;
    async_running_worker_idx = 0;

    while (async->remaining_tasks) {
        RunTasks(0, soon ? async : nullptr);

        std::unique_lock<std::mutex> lock_sync(pool_mutex);
        sync_cv.wait(lock_sync, [&]() { return pending_tasks || !async->remaining_tasks; });
    }
}

bool AsyncPool::WaitOn(Async *async, int timeout)
{
    std::unique_lock<std::mutex> lock_sync(pool_mutex);

    if (timeout >= 0) {
        std::chrono::milliseconds delay(timeout);
        bool done = sync_cv.wait_for(lock_sync, delay, [&]() { return !async->remaining_tasks; });
        return done;
    } else {
        sync_cv.wait(lock_sync, [&]() { return !async->remaining_tasks; });
        return true;
    }
}

void AsyncPool::RunTasks(int worker_idx, Async *only)
{
    // The '12' factor is pretty arbitrary, don't try to find meaning there
    for (int i = 0; i < workers.len * 12; i++) {
        WorkerData *worker = &workers[worker_idx];
        std::unique_lock<std::mutex> lock_queue(worker->queue_mutex, std::try_to_lock);

        if (lock_queue.owns_lock()) {
            Size idx = 0;

            if (only) {
                for (const Task &task: worker->tasks) {
                    if (task.async == only) {
                        std::swap(worker->tasks[0], worker->tasks[idx]);
                        break;
                    }
                    idx++;
                }
            }

            if (idx < worker->tasks.count) {
                Task task = std::move(worker->tasks[0]);

                worker->tasks.RemoveFirst();
                worker->tasks.Trim();

                lock_queue.unlock();

                RunTask(&task);
                continue;
            }
        }

        worker_idx = GetRandomInt(0, (int)workers.len);
    }
}

void AsyncPool::RunTask(Task *task)
{
    Async *async = task->async;

    K_DEFER_C(running = async_running_task) { async_running_task = running; };
    async_running_task = true;

    pending_tasks--;

    if (!task->func()) {
        async->success = false;
    }

    if (!--async->remaining_tasks) {
        std::lock_guard<std::mutex> lock_sync(pool_mutex);
        sync_cv.notify_all();
    }
}

#else


Async::Async(int threads)
{
    K_ASSERT(threads);
}

Async::Async(Async *parent)
{
    K_ASSERT(parent);
}

Async::~Async()
{
    // Nothing to do
}

void Async::Run(const std::function<bool()> &func)
{
    success &= !!func();
}

void Async::Run(int, const std::function<bool()> &func)
{
    success &= !!func();
}

bool Async::Sync()
{
    return success;
}

bool Async::IsTaskRunning()
{
    return false;
}

int Async::GetWorkerIdx()
{
    return 0;
}

int Async::GetWorkerCount()
{
    return 1;
}

#endif

// ------------------------------------------------------------------------
// Streams
// ------------------------------------------------------------------------

static NoDestroy<StreamReader> StdInStream(STDIN_FILENO, "<stdin>");
static NoDestroy<StreamWriter> StdOutStream(STDOUT_FILENO, "<stdout>", (int)StreamWriterFlag::LineBuffer);
static NoDestroy<StreamWriter> StdErrStream(STDERR_FILENO, "<stderr>", (int)StreamWriterFlag::LineBuffer);

extern StreamReader *const StdIn = StdInStream.Get();
extern StreamWriter *const StdOut = StdOutStream.Get();
extern StreamWriter *const StdErr = StdErrStream.Get();

static CreateDecompressorFunc *DecompressorFunctions[K_LEN(CompressionTypeNames)];
static CreateCompressorFunc *CompressorFunctions[K_LEN(CompressionTypeNames)];

K_EXIT(FlushStd)
{
    StdOut->Flush();
    StdErr->Flush();
}

void StreamReader::SetDecoder(StreamDecoder *decoder)
{
    K_ASSERT(decoder);
    K_ASSERT(!filename);
    K_ASSERT(!this->decoder);

    this->decoder = decoder;
}

bool StreamReader::Open(Span<const uint8_t> buf, const char *filename, CompressionType compression_type)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };

    error = false;
    raw_read = 0;
    read_total = 0;
    read_max = -1;

    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    source.type = SourceType::Memory;
    source.u.memory.buf = buf;
    source.u.memory.pos = 0;

    if (!InitDecompressor(compression_type))
        return false;

    err_guard.Disable();
    return true;
}

bool StreamReader::Open(int fd, const char *filename, CompressionType compression_type)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };

    error = false;
    raw_read = 0;
    read_total = 0;
    read_max = -1;

    K_ASSERT(fd >= 0);
    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    source.type = SourceType::File;
    source.u.file.fd = fd;
    source.u.file.owned = false;

    if (!InitDecompressor(compression_type))
        return false;

    err_guard.Disable();
    return true;
}

OpenResult StreamReader::Open(const char *filename, CompressionType compression_type)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };

    error = false;
    raw_read = 0;
    read_total = 0;
    read_max = -1;

    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    source.type = SourceType::File;
    {
        OpenResult ret = OpenFile(filename, (int)OpenFlag::Read, &source.u.file.fd);
        if (ret != OpenResult::Success)
            return ret;
    }
    source.u.file.owned = true;

    if (!InitDecompressor(compression_type))
        return OpenResult::OtherError;

    err_guard.Disable();
    return OpenResult::Success;
}

bool StreamReader::Open(const std::function<Size(Span<uint8_t>)> &func, const char *filename, CompressionType compression_type)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };

    error = false;
    raw_read = 0;
    read_total = 0;
    read_max = -1;

    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    source.type = SourceType::Function;
    new (&source.u.func) std::function<Size(Span<uint8_t>)>(func);

    if (!InitDecompressor(compression_type))
        return false;

    err_guard.Disable();
    return true;
}

bool StreamReader::Close(bool implicit)
{
    K_ASSERT(implicit || this != StdIn);

    if (decoder) {
        delete decoder;
        decoder = nullptr;
    }

    switch (source.type) {
        case SourceType::Memory: { source.u.memory = {}; } break;
        case SourceType::File: {
            if (source.u.file.owned && source.u.file.fd >= 0) {
                CloseDescriptor(source.u.file.fd);
            }

            source.u.file.fd = -1;
            source.u.file.owned = false;
        } break;
        case SourceType::Function: { source.u.func.~function(); } break;
    }

    bool ret = !filename || !error;

    filename = nullptr;
    error = true;
    source.type = SourceType::Memory;
    source.eof = false;
    eof = false;
    raw_len = -1;
    str_alloc.Reset();

    return ret;
}

bool StreamReader::Rewind()
{
    if (error) [[unlikely]]
        return false;

    if (decoder) [[unlikely]] {
        LogError("Cannot rewind stream with decoder");
        return false;
    }

    switch (source.type) {
        case SourceType::Memory: { source.u.memory.pos = 0; } break;
        case SourceType::File: {
            if (lseek(source.u.file.fd, 0, SEEK_SET) < 0) {
                LogError("Failed to rewind '%1': %2", filename, strerror(errno));
                error = true;
                return false;
            }
        } break;
        case SourceType::Function: {
            LogError("Cannot rewind stream '%1'", filename);
            error = true;
            return false;
        } break;
    }

    source.eof = false;
    raw_len = -1;
    raw_read = 0;
    eof = false;

    return true;
}

int StreamReader::GetDescriptor() const
{
    K_ASSERT(source.type == SourceType::File);
    return source.u.file.fd;
}

void StreamReader::SetDescriptorOwned(bool owned)
{
    K_ASSERT(source.type == SourceType::File);
    source.u.file.owned = owned;
}

Size StreamReader::Read(Span<uint8_t> out_buf)
{
#if !defined(__wasm__)
    std::lock_guard<std::mutex> lock(mutex);
#endif

    if (error) [[unlikely]]
        return -1;

    Size len = 0;

    if (decoder) {
        len = decoder->Read(out_buf.len, out_buf.ptr);
        if (len < 0) [[unlikely]] {
            error = true;
            return -1;
        }
    } else {
        len = ReadRaw(out_buf.len, out_buf.ptr);
        if (len < 0) [[unlikely]]
            return -1;
        eof = source.eof;
    }

    if (!error && read_max >= 0 && len > read_max - read_total) [[unlikely]] {
        LogError("Exceeded max stream size of %1", FmtDiskSize(read_max));
        error = true;
        return -1;
    }

    read_total += len;
    return len;
}

Size StreamReader::ReadFill(Span<uint8_t> out_buf)
{
#if !defined(__wasm__)
    std::lock_guard<std::mutex> lock(mutex);
#endif

    if (error) [[unlikely]]
        return -1;

    Size read_len = 0;

    while (out_buf.len) {
        Size len = 0;

        if (decoder) {
            len = decoder->Read(out_buf.len, out_buf.ptr);
            if (len < 0) [[unlikely]] {
                error = true;
                return -1;
            }
        } else {
            len = ReadRaw(out_buf.len, out_buf.ptr);
            if (len < 0) [[unlikely]]
                return -1;
            eof = source.eof;
        }

        out_buf.ptr += len;
        out_buf.len -= len;
        read_len += len;

        if (!error && read_max >= 0 && read_len > read_max - read_total) [[unlikely]] {
            LogError("Exceeded max stream size of %1", FmtDiskSize(read_max));
            error = true;
            return -1;
        }

        if (eof)
            break;
    }

    read_total += read_len;
    return read_len;
}

Size StreamReader::ReadAll(Size max_len, HeapArray<uint8_t> *out_buf)
{
    if (error) [[unlikely]]
        return -1;

    K_DEFER_NC(buf_guard, buf_len = out_buf->len) { out_buf->RemoveFrom(buf_len); };

    // Check virtual memory limits
    {
        Size memory_max = K_SIZE_MAX - out_buf->len - 1;

        if (memory_max <= 0) [[unlikely]] {
            LogError("Exhausted memory limit reading file '%1'", filename);
            return -1;
        }

        K_ASSERT(max_len);
        max_len = (max_len >= 0) ? std::min(max_len, memory_max) : memory_max;
    }

    // For some files (such as in /proc), the file size is reported as 0 even though there
    // is content inside, because these files are generated on demand. So we need to take
    // the slow path for apparently empty files.
    if (!decoder && ComputeRawLen() > 0) {
        if (raw_len > max_len) {
            LogError("File '%1' is too large (limit = %2)", filename, FmtDiskSize(max_len));
            return -1;
        }

        // Count one trailing byte (if possible) to avoid reallocation for users
        // who need/want to append a NUL character.
        out_buf->Grow((Size)raw_len + 1);

        Size read_len = ReadFill(out_buf->TakeAvailable());
        if (read_len < 0)
            return -1;
        out_buf->len += (Size)std::min(raw_len, (int64_t)read_len);

        buf_guard.Disable();
        return read_len;
    } else {
        Size total_len = 0;

        while (!eof) {
            Size grow = std::min(total_len ? Megabytes(1) : Kibibytes(64), K_SIZE_MAX - out_buf->len);
            out_buf->Grow(grow);

            Size read_len = Read(out_buf->TakeAvailable());
            if (read_len < 0)
                return -1;

            if (read_len > max_len - total_len) [[unlikely]] {
                LogError("File '%1' is too large (limit = %2)", filename, FmtDiskSize(max_len));
                return -1;
            }

            total_len += read_len;
            out_buf->len += read_len;
        }

        buf_guard.Disable();
        return total_len;
    }
}

int64_t StreamReader::ComputeRawLen()
{
    if (error) [[unlikely]]
        return -1;
    if (raw_read || raw_len >= 0)
        return raw_len;

    switch (source.type) {
        case SourceType::Memory: {
            raw_len = source.u.memory.buf.len;
        } break;

        case SourceType::File: {
#if defined(_WIN32)
            struct __stat64 sb;
            if (_fstat64(source.u.file.fd, &sb) < 0)
                return -1;
            raw_len = (int64_t)sb.st_size;
#else
            struct stat sb;
            if (fstat(source.u.file.fd, &sb) < 0 || S_ISFIFO(sb.st_mode) | S_ISSOCK(sb.st_mode))
                return -1;
            raw_len = (int64_t)sb.st_size;
#endif
        } break;

        case SourceType::Function: {
            return -1;
        } break;
    }

    return raw_len;
}

bool StreamReader::InitDecompressor(CompressionType type)
{
    if (type != CompressionType::None) {
        CreateDecompressorFunc *func = DecompressorFunctions[(int)type];

        if (!func) {
            LogError("%1 decompression is not available for '%2'", CompressionTypeNames[(int)type], filename);
            error = true;
            return false;
        }

        decoder = func(this, type);
        K_ASSERT(decoder);
    }

    return true;
}

Size StreamReader::ReadRaw(Size max_len, void *out_buf)
{
    ComputeRawLen();

    Size read_len = 0;
    switch (source.type) {
        case SourceType::Memory: {
            read_len = source.u.memory.buf.len - source.u.memory.pos;
            if (read_len > max_len) {
                read_len = max_len;
            }
            MemCpy(out_buf, source.u.memory.buf.ptr + source.u.memory.pos, read_len);
            source.u.memory.pos += read_len;
            source.eof = (source.u.memory.pos >= source.u.memory.buf.len);
        } break;

        case SourceType::File: {
#if defined(_WIN32)
            max_len = std::min(max_len, (Size)UINT_MAX);
            read_len = _read(source.u.file.fd, out_buf, (unsigned int)max_len);
#else
            read_len = K_RESTART_EINTR(read(source.u.file.fd, out_buf, (size_t)max_len), < 0);
#endif
            if (read_len < 0) {
                LogError("Error while reading file '%1': %2", filename, strerror(errno));
                error = true;
                return -1;
            }
            source.eof = (read_len == 0);
        } break;

        case SourceType::Function: {
            read_len = source.u.func(MakeSpan((uint8_t *)out_buf, max_len));
            if (read_len < 0) {
                error = true;
                return -1;
            }
            source.eof = (read_len == 0);
        } break;
    }

    raw_read += read_len;
    return read_len;
}

StreamDecompressorHelper::StreamDecompressorHelper(CompressionType compression_type, CreateDecompressorFunc *func)
{
    K_ASSERT(!DecompressorFunctions[(int)compression_type]);
    DecompressorFunctions[(int)compression_type] = func;
}

// XXX: Maximum line length
bool LineReader::Next(Span<char> *out_line)
{
    if (eof) {
        line_number = 0;
        return false;
    }
    if (error) [[unlikely]]
        return false;

    for (;;) {
        if (!view.len) {
            buf.Grow(K_LINE_READER_STEP_SIZE + 1);

            Span<char> available = MakeSpan(buf.end(), K_LINE_READER_STEP_SIZE);

            Size read_len = st->Read(available);
            if (read_len < 0) {
                error = true;
                return false;
            }
            buf.len += read_len;
            eof = !read_len;

            view = buf;
        }

        line = SplitStrLine(view, &view);
        if (view.len || eof) {
            line.ptr[line.len] = 0;
            line_number++;
            *out_line = line;
            return true;
        }

        buf.len = view.ptr - line.ptr;
        MemMove(buf.ptr, line.ptr, buf.len);
    }
}

void LineReader::PushLogFilter()
{
    K::PushLogFilter([this](LogLevel level, const char *, const char *msg, FunctionRef<LogFunc> func) {
        char ctx[1024];

        if (line_number > 0) {
            Fmt(ctx, "%1(%2): ", st->GetFileName(), line_number);
        } else {
            Fmt(ctx, "%1: ", st->GetFileName());
        }

        func(level, ctx, msg);
    });
}

void StreamWriter::SetEncoder(StreamEncoder *encoder)
{
    K_ASSERT(encoder);
    K_ASSERT(!filename);
    K_ASSERT(!this->encoder);

    this->encoder = encoder;
}

bool StreamWriter::Open(HeapArray<uint8_t> *mem, const char *filename, unsigned int,
                        CompressionType compression_type, CompressionSpeed compression_speed)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };
    error = false;
    raw_written = 0;

    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    dest.type = DestinationType::Memory;
    dest.u.mem.memory = mem;
    dest.u.mem.start = mem->len;
    dest.vt100 = false;

    if (!InitCompressor(compression_type, compression_speed))
        return false;

    err_guard.Disable();
    return true;
}

bool StreamWriter::Open(int fd, const char *filename, unsigned int flags,
                        CompressionType compression_type, CompressionSpeed compression_speed)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };
    error = false;
    raw_written = 0;

    K_ASSERT(fd >= 0);
    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    InitFile(flags);

    dest.u.file.fd = fd;
    dest.vt100 = FileIsVt100(fd);

    if (!InitCompressor(compression_type, compression_speed))
        return false;

    err_guard.Disable();
    return true;
}

bool StreamWriter::Open(const char *filename, unsigned int flags,
                        CompressionType compression_type, CompressionSpeed compression_speed)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };
    error = false;
    raw_written = 0;

    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    InitFile(flags);

    dest.u.file.atomic = (flags & (int)StreamWriterFlag::Atomic);
    dest.u.file.exclusive = (flags & (int)StreamWriterFlag::Exclusive);

    if (dest.u.file.atomic) {
        Span<const char> directory = GetPathDirectory(filename);

        if (dest.u.file.exclusive) {
            int fd = OpenFile(filename, (int)OpenFlag::Write | (int)OpenFlag::Exclusive);
            if (fd < 0)
                return false;
            CloseDescriptor(fd);

            dest.u.file.unlink_on_error = true;
        }

#if defined(O_TMPFILE)
        {
            static bool has_proc = !access("/proc/self/fd", X_OK);

            if (has_proc) {
                const char *dirname = DuplicateString(directory, &str_alloc).ptr;
                dest.u.file.fd = K_RESTART_EINTR(open(dirname, O_WRONLY | O_TMPFILE | O_CLOEXEC, 0644), < 0);

                if (dest.u.file.fd >= 0) {
                    dest.u.file.owned = true;
                } else if (errno != EINVAL && errno != EOPNOTSUPP) {
                    LogError("Cannot open temporary file in '%1': %2", directory, strerror(errno));
                    return false;
                }
            }
        }
#endif

        if (!dest.u.file.owned) {
            const char *basename = SplitStrReverseAny(filename, K_PATH_SEPARATORS).ptr;

            dest.u.file.tmp_filename = CreateUniqueFile(directory, basename, ".tmp", &str_alloc, &dest.u.file.fd);
            if (!dest.u.file.tmp_filename)
                return false;
            dest.u.file.owned = true;
        }
    } else {
        unsigned int open_flags = (int)OpenFlag::Write;
        open_flags |= dest.u.file.exclusive ? (int)OpenFlag::Exclusive : 0;

        dest.u.file.fd = OpenFile(filename, open_flags);
        if (dest.u.file.fd < 0)
            return false;
        dest.u.file.owned = true;

        dest.u.file.unlink_on_error = dest.u.file.exclusive;
    }
    dest.vt100 = FileIsVt100(dest.u.file.fd);

    if (!InitCompressor(compression_type, compression_speed))
        return false;

    err_guard.Disable();
    return true;
}

bool StreamWriter::Open(const std::function<bool(Span<const uint8_t>)> &func, const char *filename, unsigned int,
                        CompressionType compression_type, CompressionSpeed compression_speed)
{
    Close(true);

    K_DEFER_N(err_guard) { error = true; };
    error = false;
    raw_written = 0;

    K_ASSERT(filename);
    this->filename = DuplicateString(filename, &str_alloc).ptr;

    dest.type = DestinationType::Function;
    new (&dest.u.func) std::function<bool(Span<const uint8_t>)>(func);
    dest.vt100 = false;

    if (!InitCompressor(compression_type, compression_speed))
        return false;

    err_guard.Disable();
    return true;
}

bool StreamWriter::Rewind()
{
    if (error) [[unlikely]]
        return false;

    if (encoder) [[unlikely]] {
        LogError("Cannot rewind stream with encoder");
        return false;
    }

    switch (dest.type) {
        case DestinationType::Memory: { dest.u.mem.memory->RemoveFrom(dest.u.mem.start); } break;

        case DestinationType::LineFile:
        case DestinationType::BufferedFile:
        case DestinationType::DirectFile: {
            if (lseek(dest.u.file.fd, 0, SEEK_SET) < 0) {
                LogError("Failed to rewind '%1': %2", filename, strerror(errno));
                error = true;
                return false;
            }

#if defined(_WIN32)
            HANDLE h = (HANDLE)_get_osfhandle(dest.u.file.fd);

            if (!SetEndOfFile(h)) {
                LogError("Failed to truncate '%1': %2", filename, GetWin32ErrorString());
                error = true;
                return false;
            }
#else
            if (ftruncate(dest.u.file.fd, 0) < 0) {
                LogError("Failed to truncate '%1': %2", filename, strerror(errno));
                error = true;
                return false;
            }
#endif

            dest.u.file.buf_used = 0;
        } break;

        case DestinationType::Function: {
            LogError("Cannot rewind stream '%1'", filename);
            error = true;
            return false;
        } break;
    }

    raw_written = 0;

    return true;
}

bool StreamWriter::Flush()
{
#if !defined(__wasm__)
    std::lock_guard<std::mutex> lock(mutex);
#endif

    if (error) [[unlikely]]
        return false;

    switch (dest.type) {
        case DestinationType::Memory: return true;

        case DestinationType::LineFile:
        case DestinationType::BufferedFile: {
            if (!FlushBuffer())
                return false;
        } [[fallthrough]];
        case DestinationType::DirectFile: {
            if (!FlushFile(dest.u.file.fd, filename)) {
                error = true;
                return false;
            }

            return true;
        } break;

        case DestinationType::Function: return true;
    }

    K_UNREACHABLE();
}

int StreamWriter::GetDescriptor() const
{
    K_ASSERT(dest.type == DestinationType::BufferedFile ||
              dest.type == DestinationType::LineFile ||
              dest.type == DestinationType::DirectFile);

    return dest.u.file.fd;
}

void StreamWriter::SetDescriptorOwned(bool owned)
{
    K_ASSERT(dest.type == DestinationType::BufferedFile ||
              dest.type == DestinationType::LineFile ||
              dest.type == DestinationType::DirectFile);

    dest.u.file.owned = owned;
}

bool StreamWriter::Write(Span<const uint8_t> buf)
{
#if !defined(__wasm__)
    std::lock_guard<std::mutex> lock(mutex);
#endif

    if (error) [[unlikely]]
        return false;

    if (encoder) {
        error |= !encoder->Write(buf);
        return !error;
    } else {
        return WriteRaw(buf);
    }
}

bool StreamWriter::Close(bool implicit)
{
    K_ASSERT(implicit || this != StdOut);
    K_ASSERT(implicit || this != StdErr);

    if (encoder) {
        error = error || !encoder->Finalize();

        delete encoder;
        encoder = nullptr;
    }

    switch (dest.type) {
        case DestinationType::Memory: { dest.u.mem = {}; } break;

        case DestinationType::BufferedFile:
        case DestinationType::LineFile: {
            if (IsValid()) {
                FlushBuffer();
            }
        } [[fallthrough]];
        case DestinationType::DirectFile: {
            if (dest.u.file.atomic) {
                if (IsValid()) {
                    if (implicit) {
                        LogDebug("Deleting implicitly closed file '%1'", filename);
                        error = true;
                    } else if (!FlushFile(dest.u.file.fd, filename)) {
                        error = true;
                    }
                }

                if (IsValid()) {
#if defined(O_TMPFILE)
                    if (!dest.u.file.tmp_filename) {
                        bool linked = false;

                        // AT_EMPTY_PATH requires CAP_DAC_READ_SEARCH so use the /proc trick instead.
                        // Will revisit once this restriction is lifted (if ever).
                        char proc[256];
                        Fmt(proc, "/proc/self/fd/%1", dest.u.file.fd);

                        for (int i = 0; i < 10; i++) {
                            if (linkat(AT_FDCWD, proc, AT_FDCWD, filename, AT_SYMLINK_FOLLOW) < 0) {
                                if (errno == EEXIST) {
                                    unlink(filename);
                                    continue;
                                }

                                LogError("Failed to materialize file '%1': %2", filename, strerror(errno));
                                return false;
                            }

                            linked = true;
                            break;
                        }

                        // The linkat() call cannot overwrite an existing file. We try to unlink() the file if
                        // needed several times (see loop above) to make it work but it it still doesn't, link to
                        // a temporary file and let RenameFile() handle the final step. Should be rare!
                        if (!linked) {
                            Span<const char> directory = GetPathDirectory(filename);
                            const char *basename = SplitStrReverseAny(filename, K_PATH_SEPARATORS).ptr;

                            dest.u.file.tmp_filename = CreateUniquePath(directory, basename, ".tmp", &str_alloc, [&](const char *path) {
                                return !linkat(AT_FDCWD, proc, AT_FDCWD, path, AT_SYMLINK_FOLLOW);
                            });
                            if (!dest.u.file.tmp_filename) {
                                LogError("Failed to materialize file '%1': %2", filename, strerror(errno));
                                error = true;
                            }
                        }
                    }
#endif

                    if (dest.u.file.owned) {
                        CloseDescriptor(dest.u.file.fd);
                        dest.u.file.owned = false;
                    }

                    if (dest.u.file.tmp_filename) {
                        unsigned int flags = (int)RenameFlag::Overwrite | (int)RenameFlag::Sync;

                        if (RenameFile(dest.u.file.tmp_filename, filename, flags) == RenameResult::Success) {
                            dest.u.file.tmp_filename = nullptr;
                        } else {
                            error = true;
                        }
                    }
                } else {
                    error = true;
                }
            }

            if (dest.u.file.owned) {
                CloseDescriptor(dest.u.file.fd);
                dest.u.file.owned = false;
            }

            // Try to clean up, though we can't do much if that fails (except log error)
            if (dest.u.file.tmp_filename) {
                UnlinkFile(dest.u.file.tmp_filename);
            }
            if (error && dest.u.file.unlink_on_error) {
                UnlinkFile(filename);
            }

            MemSet(&dest.u.file, 0, K_SIZE(dest.u.file));
        } break;

        case DestinationType::Function: {
            error |= IsValid() && !dest.u.func({});
            dest.u.func.~function();
        } break;
    }

    bool ret = !filename || !error;

    filename = nullptr;
    error = true;
    dest.type = DestinationType::Memory;
    str_alloc.Reset();

    return ret;
}

void StreamWriter::InitFile(unsigned int flags)
{
    bool direct = (flags & (int)StreamWriterFlag::NoBuffer);
    bool line = (flags & (int)StreamWriterFlag::LineBuffer);

    K_ASSERT(!direct || !line);

    MemSet(&dest.u.file, 0, K_SIZE(dest.u.file));

    if (direct) {
        dest.type = DestinationType::DirectFile;
    } else if (line) {
        dest.type = DestinationType::LineFile;
        dest.u.file.buf = AllocateSpan<uint8_t>(&str_alloc, Kibibytes(4));
    } else {
        dest.type = DestinationType::BufferedFile;
        dest.u.file.buf = AllocateSpan<uint8_t>(&str_alloc, Kibibytes(4));
    }
}

bool StreamWriter::FlushBuffer()
{
    K_ASSERT(!error);
    K_ASSERT(dest.type == DestinationType::BufferedFile ||
              dest.type == DestinationType::LineFile);

    while (dest.u.file.buf_used) {
#if defined(_WIN32)
        Size write_len = _write(dest.u.file.fd, dest.u.file.buf.ptr, (unsigned int)dest.u.file.buf_used);
#else
        Size write_len = K_RESTART_EINTR(write(dest.u.file.fd, dest.u.file.buf.ptr, (size_t)dest.u.file.buf_used), < 0);
#endif

        if (write_len < 0) {
            LogError("Failed to write to '%1': %2", filename, strerror(errno));
            error = true;
            return false;
        }

        Size move_len = dest.u.file.buf_used - write_len;
        MemMove(dest.u.file.buf.ptr, dest.u.file.buf.ptr + write_len, move_len);
        dest.u.file.buf_used -= write_len;

        raw_written += write_len;
    }

    return true;
}

bool StreamWriter::InitCompressor(CompressionType type, CompressionSpeed speed)
{
    if (type != CompressionType::None) {
        CreateCompressorFunc *func = CompressorFunctions[(int)type];

        if (!func) {
            LogError("%1 compression is not available for '%2'", CompressionTypeNames[(int)type], filename);
            error = true;
            return false;
        }

        encoder = func(this, type, speed);
        K_ASSERT(encoder);
    }

    return true;
}

#if defined(_WIN32) || defined(__APPLE__)

static void *memrchr(const void *m, int c, size_t n)
{
    const uint8_t *ptr = (const uint8_t *)m + n;

    while (ptr-- > m) {
        if (*ptr == c)
            return (void *)ptr;
    }

    return nullptr;
}

#endif

bool StreamWriter::WriteRaw(Span<const uint8_t> buf)
{
    switch (dest.type) {
        case DestinationType::Memory: {
            // dest.u.memory->Append(buf) would work but it's probably slower
            dest.u.mem.memory->Grow(buf.len);
            MemCpy(dest.u.mem.memory->ptr + dest.u.mem.memory->len, buf.ptr, buf.len);
            dest.u.mem.memory->len += buf.len;

            raw_written += buf.len;
        } break;

        case DestinationType::BufferedFile: {
            if (!buf.len)
                return true;

            for (;;) {
                Size copy_len = std::min(buf.len, dest.u.file.buf.len - dest.u.file.buf_used);
                MemCpy(dest.u.file.buf.ptr + dest.u.file.buf_used, buf.ptr, copy_len);

                buf.ptr += copy_len;
                buf.len -= copy_len;
                dest.u.file.buf_used += copy_len;

                if (!buf.len)
                    break;
                if (!FlushBuffer())
                    return false;
            }
        } break;

        case DestinationType::LineFile: {
            while (buf.len) {
                const uint8_t *end = (const uint8_t *)memrchr(buf.ptr, '\n', (size_t)buf.len);

                if (end++) {
                    Size copy_len = std::min((Size)(end - buf.ptr), dest.u.file.buf.len - dest.u.file.buf_used);
                    MemCpy(dest.u.file.buf.ptr + dest.u.file.buf_used, buf.ptr, copy_len);

                    buf.ptr += copy_len;
                    buf.len -= copy_len;
                    dest.u.file.buf_used += copy_len;
                } else {
                    Size copy_len = std::min(buf.len, dest.u.file.buf.len - dest.u.file.buf_used);
                    MemCpy(dest.u.file.buf.ptr + dest.u.file.buf_used, buf.ptr, copy_len);

                    buf.ptr += copy_len;
                    buf.len -= copy_len;
                    dest.u.file.buf_used += copy_len;

                    if (!buf.len)
                        break;
                }

                if (!FlushBuffer())
                    return false;
            }
        } break;

        case DestinationType::DirectFile: {
            while (buf.len) {
#if defined(_WIN32)
                unsigned int int_len = (unsigned int)std::min(buf.len, (Size)UINT_MAX);
                Size write_len = _write(dest.u.file.fd, buf.ptr, int_len);
#else
                Size write_len = K_RESTART_EINTR(write(dest.u.file.fd, buf.ptr, (size_t)buf.len), < 0);
#endif

                if (write_len < 0) {
                    LogError("Failed to write to '%1': %2", filename, strerror(errno));
                    error = true;
                    return false;
                }

                buf.ptr += write_len;
                buf.len -= write_len;

                raw_written += write_len;
            }
        } break;

        case DestinationType::Function: {
            // Empty writes are used to "close" the file.. don't!
            if (!buf.len)
                return true;

            if (!dest.u.func(buf)) {
                error = true;
                return false;
            }

            raw_written += buf.len;
        } break;
    }

    return true;
}

StreamCompressorHelper::StreamCompressorHelper(CompressionType compression_type, CreateCompressorFunc *func)
{
    K_ASSERT(!CompressorFunctions[(int)compression_type]);
    CompressorFunctions[(int)compression_type] = func;
}

bool SpliceStream(StreamReader *reader, int64_t max_len, StreamWriter *writer, Span<uint8_t> buf,
                  FunctionRef<void(int64_t, int64_t)> progress)
{
    K_ASSERT(buf.len >= Kibibytes(2));

    if (!reader->IsValid())
        return false;

    int64_t raw_len = reader->ComputeRawLen();
    int64_t total_len = 0;

    do {
        Size read_len = reader->Read(buf);
        if (read_len < 0)
            return false;

        if (max_len >= 0 && read_len > max_len - total_len) [[unlikely]] {
            LogError("File '%1' is too large (limit = %2)", reader->GetFileName(), FmtDiskSize(max_len));
            return false;
        }
        total_len += read_len;

        if (!writer->Write(buf.ptr, read_len))
            return false;

        progress(reader->GetRawRead(), raw_len);
    } while (!reader->IsEOF());

    return true;
}

bool IsCompressorAvailable(CompressionType compression_type)
{
    return CompressorFunctions[(int)compression_type];
}

bool IsDecompressorAvailable(CompressionType compression_type)
{
    return DecompressorFunctions[(int)compression_type];
}

// ------------------------------------------------------------------------
// INI
// ------------------------------------------------------------------------

IniParser::LineType IniParser::FindNextLine(IniProperty *out_prop)
{
    if (error) [[unlikely]]
        return LineType::Exit;

    K_DEFER_N(err_guard) { error = true; };

    Span<char> line;
    while (reader.Next(&line)) {
        line = TrimStr(line);

        if (!line.len || line[0] == ';' || line[0] == '#') {
            // Ignore this line (empty or comment)
        } else if (line[0] == '[') {
            if (line.len < 2 || line[line.len - 1] != ']') {
                LogError("Malformed [section] line");
                return LineType::Exit;
            }

            Span<const char> section = TrimStr(line.Take(1, line.len - 2));
            if (!section.len) {
                LogError("Empty section name");
                return LineType::Exit;
            }

            current_section.RemoveFrom(0);
            current_section.Grow(section.len + 1);
            current_section.Append(section);
            current_section.ptr[current_section.len] = 0;

            err_guard.Disable();
            return LineType::Section;
        } else {
            Span<char> value;

            Span<char> key = TrimStr(SplitStr(line, '=', &value));
            if (!key.len || key.end() == line.end()) {
                LogError("Expected [section] or <key> = <value> pair");
                return LineType::Exit;
            }
            key.ptr[key.len] = 0;

            value = TrimStr(value);
            *value.end() = 0;

            out_prop->section = current_section;
            out_prop->key = key;
            out_prop->value = value;

            err_guard.Disable();
            return LineType::KeyValue;
        }
    }
    if (!reader.IsValid())
        return LineType::Exit;

    eof = true;

    err_guard.Disable();
    return LineType::Exit;
}

bool IniParser::Next(IniProperty *out_prop)
{
    LineType type;
    while ((type = FindNextLine(out_prop)) == LineType::Section);
    return type == LineType::KeyValue;
}

bool IniParser::NextInSection(IniProperty *out_prop)
{
    LineType type = FindNextLine(out_prop);
    return type == LineType::KeyValue;
}

// ------------------------------------------------------------------------
// Assets
// ------------------------------------------------------------------------

#if defined(FELIX_HOT_ASSETS)

static char assets_filename[4096];
static int64_t assets_last_check = -1;
static HeapArray<AssetInfo> assets;
static HashTable<const char *, const AssetInfo *> assets_map;
static BlockAllocator assets_alloc;
static bool assets_ready;

bool ReloadAssets()
{
    const Span<const AssetInfo> *lib_assets = nullptr;

    // Make asset library filename
    if (!assets_filename[0]) {
        Span<const char> prefix = GetApplicationExecutable();
#if defined(_WIN32)
        SplitStrReverse(prefix, '.', &prefix);
#endif

        Fmt(assets_filename, "%1_assets%2", prefix, K_SHARED_LIBRARY_EXTENSION);
    }

    // Check library time
    {
        FileInfo file_info;
        if (StatFile(assets_filename, &file_info) != StatResult::Success)
            return false;

        if (assets_last_check == file_info.mtime)
            return false;
        assets_last_check = file_info.mtime;
    }

#if defined(_WIN32)
    HMODULE h;
    if (win32_utf8) {
        h = LoadLibraryA(assets_filename);
    } else {
        wchar_t filename_w[4096];
        if (ConvertUtf8ToWin32Wide(assets_filename, filename_w) < 0)
            return false;

        h = LoadLibraryW(filename_w);
    }
    if (!h) {
        LogError("Cannot load library '%1'", assets_filename);
        return false;
    }
    K_DEFER { FreeLibrary(h); };

    lib_assets = (const Span<const AssetInfo> *)(void *)GetProcAddress(h, "EmbedAssets");
#else
    void *h = dlopen(assets_filename, RTLD_LAZY | RTLD_LOCAL);
    if (!h) {
        LogError("Cannot load library '%1': %2", assets_filename, dlerror());
        return false;
    }
    K_DEFER { dlclose(h); };

    lib_assets = (const Span<const AssetInfo> *)dlsym(h, "EmbedAssets");
#endif
    if (!lib_assets) {
        LogError("Cannot find symbol 'EmbedAssets' in library '%1'", assets_filename);
        return false;
    }

    // We are not allowed to fail from now on
    assets.Clear();
    assets_map.Clear();
    assets_alloc.Reset();

    for (const AssetInfo &asset: *lib_assets) {
        AssetInfo asset_copy;

        asset_copy.name = DuplicateString(asset.name, &assets_alloc).ptr;
        asset_copy.data = AllocateSpan<uint8_t>(&assets_alloc, asset.data.len);
        MemCpy((void *)asset_copy.data.ptr, asset.data.ptr, asset.data.len);
        asset_copy.compression_type = asset.compression_type;

        assets.Append(asset_copy);
    }
    for (const AssetInfo &asset: assets) {
        assets_map.Set(&asset);
    }

    assets_ready = true;
    return true;
}

Span<const AssetInfo> GetEmbedAssets()
{
    if (!assets_ready) {
        ReloadAssets();
        K_ASSERT(assets_ready);
    }

    return assets;
}

const AssetInfo *FindEmbedAsset(const char *name)
{
    if (!assets_ready) {
        ReloadAssets();
        K_ASSERT(assets_ready);
    }

    return assets_map.FindValue(name, nullptr);
}

#else

HashTable<const char *, const AssetInfo *> EmbedAssetsMap;
static bool assets_ready;

void InitEmbedMap(Span<const AssetInfo> assets)
{
    if (!assets_ready) [[likely]] {
        for (const AssetInfo &asset: assets) {
            EmbedAssetsMap.Set(&asset);
        }
    }
}

#endif

bool PatchFile(StreamReader *reader, StreamWriter *writer,
               FunctionRef<void(Span<const char>, StreamWriter *)> func)
{
    LineReader splitter(reader);

    Span<const char> line;
    while (splitter.Next(&line) && writer->IsValid()) {
        while (line.len) {
            Span<const char> before = SplitStr(line, "{{", &line);

            writer->Write(before);

            if (before.end() < line.ptr) {
                Span<const char> expr = SplitStr(line, "}}", &line);

                if (expr.end() < line.ptr) {
                    func(expr, writer);
                } else {
                    Print(writer, "{{%1", expr);
                }
            }
        }

        writer->Write('\n');
    }

    if (!reader->IsValid())
        return false;
    if (!writer->IsValid())
        return false;

    return true;
}

bool PatchFile(Span<const uint8_t> data, StreamWriter *writer,
               FunctionRef<void(Span<const char>, StreamWriter *)> func)
{
    StreamReader reader(data, "<asset>");

    if (!PatchFile(&reader, writer, func)) {
        K_ASSERT(reader.IsValid());
        return false;
    }

    return true;
}

bool PatchFile(const AssetInfo &asset, StreamWriter *writer,
               FunctionRef<void(Span<const char>, StreamWriter *)> func)
{
    StreamReader reader(asset.data, "<asset>", asset.compression_type);

    if (!PatchFile(&reader, writer, func)) {
        K_ASSERT(reader.IsValid());
        return false;
    }

    return true;
}

Span<const uint8_t> PatchFile(Span<const uint8_t> data, Allocator *alloc,
                              FunctionRef<void(Span<const char>, StreamWriter *)> func)
{
    K_ASSERT(alloc);

    HeapArray<uint8_t> buf(alloc);
    StreamWriter writer(&buf, "<asset>");

    PatchFile(data, &writer, func);

    bool success = writer.Close();
    K_ASSERT(success);

    buf.Grow(1);
    buf.ptr[buf.len] = 0;

    return buf.Leak();
}

Span<const uint8_t> PatchFile(const AssetInfo &asset, Allocator *alloc,
                              FunctionRef<void(Span<const char>, StreamWriter *)> func)
{
    K_ASSERT(alloc);

    HeapArray<uint8_t> buf(alloc);
    StreamWriter writer(&buf, "<asset>", 0, asset.compression_type);

    PatchFile(asset, &writer, func);

    bool success = writer.Close();
    K_ASSERT(success);

    buf.Grow(1);
    buf.ptr[buf.len] = 0;

    return buf.Leak();
}

Span<const char> PatchFile(Span<const char> data, Allocator *alloc,
                           FunctionRef<void(Span<const char> key, StreamWriter *)> func)
{
    Span<const uint8_t> ret = PatchFile(data.As<const uint8_t>(), alloc, func);
    return ret.As<const char>();
}

// ------------------------------------------------------------------------
// Translations
// ------------------------------------------------------------------------

typedef HashMap<const char *, const char *> TranslationMap;

static HeapArray<TranslationTable> i18n_tables;
static NoDestroy<HeapArray<TranslationMap>> i18n_maps;
static HashMap<Span<const char> , const TranslationTable *> i18n_locales;

static const TranslationTable *i18n_default_table;
static const TranslationMap *i18n_default_map;
static thread_local const TranslationTable *i18n_thread_table = i18n_default_table;
static thread_local const TranslationMap *i18n_thread_map = i18n_default_map;

static void SetDefaultLocale(const char *default_lang)
{
    if (i18n_default_table)
        return;

    // Obey environment settings, even on Windows, for easy override
    {
        // Yeah this order makes perfect sense. Don't ask.
        static const char *const EnvVariables[] = { "LANGUAGE", "LC_MESSAGES", "LC_ALL", "LANG" };

        for (const char *variable: EnvVariables) {
            const char *env = GetEnv(variable);

            if (env) {
                ChangeThreadLocale(env);

                i18n_default_table = i18n_thread_table;
                i18n_default_map = i18n_thread_map;

                if (i18n_default_table)
                    return;
            }
        }
    }

#if defined(_WIN32)
    {
        wchar_t buffer[16384];
        unsigned long languages = 0;
        unsigned long size = K_LEN(buffer);

        if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &languages, buffer, &size)) {
            if (languages) {
                char lang[256] = {};
                ConvertWin32WideToUtf8(buffer, lang);

                ChangeThreadLocale(lang);

                i18n_default_table = i18n_thread_table;
                i18n_default_map = i18n_thread_map;

                if (i18n_default_table)
                    return;
            }
        } else {
            LogError("Failed to retrieve preferred Windows UI language: %1", GetWin32ErrorString());
        }
    }
#endif

    ChangeThreadLocale(default_lang);
    K_CRITICAL(i18n_thread_table, "Missing default locale");

    i18n_default_table = i18n_thread_table;
    i18n_default_map = i18n_thread_map;
}

void InitLocales(Span<const TranslationTable> tables, const char *default_lang)
{
    K_ASSERT(!i18n_tables.len);

    for (const TranslationTable &table: tables) {
        i18n_tables.Append(table);

        TranslationMap *map = i18n_maps->AppendDefault();

        for (const TranslationTable::Pair &pair: table.messages) {
            map->Set(pair.key, pair.value);
        }
    }
    for (const TranslationTable &table: i18n_tables) {
        i18n_locales.Set(table.language, &table);
    }

    SetDefaultLocale(default_lang);
}

void ChangeThreadLocale(const char *name)
{
    Span<const char> lang = name ? SplitStrAny(name, "_-") : "";
    const TranslationTable *table = i18n_locales.FindValue(lang, nullptr);

    if (table) {
        Size idx = table - i18n_tables.ptr;

        i18n_thread_table = table;
        i18n_thread_map = &(*i18n_maps)[idx];
    } else {
        i18n_thread_table = i18n_default_table;
        i18n_thread_map = i18n_default_map;
    }
}

const char *GetThreadLocale()
{
    K_ASSERT(i18n_thread_table);
    return i18n_thread_table->language;
}

const char *T(const char *key)
{
    if (!i18n_thread_map)
        return key;

    return i18n_thread_map->FindValue(key, key);
}

// ------------------------------------------------------------------------
// Options
// ------------------------------------------------------------------------

static inline bool IsOption(const char *arg)
{
    return arg[0] == '-' && arg[1];
}

static inline bool IsLongOption(const char *arg)
{
    return arg[0] == '-' && arg[1] == '-' && arg[2];
}

static inline bool IsDashDash(const char *arg)
{
    return arg[0] == '-' && arg[1] == '-' && !arg[2];
}

const char *OptionParser::Next()
{
    current_option = nullptr;
    current_value = nullptr;
    test_failed = false;

    // Support aggregate short options, such as '-fbar'. Note that this can also be
    // parsed as the short option '-f' with value 'bar', if the user calls
    // ConsumeOptionValue() after getting '-f'.
    if (smallopt_offset) {
        const char *opt = args[pos];

        buf[1] = opt[smallopt_offset];
        current_option = buf;

        if (!opt[++smallopt_offset]) {
            smallopt_offset = 0;
            pos++;
        }

        return current_option;
    }

    if (mode == OptionMode::Stop && (pos >= limit || !IsOption(args[pos]))) {
        limit = pos;
        return nullptr;
    }

    // Skip non-options, do the permutation once we reach an option or the last argument
    Size next_index = pos;
    while (next_index < limit && !IsOption(args[next_index])) {
        next_index++;
    }
    if (mode == OptionMode::Rotate) {
        std::rotate(args.ptr + pos, args.ptr + next_index, args.end());
        limit -= (next_index - pos);
    } else if (mode == OptionMode::Skip) {
        pos = next_index;
    }
    if (pos >= limit)
        return nullptr;

    const char *opt = args[pos];

    if (IsLongOption(opt)) {
        const char *needle = strchr(opt, '=');
        if (needle) {
            // We can reorder args, but we don't want to change strings. So copy the
            // option up to '=' in our buffer. And store the part after '=' as the
            // current value.
            Size len = needle - opt;
            if (len > K_SIZE(buf) - 1) {
                len = K_SIZE(buf) - 1;
            }
            MemCpy(buf, opt, len);
            buf[len] = 0;
            current_option = buf;
            current_value = needle + 1;
        } else {
            current_option = opt;
        }
        pos++;
    } else if (IsDashDash(opt)) {
        // We may have previously moved non-options to the end of args. For example,
        // at this point 'a b c -- d e' is reordered to '-- d e a b c'. Fix it.
        std::rotate(args.ptr + pos + 1, args.ptr + limit, args.end());
        limit = pos;
        pos++;
    } else if (opt[2]) {
        // We either have aggregated short options or one short option with a value,
        // depending on whether or not the user calls ConsumeOptionValue().
        buf[0] = '-';
        buf[1] = opt[1];
        buf[2] = 0;
        current_option = buf;
        smallopt_offset = opt[2] ? 2 : 0;

        // The main point of Skip mode is to be able to parse arguments in
        // multiple passes. This does not work well with ambiguous short options
        // (such as -oOption, which can be interpeted as multiple one-char options
        // or one -o option with a value), so force the value interpretation.
        if (mode == OptionMode::Skip) {
            ConsumeValue();
        }
    } else {
        current_option = opt;
        pos++;
    }

    return current_option;
}

const char *OptionParser::ConsumeValue()
{
    if (current_value)
        return current_value;

    // Support '-fbar' where bar is the value, but only for the first short option
    // if it's an aggregate.
    if (smallopt_offset == 2 && args[pos][2]) {
        smallopt_offset = 0;
        current_value = args[pos] + 2;
        pos++;
    // Support '-f bar' and '--foo bar', see ConsumeOption() for '--foo=bar'
    } else if (current_option != buf && pos < limit && !IsOption(args[pos])) {
        current_value = args[pos];
        pos++;
    }

    return current_value;
}

const char *OptionParser::ConsumeNonOption()
{
    if (pos == args.len)
        return nullptr;
    // Beyond limit there are only non-options, the limit is moved when we move non-options
    // to the end or upon encouteering a double dash '--'.
    if (pos < limit && IsOption(args[pos]))
        return nullptr;

    return args[pos++];
}

void OptionParser::ConsumeNonOptions(HeapArray<const char *> *non_options)
{
    const char *non_option;
    while ((non_option = ConsumeNonOption())) {
        non_options->Append(non_option);
    }
}

bool OptionParser::Test(const char *test1, const char *test2, OptionType type)
{
    K_ASSERT(test1 && IsOption(test1));
    K_ASSERT(!test2 || IsOption(test2));

    if (TestStr(test1, current_option) || (test2 && TestStr(test2, current_option))) {
        switch (type) {
            case OptionType::NoValue: {
                if (current_value) {
                    LogError("Option '%1' does not support values", current_option);
                    test_failed = true;
                    return false;
                }
            } break;
            case OptionType::Value: {
                if (!ConsumeValue()) {
                    LogError("Option '%1' requires a value", current_option);
                    test_failed = true;
                    return false;
                }
            } break;
            case OptionType::OptionalValue: {
                ConsumeValue();
            } break;
        }

        return true;
    } else {
        return false;
    }
}

void OptionParser::LogUnknownError() const
{
    if (!TestHasFailed()) {
        LogError("Unknown option '%1'", current_option);
    }
}

void OptionParser::LogUnusedArguments() const
{
    if (pos < args.len) {
       LogWarning("Unused command-line arguments");
    }
}

// ------------------------------------------------------------------------
// Console prompter (simplified readline)
// ------------------------------------------------------------------------

static bool input_is_raw;
#if defined(_WIN32)
static HANDLE stdin_handle;
static DWORD input_orig_mode;
#elif !defined(__wasm__)
static struct termios input_orig_tio;
#endif

ConsolePrompter::ConsolePrompter()
{
    entries.AppendDefault();
}

static bool EnableRawMode()
{
#if defined(_WIN32)
    static bool init_atexit = false;

    if (!input_is_raw) {
        stdin_handle = (HANDLE)_get_osfhandle(STDIN_FILENO);

        if (GetConsoleMode(stdin_handle, &input_orig_mode)) {
            input_is_raw = SetConsoleMode(stdin_handle, ENABLE_WINDOW_INPUT);

            if (input_is_raw && !init_atexit) {
                atexit([]() { SetConsoleMode(stdin_handle, input_orig_mode); });
                init_atexit = true;
            }
        }
    }

    return input_is_raw;
#elif !defined(__wasm__)
    static bool init_atexit = false;

    if (!input_is_raw) {
        if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &input_orig_tio) >= 0) {
            struct termios raw = input_orig_tio;
            cfmakeraw(&raw);

            input_is_raw = (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) >= 0);

            if (input_is_raw && !init_atexit) {
                atexit([]() { tcsetattr(STDIN_FILENO, TCSAFLUSH, &input_orig_tio); });
                init_atexit = true;
            }
        }
    }

    return input_is_raw;
#else
    return false;
#endif
}

static void DisableRawMode()
{
    if (input_is_raw) {
#if defined(_WIN32)
        input_is_raw = !SetConsoleMode(stdin_handle, input_orig_mode);
#elif !defined(__wasm__)
        input_is_raw = !(tcsetattr(STDIN_FILENO, TCSAFLUSH, &input_orig_tio) >= 0);
#endif
    }
}

#if !defined(_WIN32) && !defined(__wasm__)
static void IgnoreSigWinch(struct sigaction *old_sa)
{
    struct sigaction sa;

    sa.sa_handler = [](int) {};
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGWINCH, &sa, old_sa);
}
#endif

bool ConsolePrompter::Read(Span<const char> *out_str)
{
#if !defined(_WIN32) && !defined(__wasm__)
    struct sigaction old_sa;
    IgnoreSigWinch(&old_sa);
    K_DEFER { sigaction(SIGWINCH, &old_sa, nullptr); };
#endif

    if (FileIsVt100(STDERR_FILENO) && EnableRawMode()) {
        K_DEFER {
            Print(StdErr, "%!0");
            DisableRawMode();
        };

        return ReadRaw(out_str);
    } else {
        return ReadBuffered(out_str);
    }
}

Size ConsolePrompter::ReadEnum(Span<const PromptChoice> choices, Size value)
{
    K_ASSERT(value < choices.len);

#if !defined(_WIN32) && !defined(__wasm__)
    struct sigaction old_sa;
    IgnoreSigWinch(&old_sa);
    K_DEFER { sigaction(SIGWINCH, &old_sa, nullptr); };
#endif

    if (FileIsVt100(STDERR_FILENO) && EnableRawMode()) {
        K_DEFER {
            Print(StdErr, "%!0");
            DisableRawMode();
        };

        return ReadRawEnum(choices, value);
    } else {
        return ReadBufferedEnum(choices);
    }
}

void ConsolePrompter::Commit()
{
    str.len = TrimStrRight(str.Take(), "\r\n").len;

    if (str.len) {
        std::swap(str, entries[entries.len - 1]);
        entries.AppendDefault();
    }
    entry_idx = entries.len - 1;
    str.RemoveFrom(0);
    str_offset = 0;

    rows = 0;
    rows_with_extra = 0;
    x = 0;
    y = 0;
}

bool ConsolePrompter::ReadRaw(Span<const char> *out_str)
{
    StdErr->Flush();

    prompt_columns = ComputeUnicodeWidth(prompt) + 1;
    str_offset = str.len;

    RenderRaw();

    int32_t uc;
    while ((uc = ReadChar()) >= 0) {
        // Fix display if terminal is resized
        if (GetConsoleSize().x != columns) {
            RenderRaw();
        }

        switch (uc) {
            case 0x1B: {
                LocalArray<char, 16> buf;

                const auto match_escape = [&](const char *seq) {
                    K_ASSERT(strlen(seq) < K_SIZE(buf.data));

                    for (Size i = 0; seq[i]; i++) {
                        if (i >= buf.len) {
                            uc = ReadChar();

                            if (uc >= 128) {
                                // Got some kind of non-ASCII character, make sure nothing else matches
                                buf.Append(0);
                                return false;
                            }

                            buf.Append((char)uc);
                        }
                        if (buf[i] != seq[i])
                            return false;
                    }

                    return true;
                };

                if (match_escape("[1;5D")) { // Ctrl-Left
                    str_offset = FindBackward(str_offset, " \t\r\n");
                    RenderRaw();
                } else if (match_escape("[1;5C")) { // Ctrl-Right
                    str_offset = FindForward(str_offset, " \t\r\n");
                    RenderRaw();
                } else if (match_escape("[3~")) { // Delete
                    if (str_offset < str.len) {
                        Delete(str_offset, SkipForward(str_offset, 1));
                        RenderRaw();
                    }
                } else if (match_escape("\x1B")) { // Double escape
                    StdErr->Write("\r\n");
                    StdErr->Flush();
                    return false;
                } else if (match_escape("\x7F")) { // Alt-Backspace
                    Delete(FindBackward(str_offset, " \t\r\n"), str_offset);
                    RenderRaw();
                } else if (match_escape("d")) { // Alt-D
                    Delete(str_offset, FindForward(str_offset, " \t\r\n"));
                    RenderRaw();
                } else if (match_escape("[A")) { // Up
                    fake_input = "\x10";
                } else if (match_escape("[B")) { // Down
                    fake_input = "\x0E";
                } else if (match_escape("[D")) { // Left
                    fake_input = "\x02";
                } else if (match_escape("[C")) { // Right
                    fake_input = "\x06";
                } else if (match_escape("[H")) { // Home
                    fake_input = "\x01";
                } else if (match_escape("[F")) { // End
                    fake_input = "\x05";
                }
            } break;

            case 0x2: { // Left
                if (str_offset > 0) {
                    str_offset = SkipBackward(str_offset, 1);
                    RenderRaw();
                }
            } break;
            case 0x6: { // Right
                if (str_offset < str.len) {
                    str_offset = SkipForward(str_offset, 1);
                    RenderRaw();
                }
            } break;
            case 0xE: { // Down
                Span<const char> remain = str.Take(str_offset, str.len - str_offset);
                SplitStr(remain, '\n', &remain);

                if (remain.len) {
                    Span<const char> line = SplitStr(remain, '\n', &remain);

                    Size line_offset = std::min(line.len, (Size)x - prompt_columns);
                    str_offset = std::min((Size)(line.ptr - str.ptr + line_offset), str.len);

                    RenderRaw();
                } else if (entry_idx < entries.len - 1) {
                    ChangeEntry(entry_idx + 1);
                    RenderRaw();
                }
            } break;
            case 0x10: { // Up
                Span<const char> remain = str.Take(0, str_offset);
                SplitStrReverse(remain, '\n', &remain);

                if (remain.len) {
                    Span<const char> line = SplitStrReverse(remain, '\n', &remain);

                    Size line_offset = std::min(line.len, (Size)x - prompt_columns);
                    str_offset = std::min((Size)(line.ptr - str.ptr + line_offset), str.len);

                    RenderRaw();
                } else if (entry_idx > 0) {
                    ChangeEntry(entry_idx - 1);
                    RenderRaw();
                }
            } break;

            case 0x1: { // Home
                str_offset = FindBackward(str_offset, "\n");
                RenderRaw();
            } break;
            case 0x5: { // End
                str_offset = FindForward(str_offset, "\n");
                RenderRaw();
            } break;

            case 0x8:
            case 0x7F: { // Backspace
                if (str.len) {
                    Delete(SkipBackward(str_offset, 1), str_offset);
                    RenderRaw();
                }
            } break;
            case 0x3: { // Ctrl-C
                if (str.len) {
                    str.RemoveFrom(0);
                    str_offset = 0;
                    entry_idx = entries.len - 1;
                    entries[entry_idx].RemoveFrom(0);

                    RenderRaw();
                } else {
                    StdErr->Write("\r\n");
                    StdErr->Flush();
                    return false;
                }
            } break;
            case 0x4: { // Ctrl-D
                if (str.len) {
                    Delete(str_offset, SkipForward(str_offset, 1));
                    RenderRaw();
                } else {
                    return false;
                }
            } break;
            case 0x14: { // Ctrl-T
                Size middle = SkipBackward(str_offset, 1);
                Size start = SkipBackward(middle, 1);

                if (start < middle) {
                    std::rotate(str.ptr + start, str.ptr + middle, str.ptr + str_offset);
                    RenderRaw();
                }
            } break;
            case 0xB: { // Ctrl-K
                Delete(str_offset, FindForward(str_offset, "\n"));
                RenderRaw();
            } break;
            case 0x15: { // Ctrl-U
                Delete(FindBackward(str_offset, "\n"), str_offset);
                RenderRaw();
            } break;
            case 0xC: { // Ctrl-L
                StdErr->Write("\x1B[2J\x1B[999A");
                RenderRaw();
            } break;

            case '\r':
            case '\n': {
                if (rows > y) {
                    Print(StdErr, "\x1B[%1B", rows - y);
                }
                StdErr->Write("\r\n");
                StdErr->Flush();
                y = rows + 1;

                EnsureNulTermination();
                if (out_str) {
                    *out_str = str;
                }
                return true;
            } break;

            case '\t': {
                if (complete) {
                    BlockAllocator temp_alloc;
                    HeapArray<CompleteChoice> choices;

                    PushLogFilter([](LogLevel, const char *, const char *, FunctionRef<LogFunc>) {});
                    K_DEFER_N(log_guard) { PopLogFilter(); };

                    CompleteResult ret = complete(str, &temp_alloc, &choices);

                    switch (ret) {
                        case CompleteResult::Success: {
                            if (choices.len == 1) {
                                const CompleteChoice &choice = choices[0];

                                str.RemoveFrom(0);
                                str.Append(choice.value);
                                str_offset = str.len;
                                RenderRaw();
                            } else if (choices.len) {
                                for (const CompleteChoice &choice: choices) {
                                    Print(StdErr, "\r\n  %!0%!Y..%1%!0", choice.name);
                                }
                                StdErr->Write("\r\n");

                                RenderRaw();
                            }
                        } break;

                        case CompleteResult::TooMany: {
                            Print(StdErr, "\r\n  %!0%!Y..%1%!0\r\n", T("Too many possibilities to show"));
                            RenderRaw();
                        } break;
                        case CompleteResult::Error: {
                            Print(StdErr, "\r\n  %!0%!Y..%1%!0\r\n", T("Autocompletion error"));
                            RenderRaw();
                        } break;
                    }

                    break;
                }
            } [[fallthrough]];

            default: {
                LocalArray<char, 16> frag;
                if (uc == '\t') {
                    frag.Append("    ");
                } else if (!IsAsciiControl(uc)) {
                    frag.len = EncodeUtf8(uc, frag.data);
                } else {
                    continue;
                }

                str.Grow(frag.len);
                MemMove(str.ptr + str_offset + frag.len, str.ptr + str_offset, str.len - str_offset);
                MemCpy(str.ptr + str_offset, frag.data, frag.len);
                str.len += frag.len;
                str_offset += frag.len;

                if (!mask && str_offset == str.len && uc < 128 && x + frag.len < columns) {
                    StdErr->Write(frag.data, frag.len);
                    StdErr->Flush();
                    x += (int)frag.len;
                } else {
                    RenderRaw();
                }
            } break;
        }
    }

    EnsureNulTermination();
    if (out_str) {
        *out_str = str;
    }
    return true;
}

Size ConsolePrompter::ReadRawEnum(Span<const PromptChoice> choices, Size value)
{
    StdErr->Flush();

    prompt_columns = 0;
    FormatChoices(choices, value);
    RenderRaw();

    int32_t uc;
    while ((uc = ReadChar()) >= 0) {
        // Fix display if terminal is resized
        if (GetConsoleSize().x != columns) {
            RenderRaw();
            Print(StdErr, "%!D..[Y/N]%!0 ");
        }

        switch (uc) {
            case 0x1B: {
                LocalArray<char, 16> buf;

                const auto match_escape = [&](const char *seq) {
                    K_ASSERT(strlen(seq) < K_SIZE(buf.data));

                    for (Size i = 0; seq[i]; i++) {
                        if (i >= buf.len) {
                            uc = ReadChar();

                            if (uc >= 128) {
                                // Got some kind of non-ASCII character, make sure nothing else matches
                                buf.Append(0);
                                return false;
                            }

                            buf.Append((char)uc);
                        }
                        if (buf[i] != seq[i])
                            return false;
                    }

                    return true;
                };

                if (match_escape("[A")) { // Up
                    fake_input = "\x10";
                } else if (match_escape("[B")) { // Down
                    fake_input = "\x0E";
                } else if (match_escape("\x1B")) { // Double escape
                    if (rows > y) {
                        Print(StdErr, "\x1B[%1B", rows - y);
                    }
                    StdErr->Write("\r");
                    StdErr->Flush();

                    return -1;
                }
            } break;

            case 0x3:   // Ctrl-C
            case 0x4: { // Ctrl-D
                if (rows > y) {
                    Print(StdErr, "\x1B[%1B", rows - y);
                }
                StdErr->Write("\r");
                StdErr->Flush();

                return -1;
            } break;

            case 0xE: { // Down
                if (value + 1 < choices.len) {
                    FormatChoices(choices, ++value);
                    RenderRaw();
                }
            } break;
            case 0x10: { // Up
                if (value > 0) {
                    FormatChoices(choices, --value);
                    RenderRaw();
                }
            } break;

            default: {
                const auto it = std::find_if(choices.begin(), choices.end(),
                                             [&](const PromptChoice &choice) { return choice.c == uc; });
                if (it == choices.end())
                    break;
                value = it - choices.begin();
            } [[fallthrough]];

            case '\r':
            case '\n': {
                str.RemoveFrom(0);
                str.Append(choices[value].str);
                str_offset = str.len;
                RenderRaw();

                StdErr->Write("\r\n");
                StdErr->Flush();

                return value;
            } break;
        }
    }

    return -1;
}

bool ConsolePrompter::ReadBuffered(Span<const char> *out_str)
{
    prompt_columns = ComputeUnicodeWidth(prompt) + 1;

    RenderBuffered();

    do {
        uint8_t c = 0;
        if (StdIn->Read(MakeSpan(&c, 1)) < 0)
            return false;

        if (c == '\n') {
            EnsureNulTermination();
            if (out_str) {
                *out_str = str;
            }
            return true;
        } else if (!IsAsciiControl(c)) {
            str.Append((char)c);
        }
    } while (!StdIn->IsEOF());

    // EOF
    return false;
}

Size ConsolePrompter::ReadBufferedEnum(Span<const PromptChoice> choices)
{
    static const Span<const char> prefix = "Input your choice: ";

    prompt_columns = 0;
    FormatChoices(choices, 0);
    RenderBuffered();

    Print(StdErr, "\n%1", prefix);
    StdErr->Flush();

    do {
        uint8_t c = 0;
        if (StdIn->Read(MakeSpan(&c, 1)) < 0)
            return -1;

        if (c == '\n') {
            Span<const char> end = TrimStr(SplitStrReverse(str, '\n'));

            if (end.len == 1) {
                const auto it = std::find_if(choices.begin(), choices.end(),
                                             [&](const PromptChoice &choice) { return choice.c == end[0]; });
                if (it != choices.end())
                    return it - choices.ptr;
            }

            str.RemoveFrom(end.ptr - str.ptr);

            StdErr->Write(prefix);
            StdErr->Flush();
        } else if (!IsAsciiControl(c)) {
            str.Append((char)c);
        }
    } while (!StdIn->IsEOF());

    // EOF
    return -1;
}

void ConsolePrompter::ChangeEntry(Size new_idx)
{
    if (str.len) {
        std::swap(str, entries[entry_idx]);
    }

    str.RemoveFrom(0);
    str.Append(entries[new_idx]);
    str_offset = str.len;
    entry_idx = new_idx;
}

Size ConsolePrompter::SkipForward(Size offset, Size count)
{
    if (offset < str.len) {
        offset++;

        while (offset < str.len && (((str[offset] & 0xC0) == 0x80) || --count)) {
            offset++;
        }
    }

    return offset;
}

Size ConsolePrompter::SkipBackward(Size offset, Size count)
{
    if (offset > 0) {
        offset--;

        while (offset > 0 && (((str[offset] & 0xC0) == 0x80) || --count)) {
            offset--;
        }
    }

    return offset;
}

Size ConsolePrompter::FindForward(Size offset, const char *chars)
{
    while (offset < str.len && strchr(chars, str[offset])) {
        offset++;
    }
    while (offset < str.len && !strchr(chars, str[offset])) {
        offset++;
    }

    return offset;
}

Size ConsolePrompter::FindBackward(Size offset, const char *chars)
{
    if (offset > 0) {
        offset--;

        while (offset > 0 && strchr(chars, str[offset])) {
            offset--;
        }
        while (offset > 0 && !strchr(chars, str[offset - 1])) {
            offset--;
        }
    }

    return offset;
}

void ConsolePrompter::Delete(Size start, Size end)
{
    K_ASSERT(start >= 0);
    K_ASSERT(end >= start && end <= str.len);

    MemMove(str.ptr + start, str.ptr + end, str.len - end);
    str.len -= end - start;

    if (str_offset > end) {
        str_offset -= end - start;
    } else if (str_offset > start) {
        str_offset = start;
    }
}

void ConsolePrompter::FormatChoices(Span<const PromptChoice> choices, Size value)
{
    int align = 0;

    for (const PromptChoice &choice: choices) {
        align = std::max(align, (int)ComputeUnicodeWidth(choice.str));
    }

    str.RemoveFrom(0);
    str.Append('\n');
    for (Size i = 0; i < choices.len; i++) {
        const PromptChoice &choice = choices[i];
        int pad = align - ComputeUnicodeWidth(choice.str);

        if (choice.c) {
            Fmt(&str, "  [%1] %2%3  ", choice.c, choice.str, FmtRepeat(" ", pad));
        } else {
            Fmt(&str, "      %1%2  ", choice.str, FmtRepeat(" ", pad));
        }
        if (i == value) {
            str_offset = str.len;
        }
        str.Append('\n');
    }
}

void ConsolePrompter::RenderRaw()
{
    columns = GetConsoleSize().x;
    rows = 0;

    int mask_columns = mask ? ComputeUnicodeWidth(mask) : 0;

    // Hide cursor during refresh
    StdErr->Write("\x1B[?25l");
    if (y) {
        Print(StdErr, "\x1B[%1A", y);
    }

    // Output prompt(s) and string lines
    {
        Size i = 0;
        int x2 = prompt_columns;

        Print(StdErr, "\r%!0%1 %!..+", prompt);

        for (;;) {
            if (i == str_offset) {
                x = x2;
                y = rows;
            }
            if (i >= str.len)
                break;

            Size bytes = std::min((Size)CountUtf8Bytes(str[i]), str.len - i);
            int width = mask ? mask_columns : ComputeUnicodeWidth(str.Take(i, bytes));

            if (x2 + width >= columns || str[i] == '\n') {
                FmtArg prefix = FmtRepeat(" ", prompt_columns - 1);
                Print(StdErr, "\x1B[0K\r\n%!D.+%1%!0 %!..+", prefix);

                x2 = prompt_columns;
                rows++;
            }
            if (width > 0) {
                if (mask) {
                    StdErr->Write(mask);
                } else {
                    StdErr->Write(str.ptr + i, bytes);
                }
            }

            x2 += width;
            i += bytes;
        }
        StdErr->Write("\x1B[0K");
    }

    // Clear remaining rows
    for (int i = rows; i < rows_with_extra; i++) {
        StdErr->Write("\r\n\x1B[0K");
    }
    rows_with_extra = std::max(rows_with_extra, rows);

    // Fix up cursor and show it
    if (rows_with_extra > y) {
        Print(StdErr, "\x1B[%1A", rows_with_extra - y);
    }
    Print(StdErr, "\r\x1B[%1C", x);
    Print(StdErr, "\x1B[?25h");

    StdErr->Flush();
}

void ConsolePrompter::RenderBuffered()
{
    Span<const char> remain = str;
    Span<const char> line = SplitStr(remain, '\n', &remain);

    Print(StdErr, "%1 %2", prompt, line);
    while (remain.len) {
        line = SplitStr(remain, '\n', &remain);
        Print(StdErr, "\n%1%2", FmtRepeat(" ", prompt_columns), line);
    }

    StdErr->Flush();
}

Vec2<int> ConsolePrompter::GetConsoleSize()
{
#if defined(_WIN32)
    HANDLE h = (HANDLE)_get_osfhandle(STDERR_FILENO);

    CONSOLE_SCREEN_BUFFER_INFO screen;
    if (GetConsoleScreenBufferInfo(h, &screen))
        return { screen.dwSize.X, screen.dwSize.Y };
#elif !defined(__wasm__)
    struct winsize ws;
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0 && ws.ws_col)
        return { ws.ws_col, ws.ws_row };
#endif

    // Give up!
    return { 80, 24 };
}

int32_t ConsolePrompter::ReadChar()
{
    if (fake_input[0]) {
        int c = fake_input[0];
        fake_input++;
        return c;
    }

#if defined(_WIN32)
    HANDLE h = (HANDLE)_get_osfhandle(STDIN_FILENO);

    for (;;) {
        INPUT_RECORD ev;
        DWORD ev_len;
        if (!ReadConsoleInputW(h, &ev, 1, &ev_len))
            return -1;
        if (!ev_len)
            return -1;

        if (ev.EventType == KEY_EVENT && ev.Event.KeyEvent.bKeyDown) {
            bool ctrl = ev.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED);
            bool alt = ev.Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED);

            if (ctrl && !alt) {
                switch (ev.Event.KeyEvent.wVirtualKeyCode) {
                    case 'A': return 0x1;
                    case 'B': return 0x2;
                    case 'C': return 0x3;
                    case 'D': return 0x4;
                    case 'E': return 0x5;
                    case 'F': return 0x6;
                    case 'H': return 0x8;
                    case 'K': return 0xB;
                    case 'L': return 0xC;
                    case 'N': return 0xE;
                    case 'P': return 0x10;
                    case 'T': return 0x14;
                    case 'U': return 0x15;
                    case VK_LEFT: {
                        fake_input = "[1;5D";
                        return 0x1B;
                    } break;
                    case VK_RIGHT: {
                        fake_input = "[1;5C";
                        return 0x1B;
                    } break;
                }
            } else {
                if (alt) {
                    switch (ev.Event.KeyEvent.wVirtualKeyCode) {
                        case VK_BACK: {
                            fake_input = "\x7F";
                            return 0x1B;
                        } break;
                        case 'D': {
                            fake_input = "d";
                            return 0x1B;
                        } break;
                    }
                }

                switch (ev.Event.KeyEvent.wVirtualKeyCode) {
                    case VK_UP: return 0x10;
                    case VK_DOWN: return 0xE;
                    case VK_LEFT: return 0x2;
                    case VK_RIGHT: return 0x6;
                    case VK_HOME: return 0x1;
                    case VK_END: return 0x5;
                    case VK_RETURN: return '\r';
                    case VK_BACK: return 0x8;
                    case VK_DELETE: {
                        fake_input = "[3~";
                        return 0x1B;
                    } break;

                    default: {
                        uint32_t uc = ev.Event.KeyEvent.uChar.UnicodeChar;

                        if ((uc - 0xD800u) < 0x800u) {
                            if ((uc & 0xFC00u) == 0xD800u) {
                                surrogate_buf = uc;
                                return 0;
                            } else if (surrogate_buf && (uc & 0xFC00) == 0xDC00) {
                                uc = (surrogate_buf << 10) + uc - 0x35FDC00;
                            } else {
                                // Yeah something is up. Give up on this character.
                                surrogate_buf = 0;
                                return 0;
                            }
                        }

                        return (int32_t)uc;
                    } break;
                }
            }
        } else if (ev.EventType == WINDOW_BUFFER_SIZE_EVENT) {
            return 0;
        }
    }
#else
    int32_t uc = 0;

    {
        uint8_t c = 0;
        ssize_t read_len = read(STDIN_FILENO, &c, 1);
        if (read_len < 0)
            goto error;
        if (!read_len)
            return -1;
        uc = c;
    }

    if (uc >= 128) {
        Size bytes = CountUtf8Bytes((char)uc);

        LocalArray<char, 4> buf;
        buf.Append((char)uc);
        buf.len += read(STDIN_FILENO, buf.end(), bytes - 1);
        if (buf.len < 1)
            goto error;

        if (buf.len != bytes)
            return 0;
        if (DecodeUtf8(buf, 0, &uc) != bytes)
            return 0;
    }

    return uc;

error:
    if (errno == EINTR) {
        // Could be SIGWINCH, give the user a chance to deal with it
        return 0;
    } else {
        LogError("Failed to read from standard input: %1", strerror(errno));
        return -1;
    }
#endif
}

void ConsolePrompter::EnsureNulTermination()
{
    str.Grow(1);
    str.ptr[str.len] = 0;
}

const char *Prompt(const char *prompt, const char *default_value, const char *mask, Allocator *alloc)
{
    K_ASSERT(alloc);

    ConsolePrompter prompter;

    prompter.prompt = prompt;
    prompter.mask = mask;

    prompter.str.allocator = alloc;
    if (default_value) {
        prompter.str.Append(default_value);
    }

    if (!prompter.Read())
        return nullptr;

    const char *str = prompter.str.Leak().ptr;
    return str;
}

Size PromptEnum(const char *prompt, Span<const PromptChoice> choices, Size value)
{
#if defined(K_DEBUG)
    {
        HashSet<char> keys;

        for (const PromptChoice &choice: choices) {
            if (!choice.c)
                continue;

            bool duplicates = !keys.InsertOrFail(choice.c);
            K_ASSERT(!duplicates);
        }
    }
#endif

    ConsolePrompter prompter;
    prompter.prompt = prompt;

    return prompter.ReadEnum(choices, value);
}

Size PromptEnum(const char *prompt, Span<const char *const> strings, Size value)
{
    static const char literals[] = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    HeapArray<PromptChoice> choices;

    for (Size i = 0; i < strings.len; i++) {
        const char *str = strings[i];
        PromptChoice choice = { str, i < K_LEN(literals) ? literals[i] : (char)0 };

        choices.Append(choice);
    }

    return PromptEnum(prompt, choices, value);
}

int PromptYN(const char *prompt)
{
    const char *yes = T("Yes");
    const char *no = T("No");

    const char *shortcuts = T("yn");
    K_ASSERT(strlen(shortcuts) == 2);

    Size ret = PromptEnum(prompt, {{ yes, shortcuts[0] }, { no, shortcuts[1] }});
    if (ret < 0)
        return -1;

    return !ret;
}

const char *PromptPath(const char *prompt, const char *default_path, Span<const char> root_directory, Allocator *alloc)
{
    K_ASSERT(alloc);

    ConsolePrompter prompter;

    prompter.prompt = prompt;
    prompter.complete = [&](Span<const char> str, Allocator *alloc, HeapArray<CompleteChoice> *out_choices) {
        Size start_len = out_choices->len;
        K_DEFER_N(err_guard) { out_choices->RemoveFrom(start_len); };

        Span<const char> path = TrimStrRight(str, K_PATH_SEPARATORS);
        bool separator = (path.len < str.len);

        // If the value points to a directory, append separator and return
        if (str.len && !separator) {
            const char *filename = NormalizePath(path, root_directory, alloc).ptr;

            FileInfo file_info;
            StatResult ret = StatFile(filename, (int)StatFlag::SilentMissing | (int)StatFlag::FollowSymlink, &file_info);

            if (ret == StatResult::Success && file_info.type == FileType::Directory) {
                const char *value = Fmt(alloc, "%1%/", path).ptr;
                out_choices->Append({ value, value });

                err_guard.Disable();
                return CompleteResult::Success;
            }
        }

        Span<const char> directory = path;
        Span<const char> prefix = separator ? "" : SplitStrReverseAny(path, K_PATH_SEPARATORS, &directory);

        // EnumerateDirectory takes a C string, so we need the NUL terminator,
        // and we also need to take root_dir into account.
        const char *dirname = nullptr;

        if (PathIsAbsolute(directory)) {
            dirname = DuplicateString(directory, alloc).ptr;
        } else {
            if (!root_directory.len)
                return CompleteResult::Success;

            dirname = NormalizePath(directory, root_directory, alloc).ptr;
            dirname = dirname[0] ? dirname : ".";
        }

        EnumResult ret = EnumerateDirectory(dirname, nullptr, -1, [&](const char *basename, FileType file_type) {
#if defined(_WIN32)
            if (!StartsWithI(basename, prefix))
                return true;
#else
            if (!StartsWith(basename, prefix))
                return true;
#endif

            if (out_choices->len - start_len >= K_COMPLETE_PATH_LIMIT)
                return false;

            CompleteChoice choice;
            {
                HeapArray<char> buf(alloc);

                // Make directory part
                buf.Append(directory);
                if (directory.len && !IsPathSeparator(directory[directory.len - 1])) {
                    buf.Append(*K_PATH_SEPARATORS);
                }

                Size name_offset = buf.len;

                // Append name
                buf.Append(basename);
                if (file_type == FileType::Directory) {
                    buf.Append(*K_PATH_SEPARATORS);
                }
                buf.Append(0);
                buf.Trim();

                choice.value = buf.Leak().ptr;
                choice.name = choice.value + name_offset;
            }

            out_choices->Append(choice);
            return true;
        });

        if (ret == EnumResult::CallbackFail) {
            return CompleteResult::TooMany;
        } else if (ret != EnumResult::Success) {
            // Just ignore it and don't print anything
            return CompleteResult::Success;
        }

        std::sort(out_choices->ptr + start_len, out_choices->end(),
                  [](const CompleteChoice &choice1, const CompleteChoice &choice2) { return CmpNaturalI(choice1.name, choice2.name) < 0; });

        err_guard.Disable();
        return CompleteResult::Success;
    };

    prompter.str.allocator = alloc;
    if (default_path) {
        prompter.str.Append(default_path);
    }

    if (!prompter.Read())
        return nullptr;

    const char *str = NormalizePath(prompter.str, alloc).ptr;
    return str;
}

// ------------------------------------------------------------------------
// Mime types
// ------------------------------------------------------------------------

const char *GetMimeType(Span<const char> extension, const char *default_type)
{
    static const HashMap<Span<const char>, const char *> mimetypes = {
        #define MIMETYPE(Extension, MimeType) { (Extension), (MimeType) },
        #include "mimetypes.inc"

        { "", "application/octet-stream" }
    };

    char lower[32];
    {
        Size take = std::min(extension.len, (Size)16);
        Span<const char> truncated = extension.Take(0, take);

        for (Size i = 0; i < truncated.len; i++) {
            lower[i] = LowerAscii(truncated[i]);
        }
        lower[truncated.len] = 0;
    }

    const char *mimetype = mimetypes.FindValue(lower, nullptr);

    if (!mimetype) {
        LogError("Unknown MIME type for extension '%1'", extension);
        mimetype = default_type;
    }

    return mimetype;
}

bool CanCompressFile(const char *filename)
{
    char extension[8];
    {
        const char *ptr = GetPathExtension(filename).ptr;

        Size i = 0;
        while (i < K_SIZE(extension) - 1 && ptr[i]) {
            extension[i] = LowerAscii(ptr[i]);
            i++;
        }
        extension[i] = 0;
    }

    if (TestStrI(extension, ".zip"))
        return false;
    if (TestStrI(extension, ".rar"))
        return false;
    if (TestStrI(extension, ".7z"))
        return false;
    if (TestStrI(extension, ".gz") || TestStrI(extension, ".tgz"))
        return false;
    if (TestStrI(extension, ".bz2") || TestStrI(extension, ".tbz2"))
        return false;
    if (TestStrI(extension, ".xz") || TestStrI(extension, ".txz"))
        return false;
    if (TestStrI(extension, ".zst") || TestStrI(extension, ".tzst"))
        return false;
    if (TestStrI(extension, ".woff") || TestStrI(extension, ".woff2"))
        return false;
    if (TestStrI(extension, ".db") || TestStrI(extension, ".sqlite3"))
        return false;

    const char *mimetype = GetMimeType(extension);

    if (StartsWith(mimetype, "video/"))
        return false;
    if (StartsWith(mimetype, "audio/"))
        return false;
    if (StartsWith(mimetype, "image/") && !TestStr(mimetype, "image/svg+xml"))
        return false;

    return true;
}

// ------------------------------------------------------------------------
// Unicode
// ------------------------------------------------------------------------

bool IsValidUtf8(Span<const char> str)
{
    Size i = 0;

    while (i < str.len) {
        int32_t uc;
        Size bytes = DecodeUtf8(str, i, &uc);

        if (!bytes) [[unlikely]]
            return false;

        i += bytes;
    }

    return i == str.len;
}

static bool TestUnicodeTable(Span<const int32_t> table, int32_t uc)
{
    K_ASSERT(table.len > 0);
    K_ASSERT(table.len % 2 == 0);

    auto it = std::upper_bound(table.begin(), table.end(), uc,
                               [](int32_t uc, int32_t x) { return uc < x; });
    Size idx = it - table.ptr;

    // Each pair of value in table represents a valid interval
    return idx & 0x1;
}

static inline int ComputeCharacterWidth(int32_t uc)
{
    // Fast path
    if (uc < 128)
        return IsAsciiControl(uc) ? 0 : 1;

    if (TestUnicodeTable(WcWidthNull, uc))
        return 0;
    if (TestUnicodeTable(WcWidthWide, uc))
        return 2;

    return 1;
}

int ComputeUnicodeWidth(Span<const char> str)
{
    Size i = 0;
    int width = 0;

    while (i < str.len) {
        int32_t uc;
        Size bytes = DecodeUtf8(str, i, &uc);

        if (!bytes) [[unlikely]]
            return false;

        i += bytes;
        width += ComputeCharacterWidth(uc);
    }

    return width;
}

bool IsXidStart(int32_t uc)
{
    if (IsAsciiAlpha(uc))
        return true;
    if (uc == '_')
        return true;
    if (TestUnicodeTable(XidStartTable, uc))
        return true;

    return false;
}

bool IsXidContinue(int32_t uc)
{
    if (IsAsciiAlphaOrDigit(uc))
        return true;
    if (uc == '_')
        return true;
    if (TestUnicodeTable(XidContinueTable, uc))
        return true;

    return false;
}

// ------------------------------------------------------------------------
// CRC
// ------------------------------------------------------------------------

uint32_t CRC32(uint32_t state, Span<const uint8_t> buf)
{
    state = ~state;

    Size right = buf.len & (K_SIZE_MAX - 3);

    for (Size i = 0; i < right; i += 4) {
        state = (state >> 8) ^ Crc32Table[(state ^ buf[i + 0]) & 0xFF];
        state = (state >> 8) ^ Crc32Table[(state ^ buf[i + 1]) & 0xFF];
        state = (state >> 8) ^ Crc32Table[(state ^ buf[i + 2]) & 0xFF];
        state = (state >> 8) ^ Crc32Table[(state ^ buf[i + 3]) & 0xFF];
    }
    for (Size i = right; i < buf.len; i++) {
        state = (state >> 8) ^ Crc32Table[(state ^ buf[i]) & 0xFF];
    }

    return ~state;
}

uint32_t CRC32C(uint32_t state, Span<const uint8_t> buf)
{
    state = ~state;

    Size right = buf.len & (K_SIZE_MAX - 3);

    for (Size i = 0; i < right; i += 4) {
        state = (state >> 8) ^ Crc32CTable[(state ^ buf[i + 0]) & 0xFF];
        state = (state >> 8) ^ Crc32CTable[(state ^ buf[i + 1]) & 0xFF];
        state = (state >> 8) ^ Crc32CTable[(state ^ buf[i + 2]) & 0xFF];
        state = (state >> 8) ^ Crc32CTable[(state ^ buf[i + 3]) & 0xFF];
    }
    for (Size i = right; i < buf.len; i++) {
        state = (state >> 8) ^ Crc32CTable[(state ^ buf[i]) & 0xFF];
    }

    return ~state;
}

static uint64_t XzUpdate1(uint64_t state, uint8_t byte)
{
    uint64_t ret = (state >> 8) ^ Crc64XzTable0[byte ^ (uint8_t)state];
    return ret;
}

static uint64_t XzUpdate16(uint64_t state, const uint8_t *bytes)
{
    uint64_t ret = Crc64XzTable0[bytes[15]] ^
                   Crc64XzTable1[bytes[14]] ^
                   Crc64XzTable2[bytes[13]] ^
                   Crc64XzTable3[bytes[12]] ^
                   Crc64XzTable4[bytes[11]] ^
                   Crc64XzTable5[bytes[10]] ^
                   Crc64XzTable6[bytes[9]] ^
                   Crc64XzTable7[bytes[8]] ^
                   Crc64XzTable8[bytes[7] ^ (uint8_t)(state >> 56)] ^
                   Crc64XzTable9[bytes[6] ^ (uint8_t)(state >> 48)] ^
                   Crc64XzTable10[bytes[5] ^ (uint8_t)(state >> 40)] ^
                   Crc64XzTable11[bytes[4] ^ (uint8_t)(state >> 32)] ^
                   Crc64XzTable12[bytes[3] ^ (uint8_t)(state >> 24)] ^
                   Crc64XzTable13[bytes[2] ^ (uint8_t)(state >> 16)] ^
                   Crc64XzTable14[bytes[1] ^ (uint8_t)(state >> 8)] ^
                   Crc64XzTable15[bytes[0] ^ (uint8_t)(state >> 0)];
    return ret;
}

uint64_t CRC64xz(uint64_t state, Span<const uint8_t> buf)
{
    state = ~state;

    Size len16 = buf.len / 16 * 16;

    for (Size i = 0; i < len16; i += 16) {
        state = XzUpdate16(state, buf.ptr + i);
    }
    for (Size i = len16; i < buf.len; i++) {
        state = XzUpdate1(state, buf[i]);
    }

    return ~state;
}

static uint64_t NvmeUpdate1(uint64_t state, uint8_t byte)
{
    uint64_t ret = (state >> 8) ^ Crc64NvmeTable0[byte ^ (uint8_t)state];
    return ret;
}

static uint64_t NvmeUpdate16(uint64_t state, const uint8_t *bytes)
{
    uint64_t ret = Crc64NvmeTable0[bytes[15]] ^
                   Crc64NvmeTable1[bytes[14]] ^
                   Crc64NvmeTable2[bytes[13]] ^
                   Crc64NvmeTable3[bytes[12]] ^
                   Crc64NvmeTable4[bytes[11]] ^
                   Crc64NvmeTable5[bytes[10]] ^
                   Crc64NvmeTable6[bytes[9]] ^
                   Crc64NvmeTable7[bytes[8]] ^
                   Crc64NvmeTable8[bytes[7] ^ (uint8_t)(state >> 56)] ^
                   Crc64NvmeTable9[bytes[6] ^ (uint8_t)(state >> 48)] ^
                   Crc64NvmeTable10[bytes[5] ^ (uint8_t)(state >> 40)] ^
                   Crc64NvmeTable11[bytes[4] ^ (uint8_t)(state >> 32)] ^
                   Crc64NvmeTable12[bytes[3] ^ (uint8_t)(state >> 24)] ^
                   Crc64NvmeTable13[bytes[2] ^ (uint8_t)(state >> 16)] ^
                   Crc64NvmeTable14[bytes[1] ^ (uint8_t)(state >> 8)] ^
                   Crc64NvmeTable15[bytes[0] ^ (uint8_t)(state >> 0)];
    return ret;
}

uint64_t CRC64nvme(uint64_t state, Span<const uint8_t> buf)
{
    state = ~state;

    Size len16 = buf.len / 16 * 16;

    for (Size i = 0; i < len16; i += 16) {
        state = NvmeUpdate16(state, buf.ptr + i);
    }
    for (Size i = len16; i < buf.len; i++) {
        state = NvmeUpdate1(state, buf[i]);
    }

    return ~state;
}

}
