<?php
/**
 * Search Settings Controller
 *
 * Handles search settings management for FleekDash universal search.
 *
 * @package FleekDash\Controllers
 * @since 2.2.0
 */

namespace FleekDash\Controllers;

/**
 * Class SearchSettingsController
 *
 * Manages search configuration including:
 * - Default search types
 * - Type boost weights
 * - Synonyms
 * - Preview behavior
 *
 * @package FleekDash\Controllers
 */
class SearchSettingsController
{
    /**
     * Option name for search settings
     *
     * @var string
     */
    const OPTION_NAME = 'fleekdash_search_settings';

    /**
     * All available search types
     *
     * @var array
     */
    const ALL_TYPES = ['post', 'page', 'user', 'menu', 'setting', 'media', 'comment', 'term', 'shortcut'];

    /**
     * Get search settings
     *
     * @param \WP_REST_Request $_request Request object (required by REST API callback signature)
     * @return \WP_REST_Response Response object
     */
    public function getSettings(\WP_REST_Request $_request) // NOSONAR php:S1172 - WP REST callback signature // NOSONAR php:S1172 - WP REST callback signature
    {
        $defaults = $this->getDefaultSettings();
        $saved = \get_option(self::OPTION_NAME, []);

        if (!\is_array($saved)) {
            $saved = [];
        }

        // Deep merge with defaults to ensure all keys exist
        $settings = \array_replace_recursive($defaults, $saved);

        return \rest_ensure_response([
            'success' => true,
            'data' => $settings,
        ]);
    }

    /**
     * Update search settings
     *
     * @param \WP_REST_Request $request Request object
     * @return \WP_REST_Response Response object
     */
    public function updateSettings($request)
    {
        $params = $request->get_json_params();
        if (empty($params)) {
            $params = $request->get_body_params();
        }
        if (!\is_array($params)) {
            $params = [];
        }

        // Get existing settings
        $existing_settings = \get_option(self::OPTION_NAME, []);
        if (!\is_array($existing_settings)) {
            $existing_settings = $this->getDefaultSettings();
        }

        // Validate and sanitize incoming settings
        $validated_settings = $this->validateAndSanitizeSettings($params, true);

        // Merge new settings into existing settings recursively
        // Note: array_replace_recursive is better than array_merge for partial updates of nested objects
        $updated_settings = \array_replace_recursive($existing_settings, $validated_settings);

        // Save updated settings
        \update_option(self::OPTION_NAME, $updated_settings);

        // Verify save by reading back
        $saved_settings = \get_option(self::OPTION_NAME, []);

        return \rest_ensure_response([
            'success' => true,
            'message' => 'Search settings saved successfully',
            'data' => \array_replace_recursive($this->getDefaultSettings(), $saved_settings),
        ]);
    }

    /**
     * Get search synonyms
     *
     * Returns combined default and custom synonyms.
     *
     * @param \WP_REST_Request $_request Request object (required by REST API callback signature)
     * @return \WP_REST_Response Response object
     */
    public function getSynonyms(\WP_REST_Request $_request) // NOSONAR php:S1172 - WP REST callback signature // NOSONAR php:S1172 - WP REST callback signature
    {
        $defaults = $this->getDefaultSynonyms();

        // Allow filtering of default synonyms
        $base = \apply_filters('fleekdash_search_synonyms', $defaults);
        if (!\is_array($base)) {
            $base = [];
        }

        // Get custom synonyms from settings and merge into base
        $option = \get_option(self::OPTION_NAME, []);
        if (\is_array($option) && isset($option['synonyms']) && \is_array($option['synonyms'])) {
            $base = $this->mergeCustomSynonymsIntoBase($base, $option['synonyms']);
        }

        return \rest_ensure_response([
            'synonyms' => $base,
            'updated_at' => \current_time('mysql'),
        ]);
    }

    /**
     * Get default search settings
     *
     * @return array Default settings
     */
    private function getDefaultSettings()
    {
        return [
            'synonyms' => [],
            'preview_behavior' => [
                'post' => 'preview',
                'page' => 'preview',
                'user' => 'preview',
                'menu' => 'navigate',
                'setting' => 'navigate',
                'media' => 'navigate',
                'comment' => 'navigate',
                'term' => 'navigate',
                'shortcut' => 'navigate',
            ],
        ];
    }

    /**
     * Get default synonyms
     *
     * @return array Default synonyms
     */
    private function getDefaultSynonyms()
    {
        return [
            'wp' => ['wordpress'],
            'plugins' => ['add-ons', 'extensions'],
            'posts' => ['articles', 'entries'],
            'users' => ['members', 'accounts'],
            'settings' => ['options', 'preferences'],
        ];
    }

    /**
     * Merge custom synonyms into base synonyms
     *
     * @param array $base Base synonyms (from defaults + filter)
     * @param array $customSynonyms Custom synonyms map (key => array of values)
     * @return array Merged synonyms
     */
    private function mergeCustomSynonymsIntoBase(array $base, array $customSynonyms): array
    {
        foreach ($customSynonyms as $key => $list) {
            $key = \sanitize_text_field((string) $key);
            if ($key === '') {
                continue;
            }
            $vals = $this->sanitizeSynonymList($list);
            if (!isset($base[$key])) {
                $base[$key] = $vals;
            } else {
                $merged = \array_values(\array_unique(\array_merge((array) $base[$key], $vals)));
                $base[$key] = $merged;
            }
        }
        return $base;
    }

    /**
     * Validate and sanitize settings
     *
     * For partial updates, only validated fields present in params are returned.
     * Missing fields are omitted so array_replace_recursive won't overwrite existing data.
     *
     * @param array $params Raw parameters
     * @param bool  $partialUpdate Whether this is a partial update
     * @return array Validated and sanitized settings (only keys present in params for partial)
     */
    private function validateAndSanitizeSettings(array $params, $partialUpdate = false): array
    {
        $defaults = $this->getDefaultSettings();
        $settings = [];

        if (isset($params['synonyms'])) {
            $settings['synonyms'] = $this->validateSynonyms($params['synonyms'], $defaults['synonyms'], $partialUpdate);
        } elseif (!$partialUpdate) {
            $settings['synonyms'] = $defaults['synonyms'];
        }

        if (isset($params['preview_behavior'])) {
            $settings['preview_behavior'] = $this->validatePreviewBehavior($params['preview_behavior'], $defaults['preview_behavior'], $partialUpdate);
            if (!$partialUpdate) {
                foreach (self::ALL_TYPES as $type) {
                    if (!isset($settings['preview_behavior'][$type])) {
                        $settings['preview_behavior'][$type] = $defaults['preview_behavior'][$type] ?? 'navigate';
                    }
                }
            }
        } elseif (!$partialUpdate) {
            $settings['preview_behavior'] = $defaults['preview_behavior'];
        }

        return $settings;
    }

    /**
     * Validate synonyms parameter
     *
     * @param mixed $raw Raw synonyms
     * @param array $default Default value
     * @param bool  $partialUpdate Whether this is a partial update
     * @return array
     */
    private function validateSynonyms($raw, array $default, bool $partialUpdate): array
    {
        if (!isset($raw)) {
            return $partialUpdate ? [] : $default;
        }
        if (!\is_array($raw)) {
            return $partialUpdate ? [] : $default;
        }

        $result = [];
        foreach ($raw as $key => $list) {
            $key = \sanitize_text_field((string) $key);
            if ($key !== '') {
                $result[$key] = $this->sanitizeSynonymList($list);
            }
        }
        return $result;
    }

    /**
     * Sanitize a single synonym list (array or comma-separated string)
     *
     * @param mixed $list List as array or string
     * @return array Sanitized array of values
     */
    private function sanitizeSynonymList($list): array
    {
        if (\is_array($list)) {
            return \array_values(
                \array_filter(
                    \array_map('sanitize_text_field', \array_map('strval', $list))
                )
            );
        }
        if (\is_string($list)) {
            $parts = \array_map('trim', \explode(',', $list));
            return \array_values(
                \array_filter(
                    \array_map('sanitize_text_field', $parts)
                )
            );
        }
        return [];
    }

    /**
     * Validate preview_behavior parameter
     *
     * @param mixed $raw Raw preview behavior
     * @param array $default Default value
     * @param bool  $partialUpdate Whether this is a partial update
     * @return array
     */
    private function validatePreviewBehavior($raw, array $default, bool $partialUpdate): array
    {
        if (!isset($raw)) {
            return $partialUpdate ? [] : $default;
        }
        if (!\is_array($raw)) {
            return $partialUpdate ? [] : $default;
        }

        $validBehaviors = ['preview', 'navigate'];
        $result = [];
        foreach ($raw as $type => $behavior) {
            $type = \sanitize_text_field((string) $type);
            $behavior = \sanitize_text_field((string) $behavior);
            if (\in_array($type, self::ALL_TYPES, true) && \in_array($behavior, $validBehaviors, true)) {
                $result[$type] = $behavior;
            }
        }
        return $result;
    }
}
