package main

import (
	"encoding/json"
	"os"
	"path/filepath"
	"sort"
	"time"
)

// Game represents the metadata for a single DOS game.
// Stored as game.json in data/games/{id}/
type Game struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Year        int      `json:"year,omitempty"`
	Genre       string   `json:"genre,omitempty"`
	Developer   string   `json:"developer,omitempty"`
	Publisher   string   `json:"publisher,omitempty"`
	Description string   `json:"description,omitempty"`
	Rating      float64  `json:"rating,omitempty"`
	Platforms   []string `json:"platforms,omitempty"`

	// Bundle info
	BundleType string `json:"bundle_type,omitempty"` // "standard" or "sockdrive"
	BundleFile string `json:"bundle_file,omitempty"` // path relative to game dir

	// IGDB
	IGDBID      int    `json:"igdb_id,omitempty"`
	IGDBCoverID string `json:"igdb_cover_id,omitempty"`

	// Local file hints (cover exists if cover.jpg/png/webp is present)
	HasCover      bool     `json:"has_cover"`
	HasScreenshot bool     `json:"has_screenshots"`
	Screenshots   []string `json:"screenshots,omitempty"`

	Ready     bool      `json:"ready"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// gameDir returns the directory for a game by ID.
func gameDir(id string) string {
	return filepath.Join(dataDir, "games", id)
}

// gameJSONPath returns the path to a game's game.json.
func gameJSONPath(id string) string {
	return filepath.Join(gameDir(id), "game.json")
}

// loadGame reads a game.json file and returns the Game.
func loadGame(id string) (*Game, error) {
	path := gameJSONPath(id)
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	g := &Game{}
	if err := json.Unmarshal(data, g); err != nil {
		return nil, err
	}

	// Detect if cover file exists
	g.HasCover = fileExists(filepath.Join(gameDir(id), "cover.jpg")) ||
		fileExists(filepath.Join(gameDir(id), "cover.png")) ||
		fileExists(filepath.Join(gameDir(id), "cover.webp"))

	return g, nil
}

// saveGame writes a game's metadata to its game.json.
func saveGame(g *Game) error {
	g.UpdatedAt = time.Now()
	if g.CreatedAt.IsZero() {
		g.CreatedAt = time.Now()
	}

	dir := gameDir(g.ID)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return err
	}

	data, err := json.MarshalIndent(g, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(gameJSONPath(g.ID), data, 0644)
}

// listGames scans the games directory and returns all games, sorted by title.
func listGames() ([]*Game, error) {
	gamesDir := filepath.Join(dataDir, "games")
	entries, err := os.ReadDir(gamesDir)
	if err != nil {
		if os.IsNotExist(err) {
			return []*Game{}, nil
		}
		return nil, err
	}

	var games []*Game
	if len(entries) > 0 {
		games = make([]*Game, 0, len(entries))
	} else {
		games = make([]*Game, 0)
	}
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		g, err := loadGame(e.Name())
		if err != nil {
			continue // skip broken entries
		}
		games = append(games, g)
	}

	sort.Slice(games, func(i, j int) bool {
		return games[i].Title < games[j].Title
	})

	return games, nil
}

// fileExists checks if a file exists.
func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

// sanitizeID cleans a string for use as a game directory name.
func sanitizeID(s string) string {
	result := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') {
			result = append(result, c)
		} else if c >= 'A' && c <= 'Z' {
			result = append(result, c+32) // lowercase
		} else if c == ' ' || c == '-' || c == '_' {
			result = append(result, '-')
		}
	}
	// Trim leading/trailing hyphens
	for len(result) > 0 && result[0] == '-' {
		result = result[1:]
	}
	for len(result) > 0 && result[len(result)-1] == '-' {
		result = result[:len(result)-1]
	}
	if len(result) == 0 {
		return "unknown"
	}
	return string(result)
}
