// Package acl provides pure access control types and functions.
// No I/O, no side effects — only data transformations.
package acl

// Role represents a named role with its users and allowed actions.
type Role struct {
	Name    string
	Users   []string // Matrix user IDs; "*" means everyone
	Actions []string // allowed actions; "*" means all
}

// ACL is the resolved access control list.
type ACL struct {
	roles []Role
}

// Empty returns true if no roles are configured (ACL is inactive).
func (a ACL) Empty() bool {
	return len(a.roles) == 0
}

// RoleFor returns the name of the first role that matches the given userID.
// Specific user entries are checked before wildcard ("*") entries.
// Returns "" if no role matches.
func (a ACL) RoleFor(userID string) string {
	// First pass: exact match
	for _, r := range a.roles {
		for _, u := range r.Users {
			if u == userID {
				return r.Name
			}
		}
	}
	// Second pass: wildcard
	for _, r := range a.roles {
		for _, u := range r.Users {
			if u == "*" {
				return r.Name
			}
		}
	}
	return ""
}

// CanDo checks if a userID is allowed to perform an action.
// If no roles are defined, returns true (open access).
// If roles exist but the user has none, returns false.
func (a ACL) CanDo(userID string, action string) bool {
	if a.Empty() {
		return true
	}

	for _, r := range a.roles {
		if !matchesUser(r.Users, userID) {
			continue
		}
		if matchesAction(r.Actions, action) {
			return true
		}
	}
	return false
}

// AllowedUsers returns the deduplicated list of all explicit user IDs
// (excluding "*") that have at least one role.
func (a ACL) AllowedUsers() []string {
	seen := make(map[string]bool)
	var result []string
	for _, r := range a.roles {
		for _, u := range r.Users {
			if u != "*" && !seen[u] {
				seen[u] = true
				result = append(result, u)
			}
		}
	}
	return result
}

func matchesUser(users []string, userID string) bool {
	for _, u := range users {
		if u == userID || u == "*" {
			return true
		}
	}
	return false
}

func matchesAction(actions []string, action string) bool {
	for _, a := range actions {
		if a == "*" || a == action {
			return true
		}
		// Wildcard prefix: "command:*" matches "command:deploy"
		if len(a) > 1 && a[len(a)-1] == '*' {
			prefix := a[:len(a)-1]
			if len(action) >= len(prefix) && action[:len(prefix)] == prefix {
				return true
			}
		}
	}
	return false
}
