<?php

namespace GriffinForms\Includes\Ai;

use GriffinForms\Settings;
use GriffinForms\Includes\Extensions\ExtensionRegistry;
use GriffinForms\Includes\Fields\FieldRegistry;
use GriffinForms\Includes\Packages\PackageAdmission;
use GriffinForms\Includes\Packages\PackageRegistry;
use GriffinForms\Includes\Packages\PackageStateResolver;

class AssistRequestRouter
{
    protected const DATA_SHARING_ACK_VERSION = '1';
    protected const SUPPORTED_ORIGINS = [
        'chat_prompt',
        'ui_button',
        'system_action',
    ];

    protected const SUPPORTED_RESPONSE_MODES = [
        'classify',
        'patch_required',
        'text_only',
    ];

    protected ButlerModelRouter $model_router;
    protected AssistSurfaceRegistry $surface_registry;
    protected AssistFailureFramework $failure_framework;
    protected PackageStateResolver $package_state_resolver;
    protected PackageAdmission $package_admission;
    protected PackageRegistry $package_registry;
    protected ExtensionRegistry $extension_registry;
    protected FieldRegistry $field_registry;

    public function __construct()
    {
        $this->model_router = new ButlerModelRouter();
        $this->surface_registry = new AssistSurfaceRegistry();
        $this->failure_framework = new AssistFailureFramework();
        $this->package_state_resolver = PackageStateResolver::getInstance();
        $this->package_admission = PackageAdmission::getInstance();
        $this->package_registry = PackageRegistry::getInstance();
        $this->extension_registry = ExtensionRegistry::getInstance();
        $this->field_registry = FieldRegistry::getInstance();
    }

    public function buildRequest(int $form_id, string $prompt, array $selected_context = [], array $request_context = []): array
    {
        $surface = $this->normalizeSurface((string) ($request_context['surface'] ?? 'form_builder'));
        $origin = $this->normalizeOrigin((string) ($request_context['origin'] ?? 'chat_prompt'));
        $selected_context = $this->sanitizeSelectedContext($selected_context, $form_id);
        $semantic_intent = $this->resolveSemanticIntent($prompt, $selected_context, $request_context);
        $response_mode = $this->normalizeResponseMode(
            (string) ($request_context['response_mode'] ?? 'classify'),
            $semantic_intent,
            $origin
        );
        $response_family = $this->resolveResponseFamily($response_mode, $semantic_intent);

        \GriffinForms\Log::getInstance()->add(
            'debug',
            'ai_butler_request',
            $form_id,
            (int) get_current_user_id(),
            sprintf(
                'Router buildRequest surface=%s origin=%s response_mode=%s response_family=%s semantic_intent=%s prompt=%s selected_context=%s request_context=%s',
                $surface,
                $origin,
                $response_mode,
                $response_family,
                $semantic_intent,
                sanitize_text_field($prompt),
                wp_json_encode($selected_context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                wp_json_encode($request_context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
            ),
            'ai_butler_request'
        );

        return [
            'form_id' => $form_id,
            'prompt' => trim($prompt),
            'surface' => $surface,
            'origin' => $origin,
            'response_mode' => $response_mode,
            'response_family' => $response_family,
            'semantic_intent' => $semantic_intent,
            'selected_context' => $selected_context,
            'request_context' => [
                'surface' => $surface,
                'origin' => $origin,
                'response_mode' => $response_mode,
                'semantic_intent' => $semantic_intent,
                'response_family' => $response_family,
                'suggestion_context' => is_array($request_context['suggestion_context'] ?? null) ? (array) $request_context['suggestion_context'] : [],
                'package_context' => $this->buildPackageContext(),
            ],
            'site_context' => array_merge(
                $this->buildSiteContext(),
                [
                    'package_context' => $this->buildPackageContext(),
                ]
            ),
        ];
    }

    public function preflight(array $request): ?array
    {
        \GriffinForms\Log::getInstance()->add(
            'debug',
            'ai_butler_request',
            (int) ($request['form_id'] ?? 0),
            (int) get_current_user_id(),
            sprintf(
                'Router preflight request surface=%s origin=%s response_mode=%s response_family=%s semantic_intent=%s prompt=%s selected_context=%s request_context=%s',
                (string) ($request['surface'] ?? ''),
                (string) ($request['origin'] ?? ''),
                (string) ($request['response_mode'] ?? ''),
                (string) ($request['response_family'] ?? ''),
                (string) ($request['semantic_intent'] ?? ''),
                sanitize_text_field((string) ($request['prompt'] ?? '')),
                wp_json_encode((array) ($request['selected_context'] ?? []), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                wp_json_encode((array) ($request['request_context'] ?? []), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
            ),
            'ai_butler_request'
        );

        if (empty($request['form_id'])) {
            return $this->failure('invalid_form', 400, $request);
        }

        if (($request['prompt'] ?? '') === '') {
            return $this->failure('empty_prompt', 400, $request);
        }

        if ($this->isAmbiguousFollowUpPrompt((string) ($request['prompt'] ?? ''))) {
            return $this->failure(
                'clarification_required',
                409,
                $request
            );
        }

        if (!$this->supportsSurface((string) ($request['surface'] ?? ''))) {
            return $this->failure(
                'unsupported_surface',
                422,
                $request
            );
        }

        if (!$this->supportsOrigin((string) ($request['origin'] ?? ''))) {
            return $this->failure(
                'unsupported_origin',
                422,
                $request
            );
        }

        if (!$this->supportsResponseMode((string) ($request['response_mode'] ?? 'classify'))) {
            return $this->failure(
                'unsupported_response_mode',
                422,
                $request
            );
        }

        if (($request['response_family'] ?? 'mutation') !== 'mutation') {
            if (($request['response_family'] ?? '') !== 'text') {
                return $this->failure(
                    'clarification_required',
                    409,
                    $request
                );
            }
        }

        $route = $this->model_router->route('form_drafting');
        $provider = (string) ($route['provider'] ?? '');
        if ($provider === '') {
            \GriffinForms\Log::getInstance()->add(
                'warning',
                'ai_butler_request',
                (int) ($request['form_id'] ?? 0),
                (int) get_current_user_id(),
                'Router preflight blocked provider_unavailable',
                'ai_butler_request'
            );
            return $this->failure(
                'provider_unavailable',
                412,
                $request
            );
        }

        if ($this->providerRequiresConsent($provider) && !$this->hasDataSharingAcknowledgement()) {
            \GriffinForms\Log::getInstance()->add(
                'warning',
                'ai_butler_request',
                (int) ($request['form_id'] ?? 0),
                (int) get_current_user_id(),
                'Router preflight blocked consent_required',
                'ai_butler_request'
            );
            return $this->failure(
                'consent_required',
                412,
                $request
            );
        }

        return null;
    }

    public function routeConfig(array $request = []): array
    {
        $route = $this->model_router->route($this->resolveRouteIntent($request));
        return [
            'provider' => (string) ($route['provider'] ?? ''),
            'config' => (array) ($route['config'] ?? []),
        ];
    }

    protected function supportsSurface(string $surface): bool
    {
        return $this->surface_registry->canRender($surface);
    }

    protected function supportsOrigin(string $origin): bool
    {
        return in_array($origin, self::SUPPORTED_ORIGINS, true);
    }

    protected function supportsResponseMode(string $response_mode): bool
    {
        return in_array($response_mode, self::SUPPORTED_RESPONSE_MODES, true);
    }

    protected function sanitizeSelectedContext(array $selected_context, int $form_id): array
    {
        $context = [
            'item_type' => sanitize_key((string) ($selected_context['item_type'] ?? '')),
            'item_id' => absint($selected_context['item_id'] ?? 0),
            'parent_type' => sanitize_key((string) ($selected_context['parent_type'] ?? '')),
            'parent_id' => absint($selected_context['parent_id'] ?? 0),
            'form_id' => absint($selected_context['form_id'] ?? 0) ?: $form_id,
            'item_label' => sanitize_text_field((string) ($selected_context['item_label'] ?? '')),
            'field_type' => sanitize_key((string) ($selected_context['field_type'] ?? '')),
        ];

        if ($context['item_type'] === '') {
            $context['item_type'] = 'form';
            $context['item_id'] = $context['item_id'] ?: $form_id;
        }

        return $context;
    }

    protected function resolveSemanticIntent(string $prompt, array $selected_context, array $request_context = []): string
    {
        $requested_intent = sanitize_key((string) ($request_context['semantic_intent'] ?? ''));
        $normalized = strtolower(trim($prompt));
        $selected_field_edit = !empty($selected_context['item_type']) && preg_match('/\b(label|labels|description|descriptions|placeholder|placeholders|heading|headings|title|titles|copy|wording|text|button|submit|previous|next|back|continue)\b/', $normalized);

        if (in_array($requested_intent, ['builder_bulk_text', 'builder_copy_choices'], true) && $selected_field_edit) {
            return 'builder_text_edit';
        }

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

        if ($normalized === '') {
            return 'builder_mutation';
        }

        if (preg_match('/^(why|how|what|does|is|are|do|can|will|would|has|have|explain|can you explain|suggest|recommend|idea|ideas|brainstorm|help me understand|tell me|show me|describe)\b/', $normalized)) {
            return 'builder_explanation';
        }

        if ($this->isBulkTextRequest($normalized)) {
            return 'builder_bulk_text';
        }

        if ($this->isChoiceFieldColumnSplitRequest($normalized, $selected_context)) {
            return 'builder_bulk_text';
        }

        if ($this->isChoiceFieldOptionTextRequest($normalized, $selected_context) && !$this->isChoiceFieldOptionsStructuralEdit($normalized)) {
            return 'builder_bulk_text';
        }

        if ($this->isFieldLabelQuestionRewriteRequest($normalized, $selected_context)) {
            return 'builder_bulk_text';
        }

        if ($this->isSelectedPageTextEditRequest($normalized, $selected_context)) {
            return 'builder_text_edit';
        }

        if ($this->isSelectedFieldMoveRequest($normalized, $selected_context)) {
            return 'builder_text_edit';
        }

        if (!empty($selected_context['item_type']) && preg_match('/\b(label|labels|description|descriptions|placeholder|placeholders|heading|headings|title|titles|copy|wording|text|button|submit|previous|next|back|continue)\b/', $normalized)) {
            return 'builder_text_edit';
        }

        if ($this->isOpenEndedSelectedCopyRequest($normalized, $selected_context)) {
            return 'builder_copy_choices';
        }

        if ($this->isCopyChoiceRequest($normalized, $selected_context)) {
            return 'builder_copy_choices';
        }

        return 'builder_mutation';
    }

    protected function resolveResponseFamily(string $response_mode, string $semantic_intent): string
    {
        if ($response_mode === 'text_only' || in_array($semantic_intent, ['builder_explanation', 'builder_copy_choices'], true)) {
            return 'text';
        }

        if (in_array($response_mode, ['patch_required', 'classify'], true)) {
            return 'mutation';
        }

        return 'unsupported';
    }

    protected function normalizeSurface(string $surface): string
    {
        $surface = sanitize_key($surface);
        return $surface !== '' ? $surface : 'form_builder';
    }

    protected function normalizeOrigin(string $origin): string
    {
        $origin = sanitize_key($origin);
        return $origin !== '' ? $origin : 'chat_prompt';
    }

    protected function normalizeResponseMode(string $response_mode, string $semantic_intent, string $origin): string
    {
        $response_mode = sanitize_key($response_mode);
        if (in_array($response_mode, self::SUPPORTED_RESPONSE_MODES, true)) {
            return $response_mode;
        }

        if (in_array($semantic_intent, ['builder_explanation', 'builder_copy_choices'], true)) {
            return 'text_only';
        }

        if ($origin === 'ui_button') {
            return 'patch_required';
        }

        return 'classify';
    }

    protected function resolveRouteIntent(array $request): string
    {
        $semantic_intent = sanitize_key((string) ($request['semantic_intent'] ?? ''));

        if (in_array($semantic_intent, ['builder_explanation', 'builder_copy_choices'], true)) {
            return 'field_suggestion';
        }

        if ($semantic_intent === 'builder_bulk_text') {
            return 'form_drafting';
        }

        return 'form_drafting';
    }

    protected function isCopyChoiceRequest(string $normalized_prompt, array $selected_context): bool
    {
        if (!preg_match('/\b(label|labels|description|descriptions|placeholder|placeholders|heading|headings|title|titles|copy|wording|text|button label|button labels|option|options|choice|choices)\b/', $normalized_prompt)) {
            return false;
        }

        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if ($selected_type === 'field' && !in_array($field_type, ['selectfield', 'inputcheckbox', 'inputradio'], true)) {
            return false;
        }

        if (!preg_match('/\b(give|show|suggest|generate|write|draft|offer|provide|list)\b/', $normalized_prompt)) {
            return false;
        }

        if (
            preg_match('/\b([234]|three|four)\b/', $normalized_prompt)
            || preg_match('/\bbetter\b/', $normalized_prompt)
            || preg_match('/\boptions?\b/', $normalized_prompt)
            || preg_match('/\bideas?\b/', $normalized_prompt)
        ) {
            return true;
        }

        return !empty($selected_context['item_type']) && preg_match('/\b(this|selected|current)\b/', $normalized_prompt);
    }

    protected function isChoiceFieldOptionTextRequest(string $normalized_prompt, array $selected_context): bool
    {
        if (!preg_match('/\b(change|translate|rewrite|align|normalize|update|make|convert|turn|polish|improve|edit|refine)\b/', $normalized_prompt)) {
            return false;
        }

        if (!preg_match('/\b(option label|option labels|options|choices|choice|radio option|radio options|checkbox option|checkbox options)\b/', $normalized_prompt)) {
            return false;
        }

        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($selected_type !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        return in_array($field_type, ['selectfield', 'inputcheckbox', 'inputradio'], true);
    }

    protected function isChoiceFieldOptionsStructuralEdit(string $normalized_prompt): bool
    {
        // Add/remove/reorder/rename options — structural management, not text rewriting.
        return (bool) preg_match(
            '/\b(add|remove|delete|reorder|rename|sort|swap|insert|append|drop)\b.*\b(option|choice|answer|item)s?\b|\b(option|choice|answer|item)s?\b.*\b(add|remove|delete|reorder|rename|sort|swap|insert|append|drop)\b/i',
            $normalized_prompt
        ) || (bool) preg_match(
            '/\bfor\b.{1,40}\b(option|choice|answer|item)s?\b|\b(option|choice|answer|item)s?\b.{1,40}\bfor\b/i',
            $normalized_prompt
        );
    }

    protected function isFieldLabelQuestionRewriteRequest(string $normalized_prompt, array $selected_context): bool
    {
        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($selected_type !== 'field') {
            return false;
        }

        if (!preg_match('/\b(field labels?|labels?|turn\b.*\bquestions?|question(s)?|make\b.*\bquestions?)\b/', $normalized_prompt)) {
            return false;
        }

        if (!preg_match('/\b(change|translate|rewrite|align|normalize|update|make|convert|turn|polish|improve|edit|refine|add)\b/', $normalized_prompt)) {
            return false;
        }

        return !in_array(sanitize_key((string) ($selected_context['field_type'] ?? '')), ['selectfield', 'inputcheckbox', 'inputradio'], true)
            ? true
            : true;
    }

    protected function isSelectedFieldMoveRequest(string $normalized_prompt, array $selected_context): bool
    {
        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($selected_type !== 'field') {
            return false;
        }

        return (bool) preg_match(
            '/\b(move|shift|reorder|relocate|put|place)\b.*\b(field|this|selected|current)\b|\b(field|this|selected|current)\b.*\b(move|shift|reorder|relocate|put|place)\b|\b(field|this|selected|current)\b.*\b(up|down|top|bottom|before|after|above|below)\b|\b(to\s+the\s+)?(top|bottom|beginning|end)\b/',
            $normalized_prompt
        );
    }

    protected function isBulkTextRequest(string $normalized_prompt): bool
    {
        // Step label rewrites and Likert row/column polishes are field-scoped — never treat as bulk text.
        if (preg_match('/\b(step\s+labels?|step\s+names?|navigation\s+labels?|tab\s+labels?|step\s+headings?)\b/', $normalized_prompt)) {
            return false;
        }
        if (preg_match('/\b(row\s+labels?|column\s+labels?|likert\s+rows?|likert\s+columns?)\b/', $normalized_prompt)) {
            return false;
        }

        if (!preg_match('/\b(change|translate|rewrite|align|normalize|update|make|convert|turn|polish|improve|divide|split|spread|arrange|organize)\b/', $normalized_prompt)) {
            return false;
        }

        if (preg_match('/\b(translate|convert|turn|make)\b.*\b(to|into|in)\b.*\b(french|spanish|german|deutsch|italian|portuguese|dutch|hindi|punjabi|panjabi|french|francais|fr|es|de|it|pt|nl|hi|pa)\b/i', $normalized_prompt)) {
            return true;
        }

        if (preg_match('/\b(all|every|entire|whole)\b/', $normalized_prompt)) {
            return (bool) preg_match('/\b(field labels|labels|page descriptions|descriptions|form text|page headings|headings|titles|placeholders|option labels|options|choices|columns)\b/', $normalized_prompt);
        }

        if (preg_match('/\b(field label|page heading|page description|option label|placeholder|submit label|previous label|next label)\b/', $normalized_prompt)) {
            return false;
        }

        return (bool) preg_match('/\b(page headings|page descriptions|field labels|option labels|form text|titles|headings|descriptions|options? into \d+ columns|options? into (one|two|three|four) columns|divide (field )?options into)\b/', $normalized_prompt);
    }

    protected function isChoiceFieldColumnSplitRequest(string $normalized_prompt, array $selected_context): bool
    {
        if (!preg_match('/\b(divide|split|spread|arrange|organize)\b.*\b(options?|choices?)\b.*\b(columns?|col(?:umn)?s?)\b|\b(options?|choices?)\b.*\b(columns?|col(?:umn)?s?)\b.*\b(divide|split|spread|arrange|organize)\b/', $normalized_prompt)) {
            return false;
        }

        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($selected_type !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        return in_array($field_type, ['selectfield', 'inputcheckbox', 'inputradio'], true);
    }

    protected function isAmbiguousFollowUpPrompt(string $prompt): bool
    {
        $normalized = strtolower(trim($prompt));
        if ($normalized === '') {
            return false;
        }

        if (preg_match('/^(yes|no|ok|okay|sure|continue|proceed|cancel)$/', $normalized)) {
            return true;
        }

        if (preg_match('/^(implement( it)?|do it|use this|apply( it)?|go ahead|try this|select this)$/', $normalized)) {
            return true;
        }

        return false;
    }

    protected function isOpenEndedSelectedCopyRequest(string $normalized_prompt, array $selected_context): bool
    {
        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if (!in_array($selected_type, ['field', 'formpage', 'form'], true)) {
            return false;
        }

        if (!preg_match('/\b(label|description|placeholder|heading|title|headline|help text|instructions|subtext|button)\b/', $normalized_prompt)) {
            return false;
        }

        if (!preg_match('/\b(rewrite|improve|refine|reword|make|polish|refresh)\b/', $normalized_prompt)) {
            if (
                preg_match('/\b(change|update|edit|set|turn)\b/', $normalized_prompt)
                && $this->isFuzzyReplacementRequest($normalized_prompt)
            ) {
                return true;
            }

            return false;
        }

        // Explicit replacement requests should stay on the mutation path.
        if (preg_match('/\bto\b\s+[\'"]?[a-z0-9]/i', $normalized_prompt) || preg_match('/["\']/', $normalized_prompt)) {
            return $this->isFuzzyReplacementRequest($normalized_prompt);
        }

        return true;
    }

    protected function isSelectedPageTextEditRequest(string $normalized_prompt, array $selected_context): bool
    {
        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($selected_type !== 'formpage') {
            return false;
        }

        if (!preg_match('/\b(label|labels|description|descriptions|heading|headings|title|titles|copy|text)\b/', $normalized_prompt)) {
            return false;
        }

        return (bool) preg_match('/\b(rewrite|improve|update|change|edit|refine|polish)\b/', $normalized_prompt);
    }

    protected function isFuzzyReplacementRequest(string $normalized_prompt): bool
    {
        if (!preg_match('/\bto\b\s+(.+)$/', $normalized_prompt, $matches)) {
            return false;
        }

        $tail = trim((string) ($matches[1] ?? ''));
        if ($tail === '') {
            return false;
        }

        if (preg_match('/^[\'"][^\'"]+[\'"]$/', $tail)) {
            return false;
        }

        if (preg_match('/^(something|someone|somewhere)\b/', $tail)) {
            return true;
        }

        if (preg_match('/\b(more|less|better|professional|friendly|warmer|clearer|shorter|longer|descriptive|formal|casual|polished|refined|engaging|compelling)\b/', $tail)) {
            return true;
        }

        return false;
    }

    protected function buildSiteContext(): array
    {
        $timezone = function_exists('wp_timezone_string') ? wp_timezone_string() : (string) get_option('timezone_string', 'UTC');
        if ($timezone === '') {
            $timezone = 'UTC';
        }

        return [
            'site_name' => sanitize_text_field((string) get_bloginfo('name')),
            'site_description' => sanitize_text_field((string) get_bloginfo('description')),
            'site_timezone' => sanitize_text_field($timezone),
            'site_language' => sanitize_text_field((string) get_bloginfo('language')),
        ];
    }

    protected function buildPackageContext(): array
    {
        $state = $this->package_state_resolver->getState();
        $packages = is_array($state['packages'] ?? null) ? (array) $state['packages'] : [];

        $active_package_ids = [];
        $active_package_names = [];
        $entitled_package_ids = [];
        $entitled_package_names = [];
        $package_states = [];

        foreach ($packages as $package_id => $package_state) {
            if (!is_array($package_state)) {
                continue;
            }

            $package_id = sanitize_key((string) $package_id);
            if ($package_id === '') {
                continue;
            }

            $package_name = trim((string) ($package_state['package_label_internal'] ?? $package_id));
            $is_active = !empty($package_state['is_active']);
            $entitlement_state = (string) ($package_state['entitlement_state'] ?? '');
            $is_entitled = in_array($entitlement_state, ['verified_active', 'grace_period'], true);

            if ($is_active) {
                $active_package_ids[] = $package_id;
                $active_package_names[] = $package_name;
            }

            if ($is_entitled) {
                $entitled_package_ids[] = $package_id;
                $entitled_package_names[] = $package_name;
            }

            $package_states[$package_id] = [
                'package_id' => $package_id,
                'package_name' => $package_name,
                'is_present' => !empty($package_state['is_present']),
                'is_active' => $is_active,
                'entitlement_state' => $entitlement_state,
                'compatibility_state' => (string) ($package_state['compatibility_state'] ?? ''),
                'approval_status' => (string) ($package_state['approval_status'] ?? ''),
                'recommended_user_action' => (string) ($package_state['recommended_user_action'] ?? ''),
            ];
        }

        $field_ownership = [];
        foreach ($this->extension_registry->getFieldExtensions() as $field_type => $package_id) {
            $field_type = sanitize_key((string) $field_type);
            $package_id = sanitize_key((string) $package_id);
            if ($field_type === '' || $package_id === '') {
                continue;
            }

            $field_ownership[$field_type] = [
                'package_id' => $package_id,
                'package_name' => $this->extension_registry->getName($package_id),
                'buy_url' => $this->extension_registry->getBuyUrl($package_id),
            ];
        }

        return [
            'resolved_at' => sanitize_text_field((string) ($state['resolved_at'] ?? gmdate('c'))),
            'active_package_ids' => array_values(array_unique($active_package_ids)),
            'active_package_names' => array_values(array_unique($active_package_names)),
            'entitled_package_ids' => array_values(array_unique($entitled_package_ids)),
            'entitled_package_names' => array_values(array_unique($entitled_package_names)),
            'packages' => $package_states,
            'field_ownership' => $field_ownership,
            'field_types' => array_values(array_unique($this->field_registry->getFieldTypes())),
        ];
    }

    protected function failure(string $status, int $code, array $context = [], ?string $message_override = null): array
    {
        return $this->failure_framework->build($status, $code, $context, $message_override);
    }

    protected function providerRequiresConsent(string $provider): bool
    {
        $provider = sanitize_key($provider);
        return $provider !== '' && $provider !== 'mock';
    }

    protected function hasDataSharingAcknowledgement(): bool
    {
        $settings = Settings::getInstance();
        $ack = (int) $settings->getOption('ai_data_sharing_acknowledged', 0);
        if ($ack !== 1) {
            return false;
        }

        $version = (string) $settings->getOption('ai_data_sharing_acknowledged_version', '');
        return $version === '' || $version === self::DATA_SHARING_ACK_VERSION;
    }
}
