/******************************************************************************
*
* Copyright (C) 2026  Brian Berlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _WIN32
#include <sys/stat.h>
#include <unistd.h>
#else
#include <direct.h>
#endif

#include "../src/interface/katconfig.h"
#include "../src/interface/shader_gpu.h"
#include "../src/interface/toml.h"

#ifndef KAT_SOURCE_DIR
#define KAT_SOURCE_DIR "."
#endif

static int float_near(float a, float b) {

    float diff = a - b;

    if (diff < 0.0f)
        diff = -diff;

    return diff < 0.0001f;
}

static int make_temp_filename(char *filename, size_t size) {

#ifdef _WIN32
    char *result;

    result = tmpnam(filename);
    (void) size;

    return result ? 0 : -1;
#else
    int fd;

    if (snprintf(filename, size, "/tmp/kat_shader_config_XXXXXX") >=
            (int) size)
        return -1;

    fd = mkstemp(filename);
    if (fd < 0)
        return -1;

    close(fd);
    return 0;
#endif
}

static int make_temp_dir(char *dirname, size_t size) {

    if (make_temp_filename(dirname, size))
        return -1;

    remove(dirname);

#ifdef _WIN32
    return _mkdir(dirname);
#else
    return mkdir(dirname, S_IRWXU);
#endif
}

static void remove_temp_dir(const char *dirname) {

#ifdef _WIN32
    _rmdir(dirname);
#else
    rmdir(dirname);
#endif
}

static int write_text_file(const char *filename, const char *contents) {

    FILE *fp = fopen(filename, "w");

    if (!fp)
        return -1;

    fputs(contents, fp);
    fclose(fp);

    return 0;
}

static char * read_text_file(const char *filename) {

    FILE *fp;
    long size;
    char *contents;

    fp = fopen(filename, "rb");
    if (!fp)
        return NULL;

    if (fseek(fp, 0, SEEK_END)) {
        fclose(fp);
        return NULL;
    }

    size = ftell(fp);
    if (size < 0) {
        fclose(fp);
        return NULL;
    }

    rewind(fp);

    contents = malloc((size_t)size + 1);
    if (!contents) {
        fclose(fp);
        return NULL;
    }

    if (fread(contents, 1, (size_t)size, fp) != (size_t)size) {
        free(contents);
        fclose(fp);
        return NULL;
    }

    contents[size] = '\0';
    fclose(fp);

    return contents;
}

static int count_occurrences(const char *haystack, const char *needle) {

    int count = 0;
    size_t needle_len = strlen(needle);
    const char *next = haystack;

    while ((next = strstr(next, needle))) {
        count++;
        next += needle_len;
    }

    return count;
}

static int test_shader_bundle_load(void) {

    t_shader_bundle bundle;
    t_shader_bundle_param *param;
    char bundle_dir[4096];
    int failed = 0;

    snprintf(bundle_dir, sizeof(bundle_dir), "%s/resources/shaders/bundles/crt-easymode",
            KAT_SOURCE_DIR);

    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load crt-easymode bundle metadata\n");
        return 1;
    }

    if (!bundle.id || strcmp(bundle.id, "crt-easymode")) {
        printf("Expected crt-easymode bundle id\n");
        failed = 1;
    }

    if (bundle.num_passes != 1) {
        printf("Expected one pass in crt-easymode bundle\n");
        failed = 1;
    }
    else {
        if (bundle.passes[0].num_stages != 2) {
            printf("Expected vertex and fragment stages in crt-easymode bundle\n");
            failed = 1;
        }

        if (bundle.passes[0].num_params != 17) {
            printf("Expected reflected crt-easymode parameter metadata\n");
            failed = 1;
        }

        if (!bundle.passes[0].stages[0].dxil ||
                !strstr(bundle.passes[0].stages[0].dxil, ".dxil")) {
            printf("Expected DXIL blob path in bundle metadata\n");
            failed = 1;
        }
    }

    param = shader_bundle_find_param(&bundle, 0, "SHARPNESS_H");
    if (!param) {
        printf("Expected SHARPNESS_H parameter in bundle metadata\n");
        failed = 1;
    }
    else {
        if (!float_near(param->def, 0.5f) || !float_near(param->val, 0.5f)) {
            printf("Expected SHARPNESS_H default value to initialize current value\n");
            failed = 1;
        }

        if (!param->stage || strcmp(param->stage, "fragment")) {
            printf("Expected SHARPNESS_H to be reflected for fragment stage\n");
            failed = 1;
        }

        if (param->offset != 60) {
            printf("Expected reflected SHARPNESS_H offset of 60\n");
            failed = 1;
        }
    }

    if (bundle.num_passes > 0 && bundle.passes[0].num_stages >= 2) {
        if (bundle.passes[0].stages[0].common_uniform_slot != 0 ||
                bundle.passes[0].stages[0].common_uniform_size != 112 ||
                bundle.passes[0].stages[0].param_uniform_slot != -1 ||
                bundle.passes[0].stages[0].param_uniform_size != 0) {
            printf("Expected reflected vertex UBO slot/size and no Push slot\n");
            failed = 1;
        }

        if (bundle.passes[0].stages[1].common_uniform_slot != 0 ||
                bundle.passes[0].stages[1].common_uniform_size != 112 ||
                bundle.passes[0].stages[1].param_uniform_slot != 1 ||
                bundle.passes[0].stages[1].param_uniform_size != 68) {
            printf("Expected reflected fragment UBO/Push slots and sizes\n");
            failed = 1;
        }
    }

    shader_bundle_free(&bundle);

    return failed ? 1 : 0;
}

static int test_shader_bundle_multipass(void) {

    t_shader_bundle bundle;
    char bundle_dir[4096];
    int failed = 0;

    snprintf(bundle_dir, sizeof(bundle_dir),
            "%s/resources/shaders/bundles/aa-shader-4.0", KAT_SOURCE_DIR);

    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load aa-shader-4.0 multipass bundle metadata\n");
        return 1;
    }

    if (bundle.num_passes != 2) {
        printf("Expected two passes in aa-shader-4.0 bundle\n");
        failed = 1;
    }
    else {
        const t_shader_bundle_pass *pass0 = &bundle.passes[0];
        const t_shader_bundle_pass *pass1 = &bundle.passes[1];

        /* Pass 0 renders to a viewport-sized intermediate sampled by pass 1. */
        if (!pass0->scale_type || strcmp(pass0->scale_type, "viewport")) {
            printf("Expected pass0 viewport scale_type\n");
            failed = 1;
        }

        /* Each fragment stage must carry one reflected Source sampler at slot 0
           so the runtime can chain pass0's output into pass1. */
        for (int i = 0; i < bundle.num_passes; ++i) {
            const t_shader_bundle_pass *pass = &bundle.passes[i];
            int found_fragment = 0;

            for (int j = 0; j < pass->num_stages; ++j) {
                const t_shader_bundle_stage *stage = &pass->stages[j];
                if (!stage->stage || strcmp(stage->stage, "fragment"))
                    continue;
                found_fragment = 1;
                if (stage->num_sampler_bindings != 1 ||
                        !stage->sampler_bindings[0].name ||
                        strcmp(stage->sampler_bindings[0].name, "Source") ||
                        stage->sampler_bindings[0].slot != 0) {
                    printf("Expected pass %d fragment Source sampler at slot 0\n",
                            i);
                    failed = 1;
                }
            }

            if (!found_fragment) {
                printf("Expected fragment stage in pass %d\n", i);
                failed = 1;
            }
        }

        (void) pass1;
    }

    shader_bundle_free(&bundle);

    return failed ? 1 : 0;
}

static int test_shader_bundle_lut_alias(void) {

    t_shader_bundle bundle;
    char bundle_dir[4096];
    int failed = 0;

    snprintf(bundle_dir, sizeof(bundle_dir),
            "%s/tests/shader_bundles/lut-alias-test", KAT_SOURCE_DIR);

    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load lut-alias-test bundle metadata\n");
        return 1;
    }

    if (bundle.num_textures != 1 || !bundle.textures[0].name ||
            strcmp(bundle.textures[0].name, "PALETTE") ||
            !bundle.textures[0].file ||
            !strstr(bundle.textures[0].file, "PALETTE.png")) {
        printf("Expected PALETTE LUT metadata in lut-alias-test bundle\n");
        failed = 1;
    }

    if (bundle.num_passes != 2) {
        printf("Expected two passes in lut-alias-test bundle\n");
        failed = 1;
    }
    else {
        /* Pass 0 carries the FirstPass alias. */
        if (!bundle.passes[0].alias ||
                strcmp(bundle.passes[0].alias, "FirstPass")) {
            printf("Expected pass0 alias FirstPass\n");
            failed = 1;
        }

        /* Pass 1 samples FirstPass (alias) and PALETTE (LUT). */
        if (bundle.passes[1].num_stages >= 2) {
            const t_shader_bundle_stage *frag = NULL;
            int found_first = 0, found_palette = 0;
            int found_first_size = 0, found_palette_size = 0;

            for (int j = 0; j < bundle.passes[1].num_stages; ++j) {
                if (bundle.passes[1].stages[j].stage &&
                        !strcmp(bundle.passes[1].stages[j].stage, "fragment"))
                    frag = &bundle.passes[1].stages[j];
            }

            if (!frag || frag->num_sampler_bindings != 2) {
                printf("Expected two samplers in pass1 fragment stage\n");
                failed = 1;
            }
            else {
                for (int b = 0; b < frag->num_sampler_bindings; ++b) {
                    const char *n = frag->sampler_bindings[b].name;
                    if (n && !strcmp(n, "FirstPass")) found_first = 1;
                    if (n && !strcmp(n, "PALETTE")) found_palette = 1;
                }
                for (int u = 0; u < frag->num_uniforms; ++u) {
                    const char *n = frag->uniforms[u].name;
                    if (n && !strcmp(n, "FirstPassSize")) found_first_size = 1;
                    if (n && !strcmp(n, "PALETTESize")) found_palette_size = 1;
                }
                if (!found_first || !found_palette) {
                    printf("Expected FirstPass + PALETTE samplers in pass1\n");
                    failed = 1;
                }
                if (!found_first_size || !found_palette_size) {
                    printf("Expected FirstPassSize + PALETTESize uniforms\n");
                    failed = 1;
                }
            }
        }
        else {
            printf("Expected vertex+fragment stages in pass1\n");
            failed = 1;
        }
    }

    shader_bundle_free(&bundle);

    return failed ? 1 : 0;
}

static int test_shader_bundle_history_feedback(void) {

    t_shader_bundle bundle;
    char bundle_dir[4096];
    int failed = 0;

    /* History fixture: single pass sampling OriginalHistory1/OriginalHistory2. */
    snprintf(bundle_dir, sizeof(bundle_dir),
            "%s/tests/shader_bundles/history-test", KAT_SOURCE_DIR);
    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load history-test bundle metadata\n");
        return 1;
    }
    else {
        const t_shader_bundle_stage *frag = NULL;
        int h1 = 0, h2 = 0;

        for (int j = 0; j < bundle.passes[0].num_stages; ++j) {
            if (bundle.passes[0].stages[j].stage &&
                    !strcmp(bundle.passes[0].stages[j].stage, "fragment"))
                frag = &bundle.passes[0].stages[j];
        }
        if (!frag || frag->num_sampler_bindings != 3) {
            printf("Expected Source + 2 history samplers in history-test\n");
            failed = 1;
        }
        else {
            for (int b = 0; b < frag->num_sampler_bindings; ++b) {
                const char *n = frag->sampler_bindings[b].name;
                if (n && !strcmp(n, "OriginalHistory1")) h1 = 1;
                if (n && !strcmp(n, "OriginalHistory2")) h2 = 1;
            }
            if (!h1 || !h2) {
                printf("Expected OriginalHistory1/2 samplers\n");
                failed = 1;
            }
        }
        shader_bundle_free(&bundle);
    }

    /* Feedback fixture: pass0 reads PassFeedback0, pass1 reads AccumFeedback. */
    snprintf(bundle_dir, sizeof(bundle_dir),
            "%s/tests/shader_bundles/feedback-test", KAT_SOURCE_DIR);
    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load feedback-test bundle metadata\n");
        return 1;
    }
    else {
        int pf0 = 0, accum_fb = 0;

        if (bundle.num_passes != 2 || !bundle.passes[0].alias ||
                strcmp(bundle.passes[0].alias, "Accum")) {
            printf("Expected 2-pass feedback bundle with Accum alias\n");
            failed = 1;
        }
        for (int i = 0; i < bundle.num_passes; ++i) {
            for (int j = 0; j < bundle.passes[i].num_stages; ++j) {
                const t_shader_bundle_stage *s = &bundle.passes[i].stages[j];
                for (int b = 0; b < s->num_sampler_bindings; ++b) {
                    const char *n = s->sampler_bindings[b].name;
                    if (n && !strcmp(n, "PassFeedback0")) pf0 = 1;
                    if (n && !strcmp(n, "AccumFeedback")) accum_fb = 1;
                }
            }
        }
        if (!pf0 || !accum_fb) {
            printf("Expected PassFeedback0 + AccumFeedback samplers\n");
            failed = 1;
        }
        shader_bundle_free(&bundle);
    }

    return failed ? 1 : 0;
}

static int test_shader_bundle_float_srgb(void) {

    t_shader_bundle bundle;
    char bundle_dir[4096];
    int failed = 0;
    int srgb_passes = 0;
    int sparse_found = 0;

    snprintf(bundle_dir, sizeof(bundle_dir),
            "%s/resources/shaders/bundles/crt-royale", KAT_SOURCE_DIR);
    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load crt-royale bundle metadata\n");
        return 1;
    }

    if (bundle.num_passes != 12) {
        printf("Expected 12 passes in crt-royale\n");
        failed = 1;
    }
    if (bundle.num_textures != 6) {
        printf("Expected 6 mask LUTs in crt-royale\n");
        failed = 1;
    }

    for (int i = 0; i < bundle.num_passes; ++i) {
        if (bundle.passes[i].srgb_framebuffer &&
                !strcmp(bundle.passes[i].srgb_framebuffer, "true"))
            srgb_passes++;

        /* A sparse sampler slot (reflected slot beyond binding count-1) proves
           the dummy-fill path is exercised. */
        for (int j = 0; j < bundle.passes[i].num_stages; ++j) {
            const t_shader_bundle_stage *s = &bundle.passes[i].stages[j];
            for (int b = 0; b < s->num_sampler_bindings; ++b) {
                if (s->sampler_bindings[b].slot >= s->samplers)
                    failed = 1;  /* slot must be < num_samplers */
                if (s->sampler_bindings[b].slot >= s->num_sampler_bindings)
                    sparse_found = 1;
            }
        }
    }

    if (srgb_passes < 8) {
        printf("Expected several sRGB framebuffer passes in crt-royale\n");
        failed = 1;
    }
    if (!sparse_found) {
        printf("Expected at least one sparse sampler slot in crt-royale\n");
        failed = 1;
    }

    shader_bundle_free(&bundle);

    /* float-test fixture: pass 0 declares a float framebuffer. */
    snprintf(bundle_dir, sizeof(bundle_dir),
            "%s/tests/shader_bundles/float-test", KAT_SOURCE_DIR);
    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load float-test bundle metadata\n");
        return 1;
    }
    if (bundle.num_passes != 2 || !bundle.passes[0].float_framebuffer ||
            strcmp(bundle.passes[0].float_framebuffer, "true")) {
        printf("Expected pass0 float_framebuffer=true in float-test\n");
        failed = 1;
    }
    if (bundle.num_passes > 1 && bundle.passes[1].float_framebuffer &&
            strlen(bundle.passes[1].float_framebuffer) &&
            strcmp(bundle.passes[1].float_framebuffer, "false") &&
            strcmp(bundle.passes[1].float_framebuffer, "")) {
        printf("Expected pass1 without float framebuffer in float-test\n");
        failed = 1;
    }
    shader_bundle_free(&bundle);

    return failed ? 1 : 0;
}

static int test_shader_bundle_group_divider(void) {

    char bundle_dir[4096];
    char bundle_file[4096];
    t_shader_bundle bundle;
    t_shader_bundle_param *grouped;
    t_shader_bundle_param *plain;
    int failed = 0;

    if (make_temp_dir(bundle_dir, sizeof(bundle_dir))) {
        printf("Failed to create temporary bundle directory for group/divider test\n");
        return 1;
    }

    if (snprintf(bundle_file, sizeof(bundle_file), "%s/bundle.toml", bundle_dir) >=
            (int) sizeof(bundle_file)) {
        printf("Temporary bundle path too long for group/divider test\n");
        remove_temp_dir(bundle_dir);
        return 1;
    }

    if (write_text_file(bundle_file,
                "id = \"group-divider-test\"\n"
                "\n"
                "[[pass]]\n"
                "index = 0\n"
                "\n"
                "  [[pass.param]]\n"
                "  name = \"GROUPED\"\n"
                "  label = \"Grouped Param\"\n"
                "  default = 1.0\n"
                "  min = 0.0\n"
                "  max = 2.0\n"
                "  step = 1.0\n"
                "  stage = \"fragment\"\n"
                "  target = \"push\"\n"
                "  resource = \"Params\"\n"
                "  offset = 4\n"
                "  type = \"float\"\n"
                "  group = \"FILTERING OPTIONS\"\n"
                "  divider = 1\n"
                "\n"
                "  [[pass.param]]\n"
                "  name = \"PLAIN\"\n"
                "  label = \"Plain Param\"\n"
                "  default = 0.0\n"
                "  min = 0.0\n"
                "  max = 1.0\n"
                "  step = 0.5\n"
                "  stage = \"fragment\"\n"
                "  target = \"push\"\n"
                "  resource = \"Params\"\n"
                "  offset = 8\n"
                "  type = \"float\"\n")) {
        printf("Failed to write group/divider test bundle metadata\n");
        remove_temp_dir(bundle_dir);
        return 1;
    }

    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load group/divider test bundle metadata\n");
        remove(bundle_file);
        remove_temp_dir(bundle_dir);
        return 1;
    }

    if (bundle.num_passes != 1 || bundle.passes[0].num_params != 2) {
        printf("Expected one pass with two params in group/divider test bundle\n");
        failed = 1;
    }

    grouped = shader_bundle_find_param(&bundle, 0, "GROUPED");
    if (!grouped) {
        printf("Missing GROUPED param in group/divider test bundle\n");
        failed = 1;
    }
    else if (!grouped->group || strcmp(grouped->group, "FILTERING OPTIONS") ||
            grouped->divider != 1) {
        printf("Expected GROUPED param to parse group/divider metadata\n");
        failed = 1;
    }

    plain = shader_bundle_find_param(&bundle, 0, "PLAIN");
    if (!plain) {
        printf("Missing PLAIN param in group/divider test bundle\n");
        failed = 1;
    }
    else if (plain->group != NULL || plain->divider != 0) {
        printf("Expected missing group/divider keys to default cleanly\n");
        failed = 1;
    }

    shader_bundle_free(&bundle);
    remove(bundle_file);
    remove_temp_dir(bundle_dir);

    return failed ? 1 : 0;
}

static int test_shader_bundle_config_overrides(void) {

    char filename[4096];
    char bundle_dir[4096];
    t_toml_node *tbl;
    t_shader_bundle bundle;
    t_shader_bundle_param *param;
    char *contents;
    int failed = 0;

    if (make_temp_filename(filename, sizeof(filename))) {
        printf("Failed to create temporary bundle override config filename\n");
        return 1;
    }

    if (write_text_file(filename,
                "[video]\n"
                "shader = \"crt-easymode\"\n")) {
        printf("Failed to write bundle override test config\n");
        return 1;
    }

    snprintf(bundle_dir, sizeof(bundle_dir), "%s/resources/shaders/bundles/crt-easymode",
            KAT_SOURCE_DIR);

    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load bundle for override test\n");
        remove(filename);
        return 1;
    }

    tbl = toml_parse_file(filename);
    if (!tbl) {
        printf("Failed to parse bundle override test config\n");
        shader_bundle_free(&bundle);
        remove(filename);
        return 1;
    }

    param = shader_bundle_find_param(&bundle, 0, "SHARPNESS_H");
    if (!param) {
        printf("Missing SHARPNESS_H for override test\n");
        failed = 1;
    }

    config_shader_bundle_info(tbl, &bundle, KCFG_LOAD);

    contents = read_text_file(filename);
    if (!contents) {
        printf("Failed to read config after bundle load\n");
        failed = 1;
    }
    else {
        if (strstr(contents, "shaderbundle")) {
            printf("Bundle load should not write default override tables\n");
            failed = 1;
        }
        free(contents);
    }

    if (param && !float_near(param->val, param->def)) {
        printf("Bundle load without overrides should keep default value\n");
        failed = 1;
    }

    if (param) {
        param->val = 0.75f;
        config_shader_bundle_info(tbl, &bundle, KCFG_SAVE);
        config_shader_bundle_info(tbl, &bundle, KCFG_SAVE);
    }

    contents = read_text_file(filename);
    if (!contents) {
        printf("Failed to read saved bundle override config\n");
        failed = 1;
    }
    else {
        if (count_occurrences(contents, "SHARPNESS_H") != 1) {
            printf("Expected one SHARPNESS_H key after repeated bundle saves\n");
            failed = 1;
        }

        if (!strstr(contents, "SHARPNESS_H = +0.75")) {
            printf("Expected SHARPNESS_H override to save as +0.75\n");
            failed = 1;
        }

        if (!strstr(contents, "source_hash")) {
            printf("Expected source_hash to be recorded with bundle overrides\n");
            failed = 1;
        }

        free(contents);
    }

    if (param) {
        param->val = 2.0f;
        config_shader_bundle_info(tbl, &bundle, KCFG_SAVE);
    }

    contents = read_text_file(filename);
    if (!contents) {
        printf("Failed to read clamped bundle override config\n");
        failed = 1;
    }
    else {
        if (!strstr(contents, "SHARPNESS_H = +1.00")) {
            printf("Expected SHARPNESS_H override to clamp to +1.00\n");
            failed = 1;
        }
        free(contents);
    }

    if (param) {
        param->val = param->def;
        config_shader_bundle_info(tbl, &bundle, KCFG_SAVE);
    }

    contents = read_text_file(filename);
    if (!contents) {
        printf("Failed to read reset bundle override config\n");
        failed = 1;
    }
    else {
        if (strstr(contents, "SHARPNESS_H")) {
            printf("Reset-to-default bundle parameter should remove override key\n");
            failed = 1;
        }
        free(contents);
    }

    toml_free_node_tree(tbl);
    shader_bundle_free(&bundle);
    remove(filename);

    return failed ? 1 : 0;
}

static int test_shader_bundle_override_merge(void) {

    char global_filename[4096];
    char rom_filename[4096];
    char bundle_dir[4096];
    t_toml_node *global_tbl;
    t_toml_node *rom_tbl;
    t_shader_bundle bundle;
    t_shader_bundle_param *sharpness_h;
    t_shader_bundle_param *mask_strength;
    int failed = 0;

    if (make_temp_filename(global_filename, sizeof(global_filename)) ||
            make_temp_filename(rom_filename, sizeof(rom_filename))) {
        printf("Failed to create temporary merge override config filename\n");
        return 1;
    }

    if (write_text_file(global_filename,
                "[[shaderbundle]]\n"
                "id = \"crt-easymode\"\n"
                "\n"
                "  [[shaderbundle.pass]]\n"
                "  index = 0\n"
                "  SHARPNESS_H = +0.65\n"
                "  MASK_STRENGTH = +0.25\n") ||
            write_text_file(rom_filename,
                "[[shaderbundle]]\n"
                "id = \"crt-easymode\"\n"
                "\n"
                "  [[shaderbundle.pass]]\n"
                "  index = 0\n"
                "  SHARPNESS_H = +0.90\n")) {
        printf("Failed to write merge override test configs\n");
        remove(global_filename);
        remove(rom_filename);
        return 1;
    }

    global_tbl = toml_parse_file(global_filename);
    rom_tbl = toml_parse_file(rom_filename);
    if (!global_tbl || !rom_tbl) {
        printf("Failed to parse merge override test configs\n");
        if (global_tbl)
            toml_free_node_tree(global_tbl);
        if (rom_tbl)
            toml_free_node_tree(rom_tbl);
        remove(global_filename);
        remove(rom_filename);
        return 1;
    }

    snprintf(bundle_dir, sizeof(bundle_dir), "%s/resources/shaders/bundles/crt-easymode",
            KAT_SOURCE_DIR);
    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load bundle for merge override test\n");
        toml_free_node_tree(global_tbl);
        toml_free_node_tree(rom_tbl);
        remove(global_filename);
        remove(rom_filename);
        return 1;
    }

    sharpness_h = shader_bundle_find_param(&bundle, 0, "SHARPNESS_H");
    mask_strength = shader_bundle_find_param(&bundle, 0, "MASK_STRENGTH");
    if (!sharpness_h || !mask_strength) {
        printf("Missing reflected parameters for merge override test\n");
        failed = 1;
    }
    else {
        config_load_shader_bundle_info(global_tbl, &bundle, 1);
        config_load_shader_bundle_info(rom_tbl, &bundle, 0);

        if (!float_near(sharpness_h->val, 0.90f)) {
            printf("Expected ROM bundle override to replace global SHARPNESS_H value\n");
            failed = 1;
        }

        if (!float_near(mask_strength->val, 0.25f)) {
            printf("Expected global MASK_STRENGTH override to survive ROM merge\n");
            failed = 1;
        }
    }

    shader_bundle_free(&bundle);
    toml_free_node_tree(global_tbl);
    toml_free_node_tree(rom_tbl);
    remove(global_filename);
    remove(rom_filename);

    return failed ? 1 : 0;
}

static int test_shader_gpu_format_selection(void) {

    t_shader_bundle bundle;
    SDL_GPUShaderFormat selected;
    const char *path;
    char bundle_dir[4096];
    int failed = 0;

    snprintf(bundle_dir, sizeof(bundle_dir), "%s/resources/shaders/bundles/crt-easymode",
            KAT_SOURCE_DIR);

    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load bundle for GPU format selection test\n");
        return 1;
    }

    selected = shader_gpu_select_format(
            SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL, &bundle);
    if (selected != SDL_GPU_SHADERFORMAT_SPIRV) {
        printf("Expected SPIR-V to be selected when available\n");
        failed = 1;
    }

    selected = shader_gpu_select_format(SDL_GPU_SHADERFORMAT_MSL, &bundle);
    if (selected != SDL_GPU_SHADERFORMAT_MSL) {
        printf("Expected MSL to be selected for Metal-capable backend\n");
        failed = 1;
    }

    selected = shader_gpu_select_format(SDL_GPU_SHADERFORMAT_DXBC, &bundle);
    if (selected != SDL_GPU_SHADERFORMAT_INVALID) {
        printf("Expected DXBC to be rejected when bundle only has DXIL\n");
        failed = 1;
    }

    if (bundle.num_passes < 1 || bundle.passes[0].num_stages < 1) {
        printf("Bundle missing stages for GPU path test\n");
        failed = 1;
    }
    else {
        path = shader_gpu_stage_blob_path(&bundle.passes[0].stages[0],
                SDL_GPU_SHADERFORMAT_DXIL);
        if (!path || !strstr(path, ".dxil")) {
            printf("Expected DXIL stage path from bundle metadata\n");
            failed = 1;
        }
    }

    if (shader_gpu_default_color_format() !=
            SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM) {
        printf("Expected RGBA8 default shader GPU color format\n");
        failed = 1;
    }

    shader_bundle_free(&bundle);

    return failed ? 1 : 0;
}

static int test_shader_gpu_uniform_and_param_pack(void) {

    t_shader_bundle bundle;
    t_shader_bundle_param *param;
    t_shader_gpu_common_uniforms uniforms;
    t_shader_gpu_uniform_blocks blocks;
    float packed_value = 0.0f;
    float packed_source_size[4];
    float packed_mvp = 0.0f;
    char bundle_dir[4096];
    int failed = 0;

    shader_gpu_common_uniforms_fill(&uniforms, 320, 240, 320, 240, 640, 480,
            123, 1);
    if (!float_near(uniforms.mvp[0], 1.0f) ||
            !float_near(uniforms.mvp[5], 1.0f) ||
            !float_near(uniforms.mvp[10], 1.0f) ||
            !float_near(uniforms.mvp[15], 1.0f)) {
        printf("Expected identity MVP in common shader GPU uniforms\n");
        failed = 1;
    }

    if (!float_near(uniforms.output_size[0], 640.0f) ||
            !float_near(uniforms.original_size[1], 240.0f) ||
            !float_near(uniforms.source_size[2], 1.0f / 320.0f)) {
        printf("Expected output/original/source sizes in common uniforms\n");
        failed = 1;
    }
    if (uniforms.frame_count != 123 || uniforms.frame_direction != 1) {
        printf("Expected frame values in common uniforms\n");
        failed = 1;
    }

    snprintf(bundle_dir, sizeof(bundle_dir), "%s/resources/shaders/bundles/crt-easymode",
            KAT_SOURCE_DIR);

    shader_bundle_init(&bundle);
    if (shader_bundle_load(bundle_dir, &bundle)) {
        printf("Failed to load bundle for GPU param packing test\n");
        return 1;
    }

    param = shader_bundle_find_param(&bundle, 0, "SHARPNESS_H");
    if (!param) {
        printf("Missing SHARPNESS_H for GPU param packing test\n");
        failed = 1;
    }
    else {
        param->val = 0.75f;
        if (shader_gpu_pack_uniform_blocks(&bundle, 0, &uniforms, &blocks)) {
            printf("Failed to pack shader uniform blocks\n");
            failed = 1;
        }
        else {
            memcpy(&packed_mvp, blocks.fragment_common.data, sizeof(float));
            memcpy(packed_source_size, blocks.fragment_common.data + 96,
                    sizeof(packed_source_size));
            memcpy(&packed_value, blocks.fragment_param.data + 60,
                    sizeof(float));
            if (blocks.fragment_common.size != 112 ||
                    !float_near(packed_mvp, 1.0f) ||
                    !float_near(packed_source_size[2], 1.0f / 320.0f)) {
                printf("Expected reflected UBO semantic uniforms packed\n");
                failed = 1;
            }
            if (blocks.vertex_param.size != 0 ||
                    blocks.fragment_param.size != 68 ||
                    !float_near(packed_value, 0.75f)) {
                printf("Expected SHARPNESS_H packed into Push at offset 60\n");
                failed = 1;
            }
        }
    }

    shader_bundle_free(&bundle);

    return failed ? 1 : 0;
}

int main(void) {

    int failed = 0;

    failed |= test_shader_bundle_load();
    failed |= test_shader_bundle_multipass();
    failed |= test_shader_bundle_lut_alias();
    failed |= test_shader_bundle_history_feedback();
    failed |= test_shader_bundle_float_srgb();
    failed |= test_shader_bundle_group_divider();
    failed |= test_shader_bundle_config_overrides();
    failed |= test_shader_bundle_override_merge();
    failed |= test_shader_gpu_format_selection();
    failed |= test_shader_gpu_uniform_and_param_pack();

    return failed ? 1 : 0;
}
