// Mosis Designer - Desktop designer and testing tool for Mosis virtual phone UI
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <thread>
#include <chrono>

#include <RmlUi/Core.h>
#include <RmlUi/Debugger.h>
#include <RmlUi/Lua.h>
#include <RmlUi/Lua/Interpreter.h>
#include "RmlUi_Backend.h"  // Local backend with input recording hooks

#include "platform.h"
#include "file_interface.h"
#include "service_interface.h"
#include "kernel_impl.h"
#include "data_models.h"
#include "hot_reload.h"
#include "desktop_platform.h"
#include "testing/ui_inspector.h"
#include "testing/action_recorder.h"
#include "testing/action_player.h"

// Command-line options
struct Options {
    std::string document_path;
    uint32_t width = 540;
    uint32_t height = 960;
    bool dump_mode = false;
    bool debug_mode = false;
    std::string output_dir = "dump";
    std::string log_file;  // If set, write logs to this file
    std::string hierarchy_file;  // If set, dump UI hierarchy to this file each frame
    std::string record_file;  // If set, record actions to JSON file
    std::string playback_file;  // If set, play back actions from JSON file
};

// Global log file stream
static std::ofstream g_log_file;

// Pointer for shared logging (used by kernel_impl.cpp via log.h)
namespace mosis {
    std::ofstream* g_log_file_ptr = nullptr;
}

// Log function that writes to both stdout and file
void LogMessage(const std::string& message) {
    std::cout << message << std::endl;
    if (g_log_file.is_open()) {
        g_log_file << message << std::endl;
        g_log_file.flush();
    }
}

// Forward declarations
void PrintUsage(const char* program);
Options ParseOptions(int argc, const char* argv[]);
void LoadFonts(const std::filesystem::path& fonts_path);
std::filesystem::path FindAssetsPath(const std::filesystem::path& start_path);

// Custom system interface with logging
class DesignerSystemInterface : public Rml::SystemInterface {
    Rml::SystemInterface* m_backend_interface;

public:
    explicit DesignerSystemInterface(Rml::SystemInterface* backend)
        : m_backend_interface(backend) {}

    double GetElapsedTime() override {
        return m_backend_interface ? m_backend_interface->GetElapsedTime() : 0.0;
    }

    bool LogMessage(Rml::Log::Type type, const Rml::String& message) override {
        const char* type_str = "INFO";
        switch (type) {
            case Rml::Log::LT_ERROR: type_str = "ERROR"; break;
            case Rml::Log::LT_WARNING: type_str = "WARNING"; break;
            case Rml::Log::LT_INFO: type_str = "INFO"; break;
            case Rml::Log::LT_DEBUG: type_str = "DEBUG"; break;
            default: break;
        }
        std::cout << "[RmlUi " << type_str << "] " << message << std::endl;
        return true;
    }
};

// Global state
static DesignerSystemInterface* g_system_interface = nullptr;
static mosis::DesktopFileInterface* g_file_interface = nullptr;
static mosis::IKernel* g_kernel = nullptr;
static std::filesystem::path g_assets_path;
static mosis::testing::UIInspector g_ui_inspector;

// Recording/playback state
static std::unique_ptr<mosis::testing::ActionRecorder> g_recorder;
static std::unique_ptr<mosis::testing::ActionPlayer> g_player;
static std::string g_record_file_path;

// Key callback for F5 (recording control)
bool HandleKeyDown(Rml::Context* context, Rml::Input::KeyIdentifier key, int key_modifier, float native_dp_ratio, bool priority) {
    // F5: Toggle recording / Save recording
    if (key == Rml::Input::KI_F5 && g_recorder) {
        if (g_recorder->IsRecording()) {
            g_recorder->StopRecording();
            if (g_recorder->SaveToFile(g_record_file_path)) {
                LogMessage("Recording saved to: " + g_record_file_path);
            } else {
                LogMessage("ERROR: Failed to save recording to: " + g_record_file_path);
            }
        } else {
            g_recorder->StartRecording();
            LogMessage("Recording started (press F5 to stop and save)");
        }
        return true;  // Consumed
    }

    // F6: Pause/resume playback
    if (key == Rml::Input::KI_F6 && g_player) {
        if (g_player->IsPlaying()) {
            g_player->Pause();
            LogMessage("Playback paused");
        } else if (g_player->IsPaused()) {
            g_player->Resume();
            LogMessage("Playback resumed");
        }
        return true;  // Consumed
    }

    return false;  // Not consumed, let RmlUi handle it
}

int main(int argc, const char* argv[])
{
    // Parse command-line options first
    Options opts = ParseOptions(argc, argv);

    // Open log file if specified
    if (!opts.log_file.empty()) {
        g_log_file.open(opts.log_file, std::ios::out | std::ios::trunc);
        if (!g_log_file.is_open()) {
            std::cerr << "Warning: Could not open log file: " << opts.log_file << std::endl;
        } else {
            mosis::g_log_file_ptr = &g_log_file;
        }
    }

    LogMessage("Mosis Designer v0.1.0");

    if (opts.document_path.empty()) {
        PrintUsage(argv[0]);
        return EXIT_FAILURE;
    }

    std::filesystem::path document_file = opts.document_path;
    if (!std::filesystem::exists(document_file)) {
        std::cerr << "File not found: " << opts.document_path << std::endl;
        return EXIT_FAILURE;
    }
    document_file = std::filesystem::absolute(document_file);

    // Initialize the RmlUi backend (GLFW + OpenGL)
    if (!Backend::Initialize("Mosis Designer", opts.width, opts.height, true)) {
        std::cerr << "Failed to initialize backend" << std::endl;
        return EXIT_FAILURE;
    }

    // Find assets path
    g_assets_path = FindAssetsPath(document_file.parent_path());
    LogMessage("Assets path: " + g_assets_path.generic_string());

    // Setup custom interfaces
    g_system_interface = new DesignerSystemInterface(Backend::GetSystemInterface());
    g_file_interface = new mosis::DesktopFileInterface(g_assets_path.string());

    // Set platform for kernel
    auto platform = std::make_unique<mosis::desktop::DesktopPlatform>();
    platform->GetFileInterface().SetAssetsPath(g_assets_path.string());
    mosis::SetPlatform(std::move(platform));

    Rml::SetSystemInterface(g_system_interface);
    Rml::SetFileInterface(g_file_interface);
    Rml::SetRenderInterface(Backend::GetRenderInterface());

    // Initialize RmlUi
    Rml::Initialise();
    Rml::Lua::Initialise();
    LogMessage("RmlUi and Lua initialized");

    // Create context
    Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(opts.width, opts.height));
    if (!context) {
        std::cerr << "Failed to create RmlUi context" << std::endl;
        Rml::Shutdown();
        Backend::Shutdown();
        return EXIT_FAILURE;
    }

    // Enable debugger in debug mode
    if (opts.debug_mode) {
        Rml::Debugger::Initialise(context);
        Rml::Debugger::SetVisible(true);
    }

    // Load fonts
    std::filesystem::path fonts_path = g_assets_path / "fonts";
    LoadFonts(fonts_path);

    // Initialize sample data and data models
    initializeSampleData();
    setupDataModels(context);

    // Create and configure kernel
    mosis::KernelConfig kernel_config;
    kernel_config.width = opts.width;
    kernel_config.height = opts.height;
    kernel_config.initial_document = document_file.string();
    kernel_config.threaded = false;

    auto kernel = mosis::CreateKernel(kernel_config);
    g_kernel = kernel.get();

    // Set context and register Lua functions
    auto* desktop_kernel = dynamic_cast<mosis::DesktopKernel*>(g_kernel);
    if (desktop_kernel) {
        desktop_kernel->SetContext(context);
        mosis::DesktopKernel::RegisterLuaFunctions();
    }

    // Load the initial document
    std::string document_path_str = document_file.generic_string();
    LogMessage("Loading document: " + document_path_str);

    Rml::ElementDocument* document = context->LoadDocument(document_path_str);
    if (document) {
        document->Show();
        if (desktop_kernel) {
            desktop_kernel->SetDocument(document);
            desktop_kernel->SetCurrentDocumentPath(document_path_str);
        }
        LogMessage("Document loaded successfully");
    } else {
        LogMessage("ERROR: Failed to load document!");
    }

    // Start kernel
    kernel->Start();

    // Initialize recording if enabled
    if (!opts.record_file.empty()) {
        g_recorder = std::make_unique<mosis::testing::ActionRecorder>(opts.width, opts.height);
        g_record_file_path = opts.record_file;

        // Set up input callbacks for recording
        Backend::SetMouseButtonCallback([](int x, int y, int button, bool pressed) {
            if (g_recorder && g_recorder->IsRecording() && button == 0) {  // Left mouse button only
                if (pressed) {
                    g_recorder->RecordMouseDown(x, y);
                } else {
                    g_recorder->RecordMouseUp(x, y);
                }
            }
        });

        Backend::SetMouseMoveCallback([](int x, int y) {
            if (g_recorder && g_recorder->IsRecording()) {
                g_recorder->RecordMouseMove(x, y);
            }
        });

        Backend::SetKeyCallback([](int key, bool pressed) {
            if (g_recorder && g_recorder->IsRecording()) {
                g_recorder->RecordKey(key, pressed);
            }
        });

        LogMessage("Recording mode enabled. Press F5 to start recording.");
    }

    // Initialize playback if enabled
    if (!opts.playback_file.empty()) {
        g_player = std::make_unique<mosis::testing::ActionPlayer>(context);
        if (g_player->LoadFromFile(opts.playback_file)) {
            LogMessage("Loaded playback file: " + opts.playback_file);
            g_player->Start();
        } else {
            LogMessage("ERROR: Failed to load playback file: " + opts.playback_file);
            g_player.reset();
        }
    }

    // Setup hot-reload
    std::unique_ptr<mosis::desktop::HotReload> hot_reload;
    if (!opts.dump_mode) {
        hot_reload = std::make_unique<mosis::desktop::HotReload>(
            g_assets_path,
            [&kernel]() {
                std::cout << "File change detected, requesting reload..." << std::endl;
                kernel->RequestReload();
            }
        );
        hot_reload->Start();
        std::cout << "Hot-reload enabled for: " << g_assets_path.generic_string() << std::endl;
    }

    // Main loop
    bool running = true;
    while (running) {
        // Process hot-reload
        if (hot_reload) {
            hot_reload->CheckForChanges();
        }

        // Process events and update (with key callback for F5/F6 control)
        running = Backend::ProcessEvents(context, HandleKeyDown);

        // Update playback if active
        if (g_player && g_player->IsPlaying()) {
            g_player->Update();
            if (g_player->IsFinished()) {
                LogMessage("Playback complete");
            }
        }

        // Update kernel (processes tasks, updates time, etc.)
        kernel->Update();

        // Render
        Backend::BeginFrame();
        kernel->Render();
        Backend::PresentFrame();

        // Dump hierarchy if enabled (quiet mode to avoid spamming console)
        if (!opts.hierarchy_file.empty() && desktop_kernel) {
            Rml::ElementDocument* doc = desktop_kernel->GetDocument();
            if (doc) {
                g_ui_inspector.SaveToFile(doc, opts.hierarchy_file, true);
            }
        }
    }

    // Cleanup
    // Stop and save recording if still active
    if (g_recorder && g_recorder->IsRecording()) {
        g_recorder->StopRecording();
        if (g_recorder->SaveToFile(g_record_file_path)) {
            LogMessage("Recording saved on exit to: " + g_record_file_path);
        }
    }
    g_recorder.reset();
    g_player.reset();

    kernel->Stop();
    kernel.reset();
    g_kernel = nullptr;

    if (hot_reload) {
        hot_reload->Stop();
    }

    if (opts.debug_mode) {
        Rml::Debugger::Shutdown();
    }

    delete g_system_interface;
    delete g_file_interface;

    Rml::Shutdown();
    Backend::Shutdown();

    std::cout << "Mosis Designer shutdown complete" << std::endl;
    return EXIT_SUCCESS;
}

void PrintUsage(const char* program)
{
    std::cout << "Usage: " << program << " <document.rml> [options]" << std::endl;
    std::cout << std::endl;
    std::cout << "Options:" << std::endl;
    std::cout << "  --resolution WxH   Set phone resolution (default: 540x960)" << std::endl;
    std::cout << "  --dump             Dump mode: screenshot + hierarchy, then exit" << std::endl;
    std::cout << "  --debug            Enable RmlUi debugger" << std::endl;
    std::cout << "  --output DIR       Output directory for dump mode (default: dump)" << std::endl;
    std::cout << "  --log FILE         Write log output to file (for automated testing)" << std::endl;
    std::cout << "  --hierarchy FILE   Continuously dump UI hierarchy to JSON file" << std::endl;
    std::cout << "  --record FILE      Record actions to JSON file (F5 to start/stop)" << std::endl;
    std::cout << "  --playback FILE    Play back recorded actions from JSON file" << std::endl;
    std::cout << std::endl;
    std::cout << "Recording Controls:" << std::endl;
    std::cout << "  F5                 Start/stop recording (when --record is enabled)" << std::endl;
    std::cout << "  F6                 Pause/resume playback (when --playback is enabled)" << std::endl;
    std::cout << std::endl;
    std::cout << "Examples:" << std::endl;
    std::cout << "  " << program << " assets/apps/home/home.rml" << std::endl;
    std::cout << "  " << program << " assets/apps/home/home.rml --resolution 720x1280" << std::endl;
    std::cout << "  " << program << " assets/apps/home/home.rml --dump" << std::endl;
    std::cout << "  " << program << " assets/apps/home/home.rml --record test.json" << std::endl;
    std::cout << "  " << program << " assets/apps/home/home.rml --playback test.json" << std::endl;
}

Options ParseOptions(int argc, const char* argv[])
{
    Options opts;

    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];

        if (arg == "--resolution" && i + 1 < argc) {
            std::string res = argv[++i];
            size_t x_pos = res.find('x');
            if (x_pos != std::string::npos) {
                opts.width = std::stoi(res.substr(0, x_pos));
                opts.height = std::stoi(res.substr(x_pos + 1));
            }
        } else if (arg == "--dump") {
            opts.dump_mode = true;
        } else if (arg == "--debug") {
            opts.debug_mode = true;
        } else if (arg == "--output" && i + 1 < argc) {
            opts.output_dir = argv[++i];
        } else if (arg == "--log" && i + 1 < argc) {
            opts.log_file = argv[++i];
        } else if (arg == "--hierarchy" && i + 1 < argc) {
            opts.hierarchy_file = argv[++i];
        } else if (arg == "--record" && i + 1 < argc) {
            opts.record_file = argv[++i];
        } else if (arg == "--playback" && i + 1 < argc) {
            opts.playback_file = argv[++i];
        } else if (arg[0] != '-') {
            opts.document_path = arg;
        }
    }

    return opts;
}

void LoadFonts(const std::filesystem::path& fonts_path)
{
    if (!std::filesystem::exists(fonts_path)) {
        std::cerr << "Fonts directory not found: " << fonts_path << std::endl;
        return;
    }

    int count = 0;
    for (const auto& entry : std::filesystem::directory_iterator(fonts_path)) {
        if (entry.path().extension() == ".ttf") {
            std::string font_path = entry.path().generic_string();
            if (Rml::LoadFontFace(font_path)) {
                std::cout << "Loaded font: " << entry.path().filename() << std::endl;
                ++count;
            }
        }
    }

    // Also try Roboto subdirectory
    std::filesystem::path roboto_path = fonts_path / "Roboto";
    if (std::filesystem::exists(roboto_path)) {
        for (const auto& entry : std::filesystem::recursive_directory_iterator(roboto_path)) {
            if (entry.path().extension() == ".ttf") {
                std::string font_path = entry.path().generic_string();
                if (Rml::LoadFontFace(font_path)) {
                    std::cout << "Loaded font: " << entry.path().filename() << std::endl;
                    ++count;
                }
            }
        }
    }

    std::cout << "Loaded " << count << " fonts" << std::endl;
}

std::filesystem::path FindAssetsPath(const std::filesystem::path& start_path)
{
    std::filesystem::path current = std::filesystem::absolute(start_path);

    // Walk up the directory tree looking for a fonts/ subdirectory
    while (!current.empty() && current.has_parent_path()) {
        std::filesystem::path fonts_path = current / "fonts";
        if (std::filesystem::exists(fonts_path) && std::filesystem::is_directory(fonts_path)) {
            // Check if it contains TTF files
            for (const auto& entry : std::filesystem::directory_iterator(fonts_path)) {
                if (entry.path().extension() == ".ttf") {
                    return current;
                }
            }
        }
        current = current.parent_path();
    }

    // Fall back to start path
    return std::filesystem::absolute(start_path);
}
