// Package imagecat is the published catalog of banger image bundles
// (rootfs.ext4 + manifest.json, packaged as a .tar.zst). It ships
// embedded in the banger binary. Downloading a bundle is the fast
// path for pulling a curated banger image — the rootfs is already
// flattened, ownership-fixed, and has banger's guest agents injected
// at build time.
//
// This package is the metadata + fetch layer. Writing to the banger
// image store is done by higher layers (the daemon's PullImage
// orchestrator), so imagecat has no local-storage concept of its own.
package imagecat

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"strings"
)

//go:embed catalog.json
var embeddedCatalog []byte

// Catalog is the list of pullable image bundles compiled into this
// banger binary.
type Catalog struct {
	Version int        `json:"version"`
	Entries []CatEntry `json:"entries"`
}

// CatEntry describes one downloadable bundle. TarballURL points at a
// .tar.zst containing rootfs.ext4 and manifest.json.
type CatEntry struct {
	Name          string `json:"name"`
	Distro        string `json:"distro,omitempty"`
	Arch          string `json:"arch,omitempty"`
	KernelRef     string `json:"kernel_ref,omitempty"` // kernelcat entry name to pair with
	TarballURL    string `json:"tarball_url"`
	TarballSHA256 string `json:"tarball_sha256"`
	SizeBytes     int64  `json:"size_bytes,omitempty"`
	Description   string `json:"description,omitempty"`
}

// LoadEmbedded returns the catalog compiled into this banger binary.
func LoadEmbedded() (Catalog, error) {
	return ParseCatalog(embeddedCatalog)
}

// ParseCatalog decodes a catalog.json payload. An empty payload is
// valid and yields a zero Catalog.
func ParseCatalog(data []byte) (Catalog, error) {
	var cat Catalog
	if len(data) == 0 {
		return cat, nil
	}
	if err := json.Unmarshal(data, &cat); err != nil {
		return Catalog{}, fmt.Errorf("parse catalog: %w", err)
	}
	return cat, nil
}

// Lookup returns the entry matching name, or os.ErrNotExist.
func (c Catalog) Lookup(name string) (CatEntry, error) {
	for _, e := range c.Entries {
		if e.Name == name {
			return e, nil
		}
	}
	return CatEntry{}, os.ErrNotExist
}

// namePattern accepts short filesystem-safe identifiers. Same rule as
// kernelcat so `--kernel-ref` and bundle-name refs share syntax.
var namePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`)

// ValidateName returns an error unless name is a non-empty identifier
// of alphanumerics, dots, hyphens, and underscores, starting with an
// alphanumeric and at most 64 characters long.
func ValidateName(name string) error {
	if strings.TrimSpace(name) == "" {
		return fmt.Errorf("image name is required")
	}
	if !namePattern.MatchString(name) {
		return fmt.Errorf("invalid image name %q: use alphanumerics, dots, hyphens, underscores (<=64 chars, starts with alphanumeric)", name)
	}
	return nil
}
