// Solid JSX/TSX compilation (part of package bundler): compiles Solid JSX/TSX to
// optimized Solid dom-expressions output (template cloning + fine-grained
// updates) with a Go-native compiler — no Node, no Babel, no goja. The compiler
// lives in compile_solid.go (parser) and compile_solid_gen.go (codegen);
// segment.go supplies the top-level splitter it uses to find component
// declarations for solid-refresh instrumentation.
//
// Compile is the prod path (SSR + release bundles); CompileDev adds solid-refresh
// HMR instrumentation. Both are plain function calls — fast enough that the old
// per-declaration incremental caching is gone. A small in-memory cache dedups
// identical transforms within a process (e.g. a module served to several page
// loads under HMR); there is no on-disk cache and no compiler bootstrap.
package bundler

import (
	"crypto/sha256"
	"encoding/hex"
	"os"
	"path/filepath"
	"sync"

	esbuild "github.com/evanw/esbuild/pkg/api"
)

// cache dedups transforms by content within a process, so a module unchanged
// across dev page reloads (or repeated in a build) isn't recompiled.
var cache = struct {
	sync.RWMutex
	m map[string]string
}{m: map[string]string{}}

// cacheKey keys a transform by filename + mode + source.
func cacheKey(src, filename string, dev bool) string {
	h := sha256.New()
	h.Write([]byte(filename))
	h.Write([]byte{0})
	if dev {
		h.Write([]byte{1})
	} else {
		h.Write([]byte{0})
	}
	h.Write([]byte{0})
	h.Write([]byte(src))
	return hex.EncodeToString(h.Sum(nil))
}

// Compile compiles one JSX/TSX source to Solid dom-expressions output (prod: no
// HMR instrumentation). Results are cached by content for this process.
func Compile(src, filename string) (string, error) { return compileCached(src, filename, false) }

// CompileDev is Compile with solid-refresh HMR instrumentation — the dev module
// server's entry.
func CompileDev(src, filename string) (string, error) { return compileCached(src, filename, true) }

func compileCached(src, filename string, dev bool) (string, error) {
	key := cacheKey(src, filename, dev)
	cache.RLock()
	if out, ok := cache.m[key]; ok {
		cache.RUnlock()
		return out, nil
	}
	cache.RUnlock()

	out, err := compileSolidGo(src, filename, dev)
	if err != nil {
		return "", err
	}
	cache.Lock()
	cache.m[key] = out
	cache.Unlock()
	return out, nil
}

// Plugin returns the esbuild OnLoad hook that Solid-compiles every .tsx/.jsx file
// in the bundle graph (prod path). Plain .ts/.js files are left to esbuild.
func Plugin() esbuild.Plugin {
	return esbuild.Plugin{
		Name: "solid-jsx",
		Setup: func(b esbuild.PluginBuild) {
			b.OnLoad(esbuild.OnLoadOptions{Filter: `\.(tsx|jsx)$`}, func(a esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
				data, err := os.ReadFile(a.Path)
				if err != nil {
					return esbuild.OnLoadResult{}, err
				}
				out, err := Compile(string(data), filepath.Base(a.Path))
				if err != nil {
					return esbuild.OnLoadResult{}, err
				}
				loader := esbuild.LoaderJS
				return esbuild.OnLoadResult{Contents: &out, Loader: loader}, nil
			})
		},
	}
}
