#!/usr/bin/env python3
"""Generate Petal's cute UI sound palette as small WAV assets.

These are warm, rounded little sounds — bubble pops, water drops, soft bells —
played when suggestions and companion bubbles appear. We synthesize them here
(rather than ship someone else's clips) so they match the app's gentle aesthetic
exactly and stay tiny. Output lands in web/src/assets/sounds/ where Vite bundles
and fingerprints them into dist (and so into the embedded Go binary).

Run:  python3 scripts/gen_sounds.py
"""
import math
import os
import struct
import wave

SR = 44100
OUT = os.path.join(os.path.dirname(__file__), "..", "web", "src", "assets", "sounds")


def env(n, attack, decay, total):
    """Smooth attack + exponential decay envelope over `total` seconds."""
    a = max(1, int(attack * SR))
    out = []
    for i in range(n):
        t = i / SR
        if i < a:
            amp = i / a
        else:
            amp = math.exp(-(t - attack) / decay)
        out.append(amp)
    return out


def sine(freq_at, n):
    """Sine with a per-sample frequency function freq_at(t)->Hz (phase-accurate)."""
    out = []
    phase = 0.0
    for i in range(n):
        t = i / SR
        f = freq_at(t)
        phase += 2 * math.pi * f / SR
        out.append(math.sin(phase))
    return out


def mix(*layers):
    n = max(len(l) for l in layers)
    out = [0.0] * n
    for l in layers:
        for i, v in enumerate(l):
            out[i] += v
    return out


def soft_clip(s):
    return [math.tanh(v * 1.2) for v in s]


def normalize(s, peak=0.9):
    m = max(1e-9, max(abs(v) for v in s))
    g = peak / m
    return [v * g for v in s]


def write_wav(name, samples):
    samples = soft_clip(samples)
    samples = normalize(samples, 0.85)
    # 4ms fade-out tail so nothing clicks at the end.
    tail = int(0.004 * SR)
    for i in range(tail):
        samples[-1 - i] *= i / tail
    os.makedirs(OUT, exist_ok=True)
    path = os.path.join(OUT, name)
    with wave.open(path, "w") as w:
        w.setnchannels(1)
        w.setsampwidth(2)
        w.setframerate(SR)
        frames = b"".join(struct.pack("<h", int(max(-1, min(1, v)) * 32767)) for v in samples)
        w.writeframes(frames)
    print(f"wrote {name}  ({len(samples)/SR*1000:.0f} ms, {len(frames)} bytes)")


def apply_env(sig, e):
    return [s * a for s, a in zip(sig, e)]


def bubble_pop():
    """A cute bubble 'bloop' — pitch swoops up fast then a rounded body."""
    dur = 0.16
    n = int(dur * SR)
    # Rising swoop is the signature of a bubble.
    body = sine(lambda t: 360 + 720 * (1 - math.exp(-t * 38)), n)
    body = apply_env(body, env(n, 0.004, 0.05, dur))
    # A soft octave shimmer on top.
    shimmer = sine(lambda t: 1080 + 400 * (1 - math.exp(-t * 38)), n)
    shimmer = apply_env(shimmer, env(n, 0.003, 0.03, dur))
    shimmer = [v * 0.25 for v in shimmer]
    return mix(body, shimmer)


def droplet():
    """A clean little water-drop ping — high, downward, watery tail."""
    dur = 0.22
    n = int(dur * SR)
    main = sine(lambda t: 1500 * math.exp(-t * 3.2) + 760, n)
    main = apply_env(main, env(n, 0.003, 0.06, dur))
    # Tiny resonant echo for a wet feel.
    echo = sine(lambda t: 980, n)
    echo = apply_env(echo, env(n, 0.05, 0.07, dur))
    echo = [v * 0.2 for v in echo]
    return mix(main, droop_delay(echo, 0.04))


def droop_delay(sig, delay_s):
    d = int(delay_s * SR)
    return [0.0] * d + sig


def bell(freq, dur, partials=((1, 1.0), (2.01, 0.5), (2.78, 0.28), (4.1, 0.12))):
    """A soft inharmonic bell tone (sum of slightly detuned partials)."""
    n = int(dur * SR)
    layers = []
    for ratio, amp in partials:
        s = sine(lambda t, f=freq * ratio: f, n)
        s = apply_env(s, env(n, 0.005, dur * 0.45, dur))
        layers.append([v * amp for v in s])
    return mix(*layers)


def chime():
    """Two gentle bell notes a soft fifth apart."""
    a = bell(784, 0.42)  # G5
    b = bell(1175, 0.40)  # D6
    b = droop_delay(b, 0.10)
    return mix(a, [v * 0.85 for v in b])


def shimmer():
    """Three quick ascending sparkles — glockenspiel-ish."""
    notes = [988, 1319, 1976]  # B5, E6, B6
    layers = []
    for i, f in enumerate(notes):
        s = bell(f, 0.30, partials=((1, 1.0), (2.7, 0.3), (5.1, 0.1)))
        layers.append(droop_delay([v * 0.8 for v in s], 0.06 * i))
    return mix(*layers)


def cheer():
    """A happy little major arpeggio C5-E5-G5-C6 on bells."""
    notes = [523, 659, 784, 1047]
    layers = []
    for i, f in enumerate(notes):
        s = bell(f, 0.36)
        layers.append(droop_delay([v * 0.8 for v in s], 0.075 * i))
    return mix(*layers)


if __name__ == "__main__":
    write_wav("pop.wav", bubble_pop())
    write_wav("droplet.wav", droplet())
    write_wav("chime.wav", chime())
    write_wav("shimmer.wav", shimmer())
    write_wav("cheer.wav", cheer())
    print("done →", os.path.normpath(OUT))
