//
// Created by Tristan on 11/6/2025.
//

#include "util.h"
#include "logger/log.h"
#include "wmo/wmo.h"
#include "mpq/mpq.h"

WMORootData out_wmo_data;

char *get_file_contents(const char *filename) {
    FILE *fp;
    long length;
    char *buffer = NULL;

    fp = fopen(filename, "rb");
    if (!fp) {
        log_error("Could not open file '%s'. Reason: %s", filename, strerror(errno));
    }

    fseek(fp, 0, SEEK_END);
    length = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    buffer = (char *)malloc(length + 1);
    if (!buffer) {
        log_error("Memory allocation failed for file '%s'.", filename);
        fclose(fp);
        return NULL;
    }

    fread(buffer, 1, length, fp);
    buffer[length] ='\0';
    fclose(fp);
    return buffer;
}

int load_wmo_file(const char *filename, FILE **file_ptr_out) {
    *file_ptr_out = fopen(filename, "rb");

    if (*file_ptr_out == NULL) {
        log_error("Error opening WMO file %s: %s", filename, GetLastError());
        return (int)GetLastError();
    }

    return 0;
}

int create_dir_recursive(const char *full_path) {
    char temp_path[FILENAME_MAX];
    log_info("Full Path in craete_dir_recursive == '%s", full_path);
    strncpy(temp_path, full_path, FILENAME_MAX - 1);
    temp_path[FILENAME_MAX - 1] = '\0';

    char *p = temp_path;

    // Check for "C:\" or "/" prefix and move the pointer past
    if (p[0] == PATH_SEPARATOR || p[0] == '/')
        p++;
#ifdef _WIN32
    if (p[1] == ':')
        p += 2; // skips (C:)
#endif

    while (*p != '\0') {
        if (*p == PATH_SEPARATOR || *p == '/') {
            // replace with null terminator to isolate
            char separator = *p;
            *p = '\0';

            // Attempt to create dir
            if (MAKE_DIR(temp_path) != 0) {
                if (errno != EEXIST) {
                    DWORD dwError = GetLastError();
                    log_error("Failed to create directory '%s'. error: %s", temp_path, dwError);
                    return -1;
                }
            }

            // Restore separator character
            *p = separator;
        }
        p++; // adv to next char
    }

    if (MAKE_DIR(temp_path) != 0) {
        if (errno != EEXIST) {
            log_error("Failed to create final directory '%s'. Error: %lu", temp_path, GetLastError());
            return -1;
        }
    }

    return 0;
}

// TODO: might not need this honestly
/*
WMOData load_mver_data() {
    WMOChunkHeader mver_header;
    WMOData result = {NULL, 0};
    DWORD bytesRead = 0;
    // magic;
    // size;
    // version;
    // return;
}*/

WMOData load_wmo_data(ArchiveManager *archives, const char *wmoFileName) {
    HANDLE hFile = get_file_in_archives(archives, wmoFileName);
    WMOData result = {NULL, 0};
    DWORD bytesRead = 0;

    if (!hFile) {
        log_error("Failed to open WMO file '%s' inside MPQ. Error: %lu", wmoFileName, GetLastError());
        return result;
    }

    result.size = get_file_size_in_mpq(hFile, wmoFileName);
    if (result.size == SFILE_INVALID_SIZE) {
        SFileCloseFile(hFile);
        return result;
    }

    result.data = (char *)malloc(result.size);
    if (result.data == NULL) {
        log_error("Memory allocation failed for WMO file '%s'.\n", wmoFileName);
        SFileCloseFile(hFile);
        return result;
    }

    // TODO: make into helper function
    if (!SFileReadFile(hFile, result.data, result.size, &bytesRead, NULL)) {
        log_error("Failed to read WMO data for '%s'. Error: %lu", wmoFileName, GetLastError());
        free(result.data);
        result.data = NULL;
        result.size = 0;
    } else if (bytesRead != result.size) {
        log_error("Read size mismatch for WMO file '%s'. Expected %lu, got %lu.",
            wmoFileName, result.size, bytesRead);
        free(result.data);
        result.data = NULL;
        result.size = 0;
    }

    SFileCloseFile(hFile);

    log_info("Successfully loaded WMO file '%s' (%lu bytes) into memory.", wmoFileName, result.size);
    return result;
}

WMOData load_wmo_data_from_file(FILE **file_ptr_in) {
    //HANDLE hFile = get_file_in_mpq(hMPQ, wmoFileName);
    WMOData result = {NULL, 0};
    //DWORD bytesRead = 0;

    if (!*file_ptr_in) {
        log_error("Failed to open WMO file. Error: %lu", GetLastError());
        return result;
    }

    fseek(*file_ptr_in, 0, SEEK_END);
    long file_size = ftell(*file_ptr_in);

    result.size = file_size;
    if (result.size == -1L) {
        log_error("Failed to determine WMO size.");
        fclose(*file_ptr_in);
        *file_ptr_in = NULL;
        return result;
    }

    result.data = (char *)malloc(result.size);
    if (result.data == NULL) {
        log_error("Memory allocation failed for WMO file.\n");
        fclose(*file_ptr_in);
        *file_ptr_in = NULL;
        return result;
    }

    fseek(*file_ptr_in, 0, SEEK_SET);

    size_t items_read = fread(result.data, 1, result.size, *file_ptr_in);

    if (items_read != result.size) {
        log_error("Error reading WMO file data. Expected %ld bytes, read %zu bytes.\n", result.size, items_read);
        free(result.data);
        result.data = NULL;
        result.size = 0;
        fclose(*file_ptr_in);
        *file_ptr_in = NULL;
        return result;
    }

    /*
    // TODO: make into helper function
    if (!SFileReadFile(hFile, result.data, result.size, &bytesRead, NULL)) {
        log_error("Failed to read WMO data for '%s'. Error: %lu", wmoFileName, GetLastError());
        free(result.data);
        result.data = NULL;
        result.size = 0;
    } else if (bytesRead != result.size) {
        log_error("Read size mismatch for WMO file '%s'. Expected %lu, got %lu.",
            wmoFileName, result.size, bytesRead);
        free(result.data);
        result.data = NULL;
        result.size = 0;
    }

    SFileCloseFile(hFile);*/
    fclose(*file_ptr_in);
    *file_ptr_in = NULL;
    log_info("Successfully loaded WMO file (%lu bytes) into memory.", result.size);
    return result;
}

size_t get_wmo_base_name(char *dest, size_t dest_size, const char *full_path) {
    size_t len = strlen(full_path);
    if (len >= dest_size) {
        //TODO: handle error
        return 0;
    }

    strncpy (dest, full_path, len);
    dest[len] = '\0';
    if (len > 4 && strcmp(dest + len - 4, ".wmo") == 0) {
        dest[len - 4] = '\0';
        return len -4;
    }
    return len;
}

void parse_wmo_chunks(ArchiveManager *archives, const char *wmo_buffer, DWORD total_size,  WMORootData *out_wmo_data,
    const char *wmo_file_path) {
    const char *current_ptr = wmo_buffer;
    const char *end_ptr = wmo_buffer + total_size;
    // WMORootData out_wmo_data; not sure if should be defined here or outside of scope at top

    // initialize struct passed into function
    memset(out_wmo_data, 0, sizeof(WMORootData));

    log_info("Starting WMO chunk traversal...");

    while (current_ptr < end_ptr) {
        // must be enough room for a header (8 bytes)
        if (current_ptr + sizeof(WMOChunkHeader) > end_ptr) {
            log_warn("End of file reached unexpectedly.");
            break;
        }

        const WMOChunkHeader *header = (const WMOChunkHeader *)current_ptr;

        log_info("Found chunk: %c%c%c%c, Size: %lu bytes",
            (char)(header->chunk_name >> 24),
            (char)(header->chunk_name >> 16),
            (char)(header->chunk_name >> 8),
            (char)(header->chunk_name),
            header->chunk_size);

        const char *data_start = current_ptr + sizeof(WMOChunkHeader); // sizeof(WMOChunkHeader);

        switch (header->chunk_name) {
        case MVER:
            out_wmo_data->mver_data_ptr = data_start;
            out_wmo_data->mver_size = header->chunk_size;
            log_info("  -> MVER Chunk found at offset %td.", out_wmo_data->mver_data_ptr - wmo_buffer);
            break;
        case MOHD:
            out_wmo_data->mohd_data_ptr = data_start;
            out_wmo_data->mohd_size = header->chunk_size;
            log_info("  -> MOHD Chunk found at offset %td.", out_wmo_data->mohd_data_ptr - wmo_buffer);
            parse_mohd_chunk(out_wmo_data);
            break;
        case MOGN:
            out_wmo_data->mogn_data_ptr = data_start;
            out_wmo_data->mogn_size = header->chunk_size;
            log_info("  -> MOGN Chunk found at offset %td.", out_wmo_data->mogn_data_ptr - wmo_buffer);
            break;
        case MOTX:
            out_wmo_data->motx_data_ptr = data_start;
            out_wmo_data->motx_size = header->chunk_size;
            log_info("  -> MOTX Chunk found at offset %td.", out_wmo_data->motx_data_ptr - wmo_buffer);
            break;
        case MOMT:
            out_wmo_data->momt_data_ptr = data_start;
            out_wmo_data->momt_size = header->chunk_size;
            log_info("  -> MOMT Chunk found at offset %td", out_wmo_data->momt_data_ptr - wmo_buffer);
            break;
        case MOGI:
            out_wmo_data->mogi_data_ptr = data_start;
            out_wmo_data->mogi_size = header->chunk_size;
            log_info("  -> MOGI Chunk found at offset %td", out_wmo_data->mogi_data_ptr - wmo_buffer);
            break;
        case MOSB:
            out_wmo_data->mosb_data_ptr = data_start;
            out_wmo_data->mosb_size = header->chunk_size;
            log_info("  -> MOSB Chunk found at offset %td", out_wmo_data->mosb_data_ptr - wmo_buffer);
            get_mosb_skybox(out_wmo_data);
            break;
        case MOPV:
            out_wmo_data->mopv_data_ptr = data_start;
            out_wmo_data->mopv_size = header->chunk_size;
            log_info("  -> MOPV Chunk found at offset %td", out_wmo_data->mopv_data_ptr - wmo_buffer);
            parse_mopv_chunk(out_wmo_data);
            break;
        case MOPT:
            out_wmo_data->mopt_data_ptr = data_start;
            out_wmo_data->mopt_size = header->chunk_size;
            log_info("  -> MOPT Chunk found at offset %td", out_wmo_data->mopt_data_ptr - wmo_buffer);
            parse_mopt_chunk(out_wmo_data);
            break;
        case MOPR:
            out_wmo_data->mopr_data_ptr = data_start;
            out_wmo_data->mopr_size = header->chunk_size;
            log_info("  -> MOPR Chunk found at offset %td", out_wmo_data->mopr_data_ptr - wmo_buffer);
            parse_mopr_chunk(out_wmo_data);
            break;
        case MOVV:
            out_wmo_data->movv_data_ptr = data_start;
            out_wmo_data->movv_size = header->chunk_size;
            log_info("  -> MOVV Chunk found at offset %td", out_wmo_data->movv_data_ptr - wmo_buffer);
            break;
        case MOVB:
            out_wmo_data->movb_data_ptr = data_start;
            out_wmo_data->movb_size = header->chunk_size;
            log_info("  -> MOVB Chunk found at offset %td", out_wmo_data->movb_data_ptr - wmo_buffer);
            break;
        case MOLT:
            out_wmo_data->molt_data_ptr = data_start;
            out_wmo_data->molt_size = header->chunk_size;
            log_info("  -> MOLT Chunk found at offset %td", out_wmo_data->molt_data_ptr - wmo_buffer);
            break;
        case MODS:
            out_wmo_data->mods_data_ptr = data_start;
            out_wmo_data->mods_size = header->chunk_size;
            log_info("  -> MODS Chunk found at offset %td", out_wmo_data->mods_data_ptr - wmo_buffer);
            break;
        case MODN:
            out_wmo_data->modn_data_ptr = data_start;
            out_wmo_data->modn_size = header->chunk_size;
            log_info("  -> MODN Chunk found at offset %td", out_wmo_data->modn_data_ptr - wmo_buffer);
            break;
        case MODD:
            out_wmo_data->modd_data_ptr = data_start;
            out_wmo_data->modd_size = header->chunk_size;
            log_info("  -> MODD Chunk found at offset %td", out_wmo_data->modd_data_ptr - wmo_buffer);
            break;
        case MFOG:
            out_wmo_data->mfog_data_ptr = data_start;
            out_wmo_data->mfog_size = header->chunk_size;
            log_info("  -> MFOG Chunk found at offset %td", out_wmo_data->mfog_data_ptr - wmo_buffer);
            break;
        default:
            log_info("TODO! %c%c%c%c",
            (char)(header->chunk_name >> 24),
            (char)(header->chunk_name >> 16),
            (char)(header->chunk_name >> 8),
            (char)(header->chunk_name));
            break;
        }
        current_ptr = data_start + header->chunk_size;
    }
    // TODO - actually setup proper loading of chunks before trying to parse NULL info LOL
    get_wmo_group_names(archives, out_wmo_data, wmo_file_path);
}

// TODO: need to pass a proper handle to the file here, not just the array of handles
//  currently we just pass the whole iteration and hopefully one of them contains (slow!!!)
BOOL extract_blp_file(HANDLE hMPQ, const char *mpqFilePath, const char *localOutPath) {
    HANDLE hFile = NULL;

    if (hMPQ == NULL || hMPQ == INVALID_HANDLE_VALUE) {
        return FALSE; // Skip invalid handles
    }

    if (!SFileOpenFileEx(hMPQ, mpqFilePath, 0, &hFile)) {
        log_error("Could not open file '%s' in MPQ, continuing...", hFile);
        return FALSE;
    }

    // if (!SFileFindFirstFile(hCurrentArchive, mpqFilePath, ))

    if (SFileExtractFile(hMPQ, mpqFilePath, localOutPath, SFILE_OPEN_FROM_MPQ)) {
        log_info("Sucessfully extracted '%s' from archive to '%s'", mpqFilePath,  localOutPath);
        return TRUE;
    }

    if (GetLastError() != ERROR_FILE_NOT_FOUND) {
        log_warn("Extraction attempt from archive failed with unexpected error (%lu) for file '%s'. Continuing...",
             GetLastError(), mpqFilePath);
    }

    log_error("Failed to extract file '%s'. File not found in any loaded archive.", mpqFilePath);
    return FALSE;
}

void parse_momt_and_extract_textures(ArchiveManager *archives, const WMORootData *wmo_data) {
    log_info("Sizeof(SMOMaterial) is '%lu'", sizeof(SMOMaterial));
    if (wmo_data->momt_size < sizeof(SMOMaterial)) {
        log_error("MOMT chunk size (%lu) is too small to contain a single material!");
        return;
    }

    size_t material_count = wmo_data->momt_size / sizeof(SMOMaterial);

    const SMOMaterial *materials = (const SMOMaterial *)wmo_data->momt_data_ptr;

    log_info("Parsing %zu materials from MOMT chunk...", material_count);

    for (size_t i = 0; i < material_count; i++) {
        uint32_t offset = materials[i].texture_name_offset;

        // Skip materials that dont reference a texture
        if (offset == 0) {
            log_info("Material [%zu]: No texture referenced (Offset 0)\n", i);
            continue;
        }

        if (offset >= wmo_data->motx_size) {
            log_error("Material [%zu]: Invalid texture offset %lu (outside MOTX boundaries).\n", i, offset);
            continue;
        }

        const char *mpq_blp_path = wmo_data->motx_data_ptr + offset; // offset was throwing us off by 4 bytes it looked like

        log_info("Material [%zu] references texture at offset %lu: %s\n", i, offset, mpq_blp_path);

        size_t file_len = strlen(mpq_blp_path);
        char *local_path_buffer = (char *)malloc(file_len + 1);
        if (local_path_buffer == NULL) {
            log_error("Memory allocation failed for output path.\n");
            continue;
        }

        // use strncpy!!!
        strcpy(local_path_buffer, mpq_blp_path);
        char *last_separator = NULL;

        // Find the last path separator ('\' or '/')
        char *last_sep_1 = strrchr(local_path_buffer, '\\');
        char *last_sep_2 = strrchr(local_path_buffer, '/');

        // Use the one that is further into the string
        last_separator = (last_sep_1 > last_sep_2) ? last_sep_1 : last_sep_2;

        // If a directory structure exists (e.g., not just "file.blp")
        if (last_separator != NULL) {
            char original_separator = *last_separator;

            // Temporarily null-terminate to isolate the directory part
            *last_separator = '\0';

            // Create the directory structure (e.g., "Textures\Doodad")
            if (create_dir_recursive(local_path_buffer) == 0) {
                // Restore the original path string
                *last_separator = original_separator;

                HANDLE hCurrentArchive = NULL;
                for (size_t i = 0; i < archives->count; i++) {
                    hCurrentArchive = archives->archives[i];

                    if (hCurrentArchive == NULL || hCurrentArchive == INVALID_HANDLE_VALUE) {
                        continue; // Skip invalid handles
                    }

                    // so we don't try to extract multiple archives where the file clearly doesnt exist!
                    // TODO: I should check other for loops and see if they're needlessly sending data to be processed
                    //  when files don't exist!
                    if (SFileVerifyFile(hCurrentArchive, mpq_blp_path, 0) != 0) {
                        continue;
                    }

                    //extract_blp_file(hCurrentArchive, mpq_blp_path, local_path_buffer);
                }
                // Extract using the full path

            }
        } else {
            // File is in the root; extract directly
            HANDLE hCurrentArchive = NULL;
            for (size_t i = 0; i < archives->count; i++) {
                hCurrentArchive = archives->archives[i];

                if (hCurrentArchive == NULL || hCurrentArchive == INVALID_HANDLE_VALUE) {
                    continue; // Skip invalid handles
                }

                //extract_blp_file(hCurrentArchive, mpq_blp_path, local_path_buffer);
            }


        }

        // Cleanup the dynamically allocated path buffer
        free(local_path_buffer);
    }
}

/*
HANDLE get_file_in_mpq(ArchiveManager *archives, const char *file_in_mpq_name) {
    HANDLE hFileInArchive = NULL;
    // DWORD dwError = 0;

    if (archives->count == 0 || archives->archives[0] == NULL) {
        log_error("No archives are mounted. Cannot search for file '%s'", file_in_mpq_name);
        return NULL;
    }

    if (!SFileOpenFileEx(archives->archives[12], file_in_mpq_name, SFILE_OPEN_FROM_MPQ, &hFileInArchive)) {
        log_error("Failed to open file '%s' inside MPQ. Error: %lu", file_in_mpq_name, GetLastError());
        return NULL;
    }

    return hFileInArchive;
}*/

uint32_t get_file_size_in_mpq(HANDLE hFileInArchive, const char *file_in_mpq_name) {
    DWORD dwFileSize = 0;

    if (SFileGetFileInfo(hFileInArchive, SFileInfoFileSize, &dwFileSize,
        sizeof(dwFileSize), NULL)) {
        log_info("File '%s' size: %lu bytes (0x%lX) or %.6lf megabytes ", file_in_mpq_name, dwFileSize, dwFileSize,
            (float)dwFileSize / 1000000);
        } else {
            log_error("Failed to get size for file '%s'. Error: %lu", file_in_mpq_name, GetLastError());
        }

    // SFileCloseFile(hFileInArchive);
    return dwFileSize;
}

HANDLE get_file_in_archives(ArchiveManager *archives, const char *file_in_mpq_name) {
    HANDLE hFile = NULL;

    for (size_t i = 0; i < archives->count; i++) {
        HANDLE hCurrentArchive = archives->archives[i];

        if (hCurrentArchive == NULL || hCurrentArchive == INVALID_HANDLE_VALUE) {
            continue;
        }

        if (SFileOpenFileEx (hCurrentArchive, file_in_mpq_name, SFILE_OPEN_FROM_MPQ, &hFile)) {
            // TODO: figure out stormlib hierachy linking, as this won't always return the newest file, just
            //  a file found...
            return hFile;
        }
    }

    log_error("Failed to open file '%s' in ANY mounted archive. Last Error: %lu", file_in_mpq_name, GetLastError());

    return NULL;
}

// pass by pointer
void get_mpq_file_path(char *buffer, size_t buffer_size) {
    printf("Enter a file path (full or current dir): ");

    if (fgets(buffer, buffer_size, stdin) == NULL){
        log_error("Error: Could not read from buffer for mpq_file_path()");
        return;
    }

    // convert '\n\r' to null terminating byte '\0'
    buffer[strcspn(buffer, "\n\r")] = '\0';

    printf("Opening MPQ at %s\n", buffer);
}

// pass by pointer
void get_wmo_file_path(char *buffer, size_t buffer_size) {
    printf("Enter a file path (full or current dir): ");

    if (fgets(buffer, buffer_size, stdin) == NULL){
        log_error("Error: Could not read from buffer for wmo_file_path()");
        return;
    }

    // convert '\n\r' to null terminating byte '\0'
    buffer[strcspn(buffer, "\n\r")] = '\0';

    printf("Opening WMO at %s\n", buffer);
}

void get_data_dir(char *buffer, size_t buffer_size) {
    printf("Enter your WoW 3.3.5a Data Directory (ex. World of Warcraft 3.3.5a\\Data): ");

    if (fgets(buffer, buffer_size, stdin) == NULL) {
        log_error("Could not read from buffer for wmo_file_path()");
        return;
    }

    // convert '\n\r' to null terminating byte '\0'
    buffer[strcspn(buffer, "\n\r")] = '\0';

}

void init_archive_manager(ArchiveManager* manager, const char* root_path) {
    manager->archives = (HANDLE*)malloc(INIT_CAPACITY * sizeof(HANDLE));

    if (manager->archives == NULL) {
        log_error("Failed to allocate memory for ArchiveManager handles");
        return;
    }

    manager->count = 0;
    manager->capacity = INIT_CAPACITY;

    if (root_path != NULL) {
        strncpy(manager->root_path, root_path, MAX_PATH - 1);
        manager->root_path[MAX_PATH - 1] = '\0';
    } else {
        manager->root_path[0] = '\0';
    }

    log_info("ArchiveManager initialized with capacity %zu", manager->capacity);
}

void init_path_list(PathList *list) {
    list->entries = (ArchiveEntry*)malloc(INIT_CAPACITY * sizeof(ArchiveEntry));
    list->count = 0;
    list->capacity = INIT_CAPACITY;
    if (list->entries == NULL) {
        log_error("Cannot allocate memory for paths in PathList");
    }
}

void expand_path_list(PathList* list) {
    list->capacity *= 2;
    list->entries = (ArchiveEntry*)realloc(list->entries, list->capacity * sizeof(ArchiveEntry));
    if (list->entries == NULL) {
        log_error("Cannot reallocate memory for paths in PathList");
    }
}

void recursively_scan_directory(const char *current_dir, PathList* list) {
    char search_path[MAX_PATH];
    WIN32_FIND_DATA find_data;
    HANDLE hFind;

    snprintf(search_path, MAX_PATH, "%s\\*.*", current_dir);

    hFind = FindFirstFile(search_path, &find_data);
    if (hFind == INVALID_HANDLE_VALUE) {
        log_error("Invalid Handle: %lu", GetLastError());
        return;
    }

    do {
        if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) {
            continue;
        }

        char full_path[MAX_PATH];
        snprintf(full_path, MAX_PATH, "%s\\%s", current_dir, find_data.cFileName);

        if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            recursively_scan_directory(full_path, list);
        } else {
            if (strstr(find_data.cFileName, ".MPQ") ||
                strstr(find_data.cFileName, ".IDX") ||
                strstr(find_data.cFileName, ".CASC")) {

                // store the path
                if (list->count >= list->capacity) {
                    expand_path_list(list);
                }

                ArchiveEntry *new_entry = &list->entries[list->count];

                snprintf(new_entry->path, MAX_PATH, "%s\\%s", current_dir, find_data.cFileName);

                new_entry->load_order_score = assign_score(find_data.cFileName);

                list->count++;

                /*
                // allocate memory for the string
                list->entries[list->count] = (char*)malloc(strlen(full_path) + 1);
                if (list->entries[list->count] != NULL) {
                    strcpy(list->entries[list->count], full_path);
                    list->count++;
                    // log_info("Found Archive: %s", full_path);
                }*/
            }
        }

    } while (FindNextFile(hFind, &find_data) != 0);

    FindClose(hFind);
}

void free_path_list(PathList* list) {
    if (list == NULL)
        return;

    /*
    for (int i = 0; i < list->count; i++) {
        if (list->entries[i] != NULL) {
            free(list->entries[i]);
        }
    }*/

    if (list->entries != NULL) {
        free(list->entries);
    }
}

int assign_score(const char *filename) {
    // TODO: Confirm loading order, I believe locales should be last! check titi discord photo
    if (strstr(filename, "base-enUS.MPQ") != 0) {
        return 10;
    }
    if (strstr(filename, "patch-enUS.MPQ") != 0) {
        return 20;
    }
    if (strstr(filename, "patch.MPQ") != 0) {
        return 30;
    }
    if (strstr(filename, "patch-enUS-2.MPQ") != 0) {
        return 40;
    }
    if (strstr(filename, "patch-enUS-3.MPQ") != 0) {
        return 50;
    }
    if (strstr(filename, "patch-2.MPQ") != 0) {
        return 60;
    }
    if (strstr(filename, "patch-3.MPQ") != 0) {
        return 70;
    }
    if (strstr(filename, "alternate.MPQ") != 0) {
        return 80;
    }
    if (strstr(filename, "expansion.MPQ") != 0) {
        return 90;
    }
    if (strstr(filename, "lichking.MPQ") != 0) {
        return 100;
    }
    if (strstr(filename, "common.MPQ") != 0) {
        return 110;
    }
    if (strstr(filename, "common-2.MPQ") != 0) {
        return 120;
    }
    if (strstr(filename, "locale-enUS.MPQ") != 0) {
        return 130;
    }
    if (strstr(filename, "speech-enUS.MPQ") != 0) {
        return 140;
    }
    if (strstr(filename, "expansion-locale-enUS.MPQ") != 0) {
        return 150;
    }
    if (strstr(filename, "lichking-locale-enUS.MPQ") != 0) {
        return 160;
    }
    if (strstr(filename, "expansion-speech-enUS.MPQ") != 0) {
        return 170;
    }
    if (strstr(filename, "lichking-speech-enUS.MPQ") != 0) {
        return 180;
    }

    /*
    if (strstr(filename, "patch-")) {
        // get patch number
        const char *num_str = strstr(filename, "patch-") + 6; // adjusts pointer past '-'
        int patch_num = atoi(num_str); // TODO; use strtol() instead for error caching?

        return 300 + (patch_num * 100);
    }*/

    // anything else not recognized gets the lowest score
    return 1;
}

int archive_comparator(const void *a, const void *b) {
    const ArchiveEntry *entryA = (const ArchiveEntry *)a;
    const ArchiveEntry *entryB = (const ArchiveEntry *)b;

    // lowest score (older) to come first, newest overwrites
    if (entryA->load_order_score < entryB->load_order_score) {
        return -1;
    }

    if (entryA->load_order_score > entryB->load_order_score) {
        return 1;
    }

    // use lexicographical comparison of the full path if scores are equal
    return strcmp(entryA->path, entryB->path);
}

void expand_archive_manager(ArchiveManager *manager) {
    manager->capacity *= 2;
    manager->archives = (HANDLE*)realloc(manager->archives, manager->capacity * sizeof(HANDLE));

    if (manager->archives == NULL) {
        log_error("Failed to reallocate ArchiveManager handles");
        return;
    }
}