package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io/fs"
	"mime"
	"os"
	"os/exec"
	"path/filepath"
	"time"
)

type memoryAsset struct {
	contentType string
	content     []byte
	etag        string
	modified    time.Time
}

func buildPackages(root string) (map[string]memoryAsset, error) {
	output, err := os.MkdirTemp("", "yawn-packages-*")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(output)

	if err := runBuild(root, "wasm-pack", "build", filepath.Join(root, "core"),
		"--target", "web", "--out-dir", filepath.Join(output, "pkg"), "--release"); err != nil {
		return nil, err
	}
	if err := runBuild(root, "node", filepath.Join(root, "server", "build.mjs"), root, output); err != nil {
		return nil, err
	}

	assets := make(map[string]memoryAsset)
	for _, name := range []string{
		"core.js",
		"handles.js",
		"worker.js",
		"importer-worker.js",
		"bvh-worker.js",
	} {
		asset, err := loadMemoryAsset(filepath.Join(output, name))
		if err != nil {
			return nil, err
		}
		assets[name] = asset
	}

	err = filepath.WalkDir(filepath.Join(output, "pkg"), func(path string, entry fs.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if entry.IsDir() {
			return nil
		}
		relative, err := filepath.Rel(filepath.Join(output, "pkg"), path)
		if err != nil {
			return err
		}
		asset, err := loadMemoryAsset(path)
		if err != nil {
			return err
		}
		assets[filepath.ToSlash(filepath.Join("pkg", relative))] = asset
		return nil
	})
	if err != nil {
		return nil, err
	}
	return assets, nil
}

func runBuild(directory, name string, arguments ...string) error {
	command := exec.Command(name, arguments...)
	command.Dir = directory
	command.Env = os.Environ()
	var output bytes.Buffer
	command.Stdout = &output
	command.Stderr = &output
	if err := command.Run(); err != nil {
		return fmt.Errorf("%s: %w\n%s", name, err, output.String())
	}
	return nil
}

func loadMemoryAsset(path string) (memoryAsset, error) {
	content, err := os.ReadFile(path)
	if err != nil {
		return memoryAsset{}, err
	}
	digest := sha256.Sum256(content)
	contentType := mime.TypeByExtension(filepath.Ext(path))
	if contentType == "" {
		contentType = "application/octet-stream"
	}
	return memoryAsset{
		contentType: contentType,
		content:     content,
		etag:        fmt.Sprintf("\"%x\"", digest[:12]),
		modified:    time.Now().UTC(),
	}, nil
}
