package main

// Panadapter auto-zoom on a spot click.
//
// A CW signal is 100 Hz wide, an FT8 slot 50 Hz in a 3 kHz sub-band, an SSB
// station 2.7 kHz. One panadapter width cannot serve all three: at 200 kHz the
// CW station you clicked is a hair, and at 25 kHz an FT8 window shows one
// eighth of the traffic. So the width follows the MODE of the spot, which is
// the only thing that determines how much spectrum is worth looking at.
//
// The command is SmartSDR's own: "display pan s <pan> bandwidth=<MHz>", with an
// optional re-centre. Same approach as DXHunter, which is where the defaults
// below come from.

import (
	"encoding/json"
	"strings"

	"hamlog/internal/cat"
)

const keyFlexZoom = "flex.spot_zoom"

// FlexZoomSettings is the per-mode-group visible width, in kHz.
//
// kHz and not MHz: an operator thinks "25 kHz of CW", and a form asking for
// 0.025 invites a zero too many — which on a panadapter is not an error message
// but a display that has silently gone somewhere else.
type FlexZoomSettings struct {
	Enabled bool `json:"enabled"`
	CWkHz   int  `json:"cw_khz"`
	SSBkHz  int  `json:"ssb_khz"`
	DigikHz int  `json:"digi_khz"`
	// Centre re-centres the panadapter on EVERY spot, not just the ones that
	// would otherwise fall outside the new window — the display always follows.
	// Off by default: an operator who has arranged their window around a run
	// frequency does not necessarily want it moved every time they look at
	// someone else, and a spot already on screen stays where it is either way.
	Centre bool `json:"centre"`
}

// defaultFlexZoom — the widths DXHunter settled on, in kHz.
var defaultFlexZoom = FlexZoomSettings{Enabled: false, CWkHz: 25, SSBkHz: 200, DigikHz: 50, Centre: false}

// flexZoomGroup maps a spot's mode to the three widths worth distinguishing.
//
// The mode arrives as whatever the cluster comment implied — "CW", "FT8",
// "USB", "RTTY" — so this takes the ADIF-ish name, not the Flex one. Anything
// unrecognised is digital, which is the safe end: a 50 kHz window is readable
// for every mode, where 25 kHz is not.
func flexZoomGroup(mode string) string {
	switch strings.ToUpper(strings.TrimSpace(mode)) {
	case "CW", "CWR", "CWL", "CWU":
		return "cw"
	case "SSB", "USB", "LSB", "AM", "FM", "NFM", "DFM", "SAM", "PHONE", "DV":
		return "ssb"
	}
	return "digi"
}

// GetFlexZoom returns the settings, defaults included.
func (a *App) GetFlexZoom() FlexZoomSettings {
	out := defaultFlexZoom
	if raw := a.settingOr(keyFlexZoom, ""); raw != "" {
		var s FlexZoomSettings
		if err := json.Unmarshal([]byte(raw), &s); err == nil {
			out = normFlexZoom(s)
		}
	}
	return out
}

// normFlexZoom clamps the widths to something a panadapter can actually show.
// 1 kHz is narrower than most filters; 14 MHz is a whole HF spectrum.
func normFlexZoom(s FlexZoomSettings) FlexZoomSettings {
	clamp := func(v, def int) int {
		if v < 1 || v > 14000 {
			return def
		}
		return v
	}
	s.CWkHz = clamp(s.CWkHz, defaultFlexZoom.CWkHz)
	s.SSBkHz = clamp(s.SSBkHz, defaultFlexZoom.SSBkHz)
	s.DigikHz = clamp(s.DigikHz, defaultFlexZoom.DigikHz)
	return s
}

// SaveFlexZoom persists the settings.
func (a *App) SaveFlexZoom(s FlexZoomSettings) error {
	b, err := json.Marshal(normFlexZoom(s))
	if err != nil {
		return err
	}
	a.setSetting(keyFlexZoom, string(b))
	return nil
}

// FlexZoomForSpot resizes the panadapter for a spot that was just clicked.
//
// Called from the click handler, AFTER the rig has been tuned: the zoom is a
// view change and must never delay the frequency change an operator is waiting
// for. Does nothing at all unless the option is on and the backend is a Flex —
// no other radio has a panadapter to zoom.
func (a *App) FlexZoomForSpot(mode string, freqHz int64) {
	if a.cat == nil {
		return
	}
	z := a.GetFlexZoom()
	if !z.Enabled {
		return
	}
	khz := z.DigikHz
	switch flexZoomGroup(mode) {
	case "cw":
		khz = z.CWkHz
	case "ssb":
		khz = z.SSBkHz
	}
	_ = a.cat.FlexDo(func(fc cat.FlexController) error {
		return fc.ZoomPan(float64(khz)/1000, float64(freqHz)/1e6, z.Centre)
	})
}
