#include "lamp_ui_wire_service.h"

#include <string.h>

static void fill_ack(const lamp_ui_dispatcher *dispatcher, uint64_t session_id,
                    uint32_t sequence, aiw_ui_ack_status status,
                    uint32_t now_ms, aiw_ui_ack *ack) {
    memset(ack, 0, sizeof(*ack));
    ack->session_id = session_id;
    ack->sequence = sequence;
    ack->status = status;
    if (dispatcher != NULL && sequence != 0u) {
        ack->selected_mode = (uint8_t)dispatcher->selected_mode;
        if (dispatcher->guard != NULL && dispatcher->guard->armed &&
            dispatcher->guard->mode != LG_FAULT) {
            ack->flags |= AIW_UI_ACK_FLAG_RUNNING;
            ack->remaining_ms = lg_remaining_ms(dispatcher->guard, now_ms);
        }
    }
}

bool lamp_ui_wire_service_init(lamp_ui_wire_service *service, lg_guard *guard) {
    if (service == NULL || guard == NULL) return false;
    memset(service, 0, sizeof(*service));
    service->guard = guard;
    return true;
}

bool lamp_ui_wire_service_handle(lamp_ui_wire_service *service,
                                 const aiw_message *message, uint32_t now_ms,
                                 aiw_ui_ack *ack) {
    if (service == NULL || service->guard == NULL || message == NULL || ack == NULL)
        return false;

    if (message->type == AIW_MESSAGE_UI_SESSION_OPEN) {
        const uint64_t session_id = message->value.ui_session_open.session_id;
        if (session_id == 0u) return false;

        if (service->session_open && service->dispatcher.session_id == session_id) {
            /* Duplicate OPEN is idempotent; preserve the sequence replay cache. */
            fill_ack(&service->dispatcher, session_id, 0u,
                     AIW_UI_ACK_SESSION_OPENED, now_ms, ack);
            ++service->session_open_count;
            return true;
        }

        const bool guard_accepts_session = !service->guard->configured ||
                                           service->guard->session_id == session_id;
        if (service->guard->armed || !guard_accepts_session) {
            fill_ack(service->session_open ? &service->dispatcher : NULL,
                     session_id, 0u, AIW_UI_ACK_SESSION_REJECTED, now_ms, ack);
            ++service->rejected_session_count;
            return true;
        }

        lg_mode initial_mode = service->guard->mode;
        if (initial_mode == LG_FAULT) initial_mode = LG_OFF;
        if (!lamp_ui_dispatcher_init(&service->dispatcher, service->guard,
                                     session_id, initial_mode)) {
            fill_ack(NULL, session_id, 0u, AIW_UI_ACK_SESSION_REJECTED, now_ms, ack);
            ++service->rejected_session_count;
            return true;
        }
        service->session_open = true;
        ++service->session_open_count;
        fill_ack(&service->dispatcher, session_id, 0u,
                 AIW_UI_ACK_SESSION_OPENED, now_ms, ack);
        return true;
    }

    if (message->type != AIW_MESSAGE_UI_COMMAND) return false;
    const aiw_ui_command *wire_command = &message->value.ui_command;
    ++service->command_count;
    if (!service->session_open) {
        fill_ack(NULL, wire_command->session_id, wire_command->sequence,
                 AIW_UI_ACK_BAD_SESSION, now_ms, ack);
        return true;
    }
    if (wire_command->session_id != service->dispatcher.session_id) {
        fill_ack(NULL, wire_command->session_id, wire_command->sequence,
                 AIW_UI_ACK_BAD_SESSION, now_ms, ack);
        return true;
    }

    const lamp_ui_event event = {
        .session_id = wire_command->session_id,
        .sequence = wire_command->sequence,
        .button = (lamp_ui_button)wire_command->button
    };
    lamp_ui_ack dispatch_ack;
    if (!lamp_ui_dispatcher_handle(&service->dispatcher, &event, now_ms,
                                   &dispatch_ack)) return false;

    fill_ack(&service->dispatcher, wire_command->session_id,
             dispatch_ack.sequence, (aiw_ui_ack_status)dispatch_ack.status,
             now_ms, ack);
    if (dispatch_ack.duplicate) ack->flags |= AIW_UI_ACK_FLAG_DUPLICATE;
    return true;
}
