// Package benchmarks loads the harness benchmark task definitions from
// `.td/benchmarks/` and exposes them for later phase runners.
//
// Frontmatter is a small YAML-ish block at the top:
//
//	---
//	id: decomposable-v1
//	kind: decomposable
//	title: Short label
//	---
//
// Body is plain markdown, available as Definition.Body.
package benchmarks

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

// Kind is the benchmark category.
type Kind string

const (
	KindDecomposable   Kind = "decomposable"
	KindCrossCutting   Kind = "cross-cutting"
	KindUnderspecified Kind = "underspecified"
)

// Definition is one benchmark task.
type Definition struct {
	ID    string
	Kind  Kind
	Title string
	Path  string
	Body  string
}

// LoadAll reads every `*.md` file under dir and parses its frontmatter +
// body. Returns the definitions sorted by ID.
func LoadAll(dir string) ([]Definition, error) {
	if dir == "" {
		return nil, errors.New("benchmarks: empty dir")
	}
	var out []Definition
	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		if !strings.HasSuffix(path, ".md") {
			return nil
		}
		raw, err := os.ReadFile(path)
		if err != nil {
			return err
		}
		def, err := parse(raw)
		if err != nil {
			return fmt.Errorf("benchmarks: %s: %w", path, err)
		}
		def.Path = path
		out = append(out, def)
		return nil
	})
	if err != nil {
		return nil, err
	}
	sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
	return out, nil
}

func parse(raw []byte) (Definition, error) {
	s := string(raw)
	if !strings.HasPrefix(s, "---") {
		return Definition{}, errors.New("missing frontmatter")
	}
	rest := strings.TrimPrefix(s, "---")
	rest = strings.TrimLeft(rest, "\r\n")
	end := strings.Index(rest, "\n---")
	if end < 0 {
		return Definition{}, errors.New("frontmatter not terminated")
	}
	fm := rest[:end]
	body := strings.TrimLeft(rest[end+len("\n---"):], "\r\n")

	def := Definition{Body: body}
	for _, line := range strings.Split(fm, "\n") {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		k, v, ok := strings.Cut(line, ":")
		if !ok {
			continue
		}
		k = strings.TrimSpace(k)
		v = strings.TrimSpace(v)
		switch k {
		case "id":
			def.ID = v
		case "kind":
			def.Kind = Kind(v)
		case "title":
			def.Title = v
		}
	}
	if def.ID == "" || def.Kind == "" {
		return def, errors.New("missing id or kind")
	}
	return def, nil
}
