package formula

import (
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// UserEntityMaps holds the three sub-entity maps that represent the current
// user in both formula-engine scope (keyed "__Profile" / "__User" /
// "__UserRole") and Decision-condition context (keyed "$Profile" / "$User" /
// "$UserRole"). Callers apply whichever key prefix they need; the field names
// and values are always derived from this single helper, satisfying CLAUDE.md
// R10 Two-Path Consistency.
type UserEntityMaps struct {
	// Profile is {"Id": profileID, "Name": profileName}.
	Profile map[string]interface{}
	// User is {"Id": userId, "Name": userName, "Email": email, "ProfileId": profileID}.
	User map[string]interface{}
	// Role is {"Id": roleID, "Name": roleName}. Always non-nil; empty strings
	// when the session carries no role.
	Role map[string]interface{}
	// Email is the user's email address (may be "" when session.Email is nil).
	Email string
}

// BuildUserEntityMaps extracts the three SF global-variable sub-entities from
// a UserSession. It is the single SSoT that decides "what fields go into the
// Profile entity" and "what fields go into the UserRole entity". Both
// BuildUserFormulaScope (formula-engine path) and injectUserGlobalsIntoContext
// (Decision-condition path) call this function, so the shapes are always
// identical regardless of which evaluation engine is running.
//
// A nil user returns zero-value structs (empty maps) so callers can safely
// range over the result.
func BuildUserEntityMaps(user *models.UserSession) UserEntityMaps {
	empty := UserEntityMaps{
		Profile: map[string]interface{}{constants.FieldID: "", constants.FieldName: ""},
		User:    map[string]interface{}{constants.FieldID: "", constants.FieldName: "", constants.FieldEmail: "", constants.FieldProfileID: ""},
		Role:    map[string]interface{}{constants.FieldID: "", constants.FieldName: ""},
	}
	if user == nil {
		return empty
	}
	email := ""
	if user.Email != nil {
		email = *user.Email
	}
	roleID := ""
	roleName := ""
	if user.RoleID != nil {
		roleID = *user.RoleID
	}
	if user.RoleName != nil {
		roleName = *user.RoleName
	}
	return UserEntityMaps{
		Profile: map[string]interface{}{
			constants.FieldID:   user.ProfileID,
			constants.FieldName: user.ProfileName,
		},
		User: map[string]interface{}{
			constants.FieldID:        user.ID,
			constants.FieldName:      user.Name,
			constants.FieldEmail:     email,
			constants.FieldProfileID: user.ProfileID,
		},
		Role: map[string]interface{}{
			constants.FieldID:   roleID,
			constants.FieldName: roleName,
		},
		Email: email,
	}
}

// BuildUserFormulaScope constructs the map[string]interface{} that represents
// the current user inside a formula evaluation context (both flow-formula and
// validation-rule paths).
//
// This is the single SSoT for the $Profile / $User / $UserRole global-variable
// shapes. Both the automation (flow) engine and the records (validation-rule)
// engine call this function, satisfying CLAUDE.md R10 Two-Path Consistency.
//
// Shape (post-normalise "__" prefix matches engine.go's flatten logic):
//
//	__Profile  — {"Id": profileID, "Name": profileName}
//	__User     — {"Id": userId, "Name": userName, "Email": email, "ProfileId": profileID}
//	__UserRole — {"Id": roleID, "Name": roleName}  (always present; empty strings when no role)
//
// Flat top-level aliases (Id, Name, Email, ProfileId) are also included so
// {{User.Email}} merge-field expressions continue to work after scope unification.
//
// No DB calls are made. ProfileName and RoleName are stamped onto
// models.UserSession at login time by auth.AuthService.resolveProfileName and
// resolveRoleName respectively, and re-stamped on every session refresh. Using
// the session values ensures flow formulas and validation rules always agree,
// regardless of mid-session admin changes (which take effect on next login —
// see auth_service.go::resolveProfileName for the known constraint).
//
// For system automation users (scheduled / record-triggered async flows),
// ProfileName is "Automated Process" per SF convention (set in system_user.go).
//
// A nil user returns an empty non-nil map so callers can safely range over it.
func BuildUserFormulaScope(user *models.UserSession) map[string]interface{} {
	if user == nil {
		return make(map[string]interface{})
	}
	m := BuildUserEntityMaps(user)
	scope := map[string]interface{}{
		// SF global-variable sub-entities (post-normalise keys that engine.go
		// flattens into top-level scope so $Profile.Name → __Profile.Name).
		"__Profile":  m.Profile,
		"__User":     m.User,
		"__UserRole": m.Role,
		// Flat user fields: ActionService.normalizeActionFormulaExpression
		// translates "User.Email" → "user.Email" and the engine resolves it
		// via env["user"]["Email"]. Keep these so {{User.Email}} email-merge
		// fields continue to work after the scope was unified.
		constants.FieldID:        user.ID,
		constants.FieldName:      user.Name,
		constants.FieldEmail:     m.Email,
		constants.FieldProfileID: user.ProfileID,
	}
	return scope
}
