OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 348 LINES
 
[ HISTORY ]  [ UP ]
 

package cmd

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"
	"ookstats/internal/database"
	"ookstats/internal/generator"
	"ookstats/internal/generator/indexes"
)

var generateCmd = &cobra.Command{
	Use:   "generate",
	Short: "Generate static artifacts",
	Long:  `Generate static API endpoints and other build artifacts from the local da
tabase.`,
}

var generateAPICmd = &cobra.Command{
	Use:   "api",
	Short: "Generate static JSON API endpoints",
	RunE: func(cmd *cobra.Command, args []string) error {
		outDir, _ := cmd.Flags().GetString("out")
		onlyPlayers, _ := cmd.Flags().GetBool("players")
		doLeaderboards, _ := cmd.Flags().GetBool("leaderboards")
		doSearch, _ := cmd.Flags().GetBool("search")
		doIndexes, _ := cmd.Flags().GetBool("indexes")
		pageSize, _ := cmd.Flags().GetInt("page-size")
		shardSize, _ := cmd.Flags().GetInt("shard-size")
		regionsCSV, _ := cmd.Flags().GetString("regions")
		workers, _ := cmd.Flags().GetInt("workers")

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		// Connect to local DB (file:)
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to db: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api")
		if err := os.MkdirAll(base, 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if onlyPlayers {
			if err := generator.GeneratePlayers(db, filepath.Join(base, "player"), ""); err
 != nil {
				return err
			}
		}

		if doLeaderboards {
			regions := parseRegions(regionsCSV)
			if err := generator.GenerateLeaderboards(db, filepath.Join(base, "leaderboard")
, pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GeneratePlayerLeaderboards(db, filepath.Join(base, "leaderb
oard"), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateTotalRunsLeaderboard(db, filepath.Join(base, "leade
rboard"), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateAccountTotalRunsLeaderboard(db, filepath.Join(base,
 "leaderboard"), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateAccountLeaderboards(db, filepath.Join(base, "leader
board"), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateAllTimePlayerLeaderboard(db, filepath.Join(base, "l
eaderboard"), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateAllTimeAccountLeaderboard(db, filepath.Join(base, "
leaderboard"), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GeneratePlayerSeasonRunsLeaderboard(db, filepath.Join(base,
 "leaderboard"), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateAccountSeasonRunsLeaderboard(db, filepath.Join(base
, "leaderboard"), pageSize, regions, workers); err != nil {
				return err
			}
		}

		if doSearch {
			if err := generator.GenerateSearchIndex(db, filepath.Join(base, "search"), shar
dSize); err != nil {
				return err
			}
		}

		if doIndexes {
			if err := indexes.GenerateAllIndexes(db, outDir); err != nil {
				return err
			}
		}

		fmt.Printf("\nStatic API generated at %s\n", base)
		return nil
	},
}

var generateHomeCmd = &cobra.Command{
	Use:   "home",
	Short: "Generate home page JSON (top runs + top players + recent feed)",
	RunE: func(cmd *cobra.Command, args []string) error {
		outDir, _ := cmd.Flags().GetString("out")
		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to db: %w", err)
		}
		defer db.Close()

		if err := os.MkdirAll(filepath.Join(outDir, "api"), 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if err := generator.GenerateHome(db, outDir); err != nil {
			return err
		}
		fmt.Printf("\nHome JSON generated at %s/api/home.json\n", outDir)
		return nil
	},
}

var generateStatsCmd = &cobra.Command{
	Use:   "stats",
	Short: "Generate stats page JSON (totals, completion tiers, spec counts, top runn
ers, weekly activity)",
	RunE: func(cmd *cobra.Command, args []string) error {
		outDir, _ := cmd.Flags().GetString("out")
		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to db: %w", err)
		}
		defer db.Close()

		if err := os.MkdirAll(filepath.Join(outDir, "api"), 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if err := generator.GenerateStats(db, outDir); err != nil {
			return err
		}
		fmt.Printf("\nStats JSON generated at %s/api/stats.json\n", outDir)
		return nil
	},
}

var generatePlayerLeaderboardsCmd = &cobra.Command{
	Use:   "player-leaderboards",
	Short: "Generate the per-season player leaderboard JSON (Overall Time, all scopes
)",
	RunE: func(cmd *cobra.Command, args []string) error {
		outDir, _ := cmd.Flags().GetString("out")
		pageSize, _ := cmd.Flags().GetInt("page-size")
		regionsCSV, _ := cmd.Flags().GetString("regions")
		workers, _ := cmd.Flags().GetInt("workers")

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to db: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api", "leaderboard")
		if err := os.MkdirAll(base, 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		regions := parseRegions(regionsCSV)
		if err := generator.GeneratePlayerLeaderboards(db, base, pageSize, regions, work
ers); err != nil {
			return err
		}
		if err := generator.GenerateAllTimePlayerLeaderboard(db, base, pageSize, regions
, workers); err != nil {
			return err
		}
		fmt.Printf("\nPlayer leaderboards generated at %s/season/<id>/players/... and %s
/all-time/characters/by-time/...\n", base, base)
		return nil
	},
}

var generateTotalRunsLeaderboardCmd = &cobra.Command{
	Use:   "total-runs-leaderboard",
	Short: "Generate the cross-season Total Runs leaderboard JSON (all scopes)",
	RunE: func(cmd *cobra.Command, args []string) error {
		outDir, _ := cmd.Flags().GetString("out")
		pageSize, _ := cmd.Flags().GetInt("page-size")
		regionsCSV, _ := cmd.Flags().GetString("regions")
		workers, _ := cmd.Flags().GetInt("workers")

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to db: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api", "leaderboard")
		if err := os.MkdirAll(base, 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		regions := parseRegions(regionsCSV)
		if err := generator.GenerateTotalRunsLeaderboard(db, base, pageSize, regions, wo
rkers); err != nil {
			return err
		}
		if err := generator.GenerateAccountTotalRunsLeaderboard(db, base, pageSize, regi
ons, workers); err != nil {
			return err
		}
		if err := generator.GeneratePlayerSeasonRunsLeaderboard(db, base, pageSize, regi
ons, workers); err != nil {
			return err
		}
		if err := generator.GenerateAccountSeasonRunsLeaderboard(db, base, pageSize, reg
ions, workers); err != nil {
			return err
		}
		fmt.Printf("\nTotal Runs leaderboards generated at %s/players/total-runs/... and
 %s/season{N}/{characters,players}/by-runs/...\n", base, base)
		return nil
	},
}

var generateAccountLeaderboardsCmd = &cobra.Command{
	Use:   "account-leaderboards",
	Short: "Generate the per-season account-grouped leaderboard JSON",
	RunE: func(cmd *cobra.Command, args []string) error {
		outDir, _ := cmd.Flags().GetString("out")
		pageSize, _ := cmd.Flags().GetInt("page-size")
		regionsCSV, _ := cmd.Flags().GetString("regions")
		workers, _ := cmd.Flags().GetInt("workers")

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to db: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api", "leaderboard")
		if err := os.MkdirAll(base, 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		regions := parseRegions(regionsCSV)
		if err := generator.GenerateAccountLeaderboards(db, base, pageSize, regions, wor
kers); err != nil {
			return err
		}
		if err := generator.GenerateAllTimeAccountLeaderboard(db, base, pageSize, region
s, workers); err != nil {
			return err
		}
		fmt.Printf("\nAccount leaderboards generated at %s/season/<id>/accounts/... and 
%s/all-time/players/by-time/...\n", base, base)
		return nil
	},
}

func parseRegions(csv string) []string {
	out := []string{}
	if strings.TrimSpace(csv) == "" {
		return out
	}
	for _, r := range strings.Split(csv, ",") {
		rr := strings.TrimSpace(r)
		if rr != "" {
			out = append(out, rr)
		}
	}
	return out
}

var generateGearCmd = &cobra.Command{
	Use:   "gear",
	Short: "Generate gear popularity JSON (per-spec top items per slot, validated by 
spec primary stat)",
	RunE: func(cmd *cobra.Command, args []string) error {
		outDir, _ := cmd.Flags().GetString("out")
		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to db: %w", err)
		}
		defer db.Close()

		if err := os.MkdirAll(filepath.Join(outDir, "api"), 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if err := generator.GenerateGear(db, outDir); err != nil {
			return err
		}
		fmt.Printf("\nGear JSON generated at %s/api/gear.json\n", outDir)
		return nil
	},
}

func init() {
	rootCmd.AddCommand(generateCmd)
	generateCmd.AddCommand(generateAPICmd)
	generateAPICmd.Flags().String("out", "public", "Output directory for static API")
	generateAPICmd.Flags().Bool("players", true, "Generate player profile JSON endpoi
nts")
	generateAPICmd.Flags().Bool("leaderboards", true, "Generate leaderboard JSON endp
oints")
	generateAPICmd.Flags().Bool("search", true, "Generate search index JSON shards")
	generateAPICmd.Flags().Bool("indexes", true, "Generate API discovery indexes")
	generateAPICmd.Flags().Int("page-size", 25, "Leaderboard page size")
	generateAPICmd.Flags().Int("shard-size", 5000, "Search index shard size")
	generateAPICmd.Flags().String("regions", "us,eu,kr,tw", "Regions to include for r
egional leaderboards")
	generateAPICmd.Flags().Int("workers", 10, "Number of parallel workers for leaderb
oard generation")

	generateCmd.AddCommand(generateHomeCmd)
	generateHomeCmd.Flags().String("out", "web/public", "Output directory (api/home.j
son will be written under it)")

	generateCmd.AddCommand(generateStatsCmd)
	generateStatsCmd.Flags().String("out", "web/public", "Output directory (api/stats
.json will be written under it)")

	generateCmd.AddCommand(generateGearCmd)
	generateGearCmd.Flags().String("out", "web/public", "Output directory (api/gear.j
son will be written under it)")

	generateCmd.AddCommand(generatePlayerLeaderboardsCmd)
	generatePlayerLeaderboardsCmd.Flags().String("out", "web/public", "Output directo
ry (api/leaderboard/... will be written under it)")
	generatePlayerLeaderboardsCmd.Flags().Int("page-size", 25, "Leaderboard page size
")
	generatePlayerLeaderboardsCmd.Flags().String("regions", "us,eu,kr,tw", "Regions t
o include for regional/realm/class leaderboards")
	generatePlayerLeaderboardsCmd.Flags().Int("workers", 10, "Number of parallel work
ers for leaderboard generation")

	generateCmd.AddCommand(generateTotalRunsLeaderboardCmd)
	generateTotalRunsLeaderboardCmd.Flags().String("out", "web/public", "Output direc
tory (api/leaderboard/players/total-runs/... will be written under it)")
	generateTotalRunsLeaderboardCmd.Flags().Int("page-size", 25, "Leaderboard page si
ze")
	generateTotalRunsLeaderboardCmd.Flags().String("regions", "us,eu,kr,tw", "Regions
 to include for regional/realm/class leaderboards")
	generateTotalRunsLeaderboardCmd.Flags().Int("workers", 10, "Number of parallel wo
rkers for leaderboard generation")

	generateCmd.AddCommand(generateAccountLeaderboardsCmd)
	generateAccountLeaderboardsCmd.Flags().String("out", "web/public", "Output direct
ory (api/leaderboard/season/<id>/accounts/... will be written under it)")
	generateAccountLeaderboardsCmd.Flags().Int("page-size", 25, "Leaderboard page siz
e")
	generateAccountLeaderboardsCmd.Flags().String("regions", "us,eu,kr,tw", "Regions 
to include for regional account leaderboards")
	generateAccountLeaderboardsCmd.Flags().Int("workers", 4, "Number of parallel work
ers for account leaderboard generation")
}

package cmd

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"
	"ookstats/internal/database"
	"ookstats/internal/generator"
	"ookstats/internal/generator/indexes"
)

var generateCmd = &cobra.Command{
	Use:   "generate",
	Short: "Generate static artifacts",
	Long:  `Generate static API endpoints and o
ther build artifacts from the local database
.`,
}

var generateAPICmd = &cobra.Command{
	Use:   "api",
	Short: "Generate static JSON API endpoints"
,
	RunE: func(cmd *cobra.Command, args []strin
g) error {
		outDir, _ := cmd.Flags().GetString("out")
		onlyPlayers, _ := cmd.Flags().GetBool("pla
yers")
		doLeaderboards, _ := cmd.Flags().GetBool("
leaderboards")
		doSearch, _ := cmd.Flags().GetBool("search
")
		doIndexes, _ := cmd.Flags().GetBool("index
es")
		pageSize, _ := cmd.Flags().GetInt("page-si
ze")
		shardSize, _ := cmd.Flags().GetInt("shard-
size")
		regionsCSV, _ := cmd.Flags().GetString("re
gions")
		workers, _ := cmd.Flags().GetInt("workers"
)

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		// Connect to local DB (file:)
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to d
b: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api")
		if err := os.MkdirAll(base, 0o755); err !=
 nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if onlyPlayers {
			if err := generator.GeneratePlayers(db, f
ilepath.Join(base, "player"), ""); err != ni
l {
				return err
			}
		}

		if doLeaderboards {
			regions := parseRegions(regionsCSV)
			if err := generator.GenerateLeaderboards(
db, filepath.Join(base, "leaderboard"), page
Size, regions, workers); err != nil {
				return err
			}
			if err := generator.GeneratePlayerLeaderb
oards(db, filepath.Join(base, "leaderboard")
, pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateTotalRunsLead
erboard(db, filepath.Join(base, "leaderboard
"), pageSize, regions, workers); err != nil 
{
				return err
			}
			if err := generator.GenerateAccountTotalR
unsLeaderboard(db, filepath.Join(base, "lead
erboard"), pageSize, regions, workers); err 
!= nil {
				return err
			}
			if err := generator.GenerateAccountLeader
boards(db, filepath.Join(base, "leaderboard"
), pageSize, regions, workers); err != nil {
				return err
			}
			if err := generator.GenerateAllTimePlayer
Leaderboard(db, filepath.Join(base, "leaderb
oard"), pageSize, regions, workers); err != 
nil {
				return err
			}
			if err := generator.GenerateAllTimeAccoun
tLeaderboard(db, filepath.Join(base, "leader
board"), pageSize, regions, workers); err !=
 nil {
				return err
			}
			if err := generator.GeneratePlayerSeasonR
unsLeaderboard(db, filepath.Join(base, "lead
erboard"), pageSize, regions, workers); err 
!= nil {
				return err
			}
			if err := generator.GenerateAccountSeason
RunsLeaderboard(db, filepath.Join(base, "lea
derboard"), pageSize, regions, workers); err
 != nil {
				return err
			}
		}

		if doSearch {
			if err := generator.GenerateSearchIndex(d
b, filepath.Join(base, "search"), shardSize)
; err != nil {
				return err
			}
		}

		if doIndexes {
			if err := indexes.GenerateAllIndexes(db, 
outDir); err != nil {
				return err
			}
		}

		fmt.Printf("\nStatic API generated at %s\n
", base)
		return nil
	},
}

var generateHomeCmd = &cobra.Command{
	Use:   "home",
	Short: "Generate home page JSON (top runs +
 top players + recent feed)",
	RunE: func(cmd *cobra.Command, args []strin
g) error {
		outDir, _ := cmd.Flags().GetString("out")
		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to d
b: %w", err)
		}
		defer db.Close()

		if err := os.MkdirAll(filepath.Join(outDir
, "api"), 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if err := generator.GenerateHome(db, outDi
r); err != nil {
			return err
		}
		fmt.Printf("\nHome JSON generated at %s/ap
i/home.json\n", outDir)
		return nil
	},
}

var generateStatsCmd = &cobra.Command{
	Use:   "stats",
	Short: "Generate stats page JSON (totals, c
ompletion tiers, spec counts, top runners, w
eekly activity)",
	RunE: func(cmd *cobra.Command, args []strin
g) error {
		outDir, _ := cmd.Flags().GetString("out")
		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to d
b: %w", err)
		}
		defer db.Close()

		if err := os.MkdirAll(filepath.Join(outDir
, "api"), 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if err := generator.GenerateStats(db, outD
ir); err != nil {
			return err
		}
		fmt.Printf("\nStats JSON generated at %s/a
pi/stats.json\n", outDir)
		return nil
	},
}

var generatePlayerLeaderboardsCmd = &cobra.C
ommand{
	Use:   "player-leaderboards",
	Short: "Generate the per-season player lead
erboard JSON (Overall Time, all scopes)",
	RunE: func(cmd *cobra.Command, args []strin
g) error {
		outDir, _ := cmd.Flags().GetString("out")
		pageSize, _ := cmd.Flags().GetInt("page-si
ze")
		regionsCSV, _ := cmd.Flags().GetString("re
gions")
		workers, _ := cmd.Flags().GetInt("workers"
)

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to d
b: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api", "lead
erboard")
		if err := os.MkdirAll(base, 0o755); err !=
 nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		regions := parseRegions(regionsCSV)
		if err := generator.GeneratePlayerLeaderbo
ards(db, base, pageSize, regions, workers); 
err != nil {
			return err
		}
		if err := generator.GenerateAllTimePlayerL
eaderboard(db, base, pageSize, regions, work
ers); err != nil {
			return err
		}
		fmt.Printf("\nPlayer leaderboards generate
d at %s/season/<id>/players/... and %s/all-t
ime/characters/by-time/...\n", base, base)
		return nil
	},
}

var generateTotalRunsLeaderboardCmd = &cobra
.Command{
	Use:   "total-runs-leaderboard",
	Short: "Generate the cross-season Total Run
s leaderboard JSON (all scopes)",
	RunE: func(cmd *cobra.Command, args []strin
g) error {
		outDir, _ := cmd.Flags().GetString("out")
		pageSize, _ := cmd.Flags().GetInt("page-si
ze")
		regionsCSV, _ := cmd.Flags().GetString("re
gions")
		workers, _ := cmd.Flags().GetInt("workers"
)

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to d
b: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api", "lead
erboard")
		if err := os.MkdirAll(base, 0o755); err !=
 nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		regions := parseRegions(regionsCSV)
		if err := generator.GenerateTotalRunsLeade
rboard(db, base, pageSize, regions, workers)
; err != nil {
			return err
		}
		if err := generator.GenerateAccountTotalRu
nsLeaderboard(db, base, pageSize, regions, w
orkers); err != nil {
			return err
		}
		if err := generator.GeneratePlayerSeasonRu
nsLeaderboard(db, base, pageSize, regions, w
orkers); err != nil {
			return err
		}
		if err := generator.GenerateAccountSeasonR
unsLeaderboard(db, base, pageSize, regions, 
workers); err != nil {
			return err
		}
		fmt.Printf("\nTotal Runs leaderboards gene
rated at %s/players/total-runs/... and %s/se
ason{N}/{characters,players}/by-runs/...\n",
 base, base)
		return nil
	},
}

var generateAccountLeaderboardsCmd = &cobra.
Command{
	Use:   "account-leaderboards",
	Short: "Generate the per-season account-gro
uped leaderboard JSON",
	RunE: func(cmd *cobra.Command, args []strin
g) error {
		outDir, _ := cmd.Flags().GetString("out")
		pageSize, _ := cmd.Flags().GetInt("page-si
ze")
		regionsCSV, _ := cmd.Flags().GetString("re
gions")
		workers, _ := cmd.Flags().GetInt("workers"
)

		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to d
b: %w", err)
		}
		defer db.Close()

		base := filepath.Join(outDir, "api", "lead
erboard")
		if err := os.MkdirAll(base, 0o755); err !=
 nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		regions := parseRegions(regionsCSV)
		if err := generator.GenerateAccountLeaderb
oards(db, base, pageSize, regions, workers);
 err != nil {
			return err
		}
		if err := generator.GenerateAllTimeAccount
Leaderboard(db, base, pageSize, regions, wor
kers); err != nil {
			return err
		}
		fmt.Printf("\nAccount leaderboards generat
ed at %s/season/<id>/accounts/... and %s/all
-time/players/by-time/...\n", base, base)
		return nil
	},
}

func parseRegions(csv string) []string {
	out := []string{}
	if strings.TrimSpace(csv) == "" {
		return out
	}
	for _, r := range strings.Split(csv, ",") {
		rr := strings.TrimSpace(r)
		if rr != "" {
			out = append(out, rr)
		}
	}
	return out
}

var generateGearCmd = &cobra.Command{
	Use:   "gear",
	Short: "Generate gear popularity JSON (per-
spec top items per slot, validated by spec p
rimary stat)",
	RunE: func(cmd *cobra.Command, args []strin
g) error {
		outDir, _ := cmd.Flags().GetString("out")
		if strings.TrimSpace(outDir) == "" {
			return errors.New("--out is required")
		}
		db, err := database.Connect()
		if err != nil {
			return fmt.Errorf("failed to connect to d
b: %w", err)
		}
		defer db.Close()

		if err := os.MkdirAll(filepath.Join(outDir
, "api"), 0o755); err != nil {
			return fmt.Errorf("mkdir base: %w", err)
		}

		if err := generator.GenerateGear(db, outDi
r); err != nil {
			return err
		}
		fmt.Printf("\nGear JSON generated at %s/ap
i/gear.json\n", outDir)
		return nil
	},
}

func init() {
	rootCmd.AddCommand(generateCmd)
	generateCmd.AddCommand(generateAPICmd)
	generateAPICmd.Flags().String("out", "publi
c", "Output directory for static API")
	generateAPICmd.Flags().Bool("players", true
, "Generate player profile JSON endpoints")
	generateAPICmd.Flags().Bool("leaderboards",
 true, "Generate leaderboard JSON endpoints"
)
	generateAPICmd.Flags().Bool("search", true,
 "Generate search index JSON shards")
	generateAPICmd.Flags().Bool("indexes", true
, "Generate API discovery indexes")
	generateAPICmd.Flags().Int("page-size", 25,
 "Leaderboard page size")
	generateAPICmd.Flags().Int("shard-size", 50
00, "Search index shard size")
	generateAPICmd.Flags().String("regions", "u
s,eu,kr,tw", "Regions to include for regiona
l leaderboards")
	generateAPICmd.Flags().Int("workers", 10, "
Number of parallel workers for leaderboard g
eneration")

	generateCmd.AddCommand(generateHomeCmd)
	generateHomeCmd.Flags().String("out", "web/
public", "Output directory (api/home.json wi
ll be written under it)")

	generateCmd.AddCommand(generateStatsCmd)
	generateStatsCmd.Flags().String("out", "web
/public", "Output directory (api/stats.json 
will be written under it)")

	generateCmd.AddCommand(generateGearCmd)
	generateGearCmd.Flags().String("out", "web/
public", "Output directory (api/gear.json wi
ll be written under it)")

	generateCmd.AddCommand(generatePlayerLeader
boardsCmd)
	generatePlayerLeaderboardsCmd.Flags().Strin
g("out", "web/public", "Output directory (ap
i/leaderboard/... will be written under it)"
)
	generatePlayerLeaderboardsCmd.Flags().Int("
page-size", 25, "Leaderboard page size")
	generatePlayerLeaderboardsCmd.Flags().Strin
g("regions", "us,eu,kr,tw", "Regions to incl
ude for regional/realm/class leaderboards")
	generatePlayerLeaderboardsCmd.Flags().Int("
workers", 10, "Number of parallel workers fo
r leaderboard generation")

	generateCmd.AddCommand(generateTotalRunsLea
derboardCmd)
	generateTotalRunsLeaderboardCmd.Flags().Str
ing("out", "web/public", "Output directory (
api/leaderboard/players/total-runs/... will 
be written under it)")
	generateTotalRunsLeaderboardCmd.Flags().Int
("page-size", 25, "Leaderboard page size")
	generateTotalRunsLeaderboardCmd.Flags().Str
ing("regions", "us,eu,kr,tw", "Regions to in
clude for regional/realm/class leaderboards"
)
	generateTotalRunsLeaderboardCmd.Flags().Int
("workers", 10, "Number of parallel workers 
for leaderboard generation")

	generateCmd.AddCommand(generateAccountLeade
rboardsCmd)
	generateAccountLeaderboardsCmd.Flags().Stri
ng("out", "web/public", "Output directory (a
pi/leaderboard/season/<id>/accounts/... will
 be written under it)")
	generateAccountLeaderboardsCmd.Flags().Int(
"page-size", 25, "Leaderboard page size")
	generateAccountLeaderboardsCmd.Flags().Stri
ng("regions", "us,eu,kr,tw", "Regions to inc
lude for regional account leaderboards")
	generateAccountLeaderboardsCmd.Flags().Int(
"workers", 4, "Number of parallel workers fo
r account leaderboard generation")
}
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET