<?php

namespace Fab\Plugin\Helper;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Option Helper
 *
 * @package    Fab
 * @subpackage Fab\Includes
 */
trait Option {

    /**
     * Array Merge Recursive
     *
     * @param array $array1 The first array.
     * @param array $array2 The second array.
     * @return array The merged array.
     */
    public function ArrayMergeRecursive( $array1, $array2 ) {
        $merged = (array) $array1; // Cast both to arrays to handle stdClass objects.
        foreach ( (array) $array2 as $key => $value ) {
            // If both elements are arrays or objects, recursively merge them.
            if ( is_array( $value ) || is_object( $value ) ) {
                if ( isset( $merged[ $key ] ) && ( is_array( $merged[ $key ] ) || is_object( $merged[ $key ] ) ) ) {
                    $merged[ $key ] = $this->ArrayMergeRecursive( $merged[ $key ], $value );
                } else {
                    $merged[ $key ] = $value;
                }
            }
            // For numeric keys, append value if not already in the array.
            elseif ( is_numeric( $key ) ) {
                if ( ! in_array( $value, $merged ) ) {
                    $merged[] = $value;
                }
            }
            // For non-numeric keys, overwrite or set the value.
            else {
                $merged[ $key ] = $value;
            }
        }

        // Return the merged array, casting back to object if necessary.
        return is_object( $array1 ) ? (object) $merged : $merged;
    }

    /**
     * Order Array By Place
     *
     * @param array $array_to_order The array to order.
     * @return array The ordered array.
     */
    public function order_array_by_place( $array_to_order ) {
        $ordered = array();
        $pending = array();

        // First pass - add items without place_after and collect those with place_after.
        foreach ( $array_to_order as $key => $item ) {
            if ( ! isset( $item['place_after'] ) ) {
                $ordered[ $key ] = $item;
            } else {
                $pending[ $key ] = $item;
            }
        }

        // Second pass - process items with place_after.
        while ( ! empty( $pending ) ) {
            foreach ( $pending as $key => $item ) {
                $after_key = $item['place_after'];

                // Find position of the target key.
                $target_pos = array_search( $after_key, array_keys( $ordered ) );

                if ( false !== $target_pos ) {
                    // Insert after the target position.
                    $ordered = array_slice( $ordered, 0, $target_pos + 1, true ) +
                                array( $key => $item ) +
                                array_slice( $ordered, $target_pos + 1, null, true );

                    unset( $pending[ $key ] );
                }
            }
        }

        return $ordered;
    }

    /**
     * Recurse function to sanitize text field
     *
     * @param array $params The params to sanitize.
     * @return array The sanitized params.
     */
    public function sanitizeTextField( $params ) {
        foreach ( $params as $key => &$value ) {
            if ( is_array( $value ) ) {
                $value = $this->sanitizeTextField( $value );
            } else {
                $value = sanitize_text_field( $value );
            }
        }
        return $params;
    }

    /**
     * Transform Boolean Value
     *
     * @param mixed $data The data to transform.
     * @return mixed The transformed data.
     */
    public function transformBooleanValue( $data ) {
        if ( is_array( $data ) ) {
            foreach ( $data as $key => &$value ) {
                if ( is_array( $value ) ) {
                    $value = $this->transformBooleanValue( $value ); } else {
                    $value = ( 1 === $value || true === $value || 'true' === $value || '1' === $value ) ? 1 : 0;
                }
            }
        } else {
            $data = ( 1 === $data || true === $data || 'true' === $data || '1' === $data ) ? 1 : 0;
        }
        return $data;
    }

    /**
     * Transform yes/no value
     *
     * @param mixed $data The data to transform.
     * @return mixed The transformed data.
     */
    public function transform_yes_no_value( $data ) {
        return 'yes' === $data || $this->transformBooleanValue( $data ) === 1 ? 'yes' : 'no';
    }

    /**
     * Transform Location Value.
     *
     * In version 1.10.0, a transformation was introduced to the location data structure
     * to adapt to database changes. Previously, location data did not include rule groups,
     * but now each rule can belong to a rule group.
     *
     * @since 1.10.0
     * @access public
     *
     * @param array $locations The location array to be transformed.
     * @return array The transformed location array, or the original array if no transformation is needed.
     */
    public function transformLocationValue( $locations ) {
        if ( is_array( $locations ) && count( $locations ) > 0 ) {
            if ( ! isset( $locations[0]['rules'] ) ) {
                // Wrap the data into a grouped format.
                $transformed = array(
                    array(
                        'logic' => 'OR', // Default logic for the group.
                        'rules' => $locations,
                    ),
                );
                return $transformed;
            }
        }

        // Return the original data if no transformation is needed.
        return $locations;
    }

    /**
     * Check if an array is in another array.
     *
     * @param array $needle The array to search for.
     * @param array $haystack The array to search within.
     * @return bool True if the array is found, false otherwise.
     */
    public function isArrayInArray( array $needle, array $haystack ): bool {
        return count( array_intersect( $needle, $haystack ) ) === count( $needle );
    }

    /**
     * Cast a given value to match the data type of a reference value.
     *
     * This is useful when comparing user-provided input (e.g., from form or rule)
     * against existing post meta values, ensuring type-safe comparison.
     *
     * @param mixed $value     The input value to cast (usually from rule definition).
     * @param mixed $reference The reference value whose type will be used for casting (usually from post meta).
     * @return mixed The casted value to match the type of the reference.
     */
    public function cast_value_to_type( $value, $reference ) {
        if ( is_bool( $reference ) ) {
            return filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
        }

        if ( is_int( $reference ) ) {
            return (int) $value;
        }

        if ( is_float( $reference ) ) {
            return (float) $value;
        }

        if ( is_array( $reference ) ) {
            return (array) $value;
        }

        return $value; // default: string.
    }
}
