// Hierarchy reader implementation
#include "hierarchy_reader.h"
#include <fstream>
#include <iostream>
#include <thread>
#include <chrono>

namespace mosis::test {

void HierarchyReader::SetHierarchyPath(const std::filesystem::path& path) {
    m_path = path;
    m_loaded = false;
}

bool HierarchyReader::Reload() {
    if (m_path.empty()) {
        return false;
    }

    // Retry with increasing delays to handle file write race conditions
    const int maxAttempts = 10;
    const int baseDelayMs = 30;

    for (int attempt = 0; attempt < maxAttempts; ++attempt) {
        if (attempt > 0) {
            // Exponential backoff: 30, 60, 120, ... ms
            int delayMs = baseDelayMs * (1 << std::min(attempt - 1, 4));
            std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
        }

        // Read entire file into string first (to avoid partial reads)
        std::string content;
        {
            std::ifstream file(m_path, std::ios::binary);
            if (!file.is_open()) {
                continue;  // File might not exist yet, retry
            }

            // Get file size
            file.seekg(0, std::ios::end);
            auto size = file.tellg();
            if (size <= 0) {
                continue;  // Empty file, retry
            }
            file.seekg(0, std::ios::beg);

            // Read content
            content.resize(static_cast<size_t>(size));
            file.read(content.data(), size);

            if (!file.good()) {
                continue;  // Read error, retry
            }
        }

        // Check for valid JSON ending (should end with "}")
        size_t lastNonSpace = content.find_last_not_of(" \t\n\r");
        if (lastNonSpace == std::string::npos || content[lastNonSpace] != '}') {
            continue;  // File is incomplete, retry
        }

        try {
            m_json = nlohmann::json::parse(content);
            m_loaded = true;
            return true;
        } catch (const nlohmann::json::parse_error& e) {
            // File might be partially written, retry
            if (attempt == maxAttempts - 1) {
                std::cerr << "Failed to parse hierarchy JSON after " << maxAttempts << " attempts: " << e.what() << std::endl;
            }
        }
    }

    std::cerr << "Failed to load hierarchy file: " << m_path << std::endl;
    m_loaded = false;
    return false;
}

std::string HierarchyReader::GetScreen() const {
    if (!m_loaded) return "";
    return m_json.value("screen", "");
}

std::string HierarchyReader::GetScreenName() const {
    std::string screen = GetScreen();
    // Extract just the filename from the path
    size_t lastSlash = screen.find_last_of("/\\");
    if (lastSlash != std::string::npos) {
        screen = screen.substr(lastSlash + 1);
    }
    return screen;
}

std::vector<ElementInfo> HierarchyReader::GetAllElements() const {
    std::vector<ElementInfo> results;
    if (!m_loaded || !m_json.contains("elements")) {
        return results;
    }

    SearchElements(m_json["elements"], [](const ElementInfo&) {
        return true;  // Match all elements
    }, results);

    return results;
}

int HierarchyReader::GetWidth() const {
    if (!m_loaded) return 0;
    if (m_json.contains("resolution") && m_json["resolution"].contains("width")) {
        return m_json["resolution"]["width"].get<int>();
    }
    return 0;
}

int HierarchyReader::GetHeight() const {
    if (!m_loaded) return 0;
    if (m_json.contains("resolution") && m_json["resolution"].contains("height")) {
        return m_json["resolution"]["height"].get<int>();
    }
    return 0;
}

ElementInfo HierarchyReader::ParseElement(const nlohmann::json& j) const {
    ElementInfo info;

    info.tag = j.value("tag", "");
    info.id = j.value("id", "");
    info.visible = j.value("visible", false);

    // Parse classes array
    if (j.contains("classes") && j["classes"].is_array()) {
        for (const auto& cls : j["classes"]) {
            if (cls.is_string()) {
                info.classes.push_back(cls.get<std::string>());
            }
        }
    }

    // Parse bounds
    if (j.contains("bounds") && j["bounds"].is_object()) {
        const auto& b = j["bounds"];
        info.bounds.x = b.value("x", 0.0f);
        info.bounds.y = b.value("y", 0.0f);
        info.bounds.width = b.value("width", 0.0f);
        info.bounds.height = b.value("height", 0.0f);
    }

    // Parse text
    if (j.contains("text") && !j["text"].is_null()) {
        info.text = j["text"].get<std::string>();
    }

    return info;
}

void HierarchyReader::SearchElements(
    const nlohmann::json& j,
    std::function<bool(const ElementInfo&)> predicate,
    std::vector<ElementInfo>& results) const
{
    if (!j.is_object()) return;

    // Parse current element
    ElementInfo info = ParseElement(j);
    if (predicate(info)) {
        results.push_back(info);
    }

    // Recurse into children
    if (j.contains("children") && j["children"].is_array()) {
        for (const auto& child : j["children"]) {
            SearchElements(child, predicate, results);
        }
    }
}

std::optional<ElementInfo> HierarchyReader::FindById(const std::string& id) const {
    if (!m_loaded || !m_json.contains("elements")) {
        return std::nullopt;
    }

    std::vector<ElementInfo> results;
    SearchElements(m_json["elements"], [&id](const ElementInfo& e) {
        return e.id == id;
    }, results);

    if (!results.empty()) {
        return results[0];
    }
    return std::nullopt;
}

std::vector<ElementInfo> HierarchyReader::FindByClass(const std::string& className) const {
    std::vector<ElementInfo> results;
    if (!m_loaded || !m_json.contains("elements")) {
        return results;
    }

    SearchElements(m_json["elements"], [&className](const ElementInfo& e) {
        for (const auto& cls : e.classes) {
            if (cls == className) return true;
        }
        return false;
    }, results);

    return results;
}

std::vector<ElementInfo> HierarchyReader::FindByTag(const std::string& tagName) const {
    std::vector<ElementInfo> results;
    if (!m_loaded || !m_json.contains("elements")) {
        return results;
    }

    SearchElements(m_json["elements"], [&tagName](const ElementInfo& e) {
        return e.tag == tagName;
    }, results);

    return results;
}

std::optional<ElementInfo> HierarchyReader::FindFirst(
    std::function<bool(const ElementInfo&)> predicate) const
{
    if (!m_loaded || !m_json.contains("elements")) {
        return std::nullopt;
    }

    std::vector<ElementInfo> results;
    SearchElements(m_json["elements"], predicate, results);

    if (!results.empty()) {
        return results[0];
    }
    return std::nullopt;
}

} // namespace mosis::test
