package main

import (
	"fmt"
	"strconv"
	"strings"

	"hamlog/internal/applog"
	"hamlog/internal/psu"
)

// ── Bench power supply (Modbus RTU) ──────────────────────────────────────────
//
// A programmable supply feeding the shack, switched on and off from OpsLog so
// the station comes up and goes down with the logbook rather than by reaching
// behind the desk.
//
// OpsLog READS the supply's measurements and its set points, and WRITES exactly
// one thing: the output on/off. The register map has the voltage and current
// set points and the three protection trip levels as writable too, and none of
// them belong to a logbook — a wrong value there is 30 V where a radio expected
// 13.8. See internal/psu for the map and where it comes from.

const (
	keyPSUEnabled = "psu.enabled"
	keyPSUPort    = "psu.com_port"
	keyPSUBaud    = "psu.baud"
	keyPSUAddress = "psu.address" // Modbus slave address, 1…15 on this family
)

// PSUSettings is the JSON shape for the Hardware → Power supply panel.
type PSUSettings struct {
	Enabled bool   `json:"enabled"`
	ComPort string `json:"com_port"`
	Baud    int    `json:"baud"`    // 9600 from the factory
	Address int    `json:"address"` // 1 from the factory
}

// GetPSUSettings returns the persisted supply config.
func (a *App) GetPSUSettings() (PSUSettings, error) {
	out := PSUSettings{Baud: 9600, Address: 1}
	if a.settings == nil {
		return out, fmt.Errorf("db not initialized")
	}
	m, err := a.settings.GetMany(a.ctx, keyPSUEnabled, keyPSUPort, keyPSUBaud, keyPSUAddress)
	if err != nil {
		return out, err
	}
	out.Enabled = m[keyPSUEnabled] == "1"
	out.ComPort = m[keyPSUPort]
	if v, e := strconv.Atoi(m[keyPSUBaud]); e == nil && v > 0 {
		out.Baud = v
	}
	if v, e := strconv.Atoi(m[keyPSUAddress]); e == nil && v >= 1 && v <= 250 {
		out.Address = v
	}
	return out, nil
}

// SavePSUSettings persists the config and (re)starts or stops the client.
func (a *App) SavePSUSettings(s PSUSettings) error {
	if a.settings == nil {
		return fmt.Errorf("db not initialized")
	}
	if s.Baud <= 0 {
		s.Baud = 9600
	}
	// The manual gives 1…15 for the address field and 1…250 for the address
	// SETTING register. Clamped to the wider range and defaulted to 1: an
	// address of 0 is the Modbus broadcast, which never answers, so accepting it
	// would give a supply that is present and permanently "not responding".
	if s.Address < 1 || s.Address > 250 {
		s.Address = 1
	}
	for k, v := range map[string]string{
		keyPSUEnabled: boolStr(s.Enabled),
		keyPSUPort:    strings.TrimSpace(s.ComPort),
		keyPSUBaud:    strconv.Itoa(s.Baud),
		keyPSUAddress: strconv.Itoa(s.Address),
	} {
		if err := a.settings.Set(a.ctx, k, v); err != nil {
			return err
		}
	}
	a.restartAsync("psu", a.startPSU)
	return nil
}

// startPSU stops any running client and starts a fresh one if the supply is
// enabled and has a port. Safe to call repeatedly (startup, settings save,
// profile switch).
func (a *App) startPSU() {
	if a.psu != nil {
		go a.psu.Stop()
		a.psu = nil
	}
	s, err := a.GetPSUSettings()
	if err != nil {
		applog.Printf("psu: not started — settings unavailable: %v", err)
		return
	}
	if !s.Enabled {
		return
	}
	if strings.TrimSpace(s.ComPort) == "" {
		applog.Printf("psu: not started — no serial port configured")
		return
	}
	applog.Printf("psu: starting on %s @ %d baud, Modbus address %d", s.ComPort, s.Baud, s.Address)
	a.psu = psu.New(psu.Config{ComPort: s.ComPort, Baud: s.Baud, Address: byte(s.Address)})
	_ = a.psu.Start()
}

// GetPSUStatus returns the supply's last polled state for the UI.
func (a *App) GetPSUStatus() psu.Status {
	if a.psu == nil {
		return psu.Status{}
	}
	return a.psu.GetStatus()
}

// SetPSUOutput switches the supply's output. The only write OpsLog makes to it.
func (a *App) SetPSUOutput(on bool) error {
	if a.psu == nil {
		return fmt.Errorf("the power supply is not enabled in Settings")
	}
	return a.psu.SetOutput(on)
}
