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"` DefaultReport string `mapstructure:"default_report"` ColorOutput bool `mapstructure:"color_output"` WeekStartDay string `mapstructure:"week_start_day"` DefaultDueTime string `mapstructure:"default_due_time"` // Urgency coefficients UrgencyDue float64 `mapstructure:"urgency_due_coefficient"` UrgencyPriorityH float64 `mapstructure:"urgency_priority_h_coefficient"` UrgencyPriorityM float64 `mapstructure:"urgency_priority_m_coefficient"` UrgencyPriorityD float64 `mapstructure:"urgency_priority_d_coefficient"` UrgencyPriorityL float64 `mapstructure:"urgency_priority_l_coefficient"` UrgencyActive float64 `mapstructure:"urgency_active_coefficient"` UrgencyAge float64 `mapstructure:"urgency_age_coefficient"` UrgencyAgeMax int `mapstructure:"urgency_age_max"` UrgencyTags float64 `mapstructure:"urgency_tags_coefficient"` UrgencyProject float64 `mapstructure:"urgency_project_coefficient"` UrgencyWaiting float64 `mapstructure:"urgency_waiting_coefficient"` UrgencyUrgentTag string `mapstructure:"urgency_urgent_tag"` UrgencyUrgentCoeff float64 `mapstructure:"urgency_urgent_coefficient"` NextLimit int `mapstructure:"next_limit"` // 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("default_report", "list") v.SetDefault("color_output", true) v.SetDefault("week_start_day", "monday") v.SetDefault("default_due_time", "") // Urgency defaults (adjusted for Opal's simpler model) v.SetDefault("urgency_due_coefficient", 12.0) v.SetDefault("urgency_priority_h_coefficient", 6.0) v.SetDefault("urgency_priority_m_coefficient", 3.9) v.SetDefault("urgency_priority_d_coefficient", 1.8) v.SetDefault("urgency_priority_l_coefficient", 0.0) v.SetDefault("urgency_active_coefficient", 4.0) v.SetDefault("urgency_age_coefficient", 2.0) v.SetDefault("urgency_age_max", 365) v.SetDefault("urgency_tags_coefficient", 1.0) v.SetDefault("urgency_project_coefficient", 1.0) v.SetDefault("urgency_waiting_coefficient", -3.0) v.SetDefault("urgency_urgent_tag", "next") v.SetDefault("urgency_urgent_coefficient", 15.0) v.SetDefault("next_limit", 5) // 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("default_report", cfg.DefaultReport) v.Set("color_output", cfg.ColorOutput) v.Set("week_start_day", cfg.WeekStartDay) v.Set("default_due_time", cfg.DefaultDueTime) // Urgency settings v.Set("urgency_due_coefficient", cfg.UrgencyDue) v.Set("urgency_priority_h_coefficient", cfg.UrgencyPriorityH) v.Set("urgency_priority_m_coefficient", cfg.UrgencyPriorityM) v.Set("urgency_priority_d_coefficient", cfg.UrgencyPriorityD) v.Set("urgency_priority_l_coefficient", cfg.UrgencyPriorityL) v.Set("urgency_active_coefficient", cfg.UrgencyActive) v.Set("urgency_age_coefficient", cfg.UrgencyAge) v.Set("urgency_age_max", cfg.UrgencyAgeMax) v.Set("urgency_tags_coefficient", cfg.UrgencyTags) v.Set("urgency_project_coefficient", cfg.UrgencyProject) v.Set("urgency_waiting_coefficient", cfg.UrgencyWaiting) v.Set("urgency_urgent_tag", cfg.UrgencyUrgentTag) v.Set("urgency_urgent_coefficient", cfg.UrgencyUrgentCoeff) v.Set("next_limit", cfg.NextLimit) // 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 }