// Package cmd implements the CLI command tree.
package cmd

import (
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"text/tabwriter"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

// Version is set at build time via -ldflags.
var Version = "dev"

// Global flags.
var (
	verbose      bool
	outputFormat string
	configPath   string
	apiURL       string
)

// rootCmd is the top-level molecule command.
var rootCmd = &cobra.Command{
	Use:     "molecule",
	Version: Version,
	Short:   "molecule — Molecule AI platform CLI",
	Long: `molecule is the CLI for the Molecule AI agent platform.

Manage workspaces, inspect agents, audit the platform, and configure
agent behaviour from the terminal.

Quick start:
  molecule workspace list
  molecule agent list
  molecule platform health`,
	SilenceUsage:   true,
	SilenceErrors:  true,
}

func init() {
	rootCmd.PersistentFlags().StringVar(&apiURL, "api-url",
		envOr("MOLECULE_API_URL", "http://localhost:8080"),
		"Platform API base URL (env: MOLECULE_API_URL)")
	rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false,
		"Enable verbose (DEBUG-level) output to stderr")
	rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table",
		"Output format: table | json | yaml")
	rootCmd.PersistentFlags().StringVar(&configPath, "config", "",
		"Path to config file (default ~/.config/molecule.yaml or ./molecule.yaml)")
	rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
		return &exitError{code: 2, msg: err.Error()}
	})
	rootCmd.SetErr(os.Stderr)
}

// Execute runs the CLI.
func Execute() error {
	// Load config file.
	if configPath != "" {
		viper.SetConfigFile(configPath)
	} else {
		viper.SetConfigName("molecule")
		viper.AddConfigPath("$HOME/.config")
		viper.AddConfigPath(".")
	}
	viper.AutomaticEnv()
	_ = viper.ReadInConfig() // ignore not-found; env vars win

	return rootCmd.Execute()
}

// envOr returns the value of env var key, or fallback if unset/empty.
func envOr(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

// init registers all subcommand trees.
func init() {
	rootCmd.AddCommand(workspaceCmd)
	rootCmd.AddCommand(agentCmd)
	rootCmd.AddCommand(platformCmd)
	rootCmd.AddCommand(configCmd)
	rootCmd.AddCommand(initCmd)
}

// exitError wraps a user-facing error with a specific exit code.
type exitError struct{ code int; msg string }

func (e *exitError) Error() string { return e.msg }

// handleErr converts an error to the right exit code.
func handleErr(err error) error {
	if err == nil {
		return nil
	}
	if ee, ok := err.(*exitError); ok {
		fmt.Fprintf(os.Stderr, "%s\n", ee.msg)
		os.Exit(ee.code)
	}
	fmt.Fprintf(os.Stderr, "error: %v\n", err)
	os.Exit(1)
	return nil
}

// printJSON writes v as JSON to stdout.
func printJSON(v interface{}) error {
	return json.NewEncoder(os.Stdout).Encode(v)
}

// kv writes a key-value pair to the tabwriter (only if v is non-empty).
func kv(w *tabwriter.Writer, k, v string) {
	if v == "" {
		return
	}
	fmt.Fprintf(w, "%s:\t%s\n", k, v)
}

func versionInfo() string {
	return fmt.Sprintf("molecule %s (go %s)", Version, runtime.Version())
}