package formula

import (
	"regexp"

	"github.com/nexuscrm/shared/pkg/models"
)

// permissionKeyRE matches $Permission.X references and captures X.
// Used to dynamically discover which permission keys an expression needs so
// super-user paths can inject them as true without hardcoding L1 names.
var permissionKeyRE = regexp.MustCompile(`\$Permission\.([A-Za-z_][A-Za-z0-9_]*)`)

// ExtractPermissionKeys returns all unique permission names X referenced via
// $Permission.X in expr. String-literal content is stripped first to avoid
// false positives from $Permission.X appearing inside quoted strings.
func ExtractPermissionKeys(expr string) []string {
	sanitized := stripStringLiterals(expr)
	matches := permissionKeyRE.FindAllStringSubmatch(sanitized, -1)
	if len(matches) == 0 {
		return nil
	}
	seen := make(map[string]struct{}, len(matches))
	keys := make([]string, 0, len(matches))
	for _, m := range matches {
		k := m[1]
		if _, dup := seen[k]; dup {
			continue
		}
		seen[k] = struct{}{}
		keys = append(keys, k)
	}
	return keys
}

// InjectSuperUserPermissions ensures that Variables["__Permission"] contains
// true for every $Permission.X key referenced in expr, when isSuperUser is
// true. This is the shared injection point for all formula consumers (flow
// formulas, approval criteria, action expressions, action-input defaults).
//
// For non-super-users the function is a no-op; the real permission map was
// already populated from _System_Permission rows by the calling service.
//
// The function never hardcodes L1 app-pack permission names — it reads names
// directly from the expression string via ExtractPermissionKeys.
func InjectSuperUserPermissions(variables map[string]interface{}, expr string, isSuperUser bool) {
	if !isSuperUser {
		return
	}
	keys := ExtractPermissionKeys(expr)
	if len(keys) == 0 {
		return
	}
	permMap, ok := variables["__Permission"].(map[string]bool)
	if !ok {
		permMap = make(map[string]bool, len(keys))
		variables["__Permission"] = permMap
	}
	for _, k := range keys {
		if !permMap[k] {
			permMap[k] = true
		}
	}
}

// stripStringLiterals replaces quoted string content with placeholder tokens
// so that $Permission.X patterns inside string literals are not matched.
func stripStringLiterals(expr string) string {
	out := make([]byte, 0, len(expr))
	i := 0
	for i < len(expr) {
		ch := expr[i]
		if ch == '"' || ch == '\'' {
			quote := ch
			out = append(out, ch)
			i++
			for i < len(expr) {
				c := expr[i]
				if c == '\\' && i+1 < len(expr) && expr[i+1] == quote {
					// escaped quote inside string — consume both chars but emit placeholders
					out = append(out, '_', '_')
					i += 2
					continue
				}
				if c == quote {
					out = append(out, c)
					i++
					break
				}
				out = append(out, '_') // mask literal content
				i++
			}
			continue
		}
		out = append(out, ch)
		i++
	}
	return string(out)
}

// BuildFormulaUserContext returns the formula evaluation user context for the
// running session — the `__User`, `__Profile`, and `__UserRole` merge-field
// globals that all formula consumers (flow formulas, validation rules,
// before-save expressions, …) MUST share as a single read path.
//
// This is the literal "one fact, one SSoT, one read path" surface for the
// Salesforce-modeled formula globals (CLAUDE.md §"Architecture Rule" and
// §"Salesforce Boundary"): values are sourced ENTIRELY from the
// UserSession token, matching Salesforce v63's $User / $Profile / $UserRole
// semantics — session-bound, stamped at login, frozen for the session
// lifetime, refreshed on re-login. Synthetic platform actors use the same
// behavior-bearing ProfileID/ProfileName fields as every other caller; they do
// not get a separate formula-only profile identity.
//
// DB lookups inside formula context construction are FORBIDDEN — they would
// create an unannotated stricter-than-Salesforce delta that lets validation
// rules and flow formulas evaluate the same merge field against different
// facts within a single save transaction.
//
// Returned shape:
//
//	__User     {Id, ID, Name, Email, ProfileId, UserRoleId}
//	__Profile  {Id, Name}
//	__UserRole {Id, Name}
//
// Callers may add their own non-formula-global context entries (e.g. platform
// permission metadata for validation rules) on top of this base. They must
// not overwrite or shadow the keys returned here.
//
// Returns nil when user is nil so callers can pass-through nil safely.
func BuildFormulaUserContext(user *models.UserSession) map[string]interface{} {
	if user == nil {
		return nil
	}

	var email interface{}
	if user.Email != nil {
		email = *user.Email
	}
	var roleIDForUser interface{}
	if user.RoleID != nil {
		roleIDForUser = *user.RoleID
	}

	var roleID, roleName string
	if user.RoleID != nil {
		roleID = *user.RoleID
	}
	if user.RoleName != nil {
		roleName = *user.RoleName
	}
	return map[string]interface{}{
		"__User": map[string]interface{}{
			"Id":         user.ID,
			"ID":         user.ID, // Salesforce alias
			"Name":       user.Name,
			"Email":      email,
			"ProfileId":  user.ProfileID,
			"UserRoleId": roleIDForUser,
		},
		"__Profile": map[string]interface{}{
			"Id":   user.ProfileID,
			"Name": user.ProfileName,
		},
		"__UserRole": map[string]interface{}{
			"Id":   roleID,
			"Name": roleName,
		},
	}
}
