//go:build ignore

// This command implements "garbage collection" for cuetxtar output artifacts.
// It removes all out/* and diff/* entries from .txtar files used in the
// CUE tests that are not actually output targets of any test.
//
// It does that by running all the CUE tests to find out all the references
// (see the CUETXTAR_GC_URI logic in txtar.go), running
// a little HTTP server to gather the results.
//
// Run as:
//
//	go run ./gc.go
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"slices"
	"strings"
	"sync"

	"golang.org/x/tools/txtar"
)

func main() {
	h := &handler{
		references: make(map[string]map[string]bool),
	}
	srv := httptest.NewServer(h)
	os.Setenv("CUETXTAR_GC_URI", srv.URL+"/ref")

	cmd := exec.Command("go", "test", "-count=1", "cuelang.org/go/...")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	h.mu.Lock()
	defer h.mu.Unlock()
	for txtarFile, refs := range h.references {
		hasDiff := false
		for name := range refs {
			if strings.HasPrefix(name, "diff/") && !isDiffAnnotationFile(name) {
				hasDiff = true
				break
			}
		}
		a, err := txtar.ParseFile(txtarFile)
		if err != nil {
			log.Fatalf("error parsing txtar file: %v", err)
		}
		files := slices.DeleteFunc(a.Files, func(f txtar.File) bool {
			if isOutputFile(f.Name) && !refs[f.Name] {
				// Unreferenced output file.
				return true
			}
			if !hasDiff && isDiffAnnotationFile(f.Name) {
				// A diff-related annotation file when there are no diffs present.
				return true
			}
			return false
		})
		if len(files) == len(a.Files) {
			continue
		}
		fmt.Printf("garbage collecting %d entries from %s\n", len(a.Files)-len(files), txtarFile)
		a.Files = files
		if err := os.WriteFile(txtarFile, txtar.Format(a), 0o644); err != nil {
			log.Fatal(err)
		}
	}
}

func isOutputFile(name string) bool {
	switch {
	case strings.HasPrefix(name, "out/"):
		return true
	case !strings.HasPrefix(name, "diff/"):
		return false
	case isDiffAnnotationFile(name):
		return false
	}
	return true
}

func isDiffAnnotationFile(name string) bool {
	switch {
	case !strings.HasPrefix(name, "diff/"):
		return false
	case strings.Contains(name, "/todo/"):
		// e.g.
		// /diff/todo/p3
		// /diff/dependencies-v3/todo/p2
		return true
	case strings.HasSuffix(name, "/explanation"):
		// e.g.
		// /diff/explanation
		// /diff/doc/explanation
		return true
	}
	return false
}

type handler struct {
	mu         sync.Mutex
	references map[string]map[string]bool
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Method != "PUT" || req.URL.Path != "/ref" {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}
	var body struct {
		TxtarFile   string   `json:"txtarfile"`
		RetainFiles []string `json:"retainFiles"`
	}
	data, _ := io.ReadAll(req.Body)
	if err := json.Unmarshal(data, &body); err != nil {
		http.Error(w, "bad request body", http.StatusBadRequest)
		return
	}
	h.mu.Lock()
	defer h.mu.Unlock()
	refs := h.references[body.TxtarFile]
	if refs == nil {
		refs = make(map[string]bool)
		h.references[body.TxtarFile] = refs
	}
	for _, name := range body.RetainFiles {
		refs[name] = true
	}
}
