#include "pxl8_sfx.h"

#include <stdlib.h>
#include <string.h>

#include "pxl8_hal.h"
#include "pxl8_log.h"
#include "pxl8_math.h"

#define VOICE_SCALE 0.15f

static inline f32 sanitize_audio(f32 x) {
    union { f32 f; u32 u; } conv = { .f = x };
    return ((conv.u & 0x7F800000) == 0x7F800000) ? 0.0f : x;
}

typedef enum envelope_state {
    ENV_ATTACK = 0,
    ENV_DECAY,
    ENV_IDLE,
    ENV_RELEASE,
    ENV_SUSTAIN
} envelope_state;

typedef struct envelope {
    pxl8_sfx_adsr adsr;
    envelope_state state;
    f32 level;
    f32 release_level;
    f32 time;
} envelope;

typedef struct oscillator {
    pxl8_sfx_waveform waveform;
    f32 frequency;
    f32 phase;
    f32 pulse_width;
    u32 noise_state;
} oscillator;

typedef struct svf {
    pxl8_sfx_filter_type type;
    f32 cutoff;
    f32 resonance;
    f32 low;
    f32 band;
    f32 high;
} svf;

typedef struct lfo {
    pxl8_sfx_waveform waveform;
    pxl8_sfx_lfo_target target;
    f32 rate;
    f32 depth;
    f32 phase;
} lfo;

typedef struct voice {
    bool active;
    u16 id;
    u8 note;
    f32 volume;
    f32 pan;
    f32 base_frequency;
    f32 base_cutoff;
    f32 filter_env_depth;
    f32 fx_send;
    f32 start_time;
    f32 duration;
    bool released;
    oscillator osc;
    envelope amp_env;
    envelope filter_env;
    svf filter;
    lfo mod_lfo;
    bool has_lfo;
} voice;

typedef struct delay_state {
    f32* buffer_l;
    f32* buffer_r;
    u32 delay_samples_l;
    u32 delay_samples_r;
    f32 feedback;
    f32 mix;
    u32 write_pos;
} delay_state;

typedef struct comb_filter {
    f32* buffer;
    u32 size;
    f32 feedback;
    f32 damping;
    f32 filter_state;
    u32 index;
} comb_filter;

typedef struct allpass_filter {
    f32* buffer;
    u32 size;
    f32 feedback;
    u32 index;
} allpass_filter;

typedef struct reverb_state {
    comb_filter combs[4];
    allpass_filter allpasses[2];
    f32 damping;
    f32 mix;
    f32 room;
} reverb_state;

typedef struct compressor_state {
    f32 threshold;
    f32 ratio;
    f32 attack_coeff;
    f32 release_coeff;
    f32 envelope;
    f32 knee_width;
    f32 makeup_gain;
} compressor_state;

struct pxl8_sfx_node {
    pxl8_sfx_node_type type;
    void* state;
    void (*process)(void* state, f32* left, f32* right);
    void (*destroy)(void* state);
    struct pxl8_sfx_node* next;
};

struct pxl8_sfx_context {
    voice voices[PXL8_SFX_MAX_VOICES];
    pxl8_sfx_node* fx_head;
    pxl8_sfx_mixer* mixer;
    f32 volume;
    f32 current_time;
    u16 next_voice_id;
    u8 context_id;
};

struct pxl8_sfx_mixer {
    const pxl8_hal* hal;
    void* audio_handle;
    pxl8_sfx_context* contexts[PXL8_SFX_MAX_CONTEXTS];
    f32 master_volume;
    f32* output_buffer;
    pxl8_sfx_event_callback event_callback;
    void* event_userdata;
};

static void envelope_trigger(envelope* env) {
    env->state = ENV_ATTACK;
    env->time = 0.0f;
    env->level = 0.0f;
}

static void envelope_release(envelope* env) {
    if (env->state != ENV_IDLE && env->state != ENV_RELEASE) {
        env->release_level = env->level;
        env->state = ENV_RELEASE;
        env->time = 0.0f;
    }
}

static f32 envelope_process(envelope* env) {
    const f32 dt = 1.0f / PXL8_SFX_SAMPLE_RATE;

    switch (env->state) {
        case ENV_IDLE:
            return 0.0f;

        case ENV_ATTACK: {
            if (env->adsr.attack <= 0.0f) {
                env->level = 1.0f;
                env->state = ENV_DECAY;
                env->time = 0.0f;
            } else {
                f32 t = env->time / env->adsr.attack;
                if (t >= 1.0f) {
                    env->level = 1.0f;
                    env->state = ENV_DECAY;
                    env->time = 0.0f;
                } else {
                    env->level = t;
                }
            }
            break;
        }

        case ENV_DECAY: {
            if (env->adsr.decay <= 0.0f) {
                env->level = env->adsr.sustain;
                env->state = ENV_SUSTAIN;
            } else {
                f32 t = env->time / env->adsr.decay;
                if (t >= 1.0f) {
                    env->level = env->adsr.sustain;
                    env->state = ENV_SUSTAIN;
                } else {
                    env->level = 1.0f - t * (1.0f - env->adsr.sustain);
                }
            }
            break;
        }

        case ENV_SUSTAIN:
            env->level = env->adsr.sustain;
            if (env->adsr.sustain < 0.001f) {
                env->state = ENV_IDLE;
            }
            break;

        case ENV_RELEASE: {
            if (env->adsr.release <= 0.0f) {
                env->level = 0.0f;
                env->state = ENV_IDLE;
            } else {
                f32 t = env->time / env->adsr.release;
                if (t >= 1.0f) {
                    env->level = 0.0f;
                    env->state = ENV_IDLE;
                } else {
                    env->level = env->release_level * (1.0f - t);
                }
            }
            break;
        }
    }

    env->time += dt;
    return env->level;
}

static void oscillator_init(oscillator* osc, pxl8_sfx_waveform waveform, f32 frequency) {
    osc->waveform = waveform;
    osc->frequency = frequency;
    osc->phase = 0.0f;
    osc->pulse_width = 0.5f;
    osc->noise_state = 0xDEADBEEF;
}

static f32 oscillator_process(oscillator* osc) {
    f32 sample = 0.0f;

    switch (osc->waveform) {
        case PXL8_SFX_WAVE_SINE:
            sample = sinf(osc->phase * PXL8_TAU);
            break;

        case PXL8_SFX_WAVE_SQUARE:
            sample = osc->phase < 0.5f ? 1.0f : -1.0f;
            break;

        case PXL8_SFX_WAVE_SAW:
            sample = 2.0f * osc->phase - 1.0f;
            break;

        case PXL8_SFX_WAVE_TRIANGLE:
            if (osc->phase < 0.25f) {
                sample = 4.0f * osc->phase;
            } else if (osc->phase < 0.75f) {
                sample = 2.0f - 4.0f * osc->phase;
            } else {
                sample = 4.0f * osc->phase - 4.0f;
            }
            break;

        case PXL8_SFX_WAVE_PULSE:
            sample = osc->phase < osc->pulse_width ? 1.0f : -1.0f;
            break;

        case PXL8_SFX_WAVE_NOISE:
            osc->noise_state = osc->noise_state * 1103515245 + 12345;
            sample = (f32)((osc->noise_state >> 16) & 0x7FFF) / 16384.0f - 1.0f;
            break;
    }

    osc->phase += osc->frequency / PXL8_SFX_SAMPLE_RATE;
    while (osc->phase >= 1.0f) osc->phase -= 1.0f;

    return sample;
}

static void svf_init(svf* f, pxl8_sfx_filter_type type, f32 cutoff, f32 resonance) {
    f->type = type;
    f->cutoff = fmaxf(20.0f, fminf(14000.0f, cutoff));
    f->resonance = fmaxf(0.0f, fminf(0.99f, resonance));
    f->low = 0.0f;
    f->band = 0.0f;
    f->high = 0.0f;
}

static f32 svf_process(svf* f, f32 input) {
    if (f->type == PXL8_SFX_FILTER_NONE) return input;

    f32 freq = 2.0f * sinf(PXL8_PI * f->cutoff / PXL8_SFX_SAMPLE_RATE);
    f32 q = 1.0f - f->resonance;

    f->low += freq * f->band;
    f->high = input - f->low - q * f->band;
    f->band += freq * f->high;

    switch (f->type) {
        case PXL8_SFX_FILTER_LOWPASS:  return f->low;
        case PXL8_SFX_FILTER_HIGHPASS: return f->high;
        case PXL8_SFX_FILTER_BANDPASS: return f->band;
        default: return input;
    }
}

static void lfo_init(lfo* l, pxl8_sfx_waveform waveform, f32 rate, f32 depth, pxl8_sfx_lfo_target target) {
    l->waveform = waveform;
    l->rate = rate;
    l->depth = depth;
    l->target = target;
    l->phase = 0.0f;
}

static f32 lfo_process(lfo* l) {
    f32 sample = 0.0f;

    switch (l->waveform) {
        case PXL8_SFX_WAVE_SINE:
            sample = sinf(l->phase * PXL8_TAU);
            break;
        case PXL8_SFX_WAVE_SQUARE:
            sample = l->phase < 0.5f ? 1.0f : -1.0f;
            break;
        case PXL8_SFX_WAVE_SAW:
            sample = 2.0f * l->phase - 1.0f;
            break;
        case PXL8_SFX_WAVE_TRIANGLE:
            if (l->phase < 0.25f) sample = 4.0f * l->phase;
            else if (l->phase < 0.75f) sample = 2.0f - 4.0f * l->phase;
            else sample = 4.0f * l->phase - 4.0f;
            break;
        default:
            sample = sinf(l->phase * PXL8_TAU);
            break;
    }

    l->phase += l->rate / PXL8_SFX_SAMPLE_RATE;
    while (l->phase >= 1.0f) l->phase -= 1.0f;

    return sample * l->depth;
}

static void voice_trigger(voice* v, u8 note, const pxl8_sfx_voice_params* params, f32 volume, u16 id, f32 time, f32 duration) {
    v->active = true;
    v->id = id;
    v->note = note;
    v->volume = volume;
    v->pan = 0.0f;
    v->base_frequency = pxl8_sfx_note_to_freq(note);
    v->base_cutoff = params->filter_cutoff;
    v->filter_env_depth = params->filter_env_depth;
    v->fx_send = params->fx_send;
    v->start_time = time;
    v->duration = duration;
    v->released = false;

    oscillator_init(&v->osc, params->waveform, v->base_frequency);
    v->osc.pulse_width = params->pulse_width;

    v->amp_env.adsr = params->amp_env;
    envelope_trigger(&v->amp_env);

    v->filter_env.adsr = params->filter_env;
    envelope_trigger(&v->filter_env);

    svf_init(&v->filter, params->filter_type, params->filter_cutoff, params->filter_resonance);

    if (params->lfo_depth > 0.0f && params->lfo_rate > 0.0f) {
        lfo_init(&v->mod_lfo, params->lfo_waveform, params->lfo_rate, params->lfo_depth, params->lfo_target);
        v->has_lfo = true;
    } else {
        v->has_lfo = false;
    }
}

static void voice_release(voice* v) {
    envelope_release(&v->amp_env);
    envelope_release(&v->filter_env);
}

static void voice_process(voice* v, f32* dry_l, f32* dry_r, f32* wet_l, f32* wet_r) {
    if (!v->active) {
        *dry_l = 0.0f;
        *dry_r = 0.0f;
        *wet_l = 0.0f;
        *wet_r = 0.0f;
        return;
    }

    f32 lfo_value = v->has_lfo ? lfo_process(&v->mod_lfo) : 0.0f;
    f32 freq = v->base_frequency;
    f32 cutoff = v->base_cutoff;
    f32 amp_mod = 1.0f;

    if (v->has_lfo) {
        switch (v->mod_lfo.target) {
            case PXL8_SFX_LFO_PITCH:
                freq *= (1.0f + lfo_value);
                break;
            case PXL8_SFX_LFO_FILTER:
                cutoff += lfo_value * 1000.0f;
                break;
            case PXL8_SFX_LFO_AMPLITUDE:
                amp_mod = 1.0f + lfo_value;
                break;
        }
    }

    f32 filter_env_value = envelope_process(&v->filter_env);
    cutoff += filter_env_value * v->filter_env_depth;
    v->filter.cutoff = fmaxf(20.0f, fminf(14000.0f, cutoff));

    v->osc.frequency = freq;
    f32 sample = oscillator_process(&v->osc);
    sample = svf_process(&v->filter, sample);

    f32 amp_env_value = envelope_process(&v->amp_env);
    sample *= amp_env_value * v->volume * amp_mod * VOICE_SCALE;

    if (v->amp_env.state == ENV_IDLE) {
        v->active = false;
    }

    f32 left_gain = v->pan < 0.0f ? 1.0f : 1.0f - v->pan;
    f32 right_gain = v->pan > 0.0f ? 1.0f : 1.0f + v->pan;

    f32 left = sample * left_gain;
    f32 right = sample * right_gain;

    f32 dry_amt = 1.0f - v->fx_send;
    f32 wet_amt = v->fx_send;

    *dry_l = left * dry_amt;
    *dry_r = right * dry_amt;
    *wet_l = left * wet_amt;
    *wet_r = right * wet_amt;
}

static void delay_process_stereo(void* state, f32* left, f32* right) {
    delay_state* d = (delay_state*)state;
    if (!d->buffer_l || !d->buffer_r) return;

    u32 read_pos_l = (d->write_pos >= d->delay_samples_l)
        ? d->write_pos - d->delay_samples_l
        : PXL8_SFX_MAX_DELAY_SAMPLES - (d->delay_samples_l - d->write_pos);
    u32 read_pos_r = (d->write_pos >= d->delay_samples_r)
        ? d->write_pos - d->delay_samples_r
        : PXL8_SFX_MAX_DELAY_SAMPLES - (d->delay_samples_r - d->write_pos);

    f32 delayed_l = d->buffer_l[read_pos_l];
    f32 delayed_r = d->buffer_r[read_pos_r];

    d->buffer_l[d->write_pos] = *left + delayed_l * d->feedback;
    d->buffer_r[d->write_pos] = *right + delayed_r * d->feedback;
    d->write_pos = (d->write_pos + 1) % PXL8_SFX_MAX_DELAY_SAMPLES;

    *left = *left * (1.0f - d->mix) + delayed_l * d->mix;
    *right = *right * (1.0f - d->mix) + delayed_r * d->mix;
}

static void delay_destroy_state(void* state) {
    delay_state* d = (delay_state*)state;
    if (d) {
        free(d->buffer_l);
        free(d->buffer_r);
        free(d);
    }
}

static void comb_init(comb_filter* c, u32 size, f32 feedback, f32 damping) {
    c->buffer = (f32*)calloc(size, sizeof(f32));
    c->size = size;
    c->feedback = feedback;
    c->damping = damping;
    c->filter_state = 0.0f;
    c->index = 0;
}

static void comb_destroy(comb_filter* c) {
    free(c->buffer);
}

static f32 comb_process(comb_filter* c, f32 input) {
    if (!c->buffer) return input;

    f32 output = c->buffer[c->index];
    c->filter_state = output * (1.0f - c->damping) + c->filter_state * c->damping;
    c->buffer[c->index] = input + c->filter_state * c->feedback;
    c->index = (c->index + 1) % c->size;

    return output;
}

static void allpass_init(allpass_filter* a, u32 size) {
    a->buffer = (f32*)calloc(size, sizeof(f32));
    a->size = size;
    a->feedback = 0.5f;
    a->index = 0;
}

static void allpass_destroy(allpass_filter* a) {
    free(a->buffer);
}

static f32 allpass_process(allpass_filter* a, f32 input) {
    if (!a->buffer) return input;

    f32 delayed = a->buffer[a->index];
    f32 output = -input + delayed;
    a->buffer[a->index] = input + delayed * a->feedback;
    a->index = (a->index + 1) % a->size;

    return output;
}

static void reverb_rebuild(reverb_state* r) {
    u32 comb_sizes[] = {1116, 1188, 1277, 1356};
    u32 allpass_sizes[] = {556, 441};

    for (int i = 0; i < 4; i++) {
        comb_destroy(&r->combs[i]);
        u32 size = (u32)(comb_sizes[i] * r->room);
        comb_init(&r->combs[i], size, 0.84f, r->damping);
    }

    for (int i = 0; i < 2; i++) {
        allpass_destroy(&r->allpasses[i]);
        u32 size = (u32)(allpass_sizes[i] * r->room);
        allpass_init(&r->allpasses[i], size);
    }
}

static void reverb_process_stereo(void* state, f32* left, f32* right) {
    reverb_state* r = (reverb_state*)state;

    f32 input = (*left + *right) * 0.5f;
    f32 comb_sum = 0.0f;

    for (int i = 0; i < 4; i++) {
        comb_sum += comb_process(&r->combs[i], input);
    }
    comb_sum *= 0.25f;

    f32 output = comb_sum;
    for (int i = 0; i < 2; i++) {
        output = allpass_process(&r->allpasses[i], output);
    }

    *left = *left * (1.0f - r->mix) + output * r->mix;
    *right = *right * (1.0f - r->mix) + output * r->mix;
}

static void reverb_destroy_state(void* state) {
    reverb_state* r = (reverb_state*)state;
    if (r) {
        for (int i = 0; i < 4; i++) comb_destroy(&r->combs[i]);
        for (int i = 0; i < 2; i++) allpass_destroy(&r->allpasses[i]);
        free(r);
    }
}

static void compressor_recalc(compressor_state* c, f32 threshold_db, f32 ratio, f32 attack_ms, f32 release_ms) {
    c->threshold = powf(10.0f, threshold_db / 20.0f);
    c->ratio = ratio;
    c->attack_coeff = expf(-1.0f / (attack_ms * 0.001f * PXL8_SFX_SAMPLE_RATE));
    c->release_coeff = expf(-1.0f / (release_ms * 0.001f * PXL8_SFX_SAMPLE_RATE));
    f32 makeup_db = (fabsf(threshold_db) / ratio) * 0.5f;
    c->makeup_gain = powf(10.0f, makeup_db / 20.0f);
}

static void compressor_process_stereo(void* state, f32* left, f32* right) {
    compressor_state* c = (compressor_state*)state;

    f32 peak = fmaxf(fabsf(*left), fabsf(*right));
    f32 coeff = (peak > c->envelope) ? c->attack_coeff : c->release_coeff;
    c->envelope = coeff * c->envelope + (1.0f - coeff) * peak;

    if (c->envelope < 0.0001f) return;

    f32 input_db = 20.0f * log10f(c->envelope);
    f32 threshold_db = 20.0f * log10f(c->threshold);
    f32 knee_min = threshold_db - c->knee_width * 0.5f;
    f32 knee_max = threshold_db + c->knee_width * 0.5f;

    f32 output_db;
    if (input_db < knee_min) {
        output_db = input_db;
    } else if (input_db > knee_max) {
        output_db = threshold_db + (input_db - threshold_db) / c->ratio;
    } else {
        f32 t = (input_db - knee_min) / c->knee_width;
        f32 soft_ratio = 1.0f + (c->ratio - 1.0f) * t * t;
        output_db = knee_min + (input_db - knee_min) / soft_ratio;
    }

    f32 gain_db = output_db - input_db;
    f32 gain = powf(10.0f, gain_db / 20.0f) * c->makeup_gain;

    *left *= gain;
    *right *= gain;
}

static void compressor_destroy_state(void* state) {
    free(state);
}

static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* out_right) {
    const f32 dt = 1.0f / PXL8_SFX_SAMPLE_RATE;

    f32 dry_left = 0.0f, dry_right = 0.0f;
    f32 wet_left = 0.0f, wet_right = 0.0f;

    for (int v = 0; v < PXL8_SFX_MAX_VOICES; v++) {
        voice* vp = &ctx->voices[v];
        if (!vp->active) continue;

        if (vp->duration > 0.0f && !vp->released &&
            ctx->current_time - vp->start_time >= vp->duration) {
            voice_release(vp);
            vp->released = true;
        }

        f32 dl, dr, wl, wr;
        voice_process(vp, &dl, &dr, &wl, &wr);

        dry_left += dl;
        dry_right += dr;
        wet_left += wl;
        wet_right += wr;
    }

    pxl8_sfx_node* node = ctx->fx_head;
    while (node) {
        if (node->process && node->state) {
            node->process(node->state, &wet_left, &wet_right);
        }
        node = node->next;
    }

    f32 left = (dry_left + wet_left) * ctx->volume;
    f32 right = (dry_right + wet_right) * ctx->volume;

    ctx->current_time += dt;

    *out_left = left;
    *out_right = right;
}

pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
    if (!hal || !hal->audio_create) return NULL;

    pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)calloc(1, sizeof(pxl8_sfx_mixer));
    if (!mixer) return NULL;

    mixer->output_buffer = (f32*)calloc(PXL8_SFX_BUFFER_SIZE * 2, sizeof(f32));
    if (!mixer->output_buffer) {
        free(mixer);
        return NULL;
    }

    mixer->hal = hal;
    mixer->master_volume = 0.8f;

    mixer->audio_handle = hal->audio_create(PXL8_SFX_SAMPLE_RATE, 2);
    if (!mixer->audio_handle) {
        free(mixer->output_buffer);
        free(mixer);
        return NULL;
    }

    hal->audio_start(mixer->audio_handle);
    pxl8_info("Audio mixer initialized: %d Hz, stereo, %d context slots", PXL8_SFX_SAMPLE_RATE, PXL8_SFX_MAX_CONTEXTS);

    return mixer;
}

void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer) {
    if (!mixer) return;

    if (mixer->hal && mixer->audio_handle) {
        mixer->hal->audio_destroy(mixer->audio_handle);
    }

    free(mixer->output_buffer);
    free(mixer);
}

void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
    if (!mixer || !mixer->hal || !mixer->audio_handle || !mixer->output_buffer) return;

    i32 queued = mixer->hal->audio_queued(mixer->audio_handle);
    i32 target = PXL8_SFX_SAMPLE_RATE / 10;

    while (queued < target) {
        i32 samples_to_generate = PXL8_SFX_BUFFER_SIZE;
        if (samples_to_generate > target - queued) {
            samples_to_generate = target - queued;
        }

        for (i32 i = 0; i < samples_to_generate; i++) {
            f32 left = 0.0f, right = 0.0f;

            for (int c = 0; c < PXL8_SFX_MAX_CONTEXTS; c++) {
                pxl8_sfx_context* ctx = mixer->contexts[c];
                if (!ctx) continue;

                f32 ctx_left, ctx_right;
                context_process_sample(ctx, &ctx_left, &ctx_right);
                left += ctx_left;
                right += ctx_right;
            }

            left *= mixer->master_volume;
            right *= mixer->master_volume;

            left = fmaxf(-1.0f, fminf(1.0f, left));
            right = fmaxf(-1.0f, fminf(1.0f, right));

            left = sanitize_audio(left);
            right = sanitize_audio(right);

            mixer->output_buffer[i * 2] = left;
            mixer->output_buffer[i * 2 + 1] = right;
        }

        mixer->hal->upload_audio(mixer->audio_handle, mixer->output_buffer, samples_to_generate);
        queued += samples_to_generate;
    }
}

void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx) {
    if (!mixer || !ctx) return;

    for (int i = 0; i < PXL8_SFX_MAX_CONTEXTS; i++) {
        if (mixer->contexts[i] == ctx) return;
    }

    for (int i = 0; i < PXL8_SFX_MAX_CONTEXTS; i++) {
        if (!mixer->contexts[i]) {
            mixer->contexts[i] = ctx;
            ctx->mixer = mixer;
            ctx->context_id = (u8)i;
            return;
        }
    }

    pxl8_warn("No free context slots in mixer");
}

void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx) {
    if (!mixer || !ctx) return;

    for (int i = 0; i < PXL8_SFX_MAX_CONTEXTS; i++) {
        if (mixer->contexts[i] == ctx) {
            mixer->contexts[i] = NULL;
            return;
        }
    }
}

void pxl8_sfx_mixer_clear(pxl8_sfx_mixer* mixer) {
    if (!mixer) return;

    for (int i = 0; i < PXL8_SFX_MAX_CONTEXTS; i++) {
        if (mixer->contexts[i]) {
            pxl8_sfx_context_destroy(mixer->contexts[i]);
            mixer->contexts[i] = NULL;
        }
    }
}

void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume) {
    if (mixer) mixer->master_volume = fmaxf(0.0f, fminf(1.0f, volume));
}

void pxl8_sfx_mixer_set_event_callback(pxl8_sfx_mixer* mixer, pxl8_sfx_event_callback cb, void* userdata) {
    if (mixer) {
        mixer->event_callback = cb;
        mixer->event_userdata = userdata;
    }
}

f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer) {
    return mixer ? mixer->master_volume : 0.0f;
}

pxl8_sfx_context* pxl8_sfx_context_create(void) {
    pxl8_sfx_context* ctx = (pxl8_sfx_context*)calloc(1, sizeof(pxl8_sfx_context));
    if (!ctx) return NULL;

    ctx->volume = 1.0f;
    ctx->next_voice_id = 1;

    return ctx;
}

void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx) {
    if (!ctx) return;

    pxl8_sfx_node* node = ctx->fx_head;
    while (node) {
        pxl8_sfx_node* next = node->next;
        pxl8_sfx_node_destroy(node);
        node = next;
    }

    free(ctx);
}

void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume) {
    if (ctx) ctx->volume = fmaxf(0.0f, fminf(1.0f, volume));
}

f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx) {
    return ctx ? ctx->volume : 0.0f;
}

void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node) {
    if (!ctx || !node) return;

    node->next = NULL;

    if (!ctx->fx_head) {
        ctx->fx_head = node;
        return;
    }

    pxl8_sfx_node* tail = ctx->fx_head;
    while (tail->next) tail = tail->next;
    tail->next = node;
}

void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node) {
    if (!ctx || !node) return;

    if (!after) {
        node->next = ctx->fx_head;
        ctx->fx_head = node;
        return;
    }

    node->next = after->next;
    after->next = node;
}

void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node) {
    if (!ctx || !node || !ctx->fx_head) return;

    if (ctx->fx_head == node) {
        ctx->fx_head = node->next;
        node->next = NULL;
        return;
    }

    pxl8_sfx_node* prev = ctx->fx_head;
    while (prev->next && prev->next != node) prev = prev->next;

    if (prev->next == node) {
        prev->next = node->next;
        node->next = NULL;
    }
}

pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx) {
    return ctx ? ctx->fx_head : NULL;
}

void pxl8_sfx_node_destroy(pxl8_sfx_node* node) {
    if (!node) return;

    if (node->destroy && node->state) {
        node->destroy(node->state);
    }
    free(node);
}

pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg) {
    pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node));
    if (!node) return NULL;

    delay_state* d = (delay_state*)calloc(1, sizeof(delay_state));
    if (!d) {
        free(node);
        return NULL;
    }

    d->buffer_l = (f32*)calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
    d->buffer_r = (f32*)calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
    if (!d->buffer_l || !d->buffer_r) {
        free(d->buffer_l);
        free(d->buffer_r);
        free(d);
        free(node);
        return NULL;
    }

    d->delay_samples_l = (cfg.time_l * PXL8_SFX_SAMPLE_RATE) / 1000;
    d->delay_samples_r = (cfg.time_r * PXL8_SFX_SAMPLE_RATE) / 1000;
    if (d->delay_samples_l >= PXL8_SFX_MAX_DELAY_SAMPLES) d->delay_samples_l = PXL8_SFX_MAX_DELAY_SAMPLES - 1;
    if (d->delay_samples_r >= PXL8_SFX_MAX_DELAY_SAMPLES) d->delay_samples_r = PXL8_SFX_MAX_DELAY_SAMPLES - 1;
    d->feedback = fminf(0.95f, cfg.feedback);
    d->mix = fminf(1.0f, cfg.mix);
    d->write_pos = 0;

    node->type = PXL8_SFX_NODE_DELAY;
    node->state = d;
    node->process = delay_process_stereo;
    node->destroy = delay_destroy_state;
    node->next = NULL;

    return node;
}

void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r) {
    if (!node || node->type != PXL8_SFX_NODE_DELAY) return;
    delay_state* d = (delay_state*)node->state;
    d->delay_samples_l = (time_l * PXL8_SFX_SAMPLE_RATE) / 1000;
    d->delay_samples_r = (time_r * PXL8_SFX_SAMPLE_RATE) / 1000;
    if (d->delay_samples_l >= PXL8_SFX_MAX_DELAY_SAMPLES) d->delay_samples_l = PXL8_SFX_MAX_DELAY_SAMPLES - 1;
    if (d->delay_samples_r >= PXL8_SFX_MAX_DELAY_SAMPLES) d->delay_samples_r = PXL8_SFX_MAX_DELAY_SAMPLES - 1;
}

void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback) {
    if (!node || node->type != PXL8_SFX_NODE_DELAY) return;
    delay_state* d = (delay_state*)node->state;
    d->feedback = fminf(0.95f, feedback);
}

void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix) {
    if (!node || node->type != PXL8_SFX_NODE_DELAY) return;
    delay_state* d = (delay_state*)node->state;
    d->mix = fminf(1.0f, mix);
}

pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg) {
    pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node));
    if (!node) return NULL;

    reverb_state* r = (reverb_state*)calloc(1, sizeof(reverb_state));
    if (!r) {
        free(node);
        return NULL;
    }

    r->room = cfg.room;
    r->damping = cfg.damping;
    r->mix = cfg.mix;

    u32 comb_sizes[] = {1116, 1188, 1277, 1356};
    u32 allpass_sizes[] = {556, 441};

    for (int i = 0; i < 4; i++) {
        u32 size = (u32)(comb_sizes[i] * r->room);
        comb_init(&r->combs[i], size, 0.84f, r->damping);
    }

    for (int i = 0; i < 2; i++) {
        u32 size = (u32)(allpass_sizes[i] * r->room);
        allpass_init(&r->allpasses[i], size);
    }

    node->type = PXL8_SFX_NODE_REVERB;
    node->state = r;
    node->process = reverb_process_stereo;
    node->destroy = reverb_destroy_state;
    node->next = NULL;

    return node;
}

void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room) {
    if (!node || node->type != PXL8_SFX_NODE_REVERB) return;
    reverb_state* r = (reverb_state*)node->state;
    r->room = room;
    reverb_rebuild(r);
}

void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping) {
    if (!node || node->type != PXL8_SFX_NODE_REVERB) return;
    reverb_state* r = (reverb_state*)node->state;
    r->damping = damping;
    for (int i = 0; i < 4; i++) {
        r->combs[i].damping = damping;
    }
}

void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix) {
    if (!node || node->type != PXL8_SFX_NODE_REVERB) return;
    reverb_state* r = (reverb_state*)node->state;
    r->mix = mix;
}

pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg) {
    pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node));
    if (!node) return NULL;

    compressor_state* c = (compressor_state*)calloc(1, sizeof(compressor_state));
    if (!c) {
        free(node);
        return NULL;
    }

    c->envelope = 0.0f;
    c->knee_width = 6.0f;
    compressor_recalc(c, cfg.threshold, cfg.ratio, cfg.attack, cfg.release);

    node->type = PXL8_SFX_NODE_COMPRESSOR;
    node->state = c;
    node->process = compressor_process_stereo;
    node->destroy = compressor_destroy_state;
    node->next = NULL;

    return node;
}

void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold) {
    if (!node || node->type != PXL8_SFX_NODE_COMPRESSOR) return;
    compressor_state* c = (compressor_state*)node->state;
    f32 attack_ms = -1.0f / (logf(c->attack_coeff) * PXL8_SFX_SAMPLE_RATE * 0.001f);
    f32 release_ms = -1.0f / (logf(c->release_coeff) * PXL8_SFX_SAMPLE_RATE * 0.001f);
    compressor_recalc(c, threshold, c->ratio, attack_ms, release_ms);
}

void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio) {
    if (!node || node->type != PXL8_SFX_NODE_COMPRESSOR) return;
    compressor_state* c = (compressor_state*)node->state;
    f32 threshold_db = 20.0f * log10f(c->threshold);
    f32 attack_ms = -1.0f / (logf(c->attack_coeff) * PXL8_SFX_SAMPLE_RATE * 0.001f);
    f32 release_ms = -1.0f / (logf(c->release_coeff) * PXL8_SFX_SAMPLE_RATE * 0.001f);
    compressor_recalc(c, threshold_db, ratio, attack_ms, release_ms);
}

void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack) {
    if (!node || node->type != PXL8_SFX_NODE_COMPRESSOR) return;
    compressor_state* c = (compressor_state*)node->state;
    c->attack_coeff = expf(-1.0f / (attack * 0.001f * PXL8_SFX_SAMPLE_RATE));
}

void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release) {
    if (!node || node->type != PXL8_SFX_NODE_COMPRESSOR) return;
    compressor_state* c = (compressor_state*)node->state;
    c->release_coeff = expf(-1.0f / (release * 0.001f * PXL8_SFX_SAMPLE_RATE));
}

f32 pxl8_sfx_note_to_freq(u8 note) {
    return 440.0f * powf(2.0f, (note - 69) / 12.0f);
}

static i32 find_free_voice(pxl8_sfx_context* ctx) {
    for (int i = 0; i < PXL8_SFX_MAX_VOICES; i++) {
        if (!ctx->voices[i].active) return i;
    }

    i32 oldest_releasing = -1;
    f32 oldest_releasing_time = ctx->current_time;
    i32 oldest_active = -1;
    f32 oldest_active_time = ctx->current_time;

    for (int i = 0; i < PXL8_SFX_MAX_VOICES; i++) {
        if (ctx->voices[i].amp_env.state == ENV_RELEASE) {
            if (ctx->voices[i].start_time < oldest_releasing_time) {
                oldest_releasing_time = ctx->voices[i].start_time;
                oldest_releasing = i;
            }
        } else if (ctx->voices[i].start_time < oldest_active_time) {
            oldest_active_time = ctx->voices[i].start_time;
            oldest_active = i;
        }
    }

    if (oldest_releasing >= 0) return oldest_releasing;
    return oldest_active;
}

u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume, f32 duration) {
    if (!ctx || !params) return 0;

    i32 slot = find_free_voice(ctx);
    if (slot < 0) return 0;

    u16 id = ctx->next_voice_id++;
    if (ctx->next_voice_id == 0) ctx->next_voice_id = 1;

    voice_trigger(&ctx->voices[slot], note, params, volume, id, ctx->current_time, duration);

    if (ctx->mixer && ctx->mixer->event_callback) {
        ctx->mixer->event_callback(PXL8_SFX_EVENT_NOTE_ON, ctx->context_id, note, volume, ctx->mixer->event_userdata);
    }

    return id;
}

void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id) {
    if (!ctx || voice_id == 0) return;

    for (int i = 0; i < PXL8_SFX_MAX_VOICES; i++) {
        if (ctx->voices[i].active && ctx->voices[i].id == voice_id) {
            voice_release(&ctx->voices[i]);
            return;
        }
    }
}

void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id) {
    if (!ctx || voice_id == 0) return;

    for (int i = 0; i < PXL8_SFX_MAX_VOICES; i++) {
        if (ctx->voices[i].active && ctx->voices[i].id == voice_id) {
            ctx->voices[i].active = false;
            return;
        }
    }
}

void pxl8_sfx_stop_all(pxl8_sfx_context* ctx) {
    if (!ctx) return;

    for (int i = 0; i < PXL8_SFX_MAX_VOICES; i++) {
        ctx->voices[i].active = false;
    }
}
