// build.h — Minimal C build system (stb-style single header)
//
// Define BUILD_IMPLEMENTATION in exactly one file before including this header.
//
// Bootstrap (one-time):
//   Windows: cl /nologo build.c
//   macOS:   cc build.c -o build
// After that, just run ./build (or build.exe) — it rebuilds itself.

#ifndef BUILD_H
#define BUILD_H

#ifdef _MSC_VER
#   define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>

#ifdef _WIN32
#   define WIN32_LEAN_AND_MEAN
#   include <windows.h>
#   include <direct.h>
#   include <io.h>
#else
#   include <sys/stat.h>
#   include <sys/wait.h>
#   include <unistd.h>
#endif

////////////////////////////////
// Macros

#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))

// Self-rebuild: call at the top of main(). Recompiles the build script
// if the source file is newer than the running binary, then re-executes.
#define GO_REBUILD_URSELF(argc, argv) \
    go_rebuild_urself((argc), (argv), __FILE__)

////////////////////////////////
// Logging

typedef enum {
    LOG_INFO,
    LOG_WARNING,
    LOG_ERROR,
} Log_Level;

void build_log(Log_Level level, const char *fmt, ...);

////////////////////////////////
// Temp allocator — ring buffer for short-lived sprintf results

#ifndef TEMP_CAPACITY
#define TEMP_CAPACITY (8 * 1024 * 1024)
#endif

char *temp_sprintf(const char *fmt, ...);
void  temp_reset(void);

////////////////////////////////
// String builder — growable byte buffer

typedef struct {
    char  *items;
    size_t count;
    size_t capacity;
} String_Builder;

bool sb_read_file(String_Builder *sb, const char *path);
void sb_free(String_Builder *sb);

////////////////////////////////
// File I/O

bool write_entire_file(const char *path, const void *data, size_t size);
bool delete_file(const char *path);
bool rename_file(const char *old_path, const char *new_path);
bool mkdir_if_not_exists(const char *path);

////////////////////////////////
// Rebuild checking — returns 1 if rebuild needed, 0 if up to date, -1 on error

int needs_rebuild(const char *output, const char **inputs, size_t count);
int needs_rebuild1(const char *output, const char *input);

////////////////////////////////
// Command builder and runner

typedef struct {
    const char **items;
    size_t       count;
    size_t       capacity;
} Cmd;

// Appends arguments to a command. Use via the macro which auto-counts args.
void cmd__append(Cmd *cmd, size_t n, ...);
void cmd__append_arr(Cmd *cmd, const char **args, size_t n);
#define cmd_append(cmd, ...) do { \
        const char *_cmd_args[] = {__VA_ARGS__}; \
        cmd__append_arr((cmd), _cmd_args, sizeof(_cmd_args) / sizeof(_cmd_args[0])); \
    } while(0)

// Runs the command synchronously, resets cmd->count to 0, returns success.
bool cmd_run(Cmd *cmd);

// Frees the command's allocated memory.
void cmd_free(Cmd *cmd);

////////////////////////////////
// Self-rebuild

void go_rebuild_urself(int argc, char **argv, const char *source);

#endif // BUILD_H

////////////////////////////////
// Implementation
////////////////////////////////

#ifdef BUILD_IMPLEMENTATION

////////////////////////////////
// Temp allocator

static size_t g_temp_size = 0;
static char   g_temp[TEMP_CAPACITY];

void temp_reset(void) {
    g_temp_size = 0;
}

static char *temp_alloc(size_t size) {
    if (g_temp_size + size > TEMP_CAPACITY) {
        g_temp_size = 0; // wrap around
    }
    char *result = g_temp + g_temp_size;
    g_temp_size += size;
    return result;
}

char *temp_sprintf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    va_list args2;
    va_copy(args2, args);
    int n = vsnprintf(NULL, 0, fmt, args);
    va_end(args);

    char *result = temp_alloc(n + 1);
    vsnprintf(result, n + 1, fmt, args2);
    va_end(args2);
    return result;
}

////////////////////////////////
// Logging

void build_log(Log_Level level, const char *fmt, ...) {
    switch (level) {
        case LOG_INFO:    fprintf(stderr, "[INFO] ");    break;
        case LOG_WARNING: fprintf(stderr, "[WARNING] "); break;
        case LOG_ERROR:   fprintf(stderr, "[ERROR] ");   break;
    }
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fprintf(stderr, "\n");
}

////////////////////////////////
// String builder

static void sb_ensure(String_Builder *sb, size_t needed) {
    if (sb->count + needed <= sb->capacity) return;
    size_t new_cap = sb->capacity ? sb->capacity * 2 : 256;
    while (new_cap < sb->count + needed) new_cap *= 2;
    sb->items = (char *)realloc(sb->items, new_cap);
    sb->capacity = new_cap;
}

bool sb_read_file(String_Builder *sb, const char *path) {
    FILE *f = fopen(path, "rb");
    if (!f) {
        build_log(LOG_ERROR, "Could not open %s: %s", path, strerror(errno));
        return false;
    }

    fseek(f, 0, SEEK_END);
#ifdef _WIN32
    long long m = _telli64(_fileno(f));
#else
    long long m = ftell(f);
#endif
    if (m < 0) {
        build_log(LOG_ERROR, "Could not get size of %s: %s", path, strerror(errno));
        fclose(f);
        return false;
    }
    fseek(f, 0, SEEK_SET);

    sb_ensure(sb, (size_t)m);
    fread(sb->items + sb->count, (size_t)m, 1, f);
    if (ferror(f)) {
        build_log(LOG_ERROR, "Could not read %s: %s", path, strerror(errno));
        fclose(f);
        return false;
    }
    sb->count += (size_t)m;

    fclose(f);
    return true;
}

void sb_free(String_Builder *sb) {
    free(sb->items);
    sb->items = NULL;
    sb->count = 0;
    sb->capacity = 0;
}

////////////////////////////////
// File I/O

bool write_entire_file(const char *path, const void *data, size_t size) {
    FILE *f = fopen(path, "wb");
    if (!f) {
        build_log(LOG_ERROR, "Could not open %s for writing: %s", path, strerror(errno));
        return false;
    }
    const char *buf = (const char *)data;
    while (size > 0) {
        size_t n = fwrite(buf, 1, size, f);
        if (ferror(f)) {
            build_log(LOG_ERROR, "Could not write to %s: %s", path, strerror(errno));
            fclose(f);
            return false;
        }
        size -= n;
        buf += n;
    }
    fclose(f);
    return true;
}

bool delete_file(const char *path) {
    build_log(LOG_INFO, "Deleting %s", path);
#ifdef _WIN32
    DWORD attr = GetFileAttributesA(path);
    if (attr == INVALID_FILE_ATTRIBUTES) return true; // doesn't exist
    if (attr & FILE_ATTRIBUTE_DIRECTORY) {
        if (!RemoveDirectoryA(path)) {
            build_log(LOG_ERROR, "Could not delete directory %s", path);
            return false;
        }
    } else {
        if (!DeleteFileA(path)) {
            build_log(LOG_ERROR, "Could not delete file %s", path);
            return false;
        }
    }
#else
    if (remove(path) < 0 && errno != ENOENT) {
        build_log(LOG_ERROR, "Could not delete %s: %s", path, strerror(errno));
        return false;
    }
#endif
    return true;
}

bool rename_file(const char *old_path, const char *new_path) {
    build_log(LOG_INFO, "Renaming %s -> %s", old_path, new_path);
#ifdef _WIN32
    if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
        build_log(LOG_ERROR, "Could not rename %s to %s", old_path, new_path);
        return false;
    }
#else
    if (rename(old_path, new_path) < 0) {
        build_log(LOG_ERROR, "Could not rename %s to %s: %s", old_path, new_path, strerror(errno));
        return false;
    }
#endif
    return true;
}

bool mkdir_if_not_exists(const char *path) {
#ifdef _WIN32
    int result = _mkdir(path);
#else
    int result = mkdir(path, 0755);
#endif
    if (result < 0) {
        if (errno == EEXIST) return true;
        build_log(LOG_ERROR, "Could not create directory %s: %s", path, strerror(errno));
        return false;
    }
    build_log(LOG_INFO, "Created directory %s", path);
    return true;
}

////////////////////////////////
// Rebuild checking

int needs_rebuild(const char *output, const char **inputs, size_t count) {
#ifdef _WIN32
    HANDLE out_h = CreateFile(output, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
    if (out_h == INVALID_HANDLE_VALUE) {
        if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
        build_log(LOG_ERROR, "Could not open %s", output);
        return -1;
    }
    FILETIME out_time;
    if (!GetFileTime(out_h, NULL, NULL, &out_time)) {
        CloseHandle(out_h);
        build_log(LOG_ERROR, "Could not get time of %s", output);
        return -1;
    }
    CloseHandle(out_h);

    for (size_t i = 0; i < count; i++) {
        HANDLE in_h = CreateFile(inputs[i], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
        if (in_h == INVALID_HANDLE_VALUE) {
            build_log(LOG_ERROR, "Could not open %s", inputs[i]);
            return -1;
        }
        FILETIME in_time;
        if (!GetFileTime(in_h, NULL, NULL, &in_time)) {
            CloseHandle(in_h);
            build_log(LOG_ERROR, "Could not get time of %s", inputs[i]);
            return -1;
        }
        CloseHandle(in_h);
        if (CompareFileTime(&in_time, &out_time) == 1) return 1;
    }
    return 0;
#else
    struct stat sb;
    memset(&sb, 0, sizeof(sb));
    if (stat(output, &sb) < 0) {
        if (errno == ENOENT) return 1;
        build_log(LOG_ERROR, "Could not stat %s: %s", output, strerror(errno));
        return -1;
    }
    time_t out_time = sb.st_mtime;

    for (size_t i = 0; i < count; i++) {
        if (stat(inputs[i], &sb) < 0) {
            build_log(LOG_ERROR, "Could not stat %s: %s", inputs[i], strerror(errno));
            return -1;
        }
        if (sb.st_mtime > out_time) return 1;
    }
    return 0;
#endif
}

int needs_rebuild1(const char *output, const char *input) {
    return needs_rebuild(output, &input, 1);
}

////////////////////////////////
// Command builder and runner

static void cmd__grow(Cmd *cmd) {
    size_t new_cap = cmd->capacity ? cmd->capacity * 2 : 32;
    cmd->items = (const char **)realloc(cmd->items, new_cap * sizeof(const char *));
    cmd->capacity = new_cap;
}

void cmd__append(Cmd *cmd, size_t n, ...) {
    va_list args;
    va_start(args, n);
    for (size_t i = 0; i < n; i++) {
        const char *arg = va_arg(args, const char *);
        if (cmd->count >= cmd->capacity) cmd__grow(cmd);
        cmd->items[cmd->count++] = arg;
    }
    va_end(args);
}

void cmd__append_arr(Cmd *cmd, const char **args, size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (cmd->count >= cmd->capacity) cmd__grow(cmd);
        cmd->items[cmd->count++] = args[i];
    }
}

static void cmd_render(Cmd *cmd, char *buf, size_t buf_size) {
    size_t pos = 0;
    for (size_t i = 0; i < cmd->count && pos < buf_size - 1; i++) {
        if (i > 0 && pos < buf_size - 1) buf[pos++] = ' ';
        const char *arg = cmd->items[i];
        size_t len = strlen(arg);
        if (pos + len < buf_size - 1) {
            memcpy(buf + pos, arg, len);
            pos += len;
        }
    }
    buf[pos] = '\0';
}

#ifdef _WIN32
// Properly quote a command line for CreateProcess on Windows.
static void cmd_quote_win32(Cmd *cmd, String_Builder *quoted) {
    for (size_t i = 0; i < cmd->count; i++) {
        const char *arg = cmd->items[i];
        size_t len = strlen(arg);
        if (i > 0) {
            sb_ensure(quoted, 1);
            quoted->items[quoted->count++] = ' ';
        }
        if (len != 0 && strpbrk(arg, " \t\n\v\"") == NULL) {
            sb_ensure(quoted, len);
            memcpy(quoted->items + quoted->count, arg, len);
            quoted->count += len;
        } else {
            sb_ensure(quoted, len * 2 + 3);
            quoted->items[quoted->count++] = '"';
            size_t backslashes = 0;
            for (size_t j = 0; j < len; j++) {
                char c = arg[j];
                if (c == '\\') {
                    backslashes++;
                } else {
                    if (c == '"') {
                        for (size_t k = 0; k < backslashes + 1; k++)
                            quoted->items[quoted->count++] = '\\';
                    }
                    backslashes = 0;
                }
                quoted->items[quoted->count++] = c;
            }
            for (size_t k = 0; k < backslashes; k++)
                quoted->items[quoted->count++] = '\\';
            quoted->items[quoted->count++] = '"';
        }
    }
    sb_ensure(quoted, 1);
    quoted->items[quoted->count] = '\0';
}
#endif

bool cmd_run(Cmd *cmd) {
    if (cmd->count == 0) {
        build_log(LOG_ERROR, "Cannot run empty command");
        return false;
    }

    // Log the command
    {
        char render_buf[4096];
        cmd_render(cmd, render_buf, sizeof(render_buf));
        build_log(LOG_INFO, "CMD: %s", render_buf);
    }

#ifdef _WIN32
    STARTUPINFO si;
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    si.dwFlags |= STARTF_USESTDHANDLES;

    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(pi));

    String_Builder quoted = {0};
    cmd_quote_win32(cmd, &quoted);

    BOOL ok = CreateProcessA(NULL, quoted.items, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
    sb_free(&quoted);

    cmd->count = 0;

    if (!ok) {
        build_log(LOG_ERROR, "Could not create process for %s", cmd->items[0]);
        return false;
    }

    CloseHandle(pi.hThread);
    WaitForSingleObject(pi.hProcess, INFINITE);

    DWORD exit_code;
    if (!GetExitCodeProcess(pi.hProcess, &exit_code)) {
        build_log(LOG_ERROR, "Could not get exit code");
        CloseHandle(pi.hProcess);
        return false;
    }
    CloseHandle(pi.hProcess);

    if (exit_code != 0) {
        build_log(LOG_ERROR, "Command exited with code %lu", exit_code);
        return false;
    }
    return true;
#else
    pid_t pid = fork();
    if (pid < 0) {
        build_log(LOG_ERROR, "Could not fork: %s", strerror(errno));
        cmd->count = 0;
        return false;
    }

    if (pid == 0) {
        // Child: build null-terminated argv
        const char **argv_null = (const char **)malloc((cmd->count + 1) * sizeof(const char *));
        memcpy(argv_null, cmd->items, cmd->count * sizeof(const char *));
        argv_null[cmd->count] = NULL;
        execvp(argv_null[0], (char *const *)argv_null);
        build_log(LOG_ERROR, "Could not exec %s: %s", argv_null[0], strerror(errno));
        exit(1);
    }

    cmd->count = 0;

    int wstatus;
    for (;;) {
        if (waitpid(pid, &wstatus, 0) < 0) {
            build_log(LOG_ERROR, "Could not wait on pid %d: %s", pid, strerror(errno));
            return false;
        }
        if (WIFEXITED(wstatus)) {
            int code = WEXITSTATUS(wstatus);
            if (code != 0) {
                build_log(LOG_ERROR, "Command exited with code %d", code);
                return false;
            }
            return true;
        }
        if (WIFSIGNALED(wstatus)) {
            build_log(LOG_ERROR, "Command killed by signal %d", WTERMSIG(wstatus));
            return false;
        }
    }
#endif
}

void cmd_free(Cmd *cmd) {
    free(cmd->items);
    cmd->items = NULL;
    cmd->count = 0;
    cmd->capacity = 0;
}

////////////////////////////////
// Self-rebuild

#ifdef _WIN32
#   define REBUILD_CMD(binary, source) "cl.exe", "/nologo", temp_sprintf("/Fe:%s", binary), source
#else
#   define REBUILD_CMD(binary, source) "cc", "-o", binary, source
#endif

void go_rebuild_urself(int argc, char **argv, const char *source) {
    const char *binary = argv[0];

#ifdef _WIN32
    // Ensure .exe extension for Windows
    size_t len = strlen(binary);
    if (len < 4 || strcmp(binary + len - 4, ".exe") != 0) {
        binary = temp_sprintf("%s.exe", binary);
    }
#endif

    int rebuild = needs_rebuild1(binary, source);
    if (rebuild < 0) exit(1);
    if (rebuild == 0) return;

    const char *old_binary = temp_sprintf("%s.old", binary);
    if (!rename_file(binary, old_binary)) exit(1);

    Cmd cmd = {0};
    cmd_append(&cmd, REBUILD_CMD(binary, source));
    if (!cmd_run(&cmd)) {
        rename_file(old_binary, binary);
        exit(1);
    }

    // Re-execute with the new binary
    cmd_append(&cmd, binary);
    {
        int i;
        for (i = 1; i < argc; i++) {
            cmd__append(&cmd, 1, argv[i]);
        }
    }
    if (!cmd_run(&cmd)) exit(1);
    exit(0);
}

#endif // BUILD_IMPLEMENTATION
