package bundler

// defaultExportShimPlugin bridges two ESM-strictness mismatches that
// existed in the previous custom bundler:
//
//  1. `export default function Name` also emits `export { Name }` so
//     callers using `import { Name } from "..."` resolve correctly.
//  2. If the source declares `export function/class/const Name` (or
//     `Name`) where Name matches the file's basename, synthesize
//     `export { Name as default }` so callers using `import Name from
//     "..."` resolve correctly.  The old bundler made every IIFE
//     return value available as both the named symbol AND the default
//     import; standard ESM does not, so esbuild needs the shim until
//     callers migrate to named imports throughout.

import (
	"os"
	"path/filepath"
	"regexp"
	"strings"

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

func defaultExportShimPlugin() esbuild.Plugin {
	return esbuild.Plugin{
		Name: "default-export-shim",
		Setup: func(b esbuild.PluginBuild) {
			b.OnLoad(esbuild.OnLoadOptions{Filter: `\.(js|ts)$`}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
				raw, err := os.ReadFile(args.Path)
				if err != nil {
					return esbuild.OnLoadResult{}, err
				}
				out := addNamedExportForDefault(string(raw))
				out = addDefaultExportForFilenameMatch(out, args.Path)
				loader := esbuild.LoaderJS
				if strings.HasSuffix(args.Path, ".ts") {
					loader = esbuild.LoaderTS
				}
				return esbuild.OnLoadResult{Contents: &out, Loader: loader}, nil
			})
		},
	}
}

var reFilenameMatchExport = regexp.MustCompile(`(?m)^export\s+(?:async\s+)?(?:function|class|const|let|var)\s+(\w+)`)
var reExistingDefault = regexp.MustCompile(`(?m)^export\s+default\b`)

// addDefaultExportForFilenameMatch synthesizes `export { Name as
// default }` if the source declares a top-level export whose name
// matches the file's basename and the file does not already have a
// default export.  This preserves the old bundler's behavior where
// `import Foo from "./Foo.js"` resolved against `export function
// Foo(...)`.
func addDefaultExportForFilenameMatch(src, path string) string {
	if reExistingDefault.MatchString(src) {
		return src
	}
	base := filepath.Base(path)
	base = strings.TrimSuffix(base, filepath.Ext(base))
	if base == "" {
		return src
	}
	for _, m := range reFilenameMatchExport.FindAllStringSubmatch(src, -1) {
		if m[1] == base {
			return src + "\nexport { " + base + " as default };\n"
		}
	}
	return src
}

var reExportDefaultDecl = regexp.MustCompile(`(?m)^export\s+default\s+(?:async\s+)?(?:function|class)\s+(\w+)`)

func addNamedExportForDefault(src string) string {
	matches := reExportDefaultDecl.FindAllStringSubmatch(src, -1)
	if len(matches) == 0 {
		return src
	}
	names := make([]string, 0, len(matches))
	seen := map[string]bool{}
	for _, m := range matches {
		if seen[m[1]] {
			continue
		}
		seen[m[1]] = true
		names = append(names, m[1])
	}
	return src + "\nexport { " + strings.Join(names, ", ") + " };\n"
}
