#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Common/String.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/HAL/Filesystem/Filesystem_Platform.h>
#include <Core/HAL/Filesystem/Filesystem_Private.h>
#include <Core/HAL/Win32.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>

#include <cstdio>

namespace Juliet
{
    namespace
    {
        String CachedBasePath      = {};
        String CachedAssetBasePath = {};

        bool DirectoryExists(const char* path)
        {
            Assert(path);
            DWORD attributes = GetFileAttributesA(path);
            return (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY);
        }
    }

    String GetBasePath()
    {
        if (!IsValid(CachedBasePath))
        {
            CachedBasePath = Platform::GetBasePath();
        }
        return CachedBasePath;
    }

    String GetAssetBasePath()
    {
        return CachedAssetBasePath;
    }

    [[nodiscard]] String GetAssetPath(String filename)
    {
        Assert(IsValid(CachedAssetBasePath));
        Assert(IsValid(filename));

        size_t totalSize = CachedAssetBasePath.Size + filename.Size + 1;
        auto*  buffer    = static_cast<char*>(Calloc(totalSize, sizeof(char)));
        if (!buffer)
        {
            return {};
        }

        snprintf(buffer, totalSize, "%s%s", CStr(CachedAssetBasePath), CStr(filename));
        return { buffer, totalSize - 1 };
    }

    bool IsAbsolutePath(String path)
    {
        if (!IsValid(path))
        {
            return false;
        }
        return Platform::IsAbsolutePath(path);
    }

    void InitFilesystem()
    {
        String basePath = GetBasePath();
        Assert(IsValid(basePath));

        // Probe candidate paths for compiled shader directory
        // 1. Shipping layout: Assets/Shaders/ next to the exe
        // 2. Dev layout:      ../../Assets/compiled/ (exe is in bin/x64Clang-<Config>/)
        constexpr const char* kCandidates[] = {
            "Assets/Shaders/",
            "../../Assets/compiled/"
        };

        for (const char* candidate : kCandidates)
        {
            char probePath[512];
            snprintf(probePath, sizeof(probePath), "%s%s", CStr(basePath), candidate);

            if (DirectoryExists(probePath))
            {
                size_t len    = strlen(probePath);
                auto*  buffer = static_cast<char*>(Calloc(len + 1, sizeof(char)));
                if (buffer)
                {
                    snprintf(buffer, len + 1, "%s", probePath);
                    CachedAssetBasePath = { buffer, len };
                    Log(LogLevel::Message, LogCategory::Core, "Asset base path: %s", buffer);
                }
                return;
            }
        }

        Log(LogLevel::Error, LogCategory::Core, "Filesystem: Could not find Assets/compiled/ directory!");
    }

    void ShutdownFilesystem()
    {
        if (IsValid(CachedBasePath))
        {
            CachedBasePath.Size = 0;
            SafeFree(CachedBasePath.Data);
        }
        if (IsValid(CachedAssetBasePath))
        {
            CachedAssetBasePath.Size = 0;
            SafeFree(CachedAssetBasePath.Data);
        }
    }
} // namespace Juliet
