#include "stm32g0xx.h"

#include "fan_manager.h"
#include "vacuum_board.h"
#include "vacuum_panel_runtime.h"
#include "vacuum_display.h"
#include "display_bus.h"
#include "pressure_decode.h"

#include <stdbool.h>
#include <stdint.h>

static volatile uint32_t g_millis;
static vacuum_fan_manager_t g_fan_manager;
static vpr_state g_panel;
static vd_driver g_display;
static uint8_t g_display_frame[512];
static bool g_display_ready,g_display_failed;
static uint32_t next_frame_ms;
/* Engineering diagnostic profile: XGZP6897DC005HPDPN, V3.1,
   nominal +/-500 Pa. This configuration does not identify the fitted part. */
static const xp_profile pressure_profile={0x58,31,-500,500};
volatile uint32_t g_pressure_decode_status;
volatile xp_result g_pressure_decoded;
volatile uint32_t g_pressure_expected_revision=31;
volatile int32_t g_pressure_expected_min_pa=-500,g_pressure_expected_max_pa=500;
/* No freshness, calibration, suction or output qualification. */
volatile uint32_t g_pressure_epoch;
volatile uint32_t g_pressure_attempts,g_pressure_bus_ok,g_pressure_status;
volatile uint32_t g_pressure_started_ms,g_pressure_finished_ms;
volatile uint8_t g_pressure_raw[5];
static void service_pressure(void){
 uint8_t raw[5]={0};
 ++g_pressure_epoch;g_pressure_bus_ok=0;
 g_pressure_decoded=(xp_result){0};g_pressure_decode_status=XP_TRANSPORT;
 g_pressure_started_ms=g_millis;++g_pressure_attempts;
 bool ok=display_bus_read_register(0x58,0x04,raw,sizeof raw);
 g_pressure_finished_ms=g_millis;
 g_pressure_status=(uint32_t)display_bus_last_status();
 g_pressure_bus_ok=ok;
 for(unsigned i=0;i<5;i++)g_pressure_raw[i]=ok?raw[i]:0;
 xp_result decoded;
 g_pressure_decode_status=xp_decode(&pressure_profile,raw,sizeof raw,ok,&decoded);
 g_pressure_decoded=decoded;
 ++g_pressure_epoch;
}
volatile uint32_t g_display_packets,g_display_frames,g_display_fault;
static void service_display(uint32_t now_ms)
{
 if(!g_display_ready||g_display_failed)return;
 if(g_display.phase==0){
  if((int32_t)(now_ms-next_frame_ms)<0)return;
  vd_view view;
  const vd_input input={.level=g_panel.output.level,
   .fan_enabled=g_fan_manager.fan_power_enabled,
   .fault=g_fan_manager.fault_latched,
   .qualified=false,.measurement_valid=false,.now_ms=now_ms};
  vd_render(&input,g_display_frame,&view);
  if(!vd_begin(&g_display,0x3c,g_display_frame)){g_display_failed=true;return;}
  next_frame_ms=now_ms+500u;
 }
 ++g_display_packets;
 if(!vd_service(&g_display,display_bus_write,0)){
  if(g_display.failed){g_display_failed=true;g_display_fault=(uint32_t)display_bus_last_status();}
  else ++g_display_frames;
 }
}
/* Debugger record only; not a product display or motor authorization. */
typedef struct {
 uint32_t epoch,samples,now_ms,raw_adc,power_pressed,keys_valid,key,level,
          run_requested,learning_request,output_locked;
} panel_debug;
volatile panel_debug g_vacuum_panel_debug;
_Static_assert(sizeof(panel_debug)==44u,"panel debug ABI");
static void service_panel(uint32_t now_ms)
{
 uint16_t raw=0u;bool power=false;
 bool valid=vacuum_board_read_panel(&raw,&power);
 vpr_output out=vpr_step(&g_panel,raw,valid,power,g_fan_manager.fault_latched||g_display_failed,
                         false,false,now_ms);
 ++g_vacuum_panel_debug.epoch;
 ++g_vacuum_panel_debug.samples;
 g_vacuum_panel_debug.now_ms=now_ms;g_vacuum_panel_debug.raw_adc=raw;
 g_vacuum_panel_debug.power_pressed=power;g_vacuum_panel_debug.keys_valid=out.keys_valid;
 g_vacuum_panel_debug.key=out.key;g_vacuum_panel_debug.level=out.ui.level;
 g_vacuum_panel_debug.run_requested=out.ui.run_requested;
 g_vacuum_panel_debug.learning_request=out.ui.learning_request;
 g_vacuum_panel_debug.output_locked=1u;
 ++g_vacuum_panel_debug.epoch;
 if(!valid)vacuum_board_force_fan_disabled();
}
static const vacuum_fan_io_t g_fan_io = {
    .context = 0,
    .set_fan_power = vacuum_board_set_fan_power,
    .set_control_duty_permille = vacuum_board_set_fan_duty,
};

void SysTick_Handler(void)
{
    ++g_millis;
}

void EXTI0_1_IRQHandler(void)
{
    if ((EXTI->RPR1 & EXTI_RPR1_RPIF1) != 0u) {
        EXTI->RPR1 = EXTI_RPR1_RPIF1;
        vacuum_board_note_tach_edge();
    }
}

static bool init_timebase(void)
{
    SystemCoreClockUpdate();
    g_millis = 0u;
    return SysTick_Config(SystemCoreClock / 1000u) == 0u;
}

static vacuum_fan_status_t service_fan_policy(const vacuum_board_sample_t *sample)
{
    const vacuum_fan_input_t input = {
        .sample_age_ms = 0u,
        .now_ms = sample->sampled_at_ms,
        .vbus_mv = sample->estimated_vbus_mv,
        .fan_current_ma = sample->estimated_current_ma,
        .tach_edges_100ms = sample->tach_edges_100ms,
        .control_duty_permille = 0u,
        /* Nominal ADC conversions are diagnostic only until bench calibration. */
        .measurement_valid = false,
        .fan_profile_valid = false,
        /* Debounced three-key UI request; physical fan profile remains unqualified. */
        .run_requested = g_panel.output.run_requested,
    };
    /* No signed-off electrical or tach profile exists yet; zero values fail closed. */
    const vacuum_fan_limits_t limits = {0u, 0u, 0u, 0u, 0u, 0u};

    return vacuum_fan_manager_step(&g_fan_manager, &input, &limits, &g_fan_io);
}

int main(void)
{
    vacuum_board_sample_t latest = {0};
    uint32_t next_sample_ms = 0u;
    uint32_t next_panel_ms = 0u;
    uint32_t next_display_ms = 0u;
    uint32_t next_pressure_ms = 250u;

    vacuum_board_force_off();
    if (!init_timebase() || !vacuum_board_init()) {
        vacuum_board_force_off();
        for (;;) {
            __WFI();
        }
    }

    vacuum_fan_manager_init(&g_fan_manager, &g_fan_io);
    vpr_init(&g_panel);
    g_display_ready=display_bus_init();g_display_failed=!g_display_ready;
    if(!g_display_ready)g_display_fault=0x100u;
    /* Candidate100ms power-up settle; module reset sequence needs bench verification. */
    next_frame_ms=g_millis+100u;next_display_ms=g_millis;
    next_panel_ms = g_millis + 10u;
    next_sample_ms = g_millis + 100u;

    for (;;) {
        const uint32_t now_ms = g_millis;

        if ((int32_t)(now_ms - next_panel_ms) >= 0) {
            next_panel_ms = now_ms + 10u;
            service_panel(now_ms);
        }

        if ((int32_t)(now_ms - next_sample_ms) >= 0) {
            next_sample_ms = now_ms + 100u;
            if (!vacuum_board_sample(&latest, now_ms)) {
                vacuum_board_force_off();
                for (;;) {
                    __WFI();
                }
            }
            latest.fan_status = (uint8_t)service_fan_policy(&latest);
            latest.fan_power_enabled = g_fan_manager.fan_power_enabled;
            latest.fan_power_command_confirmed = g_fan_manager.fan_power_command_confirmed;
            latest.fan_fault_latched = g_fan_manager.fault_latched;
            vacuum_board_publish_diagnostics(&latest);
        }

        if ((int32_t)(g_millis - next_display_ms) >= 0) {
            next_display_ms = g_millis + 5u;
            service_display(g_millis);
        }

        if(g_display_ready&&(int32_t)(g_millis-next_pressure_ms)>=0){
            next_pressure_ms=g_millis+250u;
            service_pressure();
        }

        /* Independent target-side hard lock also rejects any nonzero request. */
        vacuum_board_force_fan_disabled();
        __WFI();
    }
}
