// Package stt is maven's speech-to-text seam.
//
// Core calls exactly one method: `Transcribe(ctx, audio.Audio) (text, error)`.
// The interface is the swap seam:
//
//   - Stub: deterministic, no model. Generates a fixed phrase per utterance
//     so the daemon's reactive loop is exercisable end-to-end without any
//     weights on disk (the "no models on disk" floor). The Stub hashes the
//     audio bytes for a tiny bit of variation per utterance; the *content*
//     of the audio doesn't matter, only the wire shape round-trips.
//
//   - Remote: dials a worker module process at a unix socket (cmd/mavsttd,
//     whisper.cpp-backed). The swap is one constructor change at the daemon
//     seam; the boundary is the same.
//
// The Daemon picks the implementation from config. With no models on disk,
// it wires Stub (the audio path is "live" end to end, the transcribe step
// returns a canned string the router + action path operate on); with a
// worker socket configured, it wires Remote.
//
// Per docs/design.md § Voice pipeline (STT / TTS): whisper.cpp (CGo, Vulkan) in
// cmd/mavsttd is the production stt — the older faster-whisper/vosk picks are
// retired (docs/design.md § Superseded, "named STT/TTS model picks"). The
// server-side stt module is the heavy multilingual path; the client's
// wake-word + stage-0 command grammar (cmd/mavwaked) hits the router directly
// and never crosses this seam. Today's Remote + Stub both return plain text
// the router classifies.
package stt

import (
	"context"
	"crypto/sha256"
	"encoding/binary"
	"fmt"

	"github.com/kami/maven/internal/audio"
	"github.com/kami/maven/internal/worker"
)

// Transcriber — the speech-to-text contract. One method; one input shape;
// the output is plain text the router classifies. The implementation is
// impure (calls a model or a worker process); the Stub is pure (hash +
// template) and exists so the daemon runs end-to-end without a model.
type Transcriber interface {
	Transcribe(ctx context.Context, a audio.Audio) (text string, confidence float64, err error)
}

// Stub — the deterministic, no-model floor. Returns a canned phrase derived
// from a hash of the audio bytes so two utterances differ on the wire but
// both stay stable across runs (a test fixture is reproducible). The
// returned text is shaped like a real utterance ("maven, отметь что я выпил
// воды" or "maven, remind me in 4 hours to stretch") so the router's
// cascade has something realistic to chew on during end-to-end exercises.
type Stub struct{}

// NewStub builds the floor transcriber. No config — the Stub is stateless.
func NewStub() *Stub { return &Stub{} }

// stubPhrases — the canned outputs the Stub rotates through. Each is a
// plausible utterance shape the router's stage-0 grammar or stage-1
// classifier will route to a different intent (act / reminder / fact / note
// / query). The hash picks the phrase per utterance deterministically.
var stubPhrases = []string{
	"maven, отметь что я выпил воды",        // fact  (water tap → fact table)
	"maven, напомни через 4 часа размяться", // reminder (→ reminders table)
	"maven, restart nginx", // act (→ stage-0 grammar hit)
	"maven, что у меня сегодня по календарю",         // query (→ slm read-path, deferred)
	"note: idea — staggered cooldown by time of day", // note (→ chroma, deferred)
	"slept 6h, fan noise wrecked it",                 // compound capture (open spec; routes as fact today)
}

// Transcribe returns one of stubPhrases, indexed by a hash of the audio
// bytes. Empty audio ⇒ the first phrase (so a misconfigured client still
// gets a round-trip). Confidence is always 1.0 — the Stub is "certain" by
// construction, the router's confidence gate exercises on the classifier
// stage, not here.
func (s *Stub) Transcribe(_ context.Context, a audio.Audio) (string, float64, error) {
	if len(a.Bytes) == 0 {
		return stubPhrases[0], 1.0, nil
	}
	h := sha256.Sum256(a.Bytes)
	idx := binary.BigEndian.Uint32(h[:4]) % uint32(len(stubPhrases))
	return stubPhrases[idx], 1.0, nil
}

// Remote — the worker-backed Transcriber. Holds a worker.Client that dials
// the stt module's unix socket. The daemon constructs one when its config
// points at a worker socket; otherwise it uses the Stub.
type Remote struct {
	c    *worker.Client
	lang string
}

// NewRemote builds a Remote Transcriber. lang is the default language hint
// passed to the worker for every call ("ru"/"en"/"mixed"); the worker may
// override per-call but the daemon doesn't today.
func NewRemote(c *worker.Client, lang string) *Remote {
	return &Remote{c: c, lang: lang}
}

// Transcribe forwards to the worker module. A worker-side error (unknown
// method, bad params, internal panic-recovered) is returned wrapped so the
// daemon can log + continue — a transient stt fault doesn't kill the
// reactive path; the user gets a "sorry, didn't catch that" reply.
func (r *Remote) Transcribe(ctx context.Context, a audio.Audio) (string, float64, error) {
	resp, err := r.c.Transcribe(ctx, worker.TranscribeReq{Audio: a, Lang: r.lang})
	if err != nil {
		return "", 0, fmt.Errorf("stt: transcribe: %w", err)
	}
	return resp.Text, resp.Confidence, nil
}
