package main

import (
	"fmt"
	"time"

	"hamlog/internal/applog"
	"hamlog/internal/audio"
	"hamlog/internal/cwdecode"

	wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)

// CW decoder: taps the RX audio device (the same "From radio" capture the DVK
// and QSO recorder use) and streams decoded Morse text to the UI. It is started
// only by the frontend, and only while the entry mode is CW.
//
// Pitch targeting: the single-channel decoder is far more reliable when it locks
// to a KNOWN pitch (a narrow filter at the signal frequency, like a skimmer)
// instead of auto-searching for the loudest tone. So we follow the radio's CW
// pitch (FlexRadio cw_pitch) when available — or a manual override — and fall
// back to auto-search otherwise.

// cwTargetPitch returns the pitch (Hz) the decoder should lock to: the manual
// override if set, else the FlexRadio's CW pitch when it's in CW, else 0 (auto).
func (a *App) cwTargetPitch() int {
	if a.cwPitchHz > 0 {
		return a.cwPitchHz
	}
	if a.cat != nil {
		if st, ok := a.cat.FlexState(); ok && st.Available {
			// Only trust the radio's pitch when it's actually in CW.
			if st.Mode == "CW" || st.Mode == "CWL" || st.Mode == "CWU" {
				if st.CWPitch > 0 {
					return st.CWPitch
				}
			}
		}
	}
	return 0
}

// StartCWDecoder begins decoding CW from the configured RX audio device. The
// frontend calls this when the decoder toggle is on AND the mode is CW. Safe to
// call repeatedly; a second call is a no-op while already running.
func (a *App) StartCWDecoder() error {
	a.cwMu.Lock()
	defer a.cwMu.Unlock()
	if a.cwStop != nil {
		return nil // already running
	}
	dev := ""
	if a.settings != nil {
		dev, _ = a.settings.Get(a.ctx, keyAudioFromRadio)
	}
	if dev == "" {
		return fmt.Errorf("no RX audio device configured (set \"From radio\" in Audio settings)")
	}

	dec := cwdecode.New(audio.SampleRate,
		func(text string) {
			if a.ctx != nil {
				wruntime.EventsEmit(a.ctx, "cw:text", text)
			}
		},
		func(st cwdecode.Status) {
			if a.ctx != nil {
				wruntime.EventsEmit(a.ctx, "cw:status", st)
			}
		},
	)
	dec.SetTarget(a.cwTargetPitch())
	a.cwDecoder = dec

	stop := make(chan struct{})
	a.cwStop = stop
	go func() {
		if err := audio.StreamCapture(dev, stop, dec.Process); err != nil {
			applog.Printf("cw: capture failed: %v", err)
			if a.ctx != nil {
				wruntime.EventsEmit(a.ctx, "cw:error", err.Error())
			}
		}
		a.cwMu.Lock()
		if a.cwStop == stop {
			a.cwStop = nil
			a.cwDecoder = nil
		}
		a.cwMu.Unlock()
	}()
	// Follow the radio's CW pitch live (every second) while this run is active.
	go a.cwFollowPitch(stop, dec)
	return nil
}

// cwFollowPitch keeps the decoder locked to the current target pitch until stop.
func (a *App) cwFollowPitch(stop <-chan struct{}, dec *cwdecode.Decoder) {
	t := time.NewTicker(time.Second)
	defer t.Stop()
	for {
		select {
		case <-stop:
			return
		case <-t.C:
			dec.SetTarget(a.cwTargetPitch())
		}
	}
}

// StopCWDecoder halts the CW decoder if running.
func (a *App) StopCWDecoder() {
	a.cwMu.Lock()
	stop := a.cwStop
	a.cwStop = nil
	a.cwDecoder = nil
	a.cwMu.Unlock()
	if stop != nil {
		close(stop)
	}
}

// CWDecoderRunning reports whether the decoder is currently capturing.
func (a *App) CWDecoderRunning() bool {
	a.cwMu.Lock()
	defer a.cwMu.Unlock()
	return a.cwStop != nil
}

// SetCWDecoderPitch sets a manual decode pitch (Hz); 0 returns to auto (follow
// the Flex CW pitch, or search). Applies live to a running decoder.
func (a *App) SetCWDecoderPitch(hz int) {
	if hz < 0 {
		hz = 0
	}
	a.cwMu.Lock()
	a.cwPitchHz = hz
	dec := a.cwDecoder
	a.cwMu.Unlock()
	if dec != nil {
		dec.SetTarget(a.cwTargetPitch())
	}
}

// GetCWDecoderPitch returns the manual override (0 = auto / follow Flex).
func (a *App) GetCWDecoderPitch() int {
	a.cwMu.Lock()
	defer a.cwMu.Unlock()
	return a.cwPitchHz
}
