<?php
/**
 * ModuleAjaxSettingsTrait — Shared field-schema validation engine for module AJAX classes.
 *
 * Any module AJAX class that handles settings saves can use this trait.
 * The module defines its own field schema and calls sanitize_fields() —
 * zero coupling to the Settings class or any other module.
 *
 * ## Schema format
 *
 *   'field_key' => [
 *     'type'        => 'bool'|'int'|'float'|'text'|'textarea'|'email'|'url'|'key'|'select',
 *     'default'     => mixed,          // returned when field is absent from POST
 *     'min'         => int|float,      // int / float only
 *     'max'         => int|float,      // int / float only
 *     'options'     => ['a','b','c'],  // select only — allowed values
 *     'allow_empty' => bool,           // email/url: include key even when value is empty
 *   ]
 *
 * ## Usage
 *
 *   class MyModuleAjax {
 *       use ModuleAjaxSettingsTrait;
 *
 *       private function field_schema(): array {
 *           return [
 *               'mymodule_enabled'     => ['type' => 'bool'],
 *               'mymodule_max_items'   => ['type' => 'int', 'min' => 1, 'max' => 1000],
 *               'mymodule_mode'        => ['type' => 'select', 'options' => ['fast','slow'], 'default' => 'fast'],
 *               'mymodule_admin_email' => ['type' => 'email', 'allow_empty' => true],
 *           ];
 *       }
 *
 *       public function handle_save_settings(): void {
 *           $this->verify(); // module-level nonce + cap check
 *           $data = $this->sanitize_fields( wp_unslash( $_POST ), $this->field_schema() );
 *           $this->plugin->settings->save_module_settings( 'mymodule', $data );
 *           wp_send_json_success( [ 'message' => __( 'Saved.', 'ultraguard-security-suite' ) ] );
 *       }
 *   }
 *
 * @package UltraguardPro\modules\core
 */

namespace UltraguardPro\modules\core;

if ( ! defined( 'ABSPATH' ) ) { exit; }

trait ModuleAjaxSettingsTrait {

    /**
     * Sanitize and validate raw POST data against a field schema.
     *
     * Only fields declared in $schema are processed; all other POST keys are
     * silently ignored.  Each field is sanitized according to its declared type.
     * Fields absent from $raw are included in the output only when the schema
     * entry specifies a 'default' key (booleans default to false when absent).
     *
     * @param array $raw    Raw input array (typically wp_unslash($_POST)).
     * @param array $schema Field schema — see trait docblock for format.
     * @return array        Sanitized key-value pairs ready for persistence.
     */
    protected function sanitize_fields( array $raw, array $schema ): array {
        $data = [];

        foreach ( $schema as $key => $rules ) {
            $type    = $rules['type'] ?? 'text';
            $present = array_key_exists( $key, $raw );

            switch ( $type ) {

                // ── Boolean ──────────────────────────────────────────────────
                case 'bool':
                    // Booleans are always included: absent checkbox = false.
                    $data[ $key ] = $present
                        ? filter_var( $raw[ $key ], FILTER_VALIDATE_BOOLEAN )
                        : (bool) ( $rules['default'] ?? false );
                    break;

                // ── Integer ───────────────────────────────────────────────────
                case 'int':
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = (int) $rules['default'];
                        }
                        break;
                    }
                    $val = absint( $raw[ $key ] );
                    if ( isset( $rules['min'] ) ) { $val = max( (int) $rules['min'], $val ); }
                    if ( isset( $rules['max'] ) ) { $val = min( (int) $rules['max'], $val ); }
                    $data[ $key ] = $val;
                    break;

                // ── Float / decimal ───────────────────────────────────────────
                case 'float':
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = (float) $rules['default'];
                        }
                        break;
                    }
                    $val = (float) $raw[ $key ];
                    if ( isset( $rules['min'] ) ) { $val = max( (float) $rules['min'], $val ); }
                    if ( isset( $rules['max'] ) ) { $val = min( (float) $rules['max'], $val ); }
                    $data[ $key ] = $val;
                    break;

                // ── Email address ─────────────────────────────────────────────
                case 'email':
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = (string) $rules['default'];
                        }
                        break;
                    }
                    $val = sanitize_email( $raw[ $key ] );
                    if ( $val || ! empty( $rules['allow_empty'] ) ) {
                        $data[ $key ] = $val;
                    }
                    break;

                // ── URL ───────────────────────────────────────────────────────
                case 'url':
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = (string) $rules['default'];
                        }
                        break;
                    }
                    $val = sanitize_url( $raw[ $key ] );
                    if ( $val || ! empty( $rules['allow_empty'] ) ) {
                        $data[ $key ] = $val;
                    }
                    break;

                // ── Sanitize-key (slugs, identifiers) ─────────────────────────
                case 'key':
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = sanitize_key( (string) $rules['default'] );
                        }
                        break;
                    }
                    $data[ $key ] = sanitize_key( $raw[ $key ] );
                    break;

                // ── Textarea (multi-line text) ─────────────────────────────────
                case 'textarea':
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = (string) $rules['default'];
                        }
                        break;
                    }
                    $data[ $key ] = sanitize_textarea_field( $raw[ $key ] );
                    break;

                // ── Select (enum / allowlist) ──────────────────────────────────
                case 'select':
                    $options = $rules['options'] ?? [];
                    $default = $rules['default'] ?? ( $options[0] ?? '' );
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = $default;
                        }
                        break;
                    }
                    $val = sanitize_key( $raw[ $key ] );
                    $data[ $key ] = in_array( $val, $options, true ) ? $val : $default;
                    break;

                // ── Plain text (default) ───────────────────────────────────────
                case 'text':
                default:
                    if ( ! $present ) {
                        if ( array_key_exists( 'default', $rules ) ) {
                            $data[ $key ] = sanitize_text_field( (string) $rules['default'] );
                        }
                        break;
                    }
                    $data[ $key ] = sanitize_text_field( $raw[ $key ] );
                    break;
            }
        }

        return $data;
    }
}
