package engine import ( "fmt" "os" "path/filepath" "strings" "time" "github.com/spf13/viper" ) type Config struct { DefaultFilter string `mapstructure:"default_filter"` DefaultSort string `mapstructure:"default_sort"` ColorOutput bool `mapstructure:"color_output"` WeekStartDay string `mapstructure:"week_start_day"` DefaultDueTime string `mapstructure:"default_due_time"` // Sync settings SyncEnabled bool `mapstructure:"sync_enabled"` SyncURL string `mapstructure:"sync_url"` SyncAPIKey string `mapstructure:"sync_api_key"` SyncClientID string `mapstructure:"sync_client_id"` SyncStrategy string `mapstructure:"sync_strategy"` SyncQueueOffline bool `mapstructure:"sync_queue_offline"` } var globalConfig *Config // GetConfigDir returns the configuration directory path func GetConfigDir() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("failed to get home directory: %w", err) } return filepath.Join(home, ".config", "jade"), nil } // GetDBPath returns the path to the SQLite database func GetDBPath() (string, error) { configDir, err := GetConfigDir() if err != nil { return "", err } return filepath.Join(configDir, "opal.db"), nil } // GetConfigPath returns the path to the config file func GetConfigPath() (string, error) { configDir, err := GetConfigDir() if err != nil { return "", err } return filepath.Join(configDir, "opal.yml"), nil } // LoadConfig loads the configuration from file or creates default func LoadConfig() (*Config, error) { if globalConfig != nil { return globalConfig, nil } configDir, err := GetConfigDir() if err != nil { return nil, err } // Ensure config directory exists if err := os.MkdirAll(configDir, 0755); err != nil { return nil, fmt.Errorf("failed to create config directory: %w", err) } configPath, err := GetConfigPath() if err != nil { return nil, err } v := viper.New() v.SetConfigFile(configPath) v.SetConfigType("yaml") // Set defaults v.SetDefault("default_filter", "status:pending") v.SetDefault("default_sort", "due,priority") v.SetDefault("color_output", true) v.SetDefault("week_start_day", "monday") v.SetDefault("default_due_time", "") // Sync defaults v.SetDefault("sync_enabled", false) v.SetDefault("sync_url", "") v.SetDefault("sync_api_key", "") v.SetDefault("sync_client_id", "") v.SetDefault("sync_strategy", "last-write-wins") v.SetDefault("sync_queue_offline", true) // Try to read existing config err = v.ReadInConfig() if err != nil { // Config doesn't exist, create it with defaults // Write config with defaults (ignoring the error type check for now) if err := v.WriteConfigAs(configPath); err != nil { return nil, fmt.Errorf("failed to create config file: %w", err) } // Try reading again if err := v.ReadInConfig(); err != nil { return nil, fmt.Errorf("failed to read newly created config: %w", err) } } cfg := &Config{} if err := v.Unmarshal(cfg); err != nil { return nil, fmt.Errorf("failed to unmarshal config: %w", err) } globalConfig = cfg return cfg, nil } // SaveConfig saves the configuration to file func SaveConfig(cfg *Config) error { configPath, err := GetConfigPath() if err != nil { return err } v := viper.New() v.SetConfigFile(configPath) v.SetConfigType("yaml") v.Set("default_filter", cfg.DefaultFilter) v.Set("default_sort", cfg.DefaultSort) v.Set("color_output", cfg.ColorOutput) v.Set("week_start_day", cfg.WeekStartDay) v.Set("default_due_time", cfg.DefaultDueTime) // Sync settings v.Set("sync_enabled", cfg.SyncEnabled) v.Set("sync_url", cfg.SyncURL) v.Set("sync_api_key", cfg.SyncAPIKey) v.Set("sync_client_id", cfg.SyncClientID) v.Set("sync_strategy", cfg.SyncStrategy) v.Set("sync_queue_offline", cfg.SyncQueueOffline) return v.WriteConfig() } // GetConfig returns the loaded config or loads it if not already loaded func GetConfig() (*Config, error) { if globalConfig != nil { return globalConfig, nil } return LoadConfig() } // GetWeekStart returns the configured week start day func (c *Config) GetWeekStart() time.Weekday { if strings.ToLower(c.WeekStartDay) == "sunday" { return time.Sunday } return time.Monday // default } // GetDefaultDueTime returns the configured default due time (HH:MM format or empty) func (c *Config) GetDefaultDueTime() string { return c.DefaultDueTime }