/*
 * conversation.c — PLIP telephone conversation state machine.
 *
 * States: IDLE → DIALTONE → DIALING → RINGBACK | BUSY
 *         RINGBACK (after ~2 s) → GREET → CONNECTED
 *
 * Transitions:
 *   IDLE        + off-hook              → DIALTONE   (tones_dialtone_start)
 *   DIALTONE    + first digit           → DIALING    (tones_stop)
 *   DIALING     + ms_since_last > 3000  → RINGBACK or BUSY (route decision)
 *   RINGBACK    + 2 s elapsed           → GREET      (tones_stop, turn_client_greeting, play WAV)
 *   GREET       + play enqueued         → CONNECTED  (Stage 3: listen loop)
 *   any state   + on-hook              → IDLE        (tones_stop, audio_stop, PA off)
 *
 * Routing table (known numbers → ringback; unknown → busy after 3 s silence):
 *   "12", "3615", "15", "17", "18", "0142738200"
 */

#include "conversation.h"
#include "dialer.h"
#include "tones.h"
#include "audio.h"
#include "turn_client.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_timer.h"

#include <stdio.h>
#include <string.h>

#define TAG "conversation"

/* Duration of ringback before picking up and fetching the greeting */
#define RINGBACK_GREET_MS  2000

typedef enum {
    STATE_IDLE,
    STATE_DIALTONE,
    STATE_DIALING,
    STATE_RINGBACK,
    STATE_BUSY,
    STATE_GREET,       /* fetching + playing NPC greeting (Stage 2) */
    STATE_CONNECTED,   /* in-call — Stage 3 will add listen loop     */
} conv_state_t;

static volatile conv_state_t s_state    = STATE_IDLE;
static volatile bool         s_offhook  = false;
static volatile bool         s_hook_changed = false;

/* Ringback start timestamp (µs, from esp_timer_get_time) */
static int64_t s_ringback_start_us = 0;

/* Session ID for the current call (generated at ringback → greet transition) */
static char s_sid[32] = {0};

/* Known numbers: ringback when dialed */
static const char *KNOWN[] = {
    "12", "3615", "15", "17", "18", "0142738200", NULL
};

static bool is_known(const char *num)
{
    for (int i = 0; KNOWN[i] != NULL; i++) {
        if (strcmp(num, KNOWN[i]) == 0) return true;
    }
    return false;
}

static void go_idle(void)
{
    tones_stop();
    audio_stop();
    audio_pa_set(false);
    dialer_reset();
    s_state = STATE_IDLE;
    ESP_LOGI(TAG, "-> IDLE");
}

static void conv_task(void *arg)
{
    (void)arg;
    ESP_LOGI(TAG, "conversation task ready");

    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(50));

        /* Handle hook change events */
        if (s_hook_changed) {
            s_hook_changed = false;
            if (!s_offhook) {
                /* On-hook: always go idle regardless of current state */
                if (s_state != STATE_IDLE) {
                    ESP_LOGI(TAG, "on-hook -> IDLE");
                    go_idle();
                }
                continue;
            } else {
                /* Off-hook: start dial tone if we were idle */
                if (s_state == STATE_IDLE) {
                    dialer_reset();
                    tones_dialtone_start();
                    s_state = STATE_DIALTONE;
                    ESP_LOGI(TAG, "off-hook -> DIALTONE");
                }
                continue;
            }
        }

        /* State machine polling */
        switch (s_state) {
        case STATE_IDLE:
            break;  /* nothing to do */

        case STATE_DIALTONE:
            if (!s_offhook) {
                go_idle();
                break;
            }
            /* First digit received → stop dialtone, enter dialing */
            if (!dialer_idle()) {
                tones_stop();
                s_state = STATE_DIALING;
                ESP_LOGI(TAG, "first digit -> DIALING");
            }
            break;

        case STATE_DIALING:
            if (!s_offhook) {
                go_idle();
                break;
            }
            /* Wait for 3 s of silence after last digit, then route */
            if (dialer_ms_since_last() > 3000) {
                const char *num = dialer_current();
                if (is_known(num)) {
                    ESP_LOGI(TAG, "route %s -> known (ringback)", num);
                    tones_ringback_start();
                    s_ringback_start_us = esp_timer_get_time();
                    s_state = STATE_RINGBACK;
                } else {
                    ESP_LOGI(TAG, "route %s -> unknown (busy)", num);
                    tones_busy_start();
                    s_state = STATE_BUSY;
                }
            }
            break;

        case STATE_RINGBACK:
            if (!s_offhook) {
                go_idle();
                break;
            }
            {
                int64_t elapsed_ms =
                    (esp_timer_get_time() - s_ringback_start_us) / 1000;
                if (elapsed_ms >= RINGBACK_GREET_MS) {
                    /* Stop ringback tone synchronously before fetching */
                    tones_stop();
                    /* Generate a session ID from timer ticks */
                    snprintf(s_sid, sizeof(s_sid), "%lld",
                             (long long)esp_timer_get_time());
                    ESP_LOGI(TAG, "ringback done -> GREET (sid=%s num=%s)",
                             s_sid, dialer_current());
                    s_state = STATE_GREET;
                }
            }
            break;

        case STATE_GREET:
            if (!s_offhook) {
                go_idle();
                break;
            }
            /* Fetch greeting WAV from gateway and enqueue playback */
            if (turn_client_greeting(s_sid, dialer_current(),
                                     "/spiffs/turn.wav")) {
                audio_play_async("/spiffs/turn.wav");
            } else {
                ESP_LOGW(TAG, "turn_client_greeting failed — proceeding silent");
            }
            s_state = STATE_CONNECTED;
            ESP_LOGI(TAG, "-> CONNECTED");
            break;

        case STATE_CONNECTED:
            /* Stage 3 will add listen/speak loop here */
            if (!s_offhook) {
                go_idle();
            }
            break;

        case STATE_BUSY:
            if (!s_offhook) {
                go_idle();
            }
            break;
        }
    }
}

void conversation_init(void)
{
    s_state        = STATE_IDLE;
    s_offhook      = false;
    s_hook_changed = false;
    /* Stack bumped to 6144: STATE_GREET calls esp_http_client (blocking HTTP
     * + file I/O) which needs more stack than the baseline 3072. */
    xTaskCreate(conv_task, "conv", 6144, NULL, 4, NULL);
    ESP_LOGI(TAG, "conversation init");
}

void conversation_on_hook_change(bool offhook)
{
    s_offhook      = offhook;
    s_hook_changed = true;
}
