#include <napi.h>
#include "camera_control.h"
#include <memory>
#include <vector>

class BrioZoomControlWrapper {
private:
    std::unique_ptr<CameraControl> m_controller;

public:
    BrioZoomControlWrapper() {
        m_controller.reset(CameraControl::createInstance());
    }

    ~BrioZoomControlWrapper() = default;

    // Discover available Logitech BRIO devices
    Napi::Array DiscoverDevices(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();
        
        if (!m_controller) {
            Napi::TypeError::New(env, "Camera controller not available on this platform")
                .ThrowAsJavaScriptException();
            return Napi::Array::New(env);
        }

        std::vector<CameraDevice> devices = m_controller->discoverDevices();
        Napi::Array result = Napi::Array::New(env, devices.size());

        for (size_t i = 0; i < devices.size(); i++) {
            Napi::Object deviceObj = Napi::Object::New(env);
            deviceObj.Set("name", Napi::String::New(env, devices[i].name));
            deviceObj.Set("id", Napi::String::New(env, devices[i].id));
            deviceObj.Set("vendorId", Napi::Number::New(env, devices[i].vendorId));
            deviceObj.Set("productId", Napi::Number::New(env, devices[i].productId));
            result[i] = deviceObj;
        }

        return result;
    }

    // Initialize a specific device
    Napi::Boolean InitializeDevice(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();

        if (!m_controller) {
            Napi::TypeError::New(env, "Camera controller not available on this platform")
                .ThrowAsJavaScriptException();
            return Napi::Boolean::New(env, false);
        }

        if (info.Length() < 1 || !info[0].IsString()) {
            Napi::TypeError::New(env, "Expected device ID as string")
                .ThrowAsJavaScriptException();
            return Napi::Boolean::New(env, false);
        }

        std::string deviceId = info[0].As<Napi::String>().Utf8Value();
        bool success = m_controller->initializeDevice(deviceId);

        return Napi::Boolean::New(env, success);
    }

    // Release the current device
    Napi::Value ReleaseDevice(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();

        if (m_controller) {
            m_controller->releaseDevice();
        }

        return env.Undefined();
    }

    // Get zoom capabilities
    Napi::Object GetZoomCapabilities(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();

        if (!m_controller) {
            Napi::TypeError::New(env, "Camera controller not available on this platform")
                .ThrowAsJavaScriptException();
            return Napi::Object::New(env);
        }

        ZoomCapabilities caps;
        bool success = m_controller->getZoomCapabilities(caps);

        Napi::Object result = Napi::Object::New(env);
        if (success) {
            result.Set("min", Napi::Number::New(env, caps.min));
            result.Set("max", Napi::Number::New(env, caps.max));
            result.Set("step", Napi::Number::New(env, caps.step));
            result.Set("current", Napi::Number::New(env, caps.current));
            result.Set("supportsAbsolute", Napi::Boolean::New(env, caps.supportsAbsolute));
            result.Set("supportsRelative", Napi::Boolean::New(env, caps.supportsRelative));
            result.Set("success", Napi::Boolean::New(env, true));
        } else {
            result.Set("success", Napi::Boolean::New(env, false));
            result.Set("error", Napi::String::New(env, "Failed to get zoom capabilities"));
        }

        return result;
    }

    // Set absolute zoom value
    Napi::Boolean SetZoomAbsolute(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();

        if (!m_controller) {
            Napi::TypeError::New(env, "Camera controller not available on this platform")
                .ThrowAsJavaScriptException();
            return Napi::Boolean::New(env, false);
        }

        if (info.Length() < 1 || !info[0].IsNumber()) {
            Napi::TypeError::New(env, "Expected zoom value as number")
                .ThrowAsJavaScriptException();
            return Napi::Boolean::New(env, false);
        }

        int zoomValue = info[0].As<Napi::Number>().Int32Value();
        bool success = m_controller->setZoomAbsolute(zoomValue);

        return Napi::Boolean::New(env, success);
    }

    // Set relative zoom value (delta)
    Napi::Boolean SetZoomRelative(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();

        if (!m_controller) {
            Napi::TypeError::New(env, "Camera controller not available on this platform")
                .ThrowAsJavaScriptException();
            return Napi::Boolean::New(env, false);
        }

        if (info.Length() < 1 || !info[0].IsNumber()) {
            Napi::TypeError::New(env, "Expected zoom delta as number")
                .ThrowAsJavaScriptException();
            return Napi::Boolean::New(env, false);
        }

        int zoomDelta = info[0].As<Napi::Number>().Int32Value();
        bool success = m_controller->setZoomRelative(zoomDelta);

        return Napi::Boolean::New(env, success);
    }

    // Get current zoom value
    Napi::Object GetZoomValue(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();

        if (!m_controller) {
            Napi::TypeError::New(env, "Camera controller not available on this platform")
                .ThrowAsJavaScriptException();
            return Napi::Object::New(env);
        }

        int zoomValue;
        bool success = m_controller->getZoomValue(zoomValue);

        Napi::Object result = Napi::Object::New(env);
        if (success) {
            result.Set("value", Napi::Number::New(env, zoomValue));
            result.Set("success", Napi::Boolean::New(env, true));
        } else {
            result.Set("success", Napi::Boolean::New(env, false));
            result.Set("error", Napi::String::New(env, "Failed to get zoom value"));
        }

        return result;
    }
};

// Global instance
static std::unique_ptr<BrioZoomControlWrapper> g_wrapper;

// Initialize the wrapper
Napi::Value InitWrapper(const Napi::CallbackInfo& info) {
    g_wrapper = std::make_unique<BrioZoomControlWrapper>();
    return info.Env().Undefined();
}

// Wrapper functions
Napi::Array DiscoverDevices(const Napi::CallbackInfo& info) {
    if (!g_wrapper) {
        InitWrapper(info);
    }
    return g_wrapper->DiscoverDevices(info);
}

Napi::Boolean InitializeDevice(const Napi::CallbackInfo& info) {
    if (!g_wrapper) {
        InitWrapper(info);
    }
    return g_wrapper->InitializeDevice(info);
}

Napi::Value ReleaseDevice(const Napi::CallbackInfo& info) {
    if (!g_wrapper) {
        InitWrapper(info);
    }
    return g_wrapper->ReleaseDevice(info);
}

Napi::Object GetZoomCapabilities(const Napi::CallbackInfo& info) {
    if (!g_wrapper) {
        InitWrapper(info);
    }
    return g_wrapper->GetZoomCapabilities(info);
}

Napi::Boolean SetZoomAbsolute(const Napi::CallbackInfo& info) {
    if (!g_wrapper) {
        InitWrapper(info);
    }
    return g_wrapper->SetZoomAbsolute(info);
}

Napi::Boolean SetZoomRelative(const Napi::CallbackInfo& info) {
    if (!g_wrapper) {
        InitWrapper(info);
    }
    return g_wrapper->SetZoomRelative(info);
}

Napi::Object GetZoomValue(const Napi::CallbackInfo& info) {
    if (!g_wrapper) {
        InitWrapper(info);
    }
    return g_wrapper->GetZoomValue(info);
}

// Module initialization
Napi::Object Init(Napi::Env env, Napi::Object exports) {
    exports.Set(Napi::String::New(env, "discoverDevices"),
                Napi::Function::New(env, DiscoverDevices));
    exports.Set(Napi::String::New(env, "initializeDevice"),
                Napi::Function::New(env, InitializeDevice));
    exports.Set(Napi::String::New(env, "releaseDevice"),
                Napi::Function::New(env, ReleaseDevice));
    exports.Set(Napi::String::New(env, "getZoomCapabilities"),
                Napi::Function::New(env, GetZoomCapabilities));
    exports.Set(Napi::String::New(env, "setZoomAbsolute"),
                Napi::Function::New(env, SetZoomAbsolute));
    exports.Set(Napi::String::New(env, "setZoomRelative"),
                Napi::Function::New(env, SetZoomRelative));
    exports.Set(Napi::String::New(env, "getZoomValue"),
                Napi::Function::New(env, GetZoomValue));

    return exports;
}

NODE_API_MODULE(brio_zoom_control, Init)