// Package gitops implements config-as-code for repo-backed workloads: a
// dockerfile/static workload can read a small .tinyforge.yml from its own repo
// that declares a subset of its deploy config. The package is deliberately
// decoupled from the store and source plugins — it takes a RepoRef (repo
// coords + a decrypted token) and a live source_config blob, and returns a
// validated merged config + a field-level drift report. It never writes to the
// database and never decides to deploy.
//
// v1 scope (see plans/gitops/PLAN.md): only source_config-resident fields are
// overlayable, and the set is source-aware (dockerfile: port/healthcheck/
// deploy_strategy; static: deploy_strategy). env/faces live in separate stores
// and are intentionally out of v1; the typed ApplyPlan reserves their slots.
package gitops

import (
	"bytes"
	"errors"
	"fmt"
	"io"

	"gopkg.in/yaml.v3"
)

// Spec is the parsed shape of a .tinyforge.yml file (v1).
type Spec struct {
	Version int        `yaml:"version"`
	Deploy  DeploySpec `yaml:"deploy"`
}

// DeploySpec carries the overlayable deploy fields. Pointers so an omitted key
// is distinguishable from a zero value — only present (non-nil) fields are
// applied or drift-compared, so an absent key never clears live config.
type DeploySpec struct {
	Port           *int    `yaml:"port"`
	Healthcheck    *string `yaml:"healthcheck"`
	DeployStrategy *string `yaml:"deploy_strategy"`
}

// ParseSpec decodes a .tinyforge.yml body. Unknown keys are rejected
// (KnownFields) so a typo or an unsupported field — e.g. someone trying to
// declare env/faces in v1 — surfaces as an error instead of being silently
// dropped. Only version 1 is accepted.
func ParseSpec(data []byte) (Spec, error) {
	var s Spec
	dec := yaml.NewDecoder(bytes.NewReader(data))
	dec.KnownFields(true)
	if err := dec.Decode(&s); err != nil {
		if errors.Is(err, io.EOF) {
			return Spec{}, fmt.Errorf("gitops: empty .tinyforge.yml")
		}
		return Spec{}, fmt.Errorf("gitops: parse .tinyforge.yml: %w", err)
	}
	if s.Version != 1 {
		return Spec{}, fmt.Errorf("gitops: unsupported version %d (want 1)", s.Version)
	}
	return s, nil
}
