#include "fan_manager.h"

#include <stddef.h>

static bool io_valid(const vacuum_fan_io_t *io)
{
    return io != NULL && io->set_fan_power != NULL && io->set_control_duty_permille != NULL;
}

static vacuum_fan_status_t safe_off(vacuum_fan_manager_t *manager,
                                    const vacuum_fan_io_t *io)
{
    int power_result;
    int duty_result;

    if (!io_valid(io)) {
        manager->fan_power_command_confirmed = false;
        return VACUUM_FAN_OFF_UNCONFIRMED;
    }

    /* Try both outputs even if one callback fails; neither error short-circuits shutdown. */
    power_result = io->set_fan_power(io->context, false);
    duty_result = io->set_control_duty_permille(io->context, 0U);

    manager->fan_power_command_confirmed = (power_result == 0);
    if (power_result == 0) {
        manager->fan_power_enabled = false;
    } else {
        return VACUUM_FAN_OFF_UNCONFIRMED;
    }

    return (duty_result == 0) ? VACUUM_FAN_OK : VACUUM_FAN_IO_FAILURE;
}

static vacuum_fan_status_t fail_closed(vacuum_fan_manager_t *manager,
                                       const vacuum_fan_io_t *io,
                                       vacuum_fan_status_t reason)
{
    const vacuum_fan_status_t off_status = safe_off(manager, io);
    manager->fault_latched = true;
    manager->status = (off_status == VACUUM_FAN_OK) ? reason : off_status;
    return manager->status;
}

void vacuum_fan_manager_init(vacuum_fan_manager_t *manager, const vacuum_fan_io_t *io)
{
    if (manager == NULL) {
        return;
    }
    manager->fan_power_enabled = false;
    manager->fan_power_command_confirmed = false;
    manager->fault_latched = false;
    manager->status = VACUUM_FAN_NOT_READY;
    if (!io_valid(io)) {
        (void)fail_closed(manager, io, VACUUM_FAN_IO_FAILURE);
        return;
    }

    {
        const vacuum_fan_status_t off_status = safe_off(manager, io);
        if (off_status != VACUUM_FAN_OK) {
            manager->fault_latched = true;
            manager->status = off_status;
        }
    }
}

vacuum_fan_status_t vacuum_fan_manager_step(vacuum_fan_manager_t *manager,
                                            const vacuum_fan_input_t *input,
                                            const vacuum_fan_limits_t *limits,
                                            const vacuum_fan_io_t *io)
{
    if (manager == NULL || input == NULL || limits == NULL || !io_valid(io)) {
        if (manager != NULL) {
            return fail_closed(manager, io, VACUUM_FAN_IO_FAILURE);
        }
        return VACUUM_FAN_IO_FAILURE;
    }

    if (!input->run_requested) {
        const vacuum_fan_status_t off_status = safe_off(manager, io);
        if (off_status != VACUUM_FAN_OK) {
            manager->fault_latched = true;
            manager->status = off_status;
            return manager->status;
        }
        manager->fault_latched = false;
        manager->status = VACUUM_FAN_NOT_READY;
        return manager->status;
    }

    if (manager->fault_latched) {
        const vacuum_fan_status_t off_status = safe_off(manager, io);
        if (off_status != VACUUM_FAN_OK) {
            manager->status = off_status;
        }
        return manager->status;
    }
    if (!input->measurement_valid || !input->fan_profile_valid) {
        return fail_closed(manager, io, VACUUM_FAN_NOT_READY);
    }
    if (input->sample_age_ms > limits->max_sample_age_ms) {
        return fail_closed(manager, io, VACUUM_FAN_STALE_SAMPLE);
    }
    if (input->vbus_mv < limits->min_vbus_mv) {
        return fail_closed(manager, io, VACUUM_FAN_UNDERVOLTAGE);
    }
    if (input->vbus_mv > limits->max_vbus_mv) {
        return fail_closed(manager, io, VACUUM_FAN_OVERVOLTAGE);
    }
    if (input->fan_current_ma > limits->max_fan_current_ma || input->control_duty_permille > 1000U) {
        return fail_closed(manager, io, VACUUM_FAN_OVERCURRENT);
    }

    /* Configure fan control while its supply is still isolated, then close Q3. */
    if (io->set_control_duty_permille(io->context, input->control_duty_permille) != 0) {
        return fail_closed(manager, io, VACUUM_FAN_IO_FAILURE);
    }
    if (!manager->fan_power_enabled) {
        if (io->set_fan_power(io->context, true) != 0) {
            manager->fan_power_command_confirmed = false;
            return fail_closed(manager, io, VACUUM_FAN_IO_FAILURE);
        }
        manager->fan_power_enabled = true;
        manager->fan_power_command_confirmed = true;
    }
    manager->status = VACUUM_FAN_OK;
    return manager->status;
}
