func GetConfigFilePath() string {
	return filepath.Join(appDir, fileName)
}
GetConfigFilePath constructs the full path to the configuration file
package config
import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"github.com/ilyakaznacheev/cleanenv"
	"github.com/imdario/mergo"
	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
)
const (
	envAppDir = "TEXTNOTE_DIR"
	fileName = ".config.yml"
)
var (
	appDir = os.Getenv(envAppDir)
)
type Opts struct {
	AppDir                  string      `yaml:"-"`  
	Header                  HeaderOpts  `yaml:"header"`
	Section                 SectionOpts `yaml:"section"`
	File                    FileOpts    `yaml:"file"`
	Archive                 ArchiveOpts `yaml:"archive"`
	Cli                     CliOpts     `yaml:"cli"`
	TemplateFileCountThresh int         `yaml:"templateFileCountTresh" env:"TEXTNOTE_TEMPLATE_FILE_COUNT_THRESH" env-description:"threshold for warning too many template files"`
}
type HeaderOpts struct {
	Prefix           string `yaml:"prefix" env:"TEXTNOTE_HEADER_PREFIX" env-description:"prefix to attach to header"`
	Suffix           string `yaml:"suffix" env:"TEXTNOTE_HEADER_SUFFIX" env-description:"suffix to attach to header"`
	TrailingNewlines int    `yaml:"trailingNewlines" env:"TEXTNOTE_HEADER_TRAILING_NEWLINES" env-description:"number of newlines to attach to end of header"`
	TimeFormat       string `yaml:"timeFormat" env:"TEXTNOTE_HEADER_TIME_FORMAT" env-description:"formatting string to form headers from timestamps"`
}
type SectionOpts struct {
	Prefix           string   `yaml:"prefix" env:"TEXTNOTE_SECTION_PREFIX" env-description:"prefix to attach to section names"`
	Suffix           string   `yaml:"suffix" env:"TEXTNOTE_SECTION_SUFFIX" env-description:"suffix to attach to section names"`
	TrailingNewlines int      `yaml:"trailingNewlines" env:"TEXTNOTE_SECTION_TRAILING_NEWLINES" env-description:"number of newlines to attach to end of each section"`
	Names            []string `yaml:"names" env:"TEXTNOTE_SECTION_NAMES" env-description:"section names"`
}
type FileOpts struct {
	Ext        string `yaml:"ext" env:"TEXTNOTE_FILE_EXT" env-description:"extension for all files written"`
	TimeFormat string `yaml:"timeFormat" env:"TEXTNOTE_FILE_TIME_FORMAT" env-description:"formatting string to form file names from timestamps"`
	CursorLine int    `yaml:"cursorLine" env:"TEXTNOTE_FILE_CURSOR_LINE" env-description:"line to place cursor when opening"`
}
type ArchiveOpts struct {
	AfterDays                int    `yaml:"afterDays" env:"TEXTNOTE_ARCHIVE_AFTER_DAYS" env-description:"number of days after which to archive a file"`
	FilePrefix               string `yaml:"filePrefix" env:"TEXTNOTE_ARCHIVE_FILE_PREFIX" env-description:"prefix attached to the file name of all archive files"`
	HeaderPrefix             string `yaml:"headerPrefix" env:"TEXTNOTE_ARCHIVE_HEADER_PREFIX" env-description:"override header prefix for archive files"`
	HeaderSuffix             string `yaml:"headerSuffix" env:"TEXTNOTE_ARCHIVE_HEADER_SUFFIX" env-description:"override header suffix for archive files"`
	SectionContentPrefix     string `yaml:"sectionContentPrefix" env:"TEXTNOTE_ARCHIVE_SECTION_CONTENT_PREFIX" env-description:"prefix to attach to section content date"`
	SectionContentSuffix     string `yaml:"sectionContentSuffix" env:"TEXTNOTE_ARCHIVE_SECTION_CONTENT_SUFFIX" env-description:"suffix to attach to section content date"`
	SectionContentTimeFormat string `yaml:"sectionContentTimeFormat" env:"TEXTNOTE_ARCHIVE_SECTION_CONTENT_TIME_FORMAT" env-description:"formatting string dated section content"`
	MonthTimeFormat          string `yaml:"monthTimeFormat" env:"TEXTNOTE_ARCHIVE_MONTH_TIME_FORMAT" env-description:"formatting string for month archive timestamps"`
}
type CliOpts struct {
	TimeFormat string `yaml:"timeFormat" env:"TEXTNOTE_CLI_TIME_FORMAT" env-description:"formatting string for timestamp CLI flags"`
}
func getDefaultOpts() Opts {
	return Opts{
		Header: HeaderOpts{
			Prefix:           "",
			Suffix:           "",
			TrailingNewlines: 1,
			TimeFormat:       "[Mon] 02 Jan 2006",
		},
		Section: SectionOpts{
			Prefix:           "___",
			Suffix:           "___",
			TrailingNewlines: 3,
			Names: []string{
				"TODO",
				"DONE",
				"NOTES",
			},
		},
		File: FileOpts{
			Ext:        "txt",
			TimeFormat: "2006-01-02",
			CursorLine: 4,
		},
		Archive: ArchiveOpts{
			AfterDays:                14,
			FilePrefix:               "archive-",
			HeaderPrefix:             "ARCHIVE ",
			HeaderSuffix:             "",
			SectionContentPrefix:     "[",
			SectionContentSuffix:     "]",
			SectionContentTimeFormat: "2006-01-02",
			MonthTimeFormat:          "Jan2006",
		},
		Cli: CliOpts{
			TimeFormat: "2006-01-02",
		},
		TemplateFileCountThresh: 90,
	}
}
func Load() (Opts, error) {
	opts := Opts{}
	err := cleanenv.ReadConfig(GetConfigFilePath(), &opts)
	if err != nil {
		return opts, errors.Wrap(err, "unable to read config file")
	}
	defaults := getDefaultOpts()
	err = mergo.Merge(&opts, defaults)
	if err != nil {
		return opts, errors.Wrap(err, "unable to integrate configuration from file with defaults")
	}
	opts.AppDir = appDir
	err = ValidateOpts(opts)
	if err != nil {
		return opts, errors.Wrapf(err, "configuration error in [%s]", fileName)
	}
	return opts, nil
}
func CreateIfNotExists() error {
	configPath := GetConfigFilePath()
	_, err := os.Stat(configPath)
	if !os.IsNotExist(err) {
		return nil
	}
	defaults := getDefaultOpts()
	yml, err := yaml.Marshal(defaults)
	if err != nil {
		return errors.Wrap(err, "unable to generate config file")
	}
	err = os.WriteFile(configPath, yml, 0644)
	if err != nil {
		return errors.Wrap(err, fmt.Sprintf("unable to create configuration file: [%s]", configPath))
	}
	log.Printf("created default configuration file: [%s]", configPath)
	return nil
}
func EnsureAppDir() error {
	if appDir == "" {
		return fmt.Errorf("required environment variable [%s] is not set", envAppDir)
	}
	finfo, err := os.Stat(appDir)
	if os.IsNotExist(err) {
		err := os.MkdirAll(appDir, 0755)
		if err != nil {
			return err
		}
		log.Printf("created directory [%s]", appDir)
		return nil
	}
	if !finfo.IsDir() {
		return fmt.Errorf("[%s=%s] must be a directory", envAppDir, appDir)
	}
	return nil
}
func ValidateOpts(opts Opts) error {
	if opts.AppDir == "" {
		return fmt.Errorf("must include path to application directory in %s environment variable", envAppDir)
	}
	if len(opts.Section.Names) == 0 {
		return errors.New("must include at least one section")
	}
	uniq := map[string]struct{}{}
	for _, name := range opts.Section.Names {
		uniq[name] = struct{}{}
	}
	if len(uniq) != len(opts.Section.Names) {
		return errors.New("section names must be unique")
	}
	if opts.Archive.FilePrefix == "" || strings.ReplaceAll(opts.Archive.FilePrefix, " ", "") == "" {
		return errors.New("file prefix for archives must not be empty")
	}
	if opts.Archive.AfterDays < 1 {
		return errors.New("archive after days must be greater than or equal to 1")
	}
	if strings.HasPrefix(opts.File.Ext, ".") {
		return errors.New("file extension must not include leading dot")
	}
	if opts.File.CursorLine < 0 {
		return errors.New("cursor line must not be negative")
	}
	if opts.TemplateFileCountThresh <= opts.Archive.AfterDays {
		return errors.New("template file count threshold must be larger than archive after days")
	}
	return nil
}
func DescribeEnvVars() string {
	header := ""
	description, err := cleanenv.GetDescription(&Opts{}, &header)
	if err != nil {
		return ""
	}
	return description
}