package cmd
import (
"encoding/json"
"fmt"
"os"
"skraak/tools"
"skraak/utils"
)
func printPushCertaintyUsage() {
fmt.Fprintf(os.Stderr, "Usage: skraak calls push-certainty [options]\n\n")
fmt.Fprintf(os.Stderr, "Promote certainty=90 segments to certainty=100 for a filtered set.\n")
fmt.Fprintf(os.Stderr, "Filtering logic matches 'calls classify' exactly. Reviewer is set from config.\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
fmt.Fprintf(os.Stderr, " --folder <path> Path to folder containing .data files (required, or --file)\n")
fmt.Fprintf(os.Stderr, " --file <path> Path to a single .data file (required, or --folder)\n")
fmt.Fprintf(os.Stderr, " --filter <name> Scope to filter name (optional)\n")
fmt.Fprintf(os.Stderr, " --species <name> Scope to species, optionally with calltype (e.g. Kiwi, Kiwi+Duet)\n")
fmt.Fprintf(os.Stderr, " --night Only act on solar-night recordings (requires --location)\n")
fmt.Fprintf(os.Stderr, " --day Only act on solar-day recordings (requires --location)\n")
fmt.Fprintf(os.Stderr, " --location <lat,lng[,tz]> GPS coordinates and optional IANA timezone\n")
fmt.Fprintf(os.Stderr, " e.g. --location \"-36.85,174.76\" or --location \"-36.85,174.76,Pacific/Auckland\"\n")
fmt.Fprintf(os.Stderr, " Required with --night or --day. Timezone defaults to UTC.\n")
fmt.Fprintf(os.Stderr, " Not needed for AudioMoth data (UTC from WAV comment).\n")
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " skraak calls push-certainty --folder ./data --species Kiwi\n")
fmt.Fprintf(os.Stderr, " skraak calls push-certainty --folder ./data --species Kiwi --night --location \"-45.5,167.4\"\n")
}
type pushCertaintyFlags struct {
folder string
file string
filter string
species string
night bool
day bool
location string
}
func parsePushCertaintyArgs(args []string) pushCertaintyFlags {
var f pushCertaintyFlags
i := 0
for i < len(args) {
arg := args[i]
switch arg {
case "--folder":
f.folder = requireValue(arg, args, &i)
case "--file":
f.file = requireValue(arg, args, &i)
case "--filter":
f.filter = requireValue(arg, args, &i)
case "--species":
f.species = requireValue(arg, args, &i)
case "--night":
f.night = true
i++
case "--day":
f.day = true
i++
case "--location":
if f.location != "" {
fmt.Fprintf(os.Stderr, "Error: --location can only be specified once\n")
os.Exit(1)
}
f.location = requireValue(arg, args, &i)
case "--help", "-h":
printPushCertaintyUsage()
os.Exit(0)
default:
fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n\n", arg)
printPushCertaintyUsage()
os.Exit(1)
}
}
return f
}
func requireValue(flag string, args []string, i *int) string {
if *i+1 >= len(args) {
fmt.Fprintf(os.Stderr, "Error: %s requires a value\n", flag)
os.Exit(1)
}
v := args[*i+1]
*i += 2
return v
}
func validatePushCertaintyFlags(f pushCertaintyFlags) {
if f.folder == "" && f.file == "" {
fmt.Fprintf(os.Stderr, "Error: missing required flag: --folder or --file\n\n")
printPushCertaintyUsage()
os.Exit(1)
}
if f.night && f.day {
fmt.Fprintf(os.Stderr, "Error: --night and --day are mutually exclusive\n\n")
printPushCertaintyUsage()
os.Exit(1)
}
if (f.night || f.day) && f.location == "" {
fmt.Fprintf(os.Stderr, "Error: --night/--day requires --location\n\n")
printPushCertaintyUsage()
os.Exit(1)
}
}
func runCallsPushCertainty(args []string) {
f := parsePushCertaintyArgs(args)
validatePushCertaintyFlags(f)
cfg, cfgPath, err := utils.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintf(os.Stderr, "Create %s with a \"classify\" section; run `skraak calls classify --help` for an example.\n", cfgPath)
os.Exit(1)
}
if cfg.Classify.Reviewer == "" {
fmt.Fprintf(os.Stderr, "Error: %s is missing \"classify.reviewer\"\n", cfgPath)
os.Exit(1)
}
speciesName, callType := utils.ParseSpeciesCallType(f.species)
var lat, lng float64
var timezone string
if f.location != "" {
var err error
lat, lng, timezone, err = utils.ParseLocation(f.location)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
config := tools.PushCertaintyConfig{
Folder: f.folder,
File: f.file,
Filter: f.filter,
Species: speciesName,
CallType: callType,
Night: f.night,
Day: f.day,
Lat: lat,
Lng: lng,
Timezone: timezone,
Reviewer: cfg.Classify.Reviewer,
}
result, err := tools.PushCertainty(config)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if result.TimeFilteredCount > 0 {
label := "daytime"
if config.Day {
label = "nighttime"
}
fmt.Fprintf(os.Stderr, "Skipped %d %s files\n", result.TimeFilteredCount, label)
}
fmt.Fprintf(os.Stderr, "Updated %d segments across %d files\n",
result.SegmentsUpdated, result.FilesUpdated)
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(result); err != nil {
fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
os.Exit(1)
}
}