// Package zxcvbnjs wraps @zxcvbn-ts/core via goja (ExecJS-style) for exact
// parity between frontend and backend password strength scoring.
//
// The bundled JS contains the same @zxcvbn-ts/core library used in the
// SvelteKit frontend, so scores are identical for any given password.
package zxcvbnjs

import (
	"embed"
	"fmt"
	"log"
	"sync"

	"github.com/dop251/goja"
)

//go:embed bundle.js
var bundleFS embed.FS

var (
	vm     *goja.Runtime
	vmOnce sync.Once
	vmErr  error
)

func getVM() (*goja.Runtime, error) {
	vmOnce.Do(func() {
		vm = goja.New()
		bundleData, err := bundleFS.ReadFile("bundle.js")
		if err != nil {
			vmErr = fmt.Errorf("failed to read zxcvbn bundle: %w", err)
			return
		}
		_, err = vm.RunScript("zxcvbn-bundle.js", string(bundleData))
		if err != nil {
			vmErr = fmt.Errorf("failed to evaluate zxcvbn bundle: %w", err)
			return
		}
	})
	return vm, vmErr
}

// Score returns the zxcvbn-ts score (0-4) for the given password.
// It uses the exact same @zxcvbn-ts/core library as the frontend,
// guaranteeing identical results for the same input.
func Score(password string) (int, error) {
	runtime, err := getVM()
	if err != nil {
		return 0, fmt.Errorf("zxcvbnjs init error: %w", err)
	}

	zxcvbnScore, ok := goja.AssertFunction(runtime.Get("zxcvbnScore"))
	if !ok {
		return 0, fmt.Errorf("zxcvbnScore not found in JS runtime")
	}

	result, err := zxcvbnScore(goja.Undefined(), runtime.ToValue(password))
	if err != nil {
		log.Printf("zxcvbnjs: eval error: %v", err)
		return 0, fmt.Errorf("zxcvbnScore eval failed: %w", err)
	}

	score := int(result.ToInteger())
	if score < 0 || score > 4 {
		return 0, fmt.Errorf("unexpected zxcvbn score: %d", score)
	}
	return score, nil
}
