<?php

/**
 * Override Sanitizer - Handles sanitization and saving of product swatch overrides.
 *
 * @package B3WVS\VariationSwatches\Admin\ProductSwatches
 */
namespace B3WVS\VariationSwatches\Admin\ProductSwatches;

defined( 'ABSPATH' ) || exit;
/**
 * OverrideSanitizer Class
 *
 * Sanitizes and persists per-product swatch override data.
 */
class OverrideSanitizer {
    /**
     * Meta key for product overrides.
     *
     * @var string
     */
    private $meta_key;

    /**
     * Valid swatch types.
     *
     * @var array
     */
    private static $valid_types = array('', 'button', 'color');

    /**
     * Constructor.
     *
     * @param string $meta_key Meta key for overrides.
     */
    public function __construct( $meta_key ) {
        $this->meta_key = $meta_key;
    }

    /**
     * Save overrides via post ID.
     *
     * @param int $post_id Product ID.
     */
    public function save( $post_id ) {
        $post_id = absint( $post_id );
        if ( !$post_id ) {
            return;
        }
        if ( !current_user_can( 'edit_post', $post_id ) ) {
            return;
        }
        if ( !$this->verify_nonce() ) {
            return;
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Sanitized field-by-field below, nonce verified above.
        $raw = ( isset( $_POST['b3wvs_overrides'] ) ? wp_unslash( $_POST['b3wvs_overrides'] ) : array() );
        if ( !is_array( $raw ) ) {
            $raw = array();
        }
        $existing = get_post_meta( $post_id, $this->meta_key, true );
        if ( !is_array( $existing ) ) {
            $existing = array();
        }
        $clean = $this->sanitize_overrides_array( $raw, $existing );
        if ( empty( $clean ) ) {
            delete_post_meta( $post_id, $this->meta_key );
            return;
        }
        update_post_meta( $post_id, $this->meta_key, $clean );
    }

    /**
     * Sanitize overrides array for storage.
     *
     * This is intentionally public so other admin save flows can reuse the
     * same canonical sanitization rules without relying on POST globals.
     *
     * @param array $raw      Raw override array.
     * @param array $existing Existing stored overrides.
     * @return array Sanitized overrides.
     */
    public function sanitize_overrides_array( $raw, $existing = array() ) {
        if ( !is_array( $raw ) ) {
            return array();
        }
        if ( !is_array( $existing ) ) {
            $existing = array();
        }
        return $this->sanitize_overrides( $raw, $existing );
    }

    /**
     * Verify the nonce for saving.
     *
     * @return bool
     */
    private function verify_nonce() {
        if ( !isset( $_POST['b3wvs_product_swatches_nonce'] ) ) {
            return false;
        }
        return wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['b3wvs_product_swatches_nonce'] ) ), 'b3wvs_save_product_swatches' );
    }

    /**
     * Sanitize the raw overrides array.
     *
     * @param array $raw      Raw POST data.
     * @param array $existing Existing stored overrides.
     * @return array Sanitized overrides.
     */
    private function sanitize_overrides( $raw, $existing = array() ) {
        $clean = array();
        foreach ( $raw as $attribute_name => $options ) {
            if ( !is_array( $options ) ) {
                continue;
            }
            $attribute_key = sanitize_text_field( (string) $attribute_name );
            if ( taxonomy_exists( $attribute_key ) ) {
                continue;
            }
            foreach ( $options as $slug => $data ) {
                if ( !is_array( $data ) ) {
                    continue;
                }
                $slug_key = sanitize_text_field( (string) $slug );
                $existing_override = ( isset( $existing[$attribute_key][$slug_key] ) && is_array( $existing[$attribute_key][$slug_key] ) ? $existing[$attribute_key][$slug_key] : array() );
                $sanitized = $this->sanitize_single_override( $data, $existing_override );
                if ( $this->is_empty_override( $sanitized ) ) {
                    continue;
                }
                $clean[$attribute_key][$slug_key] = $sanitized;
            }
        }
        return $clean;
    }

    /**
     * Sanitize a single override entry.
     *
     * @param array $data              Raw data for one option.
     * @param array $existing_override Existing stored override.
     * @return array Sanitized data.
     */
    private function sanitize_single_override( $data, $existing_override = array() ) {
        $type = ( isset( $data['type'] ) ? sanitize_text_field( (string) $data['type'] ) : '' );
        if ( !in_array( $type, self::$valid_types, true ) ) {
            $resolved_type = '';
            $type = $resolved_type;
        }
        $color = ( isset( $data['color'] ) ? sanitize_text_field( (string) $data['color'] ) : '' );
        $label = ( isset( $data['label'] ) ? sanitize_text_field( (string) $data['label'] ) : '' );
        $use_color = !empty( $data['use_color'] );
        $sanitized = $this->apply_type_rules(
            $type,
            $color,
            $label,
            $use_color
        );
        return $sanitized;
    }

    /**
     * Apply type-specific sanitization rules.
     *
     * @param string $type       Swatch type.
     * @param string $color      Primary color.
     * @param string $label      Custom label.
     * @param bool   $use_color  Use color flag.
     * @return array Sanitized override data.
     */
    private function apply_type_rules(
        $type,
        $color,
        $label,
        $use_color
    ) {
        // If default type, clear all values.
        if ( '' === $type ) {
            return $this->build_empty_override( $label );
        }
        switch ( $type ) {
            case 'button':
                // Button supports optional primary color via toggle.
                if ( !$use_color ) {
                    $color = '';
                }
                break;
            case 'color':
                $use_color = false;
                break;
            default:
                return $this->build_empty_override( $label );
        }
        return array(
            'type'      => $type,
            'color'     => $color,
            'label'     => $label,
            'use_color' => $use_color,
        );
    }

    /**
     * Build an empty shared override payload.
     *
     * @param string $label Custom label text.
     * @return array
     */
    private function build_empty_override( $label = '' ) {
        return array(
            'type'      => '',
            'color'     => '',
            'label'     => $label,
            'use_color' => false,
        );
    }

    /**
     * Check if an override is effectively empty (no meaningful data).
     *
     * @param array $override Sanitized override data.
     * @return bool True if empty.
     */
    private function is_empty_override( $override ) {
        $has_extension_content = false;
        return !$has_extension_content && '' === (( isset( $override['type'] ) ? $override['type'] : '' )) && '' === (( isset( $override['color'] ) ? $override['color'] : '' )) && '' === (( isset( $override['label'] ) ? $override['label'] : '' )) && empty( $override['use_color'] );
    }

}
