package main

// The receiving half of the voice reach (V-671).
//
// mavwaked used to send and never listen. It wired no PushHandler, and
// SendRequest discards a push frame when there is none. The consequence was
// not a missing feature but a silent one: mavend routes a nudge to the voice
// session that spoke most recently, and once mavwaked had spoken once it WAS
// that session. PushToMostRecent succeeded, the dispatcher counted the nudge
// delivered and stopped rerouting to telegram and ntfy, and mavwaked threw the
// audio away. He heard nothing, anywhere.
//
// So the connection is opened at startup rather than at the first utterance,
// and it is held open. A client that has never connected has no session, and
// the dispatcher must be able to tell "he is not at the machine" from "he is,
// and she has nothing to say".

import (
	"context"
	"encoding/json"
	"log"
	"time"

	"github.com/kami/maven/internal/voice"
)

// nudgeRetry is how long to wait before dialling again after the conn ends.
// mavend restarts on every deploy, and a listener that gives up then is a
// listener that is deaf until the next reboot.
const nudgeRetry = 5 * time.Second

// nudgeHandler decodes a push and hands the audio to the session, which
// speaks it through the same player the reply path uses. It does not play
// anything itself: the half-duplex gate and barge-in live on the capture
// loop, and a nudge has to sit under both.
type nudgeHandler struct{ sess *session }

func (h *nudgeHandler) OnPush(p voice.Push) {
	if p.Kind != voice.PushKindAudioNudge {
		log.Printf("mavwaked: ignoring push of unknown kind %q", p.Kind)
		return
	}
	var ap voice.AudioNudgePush
	if err := json.Unmarshal(p.Params, &ap); err != nil {
		log.Printf("mavwaked: nudge: decode: %v", err)
		return
	}
	log.Printf("mavwaked: nudge from rule %q (severity %d): %q (%.2fs audio)",
		ap.RuleName, ap.Severity, ap.Text, ap.Audio.Duration())
	if len(ap.Audio.Bytes) == 0 {
		// mavttsd was down or the text was empty. Say so rather than going
		// quiet: the dispatcher already counted this one as delivered.
		log.Printf("mavwaked: nudge %q carried no audio, nothing to speak", ap.RuleName)
		return
	}
	h.sess.Nudge(ap.Audio)
}

// runNudgeReceiver keeps a push handler wired for as long as ctx lives,
// redialling whenever the conn ends. Returns when ctx is cancelled.
func runNudgeReceiver(ctx context.Context, vc *voice.Client, sess *session) {
	h := &nudgeHandler{sess: sess}
	for {
		err := vc.RunPushReceiver(ctx, h)
		if ctx.Err() != nil {
			return
		}
		if err != nil {
			log.Printf("mavwaked: nudge receiver: %v", err)
		} else {
			log.Printf("mavwaked: voice connection ended, reconnecting in %s", nudgeRetry)
		}
		select {
		case <-ctx.Done():
			return
		case <-time.After(nudgeRetry):
		}
	}
}
