<?php

namespace PloverCRM\Common;

use PloverCRM\Core\Services\SystemFieldRegistry;

/**
 * Role Manager
 * 
 * Handles CRM roles and field-level permissions
 */
class RoleManager
{
    const OPTION_NAME = 'plover_crm_roles';
    const SEED_VERSION_OPTION = 'plover_crm_role_seed_version';
    const SUPER_ADMIN_SLUG = 'plover_crm_super_admin';
    const ACCESS_SOURCE_NONE = 'none';
    const ACCESS_SOURCE_ASSIGNED_ROLE = 'assigned_role';
    const ACCESS_SOURCE_WORDPRESS_ADMINISTRATOR = 'wordpress_administrator';

    /**
     * Available entities for permissions
     */
    const ENTITIES = [
        'notes' => ['create', 'read', 'update', 'delete'],
        'list_definitions' => ['create', 'read', 'update', 'delete'],
        'tag_definitions' => ['create', 'read', 'update', 'delete'],
    ];

    private const HIDDEN_STANDARD_FIELD_SLUGS = [
        'id',
        'user_id',
        'hash',
        'full_name',
        'contact_owner',
        'company_id',
        'prefix',
        'timezone',
        'source',
        'status',
        'contact_type',
        'last_activity',
        'last_note',
        'total_points',
        'life_time_value',
        'avatar',
        'photo',
        'plover_crm_avatar',
    ];

    private const NON_CONFIGURABLE_STANDARD_FIELD_PERMISSIONS = [
        'avatar' => [
            'create' => true,
            'read' => true,
            'update' => true,
        ],
        'photo' => [
            'create' => true,
            'read' => true,
            'update' => true,
        ],
        'plover_crm_avatar' => [
            'create' => true,
            'read' => true,
            'update' => true,
        ],
    ];

    private const STANDARD_FIELD_LABEL_OVERRIDES = [
        'date_of_birth' => 'Date of Birth',
        'last_activity' => 'Last Activity',
        'last_note' => 'Last Note',
        'life_time_value' => 'Lifetime Value',
        'status' => 'Status',
        'tags' => 'Tags',
        'total_points' => 'Total Points',
        'updated_at' => 'Updated At',
        SystemFieldRegistry::FIELD_STAGE => 'Pipeline Stage',
    ];

    private const STANDARD_FIELD_DESCRIPTIONS = [
        'prefix' => 'Honorific such as Mr, Mrs, or Dr.',
        'last_note' => 'Latest note attached to the contact.',
        SystemFieldRegistry::FIELD_STAGE => 'PloverCRM pipeline stage used across the web and mobile apps.',
    ];

    private const STANDARD_FIELD_ORDER = [
        'prefix',
        'first_name',
        'last_name',
        'email',
        'phone',
        'timezone',
        'address_line_1',
        'address_line_2',
        'city',
        'state',
        'postal_code',
        'country',
        'source',
        'status',
        'contact_type',
        'date_of_birth',
        'created_at',
        'updated_at',
        'last_activity',
        'lists',
        'tags',
        'last_note',
        SystemFieldRegistry::FIELD_STAGE,
        'total_points',
        'life_time_value',
    ];

    /**
     * Initialize role manager
     */
    public static function init(): void
    {
        self::seedStarterRolesIfNeeded();
        self::ensureProtectedStarterRoles();
    }

    /**
     * Seed starter roles one time for each workspace.
     */
    public static function seedStarterRolesIfNeeded(): void
    {
        $seedVersion = (int) get_option(self::SEED_VERSION_OPTION, 0);
        $shouldUpdateSeedVersion = $seedVersion < RoleTemplateRegistry::SEED_VERSION;

        foreach (RoleTemplateRegistry::getStarterRolePayloads() as $template) {
            $existingRole = self::findSeededRoleForTemplate($template);

            if ($existingRole) {
                if (self::shouldRefreshSeededRole($existingRole, $template)) {
                    self::syncSeededRoleTemplate($existingRole, $template);
                    continue;
                }

                self::syncRoleTemplateMetadata($existingRole, $template);
                continue;
            }

            self::createRole($template);
        }

        if ($shouldUpdateSeedVersion) {
            update_option(self::SEED_VERSION_OPTION, RoleTemplateRegistry::SEED_VERSION);
        }
    }

    /**
     * Ensure protected starter roles exist even if role data is partially lost.
     */
    public static function ensureProtectedStarterRoles(): void
    {
        $template = RoleTemplateRegistry::getTemplate(RoleTemplateRegistry::TEMPLATE_SUPER_ADMIN);

        if (!$template) {
            return;
        }

        $existingRole = self::findSeededRoleForTemplate($template);

        if ($existingRole) {
            if (self::shouldRefreshSeededRole($existingRole, $template)) {
                self::syncSeededRoleTemplate($existingRole, $template);
                return;
            }

            self::syncRoleTemplateMetadata($existingRole, $template);
            return;
        }

        self::createRole($template);
    }

    /**
     * Back-compat wrapper for older callers.
     */
    public static function ensureSuperAdminRole(): void
    {
        self::ensureProtectedStarterRoles();
    }

    /**
     * Get all roles
     */
    public static function getAllRoles(): array
    {
        $roles = get_option(self::OPTION_NAME, []);

        if (!is_array($roles)) {
            return [];
        }

        $sanitized = array_map([self::class, 'sanitizeRoleData'], $roles);

        if ($sanitized !== $roles) {
            update_option(self::OPTION_NAME, $sanitized);
        }

        return $sanitized;
    }

    /**
     * Get role by ID
     */
    public static function getRole(string $id): ?array
    {
        $roles = self::getAllRoles();

        foreach ($roles as $role) {
            if ($role['id'] === $id) {
                return $role;
            }
        }

        return null;
    }

    /**
     * Get role by slug
     */
    public static function getRoleBySlug(string $slug): ?array
    {
        $roles = self::getAllRoles();

        foreach ($roles as $role) {
            if ($role['slug'] === $slug) {
                return $role;
            }
        }

        return null;
    }

    /**
     * Create a new role
     */
    public static function createRole(array $data): ?array
    {
        $roles = self::getAllRoles();

        // Generate ID if not provided
        if (empty($data['id'])) {
            $data['id'] = wp_generate_uuid4();
        }

        // Generate slug if not provided
        if (empty($data['slug'])) {
            $data['slug'] = 'plover_crm_' . sanitize_title($data['name']);
        }

        // Check for duplicate slug
        foreach ($roles as $role) {
            if ($role['slug'] === $data['slug']) {
                return null;
            }
        }

        $role = self::sanitizeRoleData($data);
        $roles[] = $role;

        update_option(self::OPTION_NAME, $roles);

        return $role;
    }

    /**
     * Update a role
     */
    public static function updateRole(string $id, array $data): ?array
    {
        $roles = self::getAllRoles();
        $updated = false;

        foreach ($roles as $index => $role) {
            if ($role['id'] === $id) {
                if (!array_key_exists('slug', $data) || $data['slug'] === '') {
                    $data['slug'] = $role['slug'];
                }

                // Prevent changing super admin status
                if (!empty($role['is_super_admin'])) {
                    $data['is_super_admin'] = true;
                    $data['slug'] = $role['slug'];
                }

                foreach (['template_key', 'is_system_seeded', 'is_protected', 'is_template_pristine', 'template_version', 'sort_order'] as $metadataKey) {
                    if (!array_key_exists($metadataKey, $data)) {
                        $data[$metadataKey] = $role[$metadataKey] ?? null;
                    }
                }

                if (!empty($role['is_system_seeded'])) {
                    $data['is_template_pristine'] = false;
                }

                $data['id'] = $id;
                $roles[$index] = self::sanitizeRoleData($data);
                $updated = true;
                break;
            }
        }

        if (!$updated) {
            return null;
        }

        update_option(self::OPTION_NAME, $roles);

        return $roles[$index];
    }

    /**
     * Delete a role
     */
    public static function deleteRole(string $id): bool
    {
        $roles = self::getAllRoles();
        $newRoles = [];

        if (self::countUsersWithRole($id) > 0) {
            return false;
        }

        foreach ($roles as $role) {
            // Prevent deleting super admin
            if ($role['id'] === $id && (!empty($role['is_super_admin']) || !empty($role['is_protected']))) {
                return false;
            }

            if ($role['id'] !== $id) {
                $newRoles[] = $role;
            }
        }

        if (count($newRoles) === count($roles)) {
            return false;
        }

        // Remove role from all users who have it
        self::removeRoleFromAllUsers($id);

        update_option(self::OPTION_NAME, $newRoles);

        return true;
    }

    /**
     * Valid scope values for view and update
     */
    const SCOPE_ALL = 'all';
    const SCOPE_OWNED = 'owned';

    /**
     * Sanitize role data
     */
    private static function sanitizeRoleData(array $data): array
    {
        return [
            'id' => sanitize_text_field($data['id'] ?? ''),
            'name' => sanitize_text_field($data['name'] ?? ''),
            'slug' => sanitize_title($data['slug'] ?? ''),
            'template_key' => self::sanitizeTemplateKey($data['template_key'] ?? null),
            'is_system_seeded' => !empty($data['is_system_seeded']),
            'is_protected' => !empty($data['is_protected']),
            'is_template_pristine' => !empty($data['is_template_pristine']),
            'template_version' => max(0, (int) ($data['template_version'] ?? 0)),
            'sort_order' => max(0, (int) ($data['sort_order'] ?? 1000)),
            'is_super_admin' => !empty($data['is_super_admin']),
            'can_delete_contacts' => !empty($data['can_delete_contacts']),
            'view_scope' => self::sanitizeScope($data['view_scope'] ?? self::SCOPE_ALL),
            'update_scope' => self::sanitizeScope($data['update_scope'] ?? self::SCOPE_ALL),
            'field_permissions' => self::sanitizeFieldPermissions($data['field_permissions'] ?? []),
            'custom_field_permissions' => self::sanitizeFieldPermissions($data['custom_field_permissions'] ?? []),
            'tracking_permissions' => self::sanitizeTrackingPermissions($data['tracking_permissions'] ?? []),
            'entity_permissions' => self::sanitizeEntityPermissions($data['entity_permissions'] ?? []),
            'capabilities' => self::sanitizeCapabilities((array) ($data['capabilities'] ?? [])),
        ];
    }

    private static function sanitizeTemplateKey($templateKey): ?string
    {
        $templateKey = is_string($templateKey) ? sanitize_key($templateKey) : '';

        return $templateKey !== '' ? $templateKey : null;
    }

    private static function findSeededRoleForTemplate(array $template): ?array
    {
        $templateKey = (string) ($template['template_key'] ?? '');
        $templateId = (string) ($template['id'] ?? '');
        $templateSlug = (string) ($template['slug'] ?? '');

        foreach (self::getAllRoles() as $role) {
            if ($templateKey !== '' && ($role['template_key'] ?? null) === $templateKey) {
                return $role;
            }

            if ($templateId !== '' && ($role['id'] ?? '') === $templateId) {
                return $role;
            }

            if ($templateSlug !== '' && ($role['slug'] ?? '') === $templateSlug) {
                return $role;
            }
        }

        return null;
    }

    private static function syncRoleTemplateMetadata(array $existingRole, array $template): void
    {
        $metadataKeys = ['template_key', 'is_system_seeded', 'is_protected', 'template_version', 'sort_order'];
        $updatedRole = $existingRole;
        $shouldUpdate = false;

        foreach ($metadataKeys as $metadataKey) {
            $templateValue = $template[$metadataKey] ?? null;

            if (($existingRole[$metadataKey] ?? null) === $templateValue) {
                continue;
            }

            $updatedRole[$metadataKey] = $templateValue;
            $shouldUpdate = true;
        }

        if (!$shouldUpdate) {
            return;
        }

        $roles = self::getAllRoles();

        foreach ($roles as $index => $role) {
            if (($role['id'] ?? '') !== ($existingRole['id'] ?? '')) {
                continue;
            }

            $roles[$index] = self::sanitizeRoleData($updatedRole);
            update_option(self::OPTION_NAME, $roles);
            return;
        }
    }

    private static function shouldRefreshSeededRole(array $role, array $template): bool
    {
        if (empty($role['is_system_seeded'])) {
            return false;
        }

        $isPristine = array_key_exists('is_template_pristine', $role)
            ? !empty($role['is_template_pristine'])
            : true;

        if (!$isPristine) {
            return false;
        }

        if ((int) ($role['template_version'] ?? 0) < (int) ($template['template_version'] ?? 0)) {
            return true;
        }

        return !self::seededRoleMatchesTemplate($role, $template);
    }

    private static function syncSeededRoleTemplate(array $existingRole, array $template): void
    {
        $roles = self::getAllRoles();
        $replacement = array_merge(
            $template,
            [
                'id' => $existingRole['id'] ?? ($template['id'] ?? ''),
            ]
        );

        foreach ($roles as $index => $role) {
            if (($role['id'] ?? '') !== ($existingRole['id'] ?? '')) {
                continue;
            }

            $roles[$index] = self::sanitizeRoleData($replacement);
            update_option(self::OPTION_NAME, $roles);
            return;
        }
    }

    private static function seededRoleMatchesTemplate(array $role, array $template): bool
    {
        $managedKeys = [
            'name',
            'slug',
            'template_key',
            'is_system_seeded',
            'is_protected',
            'is_template_pristine',
            'template_version',
            'sort_order',
            'is_super_admin',
            'can_delete_contacts',
            'view_scope',
            'update_scope',
            'field_permissions',
            'custom_field_permissions',
            'tracking_permissions',
            'entity_permissions',
            'capabilities',
        ];

        $normalizedRole = self::sanitizeRoleData(array_intersect_key($role, array_flip($managedKeys)));
        $normalizedTemplate = self::sanitizeRoleData(array_intersect_key($template, array_flip($managedKeys)));

        return wp_json_encode($normalizedRole) === wp_json_encode($normalizedTemplate);
    }

    /**
     * Sanitize scope value
     */
    private static function sanitizeScope(string $scope): string
    {
        return in_array($scope, [self::SCOPE_ALL, self::SCOPE_OWNED]) ? $scope : self::SCOPE_ALL;
    }

    /**
     * Sanitize field permissions
     */
    private static function sanitizeFieldPermissions(array $permissions): array
    {
        $sanitized = [];

        foreach ($permissions as $field => $perms) {
            $sanitized[sanitize_text_field($field)] = [
                'create' => !empty($perms['create']),
                'read' => !empty($perms['read']),
                'update' => !empty($perms['update']),
            ];
        }

        return $sanitized;
    }

    /**
     * Sanitize grouped tracking permissions.
     */
    private static function sanitizeTrackingPermissions(array $permissions): array
    {
        $sanitized = [];

        foreach (SystemFieldRegistry::getTrackingPermissionDefinitions() as $definition) {
            $key = sanitize_key((string) ($definition['key'] ?? ''));

            if ($key === '') {
                continue;
            }

            $sanitized[$key] = [
                'read' => !empty($permissions[$key]['read']),
            ];
        }

        return $sanitized;
    }

    /**
     * Sanitize entity permissions
     */
    private static function sanitizeEntityPermissions(array $permissions): array
    {
        $sanitized = [];

        foreach (self::ENTITIES as $entity => $allowedActions) {
            if (!isset($permissions[$entity])) {
                continue;
            }

            $sanitized[$entity] = [];
            foreach ($allowedActions as $action) {
                $sanitized[$entity][$action] = !empty($permissions[$entity][$action]);
            }
        }

        return $sanitized;
    }

    private static function sanitizeCapabilities(array $capabilities): array
    {
        $assignable = CapabilityRegistry::getAssignableCapabilityKeys();

        return array_values(array_filter(
            CapabilityRegistry::expandCapabilities($capabilities),
            static fn(string $capability): bool => in_array($capability, $assignable, true)
        ));
    }

    /**
     * Assign role to user
     */
    public static function assignRoleToUser(int $userId, string $roleId): bool
    {
        $role = self::getRole($roleId);

        if (!$role) {
            return false;
        }

        $record = CrmUserManager::getByPublicId($userId);

        if (!$record && self::isWordPressAdministrator($userId)) {
            $record = CrmUserManager::ensureWordPressAdministratorRecord($userId);
        }

        if (!$record) {
            return false;
        }

        $wpUserId = (int) ($record['wp_user_id'] ?? 0);

        if ($wpUserId <= 0 || self::isWordPressAdministrator($wpUserId)) {
            return false;
        }

        if (empty($record['role_id']) && !LicenseChecker::canAssignAnotherCrmUser()) {
            return false;
        }

        return CrmUserManager::updateRoleForPublicUser((int) $record['id'], $roleId);
    }

    /**
     * Remove role from user
     */
    public static function removeRoleFromUser(int $userId): bool
    {
        return CrmUserManager::updateRoleForPublicUser($userId, null);
    }

    /**
     * Remove role from all users
     */
    private static function removeRoleFromAllUsers(string $roleId): void
    {
        foreach (CrmUserManager::getNativeUsersWithRoles() as $user) {
            if (($user['role_id'] ?? null) !== $roleId) {
                continue;
            }

            CrmUserManager::updateRoleForPublicUser((int) $user['id'], null);
        }
    }

    /**
     * Get user's CRM role
     */
    public static function getUserRole(int $userId): ?array
    {
        $record = CrmUserManager::getByWordPressUserId($userId);

        if (!$record && self::isWordPressAdministrator($userId)) {
            $record = CrmUserManager::ensureWordPressAdministratorRecord($userId);
        }

        $roleId = $record['role_id'] ?? '';

        if (empty($roleId)) {
            return null;
        }

        return self::getRole($roleId);
    }

    /**
     * Get the current user's CRM access context.
     */
    public static function getUserAccessContext(int $userId): array
    {
        $assignedRole = self::getUserRole($userId);
        $isWordPressAdministrator = self::isWordPressAdministrator($userId);

        if ($isWordPressAdministrator) {
            CrmUserManager::ensureWordPressAdministratorRecord($userId);
        }

        if ($isWordPressAdministrator) {
            $effectiveRole = self::getAdministratorFallbackRole();
            $source = self::ACCESS_SOURCE_WORDPRESS_ADMINISTRATOR;
        } elseif ($assignedRole) {
            $effectiveRole = self::normalizeRoleForPermissions($assignedRole);
            $source = self::ACCESS_SOURCE_ASSIGNED_ROLE;
        } else {
            $effectiveRole = null;
            $source = self::ACCESS_SOURCE_NONE;
        }

        return [
            'assigned_role' => $assignedRole,
            'effective_role' => $effectiveRole,
            'access' => [
                'has_crm_access' => $effectiveRole !== null,
                'source' => $source,
                'is_wordpress_administrator' => $isWordPressAdministrator,
                'has_assigned_role' => $assignedRole !== null,
            ],
        ];
    }

    /**
     * Get the effective CRM role used for permissions.
     *
     * WordPress administrators are always treated as CRM super admins.
     * Non-admin users inherit their effective permissions from their assigned
     * CRM role, if one exists.
     */
    public static function getEffectiveUserRole(int $userId): ?array
    {
        return self::getUserAccessContext($userId)['effective_role'];
    }

    /**
     * Get all non-administrator users with assigned CRM roles.
     */
    public static function getUsersWithRoles(): array
    {
        $result = [];

        foreach (CrmUserManager::getNativeUsersWithRoles() as $user) {
            if (empty($user['role_id'])) {
                continue;
            }

            $role = self::getRole((string) $user['role_id']);

            $result[] = [
                'id' => $user['id'],
                'username' => $user['username'],
                'email' => $user['email'],
                'display_name' => $user['display_name'],
                'avatar_url' => $user['avatar_url'] ?? null,
                'avatar_source' => $user['avatar_source'] ?? CrmUserManager::AVATAR_SOURCE_NONE,
                'role_id' => $user['role_id'],
                'role_name' => $role ? $role['name'] : null,
                'user_type' => $user['user_type'],
            ];
        }

        return $result;
    }

    /**
     * Check whether the user currently has an assigned CRM role.
     */
    public static function userHasAssignedRole(int $userId): bool
    {
        $record = CrmUserManager::getByPublicId($userId);

        if (!$record) {
            $record = CrmUserManager::getByWordPressUserId($userId);
        }

        return !empty($record['role_id']);
    }

    /**
     * Count assigned CRM users that consume a seat.
     *
     * WordPress administrators always retain CRM access outside the CRM role
     * seat model, so they are excluded from this count.
     */
    public static function countAssignedUsers(): int
    {
        return CrmUserManager::countAssignedUsers();
    }

    /**
     * Count native CRM users assigned to a specific role.
     */
    public static function countUsersWithRole(string $roleId): int
    {
        return (int) (self::getRoleUsageCounts()[$roleId] ?? 0);
    }

    /**
     * Get a role_id => assigned native CRM user count map.
     */
    public static function getRoleUsageCounts(): array
    {
        $counts = [];

        foreach (CrmUserManager::getNativeUsersWithRoles() as $user) {
            $roleId = (string) ($user['role_id'] ?? '');

            if ($roleId === '') {
                continue;
            }

            $counts[$roleId] = (int) ($counts[$roleId] ?? 0) + 1;
        }

        return $counts;
    }

    /**
     * Attach assigned user counts to a role payload.
     */
    public static function appendUsageToRole(array $role, ?array $usageCounts = null): array
    {
        if ($usageCounts === null) {
            $usageCounts = self::getRoleUsageCounts();
        }

        $role['assigned_users_count'] = (int) ($usageCounts[$role['id'] ?? ''] ?? 0);

        return $role;
    }

    /**
     * Get available contact scope options for role-management UIs.
     */
    public static function getAvailableScopes(): array
    {
        return [
            [
                'value' => self::SCOPE_ALL,
                'label' => 'All Contacts',
                'description' => 'Can work with contacts across the full workspace.',
            ],
            [
                'value' => self::SCOPE_OWNED,
                'label' => 'Owned Only',
                'description' => 'Limited to contacts assigned to that CRM user.',
            ],
        ];
    }

    /**
     * Get available entities for permission matrices.
     */
    public static function getAvailableEntities(): array
    {
        $labels = [
            'notes' => 'Notes',
            'list_definitions' => 'Lists',
            'tag_definitions' => 'Tags',
        ];

        $entities = [];

        foreach (self::ENTITIES as $entity => $actions) {
            $entities[] = [
                'key' => $entity,
                'label' => $labels[$entity] ?? ucwords(str_replace('_', ' ', $entity)),
                'actions' => array_values($actions),
            ];
        }

        return $entities;
    }

    /**
     * Check if user has CRM access
     */
    public static function userHasAccess(int $userId): bool
    {
        return self::getEffectiveUserRole($userId) !== null;
    }

    /**
     * Check if user is super admin
     */
    public static function isSuperAdmin(int $userId): bool
    {
        $role = self::getEffectiveUserRole($userId);
        return $role && !empty($role['is_super_admin']);
    }

    /**
     * Check if user can delete contacts
     */
    public static function canDeleteContacts(int $userId): bool
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return false;
        }

        if (!empty($role['is_super_admin'])) {
            return true;
        }

        return !empty($role['can_delete_contacts']);
    }

    /**
     * Get user's view scope
     * 
     * @param int $userId User ID
     * @return string 'all' or 'owned'
     */
    public static function getViewScope(int $userId): string
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return self::SCOPE_OWNED; // Default to most restrictive
        }

        if (!empty($role['is_super_admin'])) {
            return self::SCOPE_ALL;
        }

        return $role['view_scope'] ?? self::SCOPE_ALL;
    }

    /**
     * Get user's update scope
     * 
     * @param int $userId User ID
     * @return string 'all' or 'owned'
     */
    public static function getUpdateScope(int $userId): string
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return self::SCOPE_OWNED; // Default to most restrictive
        }

        if (!empty($role['is_super_admin'])) {
            return self::SCOPE_ALL;
        }

        return $role['update_scope'] ?? self::SCOPE_ALL;
    }

    /**
     * Check if user can view a specific contact based on ownership
     * 
     * @param int $userId User ID
     * @param int|null $contactOwnerId Contact owner's user ID (null = unassigned)
     * @return bool
     */
    public static function canViewContact(int $userId, ?int $contactOwnerId): bool
    {
        $viewScope = self::getViewScope($userId);

        // Can view all contacts
        if ($viewScope === self::SCOPE_ALL) {
            return true;
        }

        // Can only view owned contacts
        // Unassigned contacts (null owner) are visible to everyone
        if ($contactOwnerId === null) {
            return true;
        }

        return $contactOwnerId === $userId;
    }

    /**
     * Check if user can update a specific contact based on ownership
     * 
     * @param int $userId User ID
     * @param int|null $contactOwnerId Contact owner's user ID (null = unassigned)
     * @return bool
     */
    public static function canUpdateContact(int $userId, ?int $contactOwnerId): bool
    {
        $updateScope = self::getUpdateScope($userId);

        // Can update all contacts
        if ($updateScope === self::SCOPE_ALL) {
            return true;
        }

        // Can only update owned contacts
        // Unassigned contacts can be updated by anyone with update_scope=all only
        if ($contactOwnerId === null) {
            return false;
        }

        return $contactOwnerId === $userId;
    }

    /**
     * Check if user can delete a specific contact based on ownership and permission
     * 
     * @param int $userId User ID
     * @param int|null $contactOwnerId Contact owner's user ID (null = unassigned)
     * @return bool
     */
    public static function canDeleteContact(int $userId, ?int $contactOwnerId): bool
    {
        // First check if user has delete permission at all
        if (!self::canDeleteContacts($userId)) {
            return false;
        }

        // Super admin can delete any contact
        if (self::isSuperAdmin($userId)) {
            return true;
        }

        // Delete follows update_scope
        return self::canUpdateContact($userId, $contactOwnerId);
    }

    /**
     * Check if user can reassign a contact
     * 
     * @param int $userId User ID
     * @param int|null $contactOwnerId Contact owner's user ID (null = unassigned)
     * @return bool
     */
    public static function canReassignContact(int $userId, ?int $contactOwnerId): bool
    {
        // Super admin can reassign any contact
        if (self::isSuperAdmin($userId)) {
            return true;
        }

        // User can reassign if they can update the contact
        return self::canUpdateContact($userId, $contactOwnerId);
    }

    /**
     * Check if user can claim an unassigned contact
     * Anyone with CRM access can claim unassigned contacts
     * 
     * @param int $userId User ID
     * @param int|null $contactOwnerId Contact owner's user ID
     * @return bool
     */
    public static function canClaimContact(int $userId, ?int $contactOwnerId): bool
    {
        // Can only claim unassigned contacts
        if ($contactOwnerId !== null) {
            return false;
        }

        // Any user with CRM access can claim
        return self::userHasAccess($userId);
    }

    /**
     * Get all users with effective CRM access (for reassignment dropdowns)
     * 
     * @return array Array of users with CRM access
     */
    public static function getCRMUsers(): array
    {
        $result = [];

        $allUsers = array_merge(
            CrmUserManager::getNativeUsersWithRoles(),
            CrmUserManager::getWordPressAdministratorsOverview()
        );

        foreach ($allUsers as $user) {
            $wpUserId = (int) ($user['wp_user_id'] ?? 0);

            if ($wpUserId <= 0 || !self::userHasAccess($wpUserId)) {
                continue;
            }

            $accessContext = self::getUserAccessContext($wpUserId);
            $assignedRole = $accessContext['assigned_role'];
            $effectiveRole = $accessContext['effective_role'];

            $result[] = [
                'id' => $user['id'],
                'username' => $user['username'],
                'email' => $user['email'],
                'display_name' => $user['display_name'],
                'role_id' => $assignedRole['id'] ?? null,
                'role_name' => $effectiveRole['name'] ?? null,
                'access_source' => $accessContext['access']['source'] ?? self::ACCESS_SOURCE_NONE,
            ];
        }

        return $result;
    }

    /**
     * Check field permission for user
     * 
     * @param int $userId User ID
     * @param string $field Field name
     * @param string $action Action to check (create, read, update)
     * @return bool
     */
    public static function checkFieldPermission(int $userId, string $field, string $action): bool
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return false;
        }

        return self::checkFieldPermissionForRole($role, $field, $action);
    }

    private static function checkFieldPermissionForRole(array $role, string $field, string $action): bool
    {
        // Super admin has all permissions
        if (!empty($role['is_super_admin'])) {
            return true;
        }

        $derivedPermission = self::checkDerivedFieldPermissionForRole($role, $field, $action);

        if ($derivedPermission !== null) {
            return $derivedPermission;
        }

        if (SystemFieldRegistry::isIpInformationSlug($field)) {
            return self::checkReadOnlyGroupPermissionForRole(
                $role,
                SystemFieldRegistry::TRACKING_PERMISSION_IP_INFORMATION,
                $action
            );
        }

        if (SystemFieldRegistry::isTrackingSlug($field)) {
            return self::checkReadOnlyGroupPermissionForRole(
                $role,
                SystemFieldRegistry::TRACKING_PERMISSION_CONTACT_DATA,
                $action
            );
        }

        // Check in field_permissions first, then custom_field_permissions
        $permissions = $role['field_permissions'][$field] ?? $role['custom_field_permissions'][$field] ?? null;

        if (!$permissions) {
            return false;
        }

        return !empty($permissions[$action]);
    }

    private static function checkDerivedFieldPermissionForRole(array $role, string $field, string $action): ?bool
    {
        if ($field !== 'full_name') {
            return null;
        }

        return self::roleHasExplicitFieldPermission($role, 'first_name', $action)
            && self::roleHasExplicitFieldPermission($role, 'last_name', $action);
    }

    private static function roleHasExplicitFieldPermission(array $role, string $field, string $action): bool
    {
        $permissions = $role['field_permissions'][$field] ?? null;

        return is_array($permissions) && !empty($permissions[$action]);
    }

    private static function checkReadOnlyGroupPermissionForRole(array $role, string $groupKey, string $action): bool
    {
        if ($action !== 'read') {
            return false;
        }

        return !empty($role['tracking_permissions'][$groupKey]['read']);
    }

    /**
     * Check entity permission for user
     */
    public static function checkEntityPermission(int $userId, string $entity, string $action): bool
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return false;
        }

        // Super admin has all permissions
        if (!empty($role['is_super_admin'])) {
            return true;
        }

        // Validate entity and action
        if (!isset(self::ENTITIES[$entity]) || !in_array($action, self::ENTITIES[$entity])) {
            return false;
        }

        $permissions = $role['entity_permissions'][$entity] ?? null;

        if (!$permissions) {
            return false;
        }

        return !empty($permissions[$action]);
    }

    /**
     * Check whether a CRM user has a specific workspace capability.
     */
    public static function checkCapabilityPermission(int $userId, string $capability): bool
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return false;
        }

        if (!empty($role['is_super_admin'])) {
            return true;
        }

        $capabilities = array_values((array) ($role['capabilities'] ?? []));

        return in_array($capability, $capabilities, true);
    }

    /**
     * Get readable fields for user
     */
    public static function getReadableFields(int $userId): array
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return [];
        }

        if (!empty($role['is_super_admin'])) {
            return ['*']; // All fields
        }

        $fields = [];

        // Check standard field permissions
        foreach ($role['field_permissions'] as $field => $perms) {
            if (!empty($perms['read'])) {
                $fields[] = $field;
            }
        }

        // Check custom field permissions
        foreach ($role['custom_field_permissions'] ?? [] as $field => $perms) {
            if (!empty($perms['read'])) {
                $fields[] = $field;
            }
        }

        if (self::checkReadOnlyGroupPermissionForRole($role, SystemFieldRegistry::TRACKING_PERMISSION_CONTACT_DATA, 'read')) {
            $fields = array_merge($fields, SystemFieldRegistry::getTrackingFieldSlugs());
        }

        if (self::checkReadOnlyGroupPermissionForRole($role, SystemFieldRegistry::TRACKING_PERMISSION_IP_INFORMATION, 'read')) {
            $fields = array_merge($fields, SystemFieldRegistry::getIpInformationFieldSlugs());
        }

        if (self::checkDerivedFieldPermissionForRole($role, 'full_name', 'read')) {
            $fields[] = 'full_name';
        }

        return array_values(array_unique($fields));
    }

    /**
     * Filter contact data based on user permissions
     */
    public static function filterContactData(array $contact, int $userId, string $action = 'read'): array
    {
        $role = self::getEffectiveUserRole($userId);

        if (!$role) {
            return [];
        }

        // Super admin sees everything
        if (!empty($role['is_super_admin'])) {
            return $contact;
        }

        $filtered = [];

        // Always include ID
        if (isset($contact['id'])) {
            $filtered['id'] = $contact['id'];
        }

        foreach ($contact as $field => $value) {
            if ($field === 'id') {
                continue;
            }

            // Handle custom_fields specially - it's an array of {label, key, value} objects
            if ($field === 'custom_fields' && is_array($value)) {
                $filteredCustomFields = [];
                foreach ($value as $cf) {
                    // Handle both array format [{label, key, value}] and key-value format
                    if (is_array($cf) && isset($cf['key'])) {
                        // Array of objects format
                        if (self::checkFieldPermissionForRole($role, $cf['key'], $action)) {
                            $filteredCustomFields[] = $cf;
                        }
                    } else {
                        // Key-value format
                        $cfKey = is_string($cf) ? $cf : (string)$cf;
                        if (self::checkFieldPermissionForRole($role, $cfKey, $action)) {
                            $filteredCustomFields[$cfKey] = $cf;
                        }
                    }
                }
                if (!empty($filteredCustomFields)) {
                    $filtered['custom_fields'] = $filteredCustomFields;
                }
                continue;
            }

            if (self::checkFieldPermissionForRole($role, $field, $action)) {
                $filtered[$field] = $value;
            }
        }

        self::appendDerivedContactFields($filtered, $contact, $role, $action);

        return $filtered;
    }

    private static function appendDerivedContactFields(array &$filtered, array $contact, array $role, string $action): void
    {
        if (!self::checkDerivedFieldPermissionForRole($role, 'full_name', $action)) {
            return;
        }

        $fullName = self::buildDerivedFullName($filtered, $contact);

        if ($fullName !== '') {
            $filtered['full_name'] = $fullName;
        }
    }

    private static function buildDerivedFullName(array $filtered, array $contact): string
    {
        $firstName = self::normalizeFieldString($filtered['first_name'] ?? $contact['first_name'] ?? null);
        $lastName = self::normalizeFieldString($filtered['last_name'] ?? $contact['last_name'] ?? null);
        $composedName = trim($firstName . ' ' . $lastName);

        if ($composedName !== '') {
            return $composedName;
        }

        return self::normalizeFieldString($filtered['full_name'] ?? $contact['full_name'] ?? null);
    }

    private static function normalizeFieldString($value): string
    {
        if (!is_string($value)) {
            return '';
        }

        return trim($value);
    }

    public static function isWordPressAdministrator(int $userId): bool
    {
        $user = get_userdata($userId);

        return (bool) ($user && $user->has_cap('manage_options'));
    }

    private static function getAdministratorFallbackRole(): array
    {
        return [
            'id' => 'wp-admin-fallback',
            'name' => 'WordPress Administrator',
            'slug' => 'wordpress-administrator',
            'is_super_admin' => true,
            'can_delete_contacts' => true,
            'view_scope' => self::SCOPE_ALL,
            'update_scope' => self::SCOPE_ALL,
            'field_permissions' => 'full_access',
            'custom_field_permissions' => 'full_access',
            'tracking_permissions' => [
                SystemFieldRegistry::TRACKING_PERMISSION_CONTACT_DATA => ['read' => true],
                SystemFieldRegistry::TRACKING_PERMISSION_IP_INFORMATION => ['read' => true],
            ],
            'entity_permissions' => [],
            'capabilities' => CapabilityRegistry::getAllCapabilityKeys(),
        ];
    }

    private static function normalizeRoleForPermissions(array $role): array
    {
        if (empty($role['is_super_admin'])) {
            return self::mergeNonConfigurableFieldPermissionsIntoRole($role);
        }

        $role['can_delete_contacts'] = true;
        $role['view_scope'] = self::SCOPE_ALL;
        $role['update_scope'] = self::SCOPE_ALL;
        $role['field_permissions'] = 'full_access';
        $role['custom_field_permissions'] = 'full_access';
        $role['tracking_permissions'] = [
            SystemFieldRegistry::TRACKING_PERMISSION_CONTACT_DATA => ['read' => true],
            SystemFieldRegistry::TRACKING_PERMISSION_IP_INFORMATION => ['read' => true],
        ];
        $role['entity_permissions'] = is_array($role['entity_permissions'] ?? null)
            ? $role['entity_permissions']
            : [];
        $role['capabilities'] = CapabilityRegistry::getAllCapabilityKeys();

        return $role;
    }

    private static function mergeNonConfigurableFieldPermissionsIntoRole(array $role): array
    {
        $fieldPermissions = is_array($role['field_permissions'] ?? null)
            ? $role['field_permissions']
            : [];

        foreach (self::NON_CONFIGURABLE_STANDARD_FIELD_PERMISSIONS as $field => $permissions) {
            $fieldPermissions[$field] = array_merge(
                [
                    'create' => false,
                    'read' => false,
                    'update' => false,
                ],
                is_array($fieldPermissions[$field] ?? null) ? $fieldPermissions[$field] : [],
                $permissions
            );
        }

        $role['field_permissions'] = $fieldPermissions;

        return $role;
    }

    /**
     * Get all available fields for permission matrix
     */
    public static function getAvailableFields(): array
    {
        $crm = \PloverCRM\Core\Plugin::getInstance()->getContainer()->get('crm');

        if (!$crm->isAvailable()) {
            return [
                'standard' => [],
                'custom' => [],
                'tracking' => SystemFieldRegistry::getTrackingPermissionDefinitions(),
            ];
        }

        $customFields = [];

        foreach ((array) $crm->getAllCustomFields() as $field) {
            $slug = (string) ($field['slug'] ?? '');

            if ($slug === '') {
                continue;
            }

            $isSystemManaged =
                !empty($field['system']) ||
                !empty($field['owner_capability']) ||
                SystemFieldRegistry::isSystemSlug($slug);

            if ($isSystemManaged) {
                continue;
            }

            $customFields[] = $field;
        }

        return [
            'standard' => self::getAvailableStandardFieldDefinitions($crm),
            'custom' => $customFields,
            'tracking' => SystemFieldRegistry::getTrackingPermissionDefinitions(),
        ];
    }

    private static function getAvailableStandardFieldDefinitions($crm): array
    {
        $availableSlugs = self::getAvailableStandardFieldSlugs($crm);
        $providerDefinitions = self::normalizeStandardFieldDefinitions((array) $crm->getStandardFields());
        $definitions = [];

        foreach ($providerDefinitions as $definition) {
            $slug = (string) ($definition['slug'] ?? '');

            if (!in_array($slug, $availableSlugs, true) || !self::shouldExposeStandardFieldSlug($slug)) {
                continue;
            }

            $definitions[$slug] = self::buildStandardFieldDefinition($slug, $definition['label'] ?? null);
        }

        foreach ($availableSlugs as $slug) {
            if (!self::shouldExposeStandardFieldSlug($slug)) {
                continue;
            }

            if (isset($definitions[$slug])) {
                continue;
            }

            $definitions[$slug] = self::buildStandardFieldDefinition($slug);
        }

        return self::sortStandardFieldDefinitions(array_values($definitions));
    }

    private static function getAvailableStandardFieldSlugs($crm): array
    {
        $standardFields = [];

        if ($crm->getName() === 'FluentCRM') {
            global $wpdb;
            $table_name = $wpdb->prefix . 'fc_subscribers';

            $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'");

            if ($table_exists) {
                $query = "SELECT COLUMN_NAME 
                          FROM INFORMATION_SCHEMA.COLUMNS 
                          WHERE TABLE_NAME = '" . $table_name . "'
                          AND TABLE_SCHEMA = '" . DB_NAME . "'";

                $standardFields = $wpdb->get_col($query);
            }
        } else {
            $fields = (array) $crm->getStandardFields();

            foreach ($fields as $key => $field) {
                if (is_array($field)) {
                    $slug = (string) ($field['slug'] ?? $field['key'] ?? '');
                } else {
                    $slug = is_string($key) ? $key : '';
                }

                if ($slug !== '') {
                    $standardFields[] = $slug;
                }
            }

            $standardFields = array_merge(
                ['id'],
                $standardFields,
                ['status', 'hash', 'created_at', 'updated_at']
            );
        }

        $standardFields[] = 'lists';
        $standardFields[] = 'tags';
        $standardFields[] = 'last_note';
        $standardFields[] = 'plover_crm_avatar';
        $standardFields[] = SystemFieldRegistry::FIELD_STAGE;

        return array_values(array_unique(array_map('strval', $standardFields)));
    }

    private static function normalizeStandardFieldDefinitions(array $fields): array
    {
        $definitions = [];

        foreach ($fields as $key => $field) {
            $definition = self::normalizeStandardFieldDefinition($key, $field);

            if (!$definition) {
                continue;
            }

            $definitions[] = $definition;
        }

        return $definitions;
    }

    private static function normalizeStandardFieldDefinition($key, $field): ?array
    {
        $slug = '';
        $label = '';

        if (is_array($field)) {
            $slug = (string) ($field['slug'] ?? $field['key'] ?? '');
            $label = (string) ($field['label'] ?? $field['title'] ?? $field['name'] ?? '');

            if ($slug === '' && is_string($key)) {
                $slug = $key;
            }
        } elseif (is_string($key)) {
            $slug = $key;
            $label = (string) $field;
        }

        if ($slug === '') {
            return null;
        }

        return [
            'slug' => $slug,
            'label' => $label !== '' ? $label : self::humanizeStandardFieldSlug($slug),
        ];
    }

    private static function shouldExposeStandardFieldSlug(string $slug): bool
    {
        if ($slug === '') {
            return false;
        }

        if (in_array($slug, self::HIDDEN_STANDARD_FIELD_SLUGS, true)) {
            return false;
        }

        if (SystemFieldRegistry::isIpInformationSlug($slug)) {
            return false;
        }

        return true;
    }

    private static function buildStandardFieldDefinition(string $slug, ?string $providerLabel = null): array
    {
        $label = self::STANDARD_FIELD_LABEL_OVERRIDES[$slug]
            ?? ($providerLabel !== null && $providerLabel !== '' ? $providerLabel : self::humanizeStandardFieldSlug($slug));

        $definition = [
            'slug' => $slug,
            'label' => $label,
        ];

        if (isset(self::STANDARD_FIELD_DESCRIPTIONS[$slug])) {
            $definition['description'] = self::STANDARD_FIELD_DESCRIPTIONS[$slug];
        }

        return $definition;
    }

    private static function sortStandardFieldDefinitions(array $definitions): array
    {
        usort($definitions, static function (array $left, array $right): int {
            $leftSlug = (string) ($left['slug'] ?? '');
            $rightSlug = (string) ($right['slug'] ?? '');
            $leftIndex = array_search($leftSlug, self::STANDARD_FIELD_ORDER, true);
            $rightIndex = array_search($rightSlug, self::STANDARD_FIELD_ORDER, true);

            if ($leftIndex !== false || $rightIndex !== false) {
                $leftOrder = $leftIndex !== false ? $leftIndex : PHP_INT_MAX;
                $rightOrder = $rightIndex !== false ? $rightIndex : PHP_INT_MAX;

                if ($leftOrder !== $rightOrder) {
                    return $leftOrder <=> $rightOrder;
                }
            }

            $leftLabel = (string) ($left['label'] ?? $leftSlug);
            $rightLabel = (string) ($right['label'] ?? $rightSlug);

            return strcasecmp($leftLabel, $rightLabel);
        });

        return $definitions;
    }

    private static function humanizeStandardFieldSlug(string $slug): string
    {
        return preg_replace_callback(
            '/\b\w/',
            static fn(array $matches): string => strtoupper($matches[0]),
            str_replace(['_', '-'], ' ', $slug)
        );
    }
}
