// Package emitter defines the Layer-4 emitter interface (REQ-074,
// I-B-002): the bridge between the declarative *jobspec.WorkloadSpec
// and the server-side files. An Emitter renders a *WorkloadSpec into a
// slice of File artifacts that the SSH-push transport SCPs to peers.
//
// Emitters are registered per workload kind + runtime (e.g.
// "service:wasm", "job:process", "daemonset:wasm"). The Registry looks
// up the right emitter by "<kind>:<runtime>" and delegates. Unknown
// combinations return an error so the caller can fail fast before any
// file is written.
//
// P0c only ships the interface, the Registry, a stub SystemdEmitter
// (process runtime), and the File/Node value types. The full emitter
// implementations (systemd lifecycle hooks, Traefik, Syncthing,
// sockets) land in later phases (P02 Traefik, P04 lifecycle, P08
// sockets, P09 Syncthing, v0.10-P03 secrets).
package emitter

import (
	"fmt"
	"strings"

	"git.cloudinit.dev/coreci/orca/internal/jobspec"
)

// File is a single rendered artifact destined for a peer. The SSH-push
// transport writes Content to Path atomically (write-to-tmp + rename)
// with the given Mode (an octal string like "0644").
type File struct {
	Path    string
	Content string
	Mode    string
}

// Node is the minimal peer description an emitter needs to render
// node-specific paths. It carries the hostname, the runtimes available
// on the node (so emitters can branch), and the node tags (used by
// DaemonSet matching and affinity in P05).
type Node struct {
	Hostname string
	Runtime  []string
	Tags     []string
}

// Emitter renders a *jobspec.WorkloadSpec for a given Node into a slice
// of File artifacts. Implementations are registered with a Registry
// keyed by "<kind>:<runtime>".
type Emitter interface {
	Render(spec *jobspec.WorkloadSpec, node *Node) ([]File, error)
}

// Registry holds emitters keyed by "<kind>:<runtime>" (e.g.
// "service:process", "job:wasm"). The zero-value Registry is not
// usable; construct one with NewRegistry.
type Registry struct {
	emitters map[string]Emitter
}

// NewRegistry returns an empty Registry ready for Register calls.
func NewRegistry() *Registry {
	return &Registry{emitters: make(map[string]Emitter)}
}

// Register registers an Emitter under the given key. The key is
// "<kind>:<runtime>" (e.g. "job:process"). Registering twice under the
// same key overwrites the prior registration (last-wins) to keep the
// surface simple; callers are responsible for not double-registering.
func (r *Registry) Register(key string, e Emitter) {
	r.emitters[key] = e
}

// Render looks up the emitter for "<kind>:<runtime>" in the registry and
// delegates to it. The kind is lowercased so the canonical spec kinds
// (Job, Service, DaemonSet) map to the lowercase registry keys
// ("job:process", "service:wasm", "daemonset:process"). Returns an error
// if the spec is nil, the spec is missing its Kind, the runtime is
// missing, or no emitter is registered for the combination.
func (r *Registry) Render(spec *jobspec.WorkloadSpec, node *Node) ([]File, error) {
	if spec == nil {
		return nil, fmt.Errorf("emitter: spec is nil")
	}
	if strings.TrimSpace(spec.Kind) == "" {
		return nil, fmt.Errorf("emitter: spec kind is empty")
	}
	if spec.Runtime == nil {
		return nil, fmt.Errorf("emitter: spec runtime is nil")
	}
	key := strings.ToLower(spec.Kind) + ":" + spec.Runtime.OneOf
	e, ok := r.emitters[key]
	if !ok {
		return nil, fmt.Errorf("emitter: no emitter registered for %q (kind:runtime)", key)
	}
	return e.Render(spec, node)
}
