// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#include "pch.h"
#include "WICMetadataExtractor.h"
#include "MetadataFormatHelper.h"
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <cwctype>
#include <comdef.h>
#include <shlwapi.h>

using namespace PowerRenameLib;

namespace
{
    // Documentation: https://learn.microsoft.com/en-us/windows/win32/wic/-wic-native-image-format-metadata-queries

    // WIC metadata property paths
    const std::wstring EXIF_DATE_TAKEN = L"/app1/ifd/exif/{ushort=36867}";      // DateTimeOriginal
    const std::wstring EXIF_DATE_DIGITIZED = L"/app1/ifd/exif/{ushort=36868}";  // DateTimeDigitized
    const std::wstring EXIF_DATE_MODIFIED = L"/app1/ifd/{ushort=306}";           // DateTime
    const std::wstring EXIF_CAMERA_MAKE = L"/app1/ifd/{ushort=271}";            // Make
    const std::wstring EXIF_CAMERA_MODEL = L"/app1/ifd/{ushort=272}";           // Model
    const std::wstring EXIF_LENS_MODEL = L"/app1/ifd/exif/{ushort=42036}";      // LensModel
    const std::wstring EXIF_ISO = L"/app1/ifd/exif/{ushort=34855}";             // ISOSpeedRatings
    const std::wstring EXIF_APERTURE = L"/app1/ifd/exif/{ushort=33437}";        // FNumber
    const std::wstring EXIF_SHUTTER_SPEED = L"/app1/ifd/exif/{ushort=33434}";   // ExposureTime
    const std::wstring EXIF_FOCAL_LENGTH = L"/app1/ifd/exif/{ushort=37386}";    // FocalLength
    const std::wstring EXIF_EXPOSURE_BIAS = L"/app1/ifd/exif/{ushort=37380}";   // ExposureBiasValue
    const std::wstring EXIF_FLASH = L"/app1/ifd/exif/{ushort=37385}";           // Flash
    const std::wstring EXIF_ORIENTATION = L"/app1/ifd/{ushort=274}";            // Orientation
    const std::wstring EXIF_COLOR_SPACE = L"/app1/ifd/exif/{ushort=40961}";     // ColorSpace
    const std::wstring EXIF_WIDTH = L"/app1/ifd/exif/{ushort=40962}";           // PixelXDimension - actual image width
    const std::wstring EXIF_HEIGHT = L"/app1/ifd/exif/{ushort=40963}";          // PixelYDimension - actual image height
    const std::wstring EXIF_ARTIST = L"/app1/ifd/{ushort=315}";                 // Artist
    const std::wstring EXIF_COPYRIGHT = L"/app1/ifd/{ushort=33432}";            // Copyright

    // GPS paths for JPEG format
    const std::wstring GPS_LATITUDE = L"/app1/ifd/gps/{ushort=2}";              // GPSLatitude
    const std::wstring GPS_LATITUDE_REF = L"/app1/ifd/gps/{ushort=1}";          // GPSLatitudeRef
    const std::wstring GPS_LONGITUDE = L"/app1/ifd/gps/{ushort=4}";             // GPSLongitude
    const std::wstring GPS_LONGITUDE_REF = L"/app1/ifd/gps/{ushort=3}";         // GPSLongitudeRef
    const std::wstring GPS_ALTITUDE = L"/app1/ifd/gps/{ushort=6}";              // GPSAltitude
    const std::wstring GPS_ALTITUDE_REF = L"/app1/ifd/gps/{ushort=5}";          // GPSAltitudeRef

    // WIC metadata property paths for TIFF/HEIF format (uses /ifd prefix directly)
    // HEIF/HEIC images use TIFF-style metadata paths
    const std::wstring HEIF_DATE_TAKEN = L"/ifd/exif/{ushort=36867}";           // DateTimeOriginal
    const std::wstring HEIF_DATE_DIGITIZED = L"/ifd/exif/{ushort=36868}";       // DateTimeDigitized
    const std::wstring HEIF_DATE_MODIFIED = L"/ifd/{ushort=306}";               // DateTime
    const std::wstring HEIF_CAMERA_MAKE = L"/ifd/{ushort=271}";                 // Make
    const std::wstring HEIF_CAMERA_MODEL = L"/ifd/{ushort=272}";                // Model
    const std::wstring HEIF_LENS_MODEL = L"/ifd/exif/{ushort=42036}";           // LensModel
    const std::wstring HEIF_ISO = L"/ifd/exif/{ushort=34855}";                  // ISOSpeedRatings
    const std::wstring HEIF_APERTURE = L"/ifd/exif/{ushort=33437}";             // FNumber
    const std::wstring HEIF_SHUTTER_SPEED = L"/ifd/exif/{ushort=33434}";        // ExposureTime
    const std::wstring HEIF_FOCAL_LENGTH = L"/ifd/exif/{ushort=37386}";         // FocalLength
    const std::wstring HEIF_EXPOSURE_BIAS = L"/ifd/exif/{ushort=37380}";        // ExposureBiasValue
    const std::wstring HEIF_FLASH = L"/ifd/exif/{ushort=37385}";                // Flash
    const std::wstring HEIF_ORIENTATION = L"/ifd/{ushort=274}";                 // Orientation
    const std::wstring HEIF_COLOR_SPACE = L"/ifd/exif/{ushort=40961}";          // ColorSpace
    const std::wstring HEIF_WIDTH = L"/ifd/exif/{ushort=40962}";                // PixelXDimension
    const std::wstring HEIF_HEIGHT = L"/ifd/exif/{ushort=40963}";               // PixelYDimension
    const std::wstring HEIF_ARTIST = L"/ifd/{ushort=315}";                      // Artist
    const std::wstring HEIF_COPYRIGHT = L"/ifd/{ushort=33432}";                 // Copyright

    // GPS paths for TIFF/HEIF format
    const std::wstring HEIF_GPS_LATITUDE = L"/ifd/gps/{ushort=2}";              // GPSLatitude
    const std::wstring HEIF_GPS_LATITUDE_REF = L"/ifd/gps/{ushort=1}";          // GPSLatitudeRef
    const std::wstring HEIF_GPS_LONGITUDE = L"/ifd/gps/{ushort=4}";             // GPSLongitude
    const std::wstring HEIF_GPS_LONGITUDE_REF = L"/ifd/gps/{ushort=3}";         // GPSLongitudeRef
    const std::wstring HEIF_GPS_ALTITUDE = L"/ifd/gps/{ushort=6}";              // GPSAltitude
    const std::wstring HEIF_GPS_ALTITUDE_REF = L"/ifd/gps/{ushort=5}";          // GPSAltitudeRef
    
    
    // Documentation: https://developer.adobe.com/xmp/docs/XMPNamespaces/xmp/
    // Based on actual WIC path format discovered through enumeration
    // XMP Basic schema - xmp: namespace
    const std::wstring XMP_CREATE_DATE = L"/xmp/xmp:CreateDate";        // XMP Create Date
    const std::wstring XMP_MODIFY_DATE = L"/xmp/xmp:ModifyDate";        // XMP Modify Date
    const std::wstring XMP_METADATA_DATE = L"/xmp/xmp:MetadataDate";    // XMP Metadata Date
    const std::wstring XMP_CREATOR_TOOL = L"/xmp/xmp:CreatorTool";      // XMP Creator Tool
    
    // Dublin Core schema - dc: namespace
    // Note: For language alternatives like title/description, we need to append /x-default
    const std::wstring XMP_DC_TITLE = L"/xmp/dc:title/x-default";             // Title (default language)
    const std::wstring XMP_DC_DESCRIPTION = L"/xmp/dc:description/x-default"; // Description (default language)
    const std::wstring XMP_DC_CREATOR = L"/xmp/dc:creator";                   // Creator/Author
    const std::wstring XMP_DC_SUBJECT = L"/xmp/dc:subject";                   // Subject/Keywords (array)
    
    // XMP Rights Management schema - xmpRights: namespace
    const std::wstring XMP_RIGHTS = L"/xmp/xmpRights:WebStatement";           // Copyright/Rights
    
    // XMP Media Management schema - xmpMM: namespace
    const std::wstring XMP_MM_DOCUMENT_ID = L"/xmp/xmpMM:DocumentID";                  // Document ID
    const std::wstring XMP_MM_INSTANCE_ID = L"/xmp/xmpMM:InstanceID";                  // Instance ID  
    const std::wstring XMP_MM_ORIGINAL_DOCUMENT_ID = L"/xmp/xmpMM:OriginalDocumentID"; // Original Document ID
    const std::wstring XMP_MM_VERSION_ID = L"/xmp/xmpMM:VersionID";                    // Version ID
    
    
    std::wstring TrimWhitespace(const std::wstring& value)
    {
        const auto first = value.find_first_not_of(L" \t\r\n");
        if (first == std::wstring::npos)
        {
            return {};
        }

        const auto last = value.find_last_not_of(L" \t\r\n");
        return value.substr(first, last - first + 1);
    }

    bool TryParseFixedWidthInt(const std::wstring& source, size_t start, size_t length, int& value)
    {
        if (start + length > source.size())
        {
            return false;
        }

        int result = 0;
        for (size_t i = 0; i < length; ++i)
        {
            const wchar_t ch = source[start + i];
            if (ch < L'0' || ch > L'9')
            {
                return false;
            }

            result = result * 10 + static_cast<int>(ch - L'0');
        }

        value = result;
        return true;
    }

    bool ValidateAndBuildSystemTime(int year, int month, int day, int hour, int minute, int second, int milliseconds, SYSTEMTIME& outTime)
    {
        if (year < 1601 || year > 9999 ||
            month < 1 || month > 12 ||
            day < 1 || day > 31 ||
            hour < 0 || hour > 23 ||
            minute < 0 || minute > 59 ||
            second < 0 || second > 59 ||
            milliseconds < 0 || milliseconds > 999)
        {
            return false;
        }

        SYSTEMTIME candidate{};
        candidate.wYear = static_cast<WORD>(year);
        candidate.wMonth = static_cast<WORD>(month);
        candidate.wDay = static_cast<WORD>(day);
        candidate.wHour = static_cast<WORD>(hour);
        candidate.wMinute = static_cast<WORD>(minute);
        candidate.wSecond = static_cast<WORD>(second);
        candidate.wMilliseconds = static_cast<WORD>(milliseconds);

        FILETIME fileTime{};
        if (!SystemTimeToFileTime(&candidate, &fileTime))
        {
            return false;
        }

        outTime = candidate;
        return true;
    }

    std::optional<SYSTEMTIME> ParseExifDateTime(const std::wstring& date)
    {
        if (date.size() < 19)
        {
            return std::nullopt;
        }

        if (date[4] != L':' || date[7] != L':' ||
            (date[10] != L' ' && date[10] != L'T') ||
            date[13] != L':' || date[16] != L':')
        {
            return std::nullopt;
        }

        int year = 0;
        int month = 0;
        int day = 0;
        int hour = 0;
        int minute = 0;
        int second = 0;

        if (!TryParseFixedWidthInt(date, 0, 4, year) ||
            !TryParseFixedWidthInt(date, 5, 2, month) ||
            !TryParseFixedWidthInt(date, 8, 2, day) ||
            !TryParseFixedWidthInt(date, 11, 2, hour) ||
            !TryParseFixedWidthInt(date, 14, 2, minute) ||
            !TryParseFixedWidthInt(date, 17, 2, second))
        {
            return std::nullopt;
        }

        int milliseconds = 0;
        size_t pos = 19;
        if (pos < date.size() && (date[pos] == L'.' || date[pos] == L','))
        {
            ++pos;
            int digits = 0;
            while (pos < date.size() && std::iswdigit(date[pos]) && digits < 3)
            {
                milliseconds = milliseconds * 10 + static_cast<int>(date[pos] - L'0');
                ++pos;
                ++digits;
            }

            while (digits > 0 && digits < 3)
            {
                milliseconds *= 10;
                ++digits;
            }
        }

        SYSTEMTIME result{};
        if (!ValidateAndBuildSystemTime(year, month, day, hour, minute, second, milliseconds, result))
        {
            return std::nullopt;
        }

        return result;
    }

    std::optional<SYSTEMTIME> ParseIso8601DateTime(const std::wstring& date)
    {
        if (date.size() < 19)
        {
            return std::nullopt;
        }

        size_t separator = date.find(L'T');
        if (separator == std::wstring::npos)
        {
            separator = date.find(L' ');
        }

        if (separator == std::wstring::npos)
        {
            return std::nullopt;
        }

        int year = 0;
        int month = 0;
        int day = 0;
        if (!TryParseFixedWidthInt(date, 0, 4, year) ||
            date[4] != L'-' ||
            !TryParseFixedWidthInt(date, 5, 2, month) ||
            date[7] != L'-' ||
            !TryParseFixedWidthInt(date, 8, 2, day))
        {
            return std::nullopt;
        }

        size_t timePos = separator + 1;
        if (timePos + 7 >= date.size())
        {
            return std::nullopt;
        }

        int hour = 0;
        int minute = 0;
        int second = 0;
        if (!TryParseFixedWidthInt(date, timePos, 2, hour) ||
            date[timePos + 2] != L':' ||
            !TryParseFixedWidthInt(date, timePos + 3, 2, minute) ||
            date[timePos + 5] != L':' ||
            !TryParseFixedWidthInt(date, timePos + 6, 2, second))
        {
            return std::nullopt;
        }

        size_t pos = timePos + 8;
        int milliseconds = 0;
        if (pos < date.size() && (date[pos] == L'.' || date[pos] == L','))
        {
            ++pos;
            int digits = 0;
            while (pos < date.size() && std::iswdigit(date[pos]) && digits < 3)
            {
                milliseconds = milliseconds * 10 + static_cast<int>(date[pos] - L'0');
                ++pos;
                ++digits;
            }

            while (pos < date.size() && std::iswdigit(date[pos]))
            {
                ++pos;
            }

            while (digits > 0 && digits < 3)
            {
                milliseconds *= 10;
                ++digits;
            }
        }

        bool hasOffset = false;
        int offsetMinutes = 0;
        if (pos < date.size())
        {
            const wchar_t tzIndicator = date[pos];
            if (tzIndicator == L'Z' || tzIndicator == L'z')
            {
                hasOffset = true;
                offsetMinutes = 0;
                ++pos;
            }
            else if (tzIndicator == L'+' || tzIndicator == L'-')
            {
                hasOffset = true;
                const int sign = (tzIndicator == L'-') ? -1 : 1;
                ++pos;

                int offsetHours = 0;
                int offsetMins = 0;
                if (!TryParseFixedWidthInt(date, pos, 2, offsetHours))
                {
                    return std::nullopt;
                }
                pos += 2;

                if (pos < date.size() && date[pos] == L':')
                {
                    ++pos;
                }

                if (pos + 1 < date.size() && std::iswdigit(date[pos]) && std::iswdigit(date[pos + 1]))
                {
                    if (!TryParseFixedWidthInt(date, pos, 2, offsetMins))
                    {
                        return std::nullopt;
                    }
                    pos += 2;
                }

                if (offsetHours < 0 || offsetHours > 23 || offsetMins < 0 || offsetMins > 59)
                {
                    return std::nullopt;
                }

                offsetMinutes = sign * (offsetHours * 60 + offsetMins);
            }

            while (pos < date.size() && std::iswspace(date[pos]))
            {
                ++pos;
            }

            if (pos != date.size())
            {
                return std::nullopt;
            }
        }

        SYSTEMTIME baseTime{};
        if (!ValidateAndBuildSystemTime(year, month, day, hour, minute, second, milliseconds, baseTime))
        {
            return std::nullopt;
        }

        if (!hasOffset)
        {
            return baseTime;
        }

        FILETIME utcFileTime{};
        if (!SystemTimeToFileTime(&baseTime, &utcFileTime))
        {
            return std::nullopt;
        }

        ULARGE_INTEGER timeValue{};
        timeValue.LowPart = utcFileTime.dwLowDateTime;
        timeValue.HighPart = utcFileTime.dwHighDateTime;

        constexpr long long TicksPerMinute = 60LL * 10000000LL;
        timeValue.QuadPart -= static_cast<long long>(offsetMinutes) * TicksPerMinute;

        FILETIME adjustedUtc{};
        adjustedUtc.dwLowDateTime = timeValue.LowPart;
        adjustedUtc.dwHighDateTime = timeValue.HighPart;

        FILETIME localFileTime{};
        if (!FileTimeToLocalFileTime(&adjustedUtc, &localFileTime))
        {
            return std::nullopt;
        }

        SYSTEMTIME localTime{};
        if (!FileTimeToSystemTime(&localFileTime, &localTime))
        {
            return std::nullopt;
        }

        return localTime;
    }
// Global WIC factory management with thread-safe access
    CComPtr<IWICImagingFactory> g_wicFactory;
    std::once_flag g_wicInitFlag;
    std::mutex g_wicFactoryMutex;  // Protect access to g_wicFactory
}

WICMetadataExtractor::WICMetadataExtractor()
{
    InitializeWIC();
}

WICMetadataExtractor::~WICMetadataExtractor()
{
    // WIC cleanup handled statically
}

void WICMetadataExtractor::InitializeWIC()
{
    std::call_once(g_wicInitFlag, []() {
        // Don't initialize COM in library code - assume caller has done it
        // Just create the WIC factory
        HRESULT hr = CoCreateInstance(
            CLSID_WICImagingFactory,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_IWICImagingFactory,
            reinterpret_cast<LPVOID*>(&g_wicFactory)
        );

        if (FAILED(hr))
        {
            g_wicFactory = nullptr;
        }
    });
}

CComPtr<IWICImagingFactory> WICMetadataExtractor::GetWICFactory()
{
    std::lock_guard<std::mutex> lock(g_wicFactoryMutex);
    return g_wicFactory;
}

bool WICMetadataExtractor::ExtractEXIFMetadata(
    const std::wstring& filePath,
    EXIFMetadata& outMetadata)
{
    return cache.GetOrLoadEXIF(filePath, outMetadata, [this, &filePath](EXIFMetadata& metadata) {
        return LoadEXIFMetadata(filePath, metadata);
    });
}

bool WICMetadataExtractor::LoadEXIFMetadata(
    const std::wstring& filePath,
    EXIFMetadata& outMetadata)
{
    CComPtr<IWICMetadataQueryReader> reader;

    if (!PathFileExistsW(filePath.c_str()))
    {
#ifdef _DEBUG
        std::wstring msg = L"[PowerRename] EXIF metadata extraction failed: File not found - " + filePath + L"\n";
        OutputDebugStringW(msg.c_str());
#endif
        return false;
    }

    auto decoder = CreateDecoder(filePath);
    if (!decoder)
    {
#ifdef _DEBUG
        std::wstring msg = L"[PowerRename] EXIF metadata extraction: Unsupported format or unable to create decoder - " + filePath + L"\n";
        OutputDebugStringW(msg.c_str());
#endif
        return false;
    }

    CComPtr<IWICBitmapFrameDecode> frame;
    if (FAILED(decoder->GetFrame(0, &frame)))
    {
#ifdef _DEBUG
        std::wstring msg = L"[PowerRename] EXIF metadata extraction failed: WIC decoder error - " + filePath + L"\n";
        OutputDebugStringW(msg.c_str());
#endif
        return false;
    }

    reader = GetMetadataReader(decoder);
    if (!reader)
    {
        // No metadata is not necessarily an error - just means the file has no EXIF data
        return false;
    }

    // Detect container format to determine correct metadata paths
    MetadataPathFormat pathFormat = GetMetadataPathFormatFromDecoder(decoder);

    ExtractAllEXIFFields(reader, outMetadata, pathFormat);
    ExtractGPSData(reader, outMetadata, pathFormat);

    return true;
}

void WICMetadataExtractor::ClearCache()
{
    cache.ClearAll();
}

CComPtr<IWICBitmapDecoder> WICMetadataExtractor::CreateDecoder(const std::wstring& filePath)
{
    auto factory = GetWICFactory();
    if (!factory)
    {
        return nullptr;
    }
    
    CComPtr<IWICBitmapDecoder> decoder;
    HRESULT hr = factory->CreateDecoderFromFilename(
        filePath.c_str(),
        nullptr,
        GENERIC_READ,
        WICDecodeMetadataCacheOnLoad,
        &decoder
    );
    
    if (FAILED(hr))
    {
        return nullptr;
    }
    
    return decoder;
}

CComPtr<IWICMetadataQueryReader> WICMetadataExtractor::GetMetadataReader(IWICBitmapDecoder* decoder)
{
    if (!decoder)
    {
        return nullptr;
    }

    CComPtr<IWICBitmapFrameDecode> frame;
    if (FAILED(decoder->GetFrame(0, &frame)))
    {
        return nullptr;
    }

    CComPtr<IWICMetadataQueryReader> reader;
    frame->GetMetadataQueryReader(&reader);

    return reader;
}

MetadataPathFormat WICMetadataExtractor::GetMetadataPathFormatFromDecoder(IWICBitmapDecoder* decoder)
{
    if (!decoder)
    {
        return MetadataPathFormat::JPEG;
    }

    GUID containerFormat;
    if (SUCCEEDED(decoder->GetContainerFormat(&containerFormat)))
    {
        // HEIF and TIFF use /ifd/... paths directly
        if (containerFormat == GUID_ContainerFormatHeif ||
            containerFormat == GUID_ContainerFormatTiff)
        {
            return MetadataPathFormat::IFD;
        }
    }

    // JPEG and other formats use /app1/ifd/... paths
    return MetadataPathFormat::JPEG;
}

void WICMetadataExtractor::ExtractAllEXIFFields(IWICMetadataQueryReader* reader, EXIFMetadata& metadata, MetadataPathFormat pathFormat)
{
    if (!reader)
        return;

    // Select the correct paths based on container format
    const bool useIfdPaths = (pathFormat == MetadataPathFormat::IFD);

    // Date/time paths
    const auto& dateTakenPath = useIfdPaths ? HEIF_DATE_TAKEN : EXIF_DATE_TAKEN;
    const auto& dateDigitizedPath = useIfdPaths ? HEIF_DATE_DIGITIZED : EXIF_DATE_DIGITIZED;
    const auto& dateModifiedPath = useIfdPaths ? HEIF_DATE_MODIFIED : EXIF_DATE_MODIFIED;

    // Camera info paths
    const auto& cameraMakePath = useIfdPaths ? HEIF_CAMERA_MAKE : EXIF_CAMERA_MAKE;
    const auto& cameraModelPath = useIfdPaths ? HEIF_CAMERA_MODEL : EXIF_CAMERA_MODEL;
    const auto& lensModelPath = useIfdPaths ? HEIF_LENS_MODEL : EXIF_LENS_MODEL;

    // Shooting parameter paths
    const auto& isoPath = useIfdPaths ? HEIF_ISO : EXIF_ISO;
    const auto& aperturePath = useIfdPaths ? HEIF_APERTURE : EXIF_APERTURE;
    const auto& shutterSpeedPath = useIfdPaths ? HEIF_SHUTTER_SPEED : EXIF_SHUTTER_SPEED;
    const auto& focalLengthPath = useIfdPaths ? HEIF_FOCAL_LENGTH : EXIF_FOCAL_LENGTH;
    const auto& exposureBiasPath = useIfdPaths ? HEIF_EXPOSURE_BIAS : EXIF_EXPOSURE_BIAS;
    const auto& flashPath = useIfdPaths ? HEIF_FLASH : EXIF_FLASH;

    // Image property paths
    const auto& widthPath = useIfdPaths ? HEIF_WIDTH : EXIF_WIDTH;
    const auto& heightPath = useIfdPaths ? HEIF_HEIGHT : EXIF_HEIGHT;
    const auto& orientationPath = useIfdPaths ? HEIF_ORIENTATION : EXIF_ORIENTATION;
    const auto& colorSpacePath = useIfdPaths ? HEIF_COLOR_SPACE : EXIF_COLOR_SPACE;

    // Author info paths
    const auto& artistPath = useIfdPaths ? HEIF_ARTIST : EXIF_ARTIST;
    const auto& copyrightPath = useIfdPaths ? HEIF_COPYRIGHT : EXIF_COPYRIGHT;

    // Extract date/time fields
    metadata.dateTaken = ReadDateTime(reader, dateTakenPath);
    metadata.dateDigitized = ReadDateTime(reader, dateDigitizedPath);
    metadata.dateModified = ReadDateTime(reader, dateModifiedPath);

    // Extract camera information
    metadata.cameraMake = ReadString(reader, cameraMakePath);
    metadata.cameraModel = ReadString(reader, cameraModelPath);
    metadata.lensModel = ReadString(reader, lensModelPath);

    // Extract shooting parameters
    metadata.iso = ReadInteger(reader, isoPath);
    metadata.aperture = ReadDouble(reader, aperturePath);
    metadata.shutterSpeed = ReadDouble(reader, shutterSpeedPath);
    metadata.focalLength = ReadDouble(reader, focalLengthPath);
    metadata.exposureBias = ReadDouble(reader, exposureBiasPath);
    metadata.flash = ReadInteger(reader, flashPath);

    // Extract image properties
    metadata.width = ReadInteger(reader, widthPath);
    metadata.height = ReadInteger(reader, heightPath);
    metadata.orientation = ReadInteger(reader, orientationPath);
    metadata.colorSpace = ReadInteger(reader, colorSpacePath);

    // Extract author information
    metadata.author = ReadString(reader, artistPath);
    metadata.copyright = ReadString(reader, copyrightPath);
}

void WICMetadataExtractor::ExtractGPSData(IWICMetadataQueryReader* reader, EXIFMetadata& metadata, MetadataPathFormat pathFormat)
{
    if (!reader)
    {
        return;
    }

    // Select the correct paths based on container format
    const bool useIfdPaths = (pathFormat == MetadataPathFormat::IFD);

    const auto& latitudePath = useIfdPaths ? HEIF_GPS_LATITUDE : GPS_LATITUDE;
    const auto& longitudePath = useIfdPaths ? HEIF_GPS_LONGITUDE : GPS_LONGITUDE;
    const auto& latitudeRefPath = useIfdPaths ? HEIF_GPS_LATITUDE_REF : GPS_LATITUDE_REF;
    const auto& longitudeRefPath = useIfdPaths ? HEIF_GPS_LONGITUDE_REF : GPS_LONGITUDE_REF;
    const auto& altitudePath = useIfdPaths ? HEIF_GPS_ALTITUDE : GPS_ALTITUDE;

    auto lat = ReadMetadata(reader, latitudePath);
    auto lon = ReadMetadata(reader, longitudePath);
    auto latRef = ReadMetadata(reader, latitudeRefPath);
    auto lonRef = ReadMetadata(reader, longitudeRefPath);

    if (lat && lon)
    {
        PropVariantValue emptyLatRef;
        PropVariantValue emptyLonRef;

        const PROPVARIANT& latRefVar = latRef ? latRef->Get() : emptyLatRef.Get();
        const PROPVARIANT& lonRefVar = lonRef ? lonRef->Get() : emptyLonRef.Get();

        auto coords = MetadataFormatHelper::ParseGPSCoordinates(
            lat->Get(),
            lon->Get(),
            latRefVar,
            lonRefVar);

        metadata.latitude = coords.first;
        metadata.longitude = coords.second;
    }

    auto alt = ReadMetadata(reader, altitudePath);
    if (alt)
    {
        metadata.altitude = MetadataFormatHelper::ParseGPSRational(alt->Get());
    }
}


std::optional<SYSTEMTIME> WICMetadataExtractor::ReadDateTime(IWICMetadataQueryReader* reader, const std::wstring& path)
{
    auto propVar = ReadMetadata(reader, path);
    if (!propVar)
    {
        return std::nullopt;
    }

    std::wstring rawValue;
    const PROPVARIANT& variant = propVar->Get();

    switch (variant.vt)
    {
    case VT_LPWSTR:
        if (variant.pwszVal)
        {
            rawValue = variant.pwszVal;
        }
        break;
    case VT_BSTR:
        if (variant.bstrVal)
        {
            rawValue = variant.bstrVal;
        }
        break;
    case VT_LPSTR:
        if (variant.pszVal)
        {
            const int size = MultiByteToWideChar(CP_UTF8, 0, variant.pszVal, -1, nullptr, 0);
            if (size > 1)
            {
                rawValue.resize(static_cast<size_t>(size) - 1);
                MultiByteToWideChar(CP_UTF8, 0, variant.pszVal, -1, &rawValue[0], size);
            }
        }
        break;
    default:
        break;
    }

    if (rawValue.empty())
    {
        return std::nullopt;
    }

    const std::wstring normalized = TrimWhitespace(rawValue);
    if (normalized.empty())
    {
        return std::nullopt;
    }

    if (auto exifDate = ParseExifDateTime(normalized))
    {
        return exifDate;
    }

    if (auto isoDate = ParseIso8601DateTime(normalized))
    {
        return isoDate;
    }

    return std::nullopt;
}

std::optional<std::wstring> WICMetadataExtractor::ReadString(IWICMetadataQueryReader* reader, const std::wstring& path)
{
    auto propVar = ReadMetadata(reader, path);
    if (!propVar.has_value())
        return std::nullopt;

    std::wstring result;
    switch (propVar->Get().vt)
    {
    case VT_LPWSTR:
        if (propVar->Get().pwszVal)
            result = propVar->Get().pwszVal;
        break;
    case VT_BSTR:
        if (propVar->Get().bstrVal)
            result = propVar->Get().bstrVal;
        break;
    case VT_LPSTR:
        if (propVar->Get().pszVal)
        {
            int size = MultiByteToWideChar(CP_UTF8, 0, propVar->Get().pszVal, -1, nullptr, 0);
            if (size > 1)
            {
                result.resize(static_cast<size_t>(size) - 1);
                MultiByteToWideChar(CP_UTF8, 0, propVar->Get().pszVal, -1, &result[0], size);
            }
        }
        break;
    }
    
    
    // Trim whitespace from both ends
    if (!result.empty())
    {
        size_t start = result.find_first_not_of(L" \t\r\n");
        size_t end = result.find_last_not_of(L" \t\r\n");
        if (start != std::wstring::npos && end != std::wstring::npos)
        {
            result = result.substr(start, end - start + 1);
        }
        else if (start == std::wstring::npos)
        {
            result.clear();
        }
    }
    
    return result.empty() ? std::nullopt : std::make_optional(result);
}

std::optional<int64_t> WICMetadataExtractor::ReadInteger(IWICMetadataQueryReader* reader, const std::wstring& path)
{
    auto propVar = ReadMetadata(reader, path);
    if (!propVar.has_value())
        return std::nullopt;

    int64_t result = 0;
    switch (propVar->Get().vt)
    {
        case VT_I1: result = propVar->Get().cVal; break;
        case VT_I2: result = propVar->Get().iVal; break;
        case VT_I4: result = propVar->Get().lVal; break;
        case VT_I8: result = propVar->Get().hVal.QuadPart; break;
        case VT_UI1: result = propVar->Get().bVal; break;
        case VT_UI2: result = propVar->Get().uiVal; break;
        case VT_UI4: result = propVar->Get().ulVal; break;
        case VT_UI8: result = static_cast<int64_t>(propVar->Get().uhVal.QuadPart); break;
        default:
            return std::nullopt;
    }
    
    return result;
}

std::optional<double> WICMetadataExtractor::ReadDouble(IWICMetadataQueryReader* reader, const std::wstring& path)
{
    auto propVar = ReadMetadata(reader, path);
    if (!propVar.has_value())
        return std::nullopt;

    double result = 0.0;
    switch (propVar->Get().vt)
    {
        case VT_R4: 
            result = static_cast<double>(propVar->Get().fltVal); 
            break;
        case VT_R8: 
            result = propVar->Get().dblVal; 
            break;
        case VT_UI1 | VT_VECTOR:
        case VT_UI4 | VT_VECTOR:
            // Handle rational number (common for EXIF values)
            // Rational data is stored as 8 bytes: 4-byte numerator + 4-byte denominator
            if (propVar->Get().caub.cElems >= 8)
            {
                // ExposureBias (EXIF tag 37380) uses SRATIONAL type (signed rational)
                // which can represent negative values like -0.33 EV for exposure compensation.
                // Most other EXIF fields use RATIONAL type (unsigned) for values like aperture, shutter speed.
                if (path == EXIF_EXPOSURE_BIAS)
                {
                    // Parse as signed rational: int32_t / int32_t
                    result = MetadataFormatHelper::ParseSingleSRational(propVar->Get().caub.pElems, 0);
                    break;
                }
                else
                {
                    // Parse as unsigned rational: uint32_t / uint32_t
                    // First check if denominator is valid (non-zero) to avoid division by zero
                    const uint8_t* bytes = propVar->Get().caub.pElems;
                    uint32_t denominator = static_cast<uint32_t>(bytes[4]) |
                                         (static_cast<uint32_t>(bytes[5]) << 8) |
                                         (static_cast<uint32_t>(bytes[6]) << 16) |
                                         (static_cast<uint32_t>(bytes[7]) << 24);
                
                    if (denominator != 0)
                    {
                        result = MetadataFormatHelper::ParseSingleRational(propVar->Get().caub.pElems, 0);
                        break;
                    }
                }
            }
            return std::nullopt;
        default:
            // Try integer conversion
            switch (propVar->Get().vt)
            {
            case VT_I1: result = static_cast<double>(propVar->Get().cVal); break;
            case VT_I2: result = static_cast<double>(propVar->Get().iVal); break;
            case VT_I4: result = static_cast<double>(propVar->Get().lVal); break;
            case VT_I8: 
                {
                    // ExposureBias (EXIF tag 37380) may be stored as VT_I8 in some WIC implementations
                    // It represents a signed rational (SRATIONAL) packed into a 64-bit integer
                    if (path == EXIF_EXPOSURE_BIAS)
                    {
                        // Parse signed rational from int64: low 32 bits = numerator, high 32 bits = denominator
                        // Some implementations may reverse the order, so we try both
                        int32_t numerator = static_cast<int32_t>(propVar->Get().hVal.QuadPart & 0xFFFFFFFF);
                        int32_t denominator = static_cast<int32_t>(propVar->Get().hVal.QuadPart >> 32);
                        if (denominator != 0)
                        {
                            result = static_cast<double>(numerator) / static_cast<double>(denominator);
                        }
                        else
                        {
                            // Try reversed order: high 32 bits = numerator, low 32 bits = denominator
                            numerator = static_cast<int32_t>(propVar->Get().hVal.QuadPart >> 32);
                            denominator = static_cast<int32_t>(propVar->Get().hVal.QuadPart & 0xFFFFFFFF);
                            if (denominator != 0)
                            {
                                result = static_cast<double>(numerator) / static_cast<double>(denominator);
                            }
                            else
                            {
                                result = 0.0; // Default to 0 if both attempts fail
                            }
                        }
                    }
                    else
                    {
                        // For other fields, treat VT_I8 as a simple 64-bit integer
                        result = static_cast<double>(propVar->Get().hVal.QuadPart);
                    }
                }
                break;
            case VT_UI1: result = static_cast<double>(propVar->Get().bVal); break;
            case VT_UI2: result = static_cast<double>(propVar->Get().uiVal); break;
            case VT_UI4: result = static_cast<double>(propVar->Get().ulVal); break;
            case VT_UI8: 
                {
                    // ExposureBias (EXIF tag 37380) may be stored as VT_UI8 in some WIC implementations
                    // Even though it's unsigned, we need to reinterpret it as signed for SRATIONAL
                    if (path == EXIF_EXPOSURE_BIAS)
                    {
                        // Parse signed rational from uint64 (reinterpret as signed)
                        // Low 32 bits = numerator, high 32 bits = denominator
                        int32_t numerator = static_cast<int32_t>(propVar->Get().uhVal.QuadPart & 0xFFFFFFFF);
                        int32_t denominator = static_cast<int32_t>(propVar->Get().uhVal.QuadPart >> 32);
                        if (denominator != 0)
                        {
                            result = static_cast<double>(numerator) / static_cast<double>(denominator);
                        }
                        else
                        {
                            // Try reversed order: high 32 bits = numerator, low 32 bits = denominator
                            numerator = static_cast<int32_t>(propVar->Get().uhVal.QuadPart >> 32);
                            denominator = static_cast<int32_t>(propVar->Get().uhVal.QuadPart & 0xFFFFFFFF);
                            if (denominator != 0)
                            {
                                result = static_cast<double>(numerator) / static_cast<double>(denominator);
                            }
                            else
                            {
                                result = 0.0; // Default to 0 if both attempts fail
                            }
                        }
                    }
                    else
                    {
                        // For other EXIF rational fields (unsigned), try both byte orders to handle different encodings
                        // First try: low 32 bits = numerator, high 32 bits = denominator
                        uint32_t numerator = static_cast<uint32_t>(propVar->Get().uhVal.QuadPart & 0xFFFFFFFF);
                        uint32_t denominator = static_cast<uint32_t>(propVar->Get().uhVal.QuadPart >> 32);
                    
                        if (denominator != 0)
                        {
                            result = static_cast<double>(numerator) / static_cast<double>(denominator);
                        }
                        else
                        {
                            // Second try: high 32 bits = numerator, low 32 bits = denominator
                            numerator = static_cast<uint32_t>(propVar->Get().uhVal.QuadPart >> 32);
                            denominator = static_cast<uint32_t>(propVar->Get().uhVal.QuadPart & 0xFFFFFFFF);
                            if (denominator != 0)
                            {
                                result = static_cast<double>(numerator) / static_cast<double>(denominator);
                            }
                            else
                            {
                                // Fall back to treating as regular integer if denominator is 0
                                result = static_cast<double>(propVar->Get().uhVal.QuadPart);
                            }
                        }
                    }
                }
                break;
            default:
                return std::nullopt;
            }
    }
    
    return result;
}

std::optional<PropVariantValue> WICMetadataExtractor::ReadMetadata(IWICMetadataQueryReader* reader, const std::wstring& path)
{
    if (!reader)
        return std::nullopt;
    
    PropVariantValue value;

    HRESULT hr = reader->GetMetadataByName(path.c_str(), value.GetAddressOf());
    if (SUCCEEDED(hr))
    {
        return std::optional<PropVariantValue>(std::move(value));
    }

    return std::nullopt;
}

// GPS parsing functions have been moved to MetadataFormatHelper for better testability

bool WICMetadataExtractor::ExtractXMPMetadata(
    const std::wstring& filePath,
    XMPMetadata& outMetadata)
{
    return cache.GetOrLoadXMP(filePath, outMetadata, [this, &filePath](XMPMetadata& metadata) {
        return LoadXMPMetadata(filePath, metadata);
    });
}

bool WICMetadataExtractor::LoadXMPMetadata(
    const std::wstring& filePath,
    XMPMetadata& outMetadata)
{
    if (!PathFileExistsW(filePath.c_str()))
    {
#ifdef _DEBUG
        std::wstring msg = L"[PowerRename] XMP metadata extraction failed: File not found - " + filePath + L"\n";
        OutputDebugStringW(msg.c_str());
#endif
        return false;
    }

    auto decoder = CreateDecoder(filePath);
    if (!decoder)
    {
#ifdef _DEBUG
        std::wstring msg = L"[PowerRename] XMP metadata extraction: Unsupported format or unable to create decoder - " + filePath + L"\n";
        OutputDebugStringW(msg.c_str());
#endif
        return false;
    }

    CComPtr<IWICBitmapFrameDecode> frame;
    if (FAILED(decoder->GetFrame(0, &frame)))
    {
#ifdef _DEBUG
        std::wstring msg = L"[PowerRename] XMP metadata extraction failed: WIC decoder error - " + filePath + L"\n";
        OutputDebugStringW(msg.c_str());
#endif
        return false;
    }

    CComPtr<IWICMetadataQueryReader> rootReader;
    if (FAILED(frame->GetMetadataQueryReader(&rootReader)))
    {
        // No metadata is not necessarily an error - just means the file has no XMP data
        return false;
    }

    ExtractAllXMPFields(rootReader, outMetadata);

    return true;
}

// Batch extraction method implementations
void WICMetadataExtractor::ExtractAllXMPFields(IWICMetadataQueryReader* reader, XMPMetadata& metadata)
{
    if (!reader)
        return;
    
    // XMP Basic schema - xmp: namespace
    metadata.creatorTool = ReadString(reader, XMP_CREATOR_TOOL);
    metadata.createDate = ReadDateTime(reader, XMP_CREATE_DATE);
    metadata.modifyDate = ReadDateTime(reader, XMP_MODIFY_DATE);
    metadata.metadataDate = ReadDateTime(reader, XMP_METADATA_DATE);
    
    // Dublin Core schema - dc: namespace
    metadata.title = ReadString(reader, XMP_DC_TITLE);
    metadata.description = ReadString(reader, XMP_DC_DESCRIPTION);
    metadata.creator = ReadString(reader, XMP_DC_CREATOR);
    
    // For dc:subject, we need to handle the array structure
    // Try to read individual elements
    // XMP allows for large arrays, but we limit to a reasonable number to avoid performance issues
    constexpr int MAX_XMP_SUBJECTS = 50;
    std::vector<std::wstring> subjects;
    for (int i = 0; i < MAX_XMP_SUBJECTS; ++i)
    {
        std::wstring subjectPath = L"/xmp/dc:subject/{ulong=" + std::to_wstring(i) + L"}";
        auto subject = ReadString(reader, subjectPath);
        if (subject.has_value())
        {
            subjects.push_back(subject.value());
        }
        else
        {
            break;  // No more subjects
        }
    }
    if (!subjects.empty())
    {
        metadata.subject = subjects;
    }
    
    // XMP Rights Management schema
    metadata.rights = ReadString(reader, XMP_RIGHTS);
    
    // XMP Media Management schema - xmpMM: namespace
    metadata.documentID = ReadString(reader, XMP_MM_DOCUMENT_ID);
    metadata.instanceID = ReadString(reader, XMP_MM_INSTANCE_ID);
    metadata.originalDocumentID = ReadString(reader, XMP_MM_ORIGINAL_DOCUMENT_ID);
    metadata.versionID = ReadString(reader, XMP_MM_VERSION_ID);
}









