package main

// PSK Reporter target analysis — the panel beside the FT decodes.
//
// The FT decodes list says who is on the air. It cannot say whether the station
// you are about to call can hear you, and on FT8 that is the only question that
// matters: a pileup is invisible from this end, and calling a DX who is not
// hearing your region at all is the commonest way to spend twenty minutes for
// nothing.
//
// internal/pskrtgt does the work. This file is the wiring: settings, lifecycle,
// and the bindings the panel polls.

import (
	"fmt"
	"strings"

	"hamlog/internal/applog"
	"hamlog/internal/pskrtgt"
)

const (
	keyPSKTargetOn    = "pskrtgt.enabled" // the operator's master switch
	keyPSKTargetScope = "pskrtgt.scope"   // "target" (narrow) | "band" (whole band)
)

// PSKTargetSettings is the panel's shape in Settings.
type PSKTargetSettings struct {
	Enabled bool `json:"enabled"`
	// Scope is how much of the PSK Reporter feed is subscribed to. The narrow
	// one is three filters about two callsigns; the wide one is every FTx report
	// on the band. The difference is a few messages a second against several
	// hundred, which on an old PC is the difference between a panel and a
	// problem — so the narrow one is the default, and the wide one is there for
	// an operator who switches target constantly and has the machine for it.
	Scope string `json:"scope"`
}

func (a *App) GetPSKTargetSettings() PSKTargetSettings {
	scope := a.settingOr(keyPSKTargetScope, string(pskrtgt.ScopeTarget))
	if scope != string(pskrtgt.ScopeBand) {
		scope = string(pskrtgt.ScopeTarget)
	}
	return PSKTargetSettings{
		Enabled: a.settingOr(keyPSKTargetOn, "0") == "1",
		Scope:   scope,
	}
}

func (a *App) SavePSKTargetSettings(s PSKTargetSettings) error {
	on := "0"
	if s.Enabled {
		on = "1"
	}
	scope := s.Scope
	if scope != string(pskrtgt.ScopeBand) {
		scope = string(pskrtgt.ScopeTarget)
	}
	a.setSetting(keyPSKTargetOn, on)
	a.setSetting(keyPSKTargetScope, scope)
	// Rebuilt rather than reconfigured: the scope decides the subscriptions, and
	// a watcher that changed them under itself would have to unpick filters it
	// no longer knows the shape of. It reconnects on the next Watch call, which
	// the panel makes every second anyway.
	a.stopPSKTarget()
	applog.Printf("pskr target: %v (scope %s)", s.Enabled, scope)
	return nil
}

// pskTargetWatcher returns the watcher, building it on first use. Nothing is
// connected until a target is set, so an operator who never opens the panel
// never opens a socket to the broker.
func (a *App) pskTargetWatcher() *pskrtgt.Watcher {
	a.pskTgtMu.Lock()
	defer a.pskTgtMu.Unlock()
	if a.pskTgt != nil {
		return a.pskTgt
	}
	s := a.GetPSKTargetSettings()
	a.pskTgt = pskrtgt.New(pskrtgt.Config{
		Scope:  pskrtgt.Scope(s.Scope),
		MyCall: a.opCall,
		MyGrid: a.opGrid,
		// Only a fallback, for a station with no grid set — see the package.
		Continent: func(call string) string {
			if a.dxcc == nil {
				return ""
			}
			if m, ok := a.dxcc.Lookup(call); ok {
				return m.Continent
			}
			return ""
		},
		Logf: applog.Printf,
	})
	return a.pskTgt
}

func (a *App) stopPSKTarget() {
	a.pskTgtMu.Lock()
	w := a.pskTgt
	a.pskTgt = nil
	a.pskTgtMu.Unlock()
	if w != nil {
		w.Stop()
	}
}

// SetPSKTarget points the analysis at a callsign — normally the station the
// operator has just clicked or is calling. An empty callsign takes the feed
// down: no target, no subscription.
//
// mode is the target's own (FT8/FT4), and dialHz the operator's dial, which is
// what turns a report's frequency into an audio offset.
func (a *App) SetPSKTarget(call, mode string, dialHz int64) error {
	if !a.GetPSKTargetSettings().Enabled {
		a.stopPSKTarget()
		return nil
	}
	call = strings.ToUpper(strings.TrimSpace(call))
	if call == "" {
		a.stopPSKTarget()
		return nil
	}
	w := a.pskTargetWatcher()
	// The operator's identity is re-asserted on every call rather than captured
	// once: a profile switch changes both the callsign "he decoded you" looks
	// for and the square "near me" is measured from.
	w.SetOperator(a.opCall, a.opGrid)
	if err := w.Watch(call, mode, dialHz); err != nil {
		return fmt.Errorf("PSK Reporter: %w", err)
	}
	return nil
}

// GetPSKAnalysis is what the panel polls. Always answers, even with the feature
// off or no target set — the panel says WHY it is empty, and it can only do
// that if it is told.
func (a *App) GetPSKAnalysis() pskrtgt.Analysis {
	s := a.GetPSKTargetSettings()
	a.pskTgtMu.Lock()
	w := a.pskTgt
	a.pskTgtMu.Unlock()
	if w == nil {
		return pskrtgt.Analysis{Enabled: s.Enabled}
	}
	an := w.Snapshot()
	an.Enabled = s.Enabled
	return an
}
