package text

import (
	"context"
	"fmt"
	"os"
	"slices"
	"sort"

	"github.com/clubpay/ronykit/x/rkit"
	"github.com/spf13/cobra"
	"golang.org/x/mod/modfile"
	"golang.org/x/text/language"
	"golang.org/x/text/message/pipeline"
	"golang.org/x/tools/go/packages"
)

var opt = struct {
	SourceLang     string
	Languages      []string
	DstDir         string
	ModulesFilter  []string
	GenFile        string
	GenPackageName string
}{}

func init() {
	flagSet := Cmd.Flags()
	flagSet.StringVarP(&opt.SourceLang, "src-lang", "s", "en-US", "source language")
	flagSet.StringSliceVarP(&opt.Languages, "dst-lang", "l", []string{"en-US", "fa-IR", "ar-AR"}, "languages to generate")
	flagSet.StringVarP(&opt.DstDir, "out-dir", "o", ".", "output path")
	flagSet.StringVarP(
		&opt.GenPackageName,
		"gen-package",
		"g",
		"",
		`
is the package or relative path into which to generate the
file. If not specified it is relative to the current directory.
`,
	)
	flagSet.StringVarP(&opt.GenFile, "gen-file", "f", "catalog.go", "generated file name")
	flagSet.StringSliceVarP(&opt.ModulesFilter, "modules-filter", "m", []string{}, "modules filter")
}

var wrap = func(err error, msg string) error {
	if err == nil {
		return nil
	}

	return fmt.Errorf("%s: %w", msg, err)
}

//nolint:gochecknoglobals
var Cmd = &cobra.Command{
	Use: "text",
	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) == 0 && cmd.Flags().NFlag() == 0 {
			err := RunInteractive(cmd)
			if err != nil {
				return err
			}
		}

		return runText(cmd)
	},
}

func runText(cmd *cobra.Command) error {
	allPackages, err := getAllPackages(cmd)
	if err != nil {
		return wrap(err, "failed to get packages")
	}

	config := &pipeline.Config{
		SourceLanguage: language.Make(opt.SourceLang),
		Supported:      rkit.Map(opt.Languages, language.Make),
		Packages:       allPackages,
		Dir:            opt.DstDir,
		GenFile:        opt.GenFile,
		GenPackage:     opt.GenPackageName,
	}

	state, err := pipeline.Extract(config)
	if err != nil {
		return wrap(err, "extract failed")
	}

	if err := state.Import(); err != nil {
		return wrap(err, "import failed")
	}

	if err := state.Merge(); err != nil {
		return wrap(err, "merge failed")
	}

	if err := state.Export(); err != nil {
		return wrap(err, "export failed")
	}

	return wrap(state.Generate(), "generation failed")
}

func isGoWorkspace() bool {
	_, err := os.Stat("go.work")

	return err == nil
}

func getAllWorkspaceDirectories(_ *cobra.Command) ([]string, error) {
	workData, err := os.ReadFile("go.work")
	if err != nil {
		return nil, fmt.Errorf("read go.work: %w", err)
	}

	f, err := modfile.ParseWork("go.work", workData, nil)
	if err != nil {
		return nil, fmt.Errorf("parse go.work: %w", err)
	}

	dirs := make([]string, 0, len(f.Use))
	for _, u := range f.Use {
		if len(opt.ModulesFilter) > 0 {
			filtered := slices.Contains(opt.ModulesFilter, u.Path)

			if !filtered {
				continue
			}
		}

		dirs = append(dirs, u.Path)
	}

	return dirs, nil
}

func getAllPackages(cmd *cobra.Command) ([]string, error) {
	var dirs []string

	if isGoWorkspace() {
		cmd.Println("detected go workspace")

		var err error

		dirs, err = getAllWorkspaceDirectories(cmd)
		if err != nil {
			return nil, err
		}

		cmd.Println("using directories:", dirs)
	} else {
		dirs = []string{"."}
	}

	var out []string

	for _, dir := range dirs {
		cfg := &packages.Config{
			Context: context.Background(),
			Mode:    packages.NeedName | packages.NeedFiles | packages.NeedModule,
			Dir:     dir, // module root or any subdir
		}
		// ./... expands to all packages in the current module/workspace
		pkgs, err := packages.Load(cfg, "./...")
		if err != nil {
			return nil, fmt.Errorf("packages.Load: %w", err)
		}

		seen := make(map[string]struct{})

		for _, p := range pkgs {
			if p.PkgPath == "" || len(p.GoFiles) == 0 {
				continue // skip synthetic or empty packages
			}

			if _, ok := seen[p.PkgPath]; ok {
				continue
			}

			seen[p.PkgPath] = struct{}{}

			out = append(out, p.PkgPath)
		}
	}

	// Sort packages for consistent output
	sort.Strings(out)

	// Pretty print packages
	for _, pkg := range out {
		cmd.Printf("\t%s\n", pkg)
	}

	return out, nil
}
