/**
 * aviation_state_machine.c — State machine implementation.
 *
 * Each state's behavior is implemented as a set of static functions.
 * The public dispatch functions (aviation_state_play, etc.) switch on
 * the current state and delegate to the appropriate handler.
 *
 * This is the C equivalent of the "one class per state" pattern:
 * each state only handles transitions that make sense for it,
 * and invalid actions are no-ops (return aviation_state_noop()).
 */

#include "aviation_state_machine.h"
#include <string.h>

/* ─── Context helpers ─────────────────────────────────────────────── */

void aviation_context_init(AviationPlaybackContext *ctx, AviationEventBus *bus) {
    if (!ctx)
        return;
    memset(ctx, 0, sizeof(AviationPlaybackContext));
    ctx->event_bus = bus;
    ctx->rate = 1.0;
    ctx->volume = 1.0;
    ctx->item_source = AVIATION_SOURCE_NONE;
    ctx->queue_index = -1;
}

void aviation_context_emit_command(AviationPlaybackContext *ctx, const AviationCommandEvent *event) {
    if (!ctx || !event)
        return;
    if (ctx->command_emitter) {
        ctx->command_emitter(ctx->emitter_user_data, event);
        return;
    }
    if (ctx->event_bus) {
        aviation_event_bus_emit_command(ctx->event_bus, event);
    }
}

void aviation_context_emit_domain(AviationPlaybackContext *ctx, const AviationDomainEvent *event) {
    if (!ctx || !event)
        return;
    if (ctx->domain_emitter) {
        ctx->domain_emitter(ctx->emitter_user_data, event);
        return;
    }
    if (ctx->event_bus) {
        aviation_event_bus_emit_domain(ctx->event_bus, event);
    }
}

/* ─── Command event builders (internal helpers) ───────────────────── */

static AviationCommandEvent cmd_load(const AviationPlaybackContext *ctx) {
    AviationCommandEvent e;
    memset(&e, 0, sizeof(e));
    e.type = AVIATION_CMD_DECODER_LOAD;
    /* Copy URI and media type from the current item */
    memcpy(e.uri, ctx->current_item.uri, AVIATION_MAX_URI_LEN);
    e.mediaType = ctx->current_item.mediaType;
    e.nativeHandle = ctx->current_item.nativeHandle;
    return e;
}

static AviationCommandEvent cmd_play(void) {
    AviationCommandEvent e;
    memset(&e, 0, sizeof(e));
    e.type = AVIATION_CMD_DECODER_PLAY;
    return e;
}

static AviationCommandEvent cmd_pause(void) {
    AviationCommandEvent e;
    memset(&e, 0, sizeof(e));
    e.type = AVIATION_CMD_DECODER_PAUSE;
    return e;
}

static AviationCommandEvent cmd_seek(int64_t position_ms) {
    AviationCommandEvent e;
    memset(&e, 0, sizeof(e));
    e.type = AVIATION_CMD_DECODER_SEEK;
    e.positionMs = position_ms;
    return e;
}

static AviationCommandEvent cmd_stop(void) {
    AviationCommandEvent e;
    memset(&e, 0, sizeof(e));
    e.type = AVIATION_CMD_DECODER_STOP;
    return e;
}

/* ─── Domain event builders (internal helpers) ────────────────────── */

static AviationDomainEvent evt_playback_ended(void) {
    AviationDomainEvent e;
    memset(&e, 0, sizeof(e));
    e.type = AVIATION_EVT_PLAYBACK_ENDED;
    return e;
}

static AviationDomainEvent evt_error(const char *message, const char *code) {
    AviationDomainEvent e;
    memset(&e, 0, sizeof(e));
    e.type = AVIATION_EVT_ERROR_OCCURRED;
    if (message) {
        strncpy(e.errorMessage, message, AVIATION_MAX_ERROR_LEN - 1);
    }
    if (code) {
        strncpy(e.errorCode, code, AVIATION_MAX_ERROR_CODE_LEN - 1);
    }
    return e;
}

/* ═══════════════════════════════════════════════════════════════════
 * IDLE STATE
 *
 * No media loaded.
 *   play() -> Loading (if item exists)
 *   Everything else -> no-op
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult idle_play(AviationPlaybackContext *ctx) {
    if (!ctx->has_current_item) {
        return aviation_state_noop();
    }
    AviationCommandEvent e = cmd_load(ctx);
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_LOADING);
}

/* ═══════════════════════════════════════════════════════════════════
 * LOADING STATE
 *
 * Media is being loaded / prepared by the decoder.
 *   play()  -> no-op (auto-play tracked by coordinator)
 *   stop()  -> Stopped
 *   onDecoderEvent:
 *     READY     -> Ready
 *     BUFFERING -> stay (decoder still preparing)
 *     ERROR     -> Error
 *     ENDED     -> Stopped (zero-length content)
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult loading_stop(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_stop();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_STOPPED);
}

static AviationStateResult loading_on_decoder(const AviationDecoderEvent *event, AviationPlaybackContext *ctx) {
    if (event->type == AVIATION_DECODER_EVT_STATE_CHANGED) {
        switch (event->decoderState) {
        case AVIATION_DECODER_READY:
            return aviation_state_transition(AVIATION_STATE_READY);
        case AVIATION_DECODER_PLAYING:
            /* Decoder is already rendering (load raced an external play). */
            return aviation_state_transition(AVIATION_STATE_PLAYING);
        case AVIATION_DECODER_ERROR: {
            AviationDomainEvent de = evt_error("Decoder error during load", "LOAD_ERROR");
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_ERROR);
        }
        case AVIATION_DECODER_ENDED: {
            /* An item cannot end before it has been READY, so an ENDED
             * while LOADING is a stale dispatch from the SUPERSEDED item
             * (its end notification racing the skip that replaced it).
             * Emitting PLAYBACK_ENDED here makes the bridge auto-advance
             * past the item that was just loaded. Drop it; if the NEW item
             * truly ends, its own READY→…→ENDED sequence reports that. */
            return aviation_state_noop();
        }
        case AVIATION_DECODER_BUFFERING:
            return aviation_state_noop(); /* Stay in Loading */
        default:
            return aviation_state_noop();
        }
    }
    if (event->type == AVIATION_DECODER_EVT_ERROR) {
        AviationDomainEvent de = evt_error(event->errorMessage, event->errorCode);
        aviation_context_emit_domain(ctx, &de);
        return aviation_state_transition(AVIATION_STATE_ERROR);
    }
    return aviation_state_noop();
}

/* ═══════════════════════════════════════════════════════════════════
 * READY STATE
 *
 * Media loaded, ready to play.
 *   play()  -> Playing (emits DECODER_PLAY)
 *   seek()  -> stay Ready (emits DECODER_SEEK)
 *   stop()  -> Stopped
 *   onDecoderEvent: ERROR -> Error
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult ready_play(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_play();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_PLAYING);
}

static AviationStateResult ready_stop(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_stop();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_STOPPED);
}

static AviationStateResult ready_on_decoder(const AviationDecoderEvent *event, AviationPlaybackContext *ctx) {
    if (event->type == AVIATION_DECODER_EVT_STATE_CHANGED) {
        switch (event->decoderState) {
        case AVIATION_DECODER_PLAYING:
            /* External play (PiP / fullscreen transport / notification). No
               command emitted — the decoder is already playing. */
            return aviation_state_transition(AVIATION_STATE_PLAYING);
        case AVIATION_DECODER_ENDED: {
            AviationDomainEvent de = evt_playback_ended();
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_STOPPED);
        }
        case AVIATION_DECODER_ERROR: {
            AviationDomainEvent de = evt_error("Decoder error while ready", "PLAYBACK_ERROR");
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_ERROR);
        }
        default:
            return aviation_state_noop();
        }
    }
    if (event->type == AVIATION_DECODER_EVT_ERROR) {
        AviationDomainEvent de = evt_error(event->errorMessage, event->errorCode);
        aviation_context_emit_domain(ctx, &de);
        return aviation_state_transition(AVIATION_STATE_ERROR);
    }
    return aviation_state_noop();
}

/* ═══════════════════════════════════════════════════════════════════
 * PLAYING STATE
 *
 * Actively playing.
 *   play()  -> no-op
 *   pause() -> Paused (emits DECODER_PAUSE)
 *   seek()  -> stay Playing (emits DECODER_SEEK)
 *   stop()  -> Stopped
 *   onDecoderEvent:
 *     BUFFERING -> Buffering
 *     ENDED     -> Stopped (emits PLAYBACK_ENDED)
 *     ERROR     -> Error
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult playing_pause(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_pause();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_PAUSED);
}

static AviationStateResult playing_stop(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_stop();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_STOPPED);
}

static AviationStateResult playing_on_decoder(const AviationDecoderEvent *event, AviationPlaybackContext *ctx) {
    if (event->type == AVIATION_DECODER_EVT_STATE_CHANGED) {
        switch (event->decoderState) {
        case AVIATION_DECODER_BUFFERING:
            return aviation_state_transition(AVIATION_STATE_BUFFERING);
        case AVIATION_DECODER_PAUSED:
            /* External pause (PiP / fullscreen transport / notification /
               interruption). No command emitted — the decoder is already
               paused; the machine syncs to it. */
            return aviation_state_transition(AVIATION_STATE_PAUSED);
        case AVIATION_DECODER_ENDED: {
            AviationDomainEvent de = evt_playback_ended();
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_STOPPED);
        }
        case AVIATION_DECODER_ERROR: {
            AviationDomainEvent de = evt_error("Decoder error during playback", "PLAYBACK_ERROR");
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_ERROR);
        }
        default:
            return aviation_state_noop();
        }
    }
    if (event->type == AVIATION_DECODER_EVT_ERROR) {
        AviationDomainEvent de = evt_error(event->errorMessage, event->errorCode);
        aviation_context_emit_domain(ctx, &de);
        return aviation_state_transition(AVIATION_STATE_ERROR);
    }
    return aviation_state_noop();
}

/* ═══════════════════════════════════════════════════════════════════
 * PAUSED STATE
 *
 *   play()  -> Playing (emits DECODER_PLAY)
 *   pause() -> no-op
 *   seek()  -> stay Paused (emits DECODER_SEEK)
 *   stop()  -> Stopped
 *   onDecoderEvent: ERROR -> Error
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult paused_play(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_play();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_PLAYING);
}

static AviationStateResult paused_stop(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_stop();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_STOPPED);
}

static AviationStateResult paused_on_decoder(const AviationDecoderEvent *event, AviationPlaybackContext *ctx) {
    if (event->type == AVIATION_DECODER_EVT_STATE_CHANGED) {
        switch (event->decoderState) {
        case AVIATION_DECODER_PLAYING:
            /* External resume — no command emitted, decoder already playing. */
            return aviation_state_transition(AVIATION_STATE_PLAYING);
        case AVIATION_DECODER_ENDED: {
            /* Platforms can report the pause (rate -> 0) before the
               played-to-end signal; auto-advance must still fire. */
            AviationDomainEvent de = evt_playback_ended();
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_STOPPED);
        }
        case AVIATION_DECODER_ERROR: {
            AviationDomainEvent de = evt_error("Decoder error while paused", "PLAYBACK_ERROR");
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_ERROR);
        }
        default:
            return aviation_state_noop();
        }
    }
    if (event->type == AVIATION_DECODER_EVT_ERROR) {
        AviationDomainEvent de = evt_error(event->errorMessage, event->errorCode);
        aviation_context_emit_domain(ctx, &de);
        return aviation_state_transition(AVIATION_STATE_ERROR);
    }
    return aviation_state_noop();
}

/* ═══════════════════════════════════════════════════════════════════
 * BUFFERING STATE
 *
 * Was playing, now rebuffering.
 *   play()  -> no-op (will resume when buffer recovers)
 *   pause() -> Paused (emits DECODER_PAUSE)
 *   seek()  -> stay Buffering (emits DECODER_SEEK)
 *   stop()  -> Stopped
 *   onDecoderEvent:
 *     READY  -> Playing (buffer recovered, emits DECODER_PLAY)
 *     ENDED  -> Stopped
 *     ERROR  -> Error
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult buffering_pause(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_pause();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_PAUSED);
}

static AviationStateResult buffering_stop(AviationPlaybackContext *ctx) {
    AviationCommandEvent e = cmd_stop();
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_STOPPED);
}

static AviationStateResult buffering_on_decoder(const AviationDecoderEvent *event, AviationPlaybackContext *ctx) {
    if (event->type == AVIATION_DECODER_EVT_STATE_CHANGED) {
        switch (event->decoderState) {
        case AVIATION_DECODER_READY: {
            /* Buffer recovered — resume playback */
            AviationCommandEvent ce = cmd_play();
            aviation_context_emit_command(ctx, &ce);
            return aviation_state_transition(AVIATION_STATE_PLAYING);
        }
        case AVIATION_DECODER_PLAYING:
            /* Buffer recovered and decoder already playing — no command. */
            return aviation_state_transition(AVIATION_STATE_PLAYING);
        case AVIATION_DECODER_PAUSED:
            /* External pause while rebuffering. */
            return aviation_state_transition(AVIATION_STATE_PAUSED);
        case AVIATION_DECODER_ENDED: {
            AviationDomainEvent de = evt_playback_ended();
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_STOPPED);
        }
        case AVIATION_DECODER_ERROR: {
            AviationDomainEvent de = evt_error("Decoder error during buffering", "BUFFER_ERROR");
            aviation_context_emit_domain(ctx, &de);
            return aviation_state_transition(AVIATION_STATE_ERROR);
        }
        default:
            return aviation_state_noop();
        }
    }
    if (event->type == AVIATION_DECODER_EVT_ERROR) {
        AviationDomainEvent de = evt_error(event->errorMessage, event->errorCode);
        aviation_context_emit_domain(ctx, &de);
        return aviation_state_transition(AVIATION_STATE_ERROR);
    }
    return aviation_state_noop();
}

/* ═══════════════════════════════════════════════════════════════════
 * STOPPED STATE
 *
 *   play()  -> Loading (reload current item if any)
 *   Everything else -> no-op
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult stopped_play(AviationPlaybackContext *ctx) {
    if (!ctx->has_current_item) {
        return aviation_state_noop();
    }
    AviationCommandEvent e = cmd_load(ctx);
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_LOADING);
}

/* ═══════════════════════════════════════════════════════════════════
 * ERROR STATE
 *
 *   play()  -> Loading (retry with current item)
 *   Everything else -> no-op
 * ═══════════════════════════════════════════════════════════════════ */

static AviationStateResult error_play(AviationPlaybackContext *ctx) {
    if (!ctx->has_current_item) {
        return aviation_state_noop();
    }
    AviationCommandEvent e = cmd_load(ctx);
    aviation_context_emit_command(ctx, &e);
    return aviation_state_transition(AVIATION_STATE_LOADING);
}

/* ═══════════════════════════════════════════════════════════════════
 * PUBLIC DISPATCH FUNCTIONS
 *
 * These switch on the current state and delegate to the appropriate
 * static handler above.
 * ═══════════════════════════════════════════════════════════════════ */

AviationStateResult aviation_state_play(AviationPlaybackState state, AviationPlaybackContext *ctx) {
    switch (state) {
    case AVIATION_STATE_IDLE:
        return idle_play(ctx);
    case AVIATION_STATE_READY:
        return ready_play(ctx);
    case AVIATION_STATE_PAUSED:
        return paused_play(ctx);
    case AVIATION_STATE_STOPPED:
        return stopped_play(ctx);
    case AVIATION_STATE_ERROR:
        return error_play(ctx);
    case AVIATION_STATE_LOADING:
        return aviation_state_noop(); /* auto-play tracked by coordinator */
    case AVIATION_STATE_PLAYING:
        return aviation_state_noop(); /* already playing */
    case AVIATION_STATE_BUFFERING:
        return aviation_state_noop(); /* will resume when ready */
    default:
        return aviation_state_noop();
    }
}

AviationStateResult aviation_state_pause(AviationPlaybackState state, AviationPlaybackContext *ctx) {
    switch (state) {
    case AVIATION_STATE_PLAYING:
        return playing_pause(ctx);
    case AVIATION_STATE_BUFFERING:
        return buffering_pause(ctx);
    default:
        return aviation_state_noop();
    }
}

AviationStateResult aviation_state_seek(AviationPlaybackState state, int64_t position_ms,
                                        AviationPlaybackContext *ctx) {
    switch (state) {
    case AVIATION_STATE_READY:
    case AVIATION_STATE_PLAYING:
    case AVIATION_STATE_PAUSED:
    case AVIATION_STATE_BUFFERING: {
        AviationCommandEvent e = cmd_seek(position_ms);
        aviation_context_emit_command(ctx, &e);
        return aviation_state_noop(); /* Stay in current state */
    }
    default:
        return aviation_state_noop();
    }
}

AviationStateResult aviation_state_stop(AviationPlaybackState state, AviationPlaybackContext *ctx) {
    switch (state) {
    case AVIATION_STATE_LOADING:
        return loading_stop(ctx);
    case AVIATION_STATE_READY:
        return ready_stop(ctx);
    case AVIATION_STATE_PLAYING:
        return playing_stop(ctx);
    case AVIATION_STATE_PAUSED:
        return paused_stop(ctx);
    case AVIATION_STATE_BUFFERING:
        return buffering_stop(ctx);
    default:
        return aviation_state_noop();
    }
}

AviationStateResult aviation_state_on_decoder_event(AviationPlaybackState state, const AviationDecoderEvent *event,
                                                    AviationPlaybackContext *ctx) {
    switch (state) {
    case AVIATION_STATE_LOADING:
        return loading_on_decoder(event, ctx);
    case AVIATION_STATE_READY:
        return ready_on_decoder(event, ctx);
    case AVIATION_STATE_PLAYING:
        return playing_on_decoder(event, ctx);
    case AVIATION_STATE_PAUSED:
        return paused_on_decoder(event, ctx);
    case AVIATION_STATE_BUFFERING:
        return buffering_on_decoder(event, ctx);
    default:
        return aviation_state_noop();
    }
}
