package cmd

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"

	"skraak/tools"
)

// runCallsPropagate propagates verified classifications between filters in .data files.
//
// JSON output schema (--file mode):
//
//	{
//	  "file": string,                       // .data file path
//	  "from_filter": string,               // Source filter name
//	  "to_filter": string,                 // Target filter name
//	  "species": string,                   // Species propagated
//	  "filters_missing": bool,             // True if file lacks one or both filters (omitted if false)
//	  "targets_examined": int,             // Target labels examined
//	  "propagated": int,                   // Target labels updated
//	  "skipped_no_overlap": int,           // Targets with no overlapping source
//	  "skipped_conflict": int,             // Targets with conflicting sources
//	  "conflicts": [                       // Conflict details (omitted if empty)
//	    {
//	      "file": string,                // .data filename (omitted in single-file mode)
//	      "target_start": float,         // Target segment start (seconds)
//	      "target_end": float,           // Target segment end (seconds)
//	      "target_calltype": string,     // Target call type (omitted if empty)
//	      "source_choices": [
//	        {
//	          "start": float,            // Source segment start
//	          "end": float,              // Source segment end
//	          "species": string,         // Source species
//	          "calltype": string         // Source call type (omitted if empty)
//	        }
//	      ]
//	    }
//	  ],
//	  "changes": [                          // Change details (omitted if empty)
//	    {
//	      "target_start": float,          // Target segment start
//	      "target_end": float,            // Target segment end
//	      "prev_species": string,         // Previous species
//	      "prev_calltype": string,        // Previous call type (omitted if empty)
//	      "prev_certainty": int,          // Previous certainty
//	      "new_species": string,          // New species
//	      "new_calltype": string,         // New call type (omitted if empty)
//	      "new_certainty": int            // New certainty
//	    }
//	  ],
//	  "error": string                       // Error message (omitted if empty)
//	}
//
// JSON output schema (--folder mode):
//
//	{
//	  "folder": string,                        // Folder path
//	  "from_filter": string,                  // Source filter name
//	  "to_filter": string,                    // Target filter name
//	  "species": string,                      // Species propagated
//	  "files_total": int,                     // Total .data files scanned
//	  "files_with_both_filters": int,         // Files containing both filters
//	  "files_skipped_no_filter": int,         // Files missing a filter
//	  "files_changed": int,                   // Files with at least one propagation
//	  "files_errored": int,                   // Files with errors
//	  "targets_examined": int,                // Total target labels examined
//	  "propagated": int,                      // Total target labels updated
//	  "skipped_no_overlap": int,              // Targets with no overlapping source
//	  "skipped_conflict": int,                // Targets with conflicting sources
//	  "conflicts": [PropagateConflict],       // See --file mode conflict schema
//	  "errors": [CallsPropagateOutput],        // Per-file error outputs (omitted if empty)
//	  "error": string                          // Top-level error (omitted if empty)
//	}
func runCallsPropagate(args []string) {
	fs := flag.NewFlagSet("calls propagate", flag.ExitOnError)
	file := fs.String("file", "", "Path to a single .data file (mutually exclusive with --folder)")
	folder := fs.String("folder", "", "Path to folder containing .data files (mutually exclusive with --file)")
	from := fs.String("from", "", "Source filter name (required)")
	to := fs.String("to", "", "Target filter name (required)")
	species := fs.String("species", "", "Species to propagate (required, e.g. Kiwi)")

	fs.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: skraak calls propagate [options]\n\n")
		fmt.Fprintf(os.Stderr, "Propagate verified classifications from one filter to another within a .data file\n")
		fmt.Fprintf(os.Stderr, "or across every .data file in a folder.\n\n")
		fmt.Fprintf(os.Stderr, "Only source labels with certainty=100 and matching --species are considered.\n")
		fmt.Fprintf(os.Stderr, "Target labels (filter=--to) are updated when their certainty is 70 or 0.\n")
		fmt.Fprintf(os.Stderr, "Updated target labels are set to certainty=90; file reviewer is set to \"Skraak\".\n")
		fmt.Fprintf(os.Stderr, "Targets already at certainty=100 or 90 are left alone.\n")
		fmt.Fprintf(os.Stderr, "Files that do not contain both --from and --to filter labels are skipped.\n\n")
		fmt.Fprintf(os.Stderr, "Exactly one of --file or --folder is required.\n\n")
		fmt.Fprintf(os.Stderr, "Options:\n")
		fs.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\nExamples:\n")
		fmt.Fprintf(os.Stderr, "  skraak calls propagate --file rec.wav.data \\\n")
		fmt.Fprintf(os.Stderr, "    --from opensoundscape-kiwi-1.2 --to opensoundscape-kiwi-1.5 --species Kiwi\n\n")
		fmt.Fprintf(os.Stderr, "  skraak calls propagate --folder ./recordings \\\n")
		fmt.Fprintf(os.Stderr, "    --from opensoundscape-kiwi-1.2 --to opensoundscape-kiwi-1.5 --species Kiwi\n")
	}

	if err := fs.Parse(args); err != nil {
		os.Exit(1)
	}

	if (*file == "") == (*folder == "") {
		fmt.Fprintf(os.Stderr, "Error: exactly one of --file or --folder is required\n\n")
		fs.Usage()
		os.Exit(1)
	}

	missing := []string{}
	if *from == "" {
		missing = append(missing, "--from")
	}
	if *to == "" {
		missing = append(missing, "--to")
	}
	if *species == "" {
		missing = append(missing, "--species")
	}
	if len(missing) > 0 {
		fmt.Fprintf(os.Stderr, "Error: missing required flags: %v\n\n", missing)
		fs.Usage()
		os.Exit(1)
	}

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")

	if *file != "" {
		result, err := tools.CallsPropagate(tools.CallsPropagateInput{
			File:       *file,
			FromFilter: *from,
			ToFilter:   *to,
			Species:    *species,
		})
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", result.Error)
			os.Exit(1)
		}
		if err := enc.Encode(result); err != nil {
			fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
			os.Exit(1)
		}
		return
	}

	result, err := tools.CallsPropagateFolder(tools.CallsPropagateFolderInput{
		Folder:     *folder,
		FromFilter: *from,
		ToFilter:   *to,
		Species:    *species,
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", result.Error)
		os.Exit(1)
	}
	fmt.Fprintf(os.Stderr,
		"Files: %d total, %d with both filters, %d skipped (missing filter), %d changed, %d errored\n",
		result.FilesTotal, result.FilesWithBothFilters, result.FilesSkippedNoFilter,
		result.FilesChanged, result.FilesErrored)
	fmt.Fprintf(os.Stderr,
		"Targets: %d examined, %d propagated, %d no-overlap, %d conflicts\n",
		result.TargetsExamined, result.Propagated, result.SkippedNoOverlap, result.SkippedConflict)
	if err := enc.Encode(result); err != nil {
		fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
		os.Exit(1)
	}
}