package jsbundler

// esbuild silently resolves a `.js` import to a sibling `.ts`/`.tsx`/`.jsx`,
// which lets misnamed specifiers slip through.  This check catches them up
// front so every import path names the file that actually exists on disk.

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

type importViolation struct {
	file   string
	line   int
	spec   string // the written specifier, e.g. "./Icons.js"
	actual string // the corrected specifier, e.g. "./Icons.ts"
}

// reImportSpec matches a quoted relative module specifier ending in ".js".
var reImportSpec = regexp.MustCompile(`(["'])((?:\.\.?/)[^"']*?\.js)["']`)

// tsExtensions are the non-.js source extensions a .js specifier may
// actually resolve to, in esbuild's resolution order.
var tsExtensions = []string{".ts", ".tsx", ".jsx"}

// checkImportExtensions scans every JS/TS source under frontend/src for
// relative import specifiers written with a ".js" extension whose literal
// target does not exist but a sibling ".ts"/".tsx"/".jsx" does. esbuild
// resolves these transparently, so they bundle fine — but the import path
// lies about the file it points to. Specifiers that resolve to a real ".js"
// file, and dangling specifiers with no sibling at all, are left for esbuild.
func checkImportExtensions() []importViolation {
	var violations []importViolation
	srcRoot := filepath.Join(frontendDir, "src")

	filepath.WalkDir(srcRoot, func(path string, d os.DirEntry, err error) error {
		if err != nil || d.IsDir() {
			return err
		}
		switch filepath.Ext(path) {
		case ".js", ".ts", ".tsx", ".jsx":
		default:
			return nil
		}

		raw, err := os.ReadFile(path)
		if err != nil {
			return err
		}
		dir := filepath.Dir(path)

		for i, line := range strings.Split(string(raw), "\n") {
			for _, m := range reImportSpec.FindAllStringSubmatch(line, -1) {
				spec := m[2]
				if _, statErr := os.Stat(filepath.Join(dir, spec)); statErr == nil {
					continue // resolves to a real .js file — fine
				}
				base := strings.TrimSuffix(spec, ".js")
				for _, ext := range tsExtensions {
					if _, statErr := os.Stat(filepath.Join(dir, base+ext)); statErr == nil {
						violations = append(violations, importViolation{
							file:   path,
							line:   i + 1,
							spec:   spec,
							actual: base + ext,
						})
						break
					}
				}
			}
		}
		return nil
	})

	return violations
}

func reportImportViolations(violations []importViolation) {
	fmt.Fprintf(os.Stderr, "\nImport extension check failed: %d import(s) use a .js extension for a non-.js file.\n", len(violations))
	for _, v := range violations {
		fmt.Fprintf(os.Stderr, "  %s:%d: %q should be %q\n", v.file, v.line, v.spec, v.actual)
	}
	fmt.Fprintln(os.Stderr, "Rename each specifier to match the file's real extension.")
}
