<?php
/**
 * Calculator and BMI data helper.
 *
 * Module-local service for the Calculator module.
 * Kept with the same public function/class names for backward compatibility
 * while moving ownership out of the central includes/ kernel.
 *
 * @package Noesia_Smart_Waiter
 */

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

if ( ! function_exists( 'noesia_swa_parse_float_from_post' ) ) {
	/**
	 * Parse a float safely from a user-supplied string.
	 *
	 * @param string $raw Raw value.
	 * @return float|null
	 */
	function noesia_swa_parse_float_from_post( $raw ) {
		$raw = (string) $raw;
		$raw = trim( $raw );
		$raw = str_replace( ',', '.', $raw );
		$raw = preg_replace( '/[^0-9.\-]/', '', $raw );

		if ( '' === $raw || ! is_numeric( $raw ) ) {
			return null;
		}

		return (float) $raw;
	}
}

if ( ! function_exists( 'noesia_swa_calc_is_operator' ) ) {
	/**
	 * Check whether a character is a supported operator.
	 *
	 * @param string $ch Character.
	 * @return bool
	 */
	function noesia_swa_calc_is_operator( $ch ) {
		return in_array( $ch, array( '+', '-', '*', '/' ), true );
	}
}

if ( ! function_exists( 'noesia_swa_calc_sanitize_expr' ) ) {
	/**
	 * Sanitize calculator expression.
	 *
	 * @param string $expr Raw expression.
	 * @return string
	 */
	function noesia_swa_calc_sanitize_expr( $expr ) {
		$expr = (string) $expr;
		$expr = trim( $expr );
		$expr = str_replace( array( ' ', "\t", "\n", "\r" ), '', $expr );
		$expr = str_replace( array( '×', '÷', '−' ), array( '*', '/', '-' ), $expr );
		$expr = preg_replace( '/[^0-9.\+\-\*\/]/', '', $expr );
		$expr = substr( $expr, 0, 64 );

		return $expr;
	}
}

if ( ! function_exists( 'noesia_swa_calc_tokenize' ) ) {
	/**
	 * Tokenize a simple arithmetic expression.
	 *
	 * @param string $expr Sanitized expression.
	 * @return array{tokens:array,error:string}
	 */
	function noesia_swa_calc_tokenize( $expr ) {
		$tokens = array();
		$buf    = '';
		$len    = strlen( $expr );
		$prev   = '';

		for ( $i = 0; $i < $len; $i++ ) {
			$ch = $expr[ $i ];

			if ( ctype_digit( $ch ) || '.' === $ch ) {
				$buf .= $ch;
				$prev = $ch;
				continue;
			}

			if ( noesia_swa_calc_is_operator( $ch ) ) {
				if ( '-' === $ch && ( 0 === $i || noesia_swa_calc_is_operator( $prev ) ) ) {
					$buf .= $ch;
					$prev = $ch;
					continue;
				}

				if ( '' !== $buf ) {
					if ( '-' === $buf || '.' === $buf || '-.' === $buf || ! is_numeric( $buf ) ) {
						return array(
							'tokens' => array(),
							'error'  => __( 'Invalid number format.', 'noesia-smart-waiter' ),
						);
					}

					$tokens[] = (float) $buf;
					$buf      = '';
				}

				$tokens[] = $ch;
				$prev     = $ch;
				continue;
			}

			return array(
				'tokens' => array(),
				'error'  => __( 'Invalid expression.', 'noesia-smart-waiter' ),
			);
		}

		if ( '' !== $buf ) {
			if ( '-' === $buf || '.' === $buf || '-.' === $buf || ! is_numeric( $buf ) ) {
				return array(
					'tokens' => array(),
					'error'  => __( 'Invalid number format.', 'noesia-smart-waiter' ),
				);
			}

			$tokens[] = (float) $buf;
		}

		return array(
			'tokens' => $tokens,
			'error'  => '',
		);
	}
}

if ( ! function_exists( 'noesia_swa_calc_eval_tokens' ) ) {
	/**
	 * Evaluate tokenized expression.
	 *
	 * @param array $tokens Tokens.
	 * @return array{value:float|null,error:string}
	 */
	function noesia_swa_calc_eval_tokens( $tokens ) {
		$prec   = array( '+' => 1, '-' => 1, '*' => 2, '/' => 2 );
		$output = array();
		$ops    = array();

		foreach ( $tokens as $token ) {
			if ( is_float( $token ) || is_int( $token ) ) {
				$output[] = (float) $token;
				continue;
			}

			if ( is_string( $token ) && isset( $prec[ $token ] ) ) {
				while ( ! empty( $ops ) ) {
					$top = end( $ops );

					if ( is_string( $top ) && isset( $prec[ $top ] ) && $prec[ $top ] >= $prec[ $token ] ) {
						$output[] = array_pop( $ops );
						continue;
					}

					break;
				}

				$ops[] = $token;
				continue;
			}

			return array(
				'value' => null,
				'error' => __( 'Invalid expression.', 'noesia-smart-waiter' ),
			);
		}

		while ( ! empty( $ops ) ) {
			$output[] = array_pop( $ops );
		}

		$stack = array();

		foreach ( $output as $token ) {
			if ( is_float( $token ) || is_int( $token ) ) {
				$stack[] = (float) $token;
				continue;
			}

			if ( ! is_string( $token ) || ! isset( $prec[ $token ] ) ) {
				return array(
					'value' => null,
					'error' => __( 'Invalid expression.', 'noesia-smart-waiter' ),
				);
			}

			if ( count( $stack ) < 2 ) {
				return array(
					'value' => null,
					'error' => __( 'Incomplete expression.', 'noesia-smart-waiter' ),
				);
			}

			$b = array_pop( $stack );
			$a = array_pop( $stack );

			switch ( $token ) {
				case '+':
					$stack[] = $a + $b;
					break;
				case '-':
					$stack[] = $a - $b;
					break;
				case '*':
					$stack[] = $a * $b;
					break;
				case '/':
					if ( 0.0 === (float) $b ) {
						return array(
							'value' => null,
							'error' => __( 'Division by zero.', 'noesia-smart-waiter' ),
						);
					}
					$stack[] = $a / $b;
					break;
			}
		}

		if ( 1 !== count( $stack ) ) {
			return array(
				'value' => null,
				'error' => __( 'Invalid expression.', 'noesia-smart-waiter' ),
			);
		}

		return array(
			'value' => (float) $stack[0],
			'error' => '',
		);
	}
}

if ( ! function_exists( 'noesia_swa_calc_toggle_sign' ) ) {
	/**
	 * Toggle sign of the last number in the expression.
	 *
	 * @param string $expr Expression.
	 * @return string
	 */
	function noesia_swa_calc_toggle_sign( $expr ) {
		$expr = (string) $expr;

		if ( '' === $expr ) {
			return '-';
		}

		$len = strlen( $expr );
		$pos = -1;

		for ( $i = $len - 1; $i >= 0; $i-- ) {
			$ch = $expr[ $i ];

			if ( in_array( $ch, array( '+', '*', '/' ), true ) ) {
				$pos = $i;
				break;
			}

			if ( '-' === $ch ) {
				if ( 0 === $i ) {
					$pos = -1;
					break;
				}

				$prev = $expr[ $i - 1 ];

				if ( ctype_digit( $prev ) || '.' === $prev ) {
					$pos = $i;
					break;
				}
			}
		}

		if ( -1 === $pos ) {
			if ( 0 === strpos( $expr, '-' ) ) {
				return substr( $expr, 1 );
			}

			return '-' . $expr;
		}

		$head = substr( $expr, 0, $pos + 1 );
		$tail = substr( $expr, $pos + 1 );

		if ( '' === $tail ) {
			return $expr;
		}

		if ( 0 === strpos( $tail, '-' ) ) {
			return $head . substr( $tail, 1 );
		}

		return $head . '-' . $tail;
	}
}

if ( ! function_exists( 'noesia_swa_calc_current_segment_has_dot' ) ) {
	/**
	 * Check if the current numeric segment already contains a dot.
	 *
	 * @param string $expr Expression.
	 * @return bool
	 */
	function noesia_swa_calc_current_segment_has_dot( $expr ) {
		$expr = (string) $expr;
		$len  = strlen( $expr );

		for ( $i = $len - 1; $i >= 0; $i-- ) {
			$ch = $expr[ $i ];

			if ( '.' === $ch ) {
				return true;
			}

			if ( noesia_swa_calc_is_operator( $ch ) ) {
				return false;
			}
		}

		return false;
	}
}

if ( ! function_exists( 'noesia_swa_calc_append_key' ) ) {
	/**
	 * Append a key safely to the expression.
	 *
	 * @param string $expr Expression.
	 * @param string $key  Key.
	 * @return string
	 */
	function noesia_swa_calc_append_key( $expr, $key ) {
		$expr = noesia_swa_calc_sanitize_expr( $expr );
		$key  = (string) $key;

		if ( strlen( $expr ) >= 64 ) {
			return $expr;
		}

		$last = ( '' !== $expr ) ? substr( $expr, -1 ) : '';

		if ( preg_match( '/^[0-9]$/', $key ) ) {
			if ( '0' === $expr && '0' === $key ) {
				return $expr;
			}
			if ( '0' === $expr && '0' !== $key ) {
				return $key;
			}
			return $expr . $key;
		}

		if ( '.' === $key ) {
			if ( '' === $expr ) {
				return '0.';
			}
			if ( noesia_swa_calc_is_operator( $last ) ) {
				return $expr . '0.';
			}
			if ( noesia_swa_calc_current_segment_has_dot( $expr ) ) {
				return $expr;
			}
			return $expr . '.';
		}

		if ( in_array( $key, array( '+', '-', '*', '/' ), true ) ) {
			if ( '' === $expr ) {
				return ( '-' === $key ) ? '-' : '';
			}

			if ( noesia_swa_calc_is_operator( $last ) ) {
				if ( '-' === $key ) {
					return ( '-' === $last ) ? $expr : $expr . '-';
				}
				return substr( $expr, 0, -1 ) . $key;
			}

			return $expr . $key;
		}

		return $expr;
	}
}

if ( ! function_exists( 'noesia_swa_calc_format_value' ) ) {
	/**
	 * Format a number for readable display.
	 *
	 * @param float $value Number.
	 * @return string
	 */
	function noesia_swa_calc_format_value( $value ) {
		$value = (float) $value;

		if ( abs( $value - round( $value ) ) < 0.000000001 ) {
			return (string) (int) round( $value );
		}

		$text = number_format_i18n( $value, 6 );
		$text = rtrim( $text, '0' );
		$text = rtrim( $text, '.,' );

		return $text;
	}
}

if ( ! function_exists( 'noesia_swa_bmi_category' ) ) {
	/**
	 * Return adult BMI category.
	 *
	 * @param float $bmi BMI value.
	 * @return string
	 */
	function noesia_swa_bmi_category( $bmi ) {
		$bmi = (float) $bmi;

		if ( $bmi < 18.5 ) {
			return __( 'Underweight', 'noesia-smart-waiter' );
		}
		if ( $bmi < 25.0 ) {
			return __( 'Normal weight', 'noesia-smart-waiter' );
		}
		if ( $bmi < 30.0 ) {
			return __( 'Overweight', 'noesia-smart-waiter' );
		}
		if ( $bmi < 35.0 ) {
			return __( 'Obesity (Class I)', 'noesia-smart-waiter' );
		}
		if ( $bmi < 40.0 ) {
			return __( 'Obesity (Class II)', 'noesia-smart-waiter' );
		}

		return __( 'Obesity (Class III)', 'noesia-smart-waiter' );
	}
}

if ( ! class_exists( 'Noesia_SWA_Calculator_Data' ) ) {
    /**
     * Calculator and BMI data helper.
     */
    class Noesia_SWA_Calculator_Data {

        /**
         * Build the legacy calculator and BMI view context.
         *
         * @return array<string, mixed>
         */
        public function get_context() {
            $section_url = admin_url( 'admin.php?page=noesia_swa_settings&section=calculator' );

            // -----------------------------------------------------------------------------
            // Calculator state.
            // -----------------------------------------------------------------------------

            $calc_expr   = '';
            $calc_error  = '';
            $calc_result = null;

            if ( isset( $_POST['noesia_swa_calc_key'] ) ) {
            	if ( ! current_user_can( 'manage_options' ) ) {
            		$calc_error = __( 'You do not have permission to use this tool.', 'noesia-smart-waiter' );
            	} else {
            		check_admin_referer( 'noesia_swa_calcpad_action', 'noesia_swa_calcpad_nonce' );

            		$key_raw  = sanitize_text_field( wp_unslash( (string) $_POST['noesia_swa_calc_key'] ) );
            		$expr_raw = isset( $_POST['noesia_swa_calc_expr'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['noesia_swa_calc_expr'] ) ) : '';

            		$calc_expr = noesia_swa_calc_sanitize_expr( $expr_raw );

            		$allowed_keys = array(
            			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            			'.', '+', '-', '*', '/', '=', 'C', 'BS', 'SIGN',
            		);

            		$key = in_array( $key_raw, $allowed_keys, true ) ? $key_raw : '';

            		if ( 'C' === $key ) {
            			$calc_expr   = '';
            			$calc_error  = '';
            			$calc_result = null;
            		} elseif ( 'BS' === $key ) {
            			$calc_expr = ( '' !== $calc_expr ) ? substr( $calc_expr, 0, -1 ) : '';
            		} elseif ( 'SIGN' === $key ) {
            			$calc_expr = noesia_swa_calc_toggle_sign( $calc_expr );
            		} elseif ( '=' === $key ) {
            			$expr_eval = $calc_expr;

            			if ( '' !== $expr_eval && noesia_swa_calc_is_operator( substr( $expr_eval, -1 ) ) ) {
            				$expr_eval = substr( $expr_eval, 0, -1 );
            			}

            			if ( '' === $expr_eval || '-' === $expr_eval ) {
            				$calc_error = __( 'Please enter a valid expression.', 'noesia-smart-waiter' );
            			} else {
            				$tokenized = noesia_swa_calc_tokenize( $expr_eval );

            				if ( '' !== $tokenized['error'] ) {
            					$calc_error = sanitize_text_field( (string) $tokenized['error'] );
            				} else {
            					$evaluated = noesia_swa_calc_eval_tokens( $tokenized['tokens'] );

            					if ( '' !== $evaluated['error'] ) {
            						$calc_error = sanitize_text_field( (string) $evaluated['error'] );
            					} else {
            						$calc_result = (float) $evaluated['value'];
            						$raw_expr    = rtrim( rtrim( sprintf( '%.12f', (float) $calc_result ), '0' ), '.' );
            						$calc_expr   = noesia_swa_calc_sanitize_expr( $raw_expr );
            					}
            				}
            			}
            		} else {
            			$calc_expr = noesia_swa_calc_append_key( $calc_expr, $key );
            		}
            	}
            }

            $calc_display = ( '' !== $calc_expr ) ? $calc_expr : '0';

            // -----------------------------------------------------------------------------
            // BMI state.
            // -----------------------------------------------------------------------------

            $sex_labels = array(
            	'male'   => __( 'Male', 'noesia-smart-waiter' ),
            	'female' => __( 'Female', 'noesia-smart-waiter' ),
            );

            $bmi_sex_raw    = 'male';
            $bmi_height_raw = '';
            $bmi_weight_raw = '';
            $bmi_age_raw    = '';

            $bmi_error     = '';
            $bmi_value     = null;
            $bmi_category  = '';
            $bmi_range_min = null;
            $bmi_range_max = null;
            $bmr_value     = null;
            $bf_percent    = null;

            if ( isset( $_POST['noesia_swa_bmi_submit'] ) ) {
            	if ( ! current_user_can( 'manage_options' ) ) {
            		$bmi_error = __( 'You do not have permission to use this tool.', 'noesia-smart-waiter' );
            	} else {
            		check_admin_referer( 'noesia_swa_bmi_action', 'noesia_swa_bmi_nonce' );

            		$bmi_sex_raw    = isset( $_POST['noesia_swa_bmi_sex'] ) ? sanitize_key( wp_unslash( (string) $_POST['noesia_swa_bmi_sex'] ) ) : 'male';
            		$bmi_height_raw = isset( $_POST['noesia_swa_bmi_height_cm'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['noesia_swa_bmi_height_cm'] ) ) : '';
            		$bmi_weight_raw = isset( $_POST['noesia_swa_bmi_weight_kg'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['noesia_swa_bmi_weight_kg'] ) ) : '';
            		$bmi_age_raw    = isset( $_POST['noesia_swa_bmi_age'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['noesia_swa_bmi_age'] ) ) : '';

            		if ( ! isset( $sex_labels[ $bmi_sex_raw ] ) ) {
            			$bmi_sex_raw = 'male';
            		}

            		$height_cm = noesia_swa_parse_float_from_post( $bmi_height_raw );
            		$weight_kg = noesia_swa_parse_float_from_post( $bmi_weight_raw );

            		$age = null;
            		if ( '' !== $bmi_age_raw ) {
            			$age_value = absint( $bmi_age_raw );
            			if ( $age_value > 0 && $age_value <= 120 ) {
            				$age = $age_value;
            			}
            		}

            		if ( null === $height_cm || null === $weight_kg ) {
            			$bmi_error = __( 'Please enter height (cm) and weight (kg).', 'noesia-smart-waiter' );
            		} elseif ( $height_cm <= 0 || $weight_kg <= 0 ) {
            			$bmi_error = __( 'Height and weight must be greater than zero.', 'noesia-smart-waiter' );
            		} else {
            			$height_m = $height_cm / 100.0;
            			$bmi      = $weight_kg / ( $height_m * $height_m );

            			$bmi_value     = $bmi;
            			$bmi_category  = noesia_swa_bmi_category( $bmi );
            			$bmi_range_min = 18.5 * $height_m * $height_m;
            			$bmi_range_max = 24.9 * $height_m * $height_m;

            			if ( null !== $age ) {
            				if ( 'male' === $bmi_sex_raw ) {
            					$bmr_value = ( 10.0 * $weight_kg ) + ( 6.25 * $height_cm ) - ( 5.0 * $age ) + 5.0;
            				} else {
            					$bmr_value = ( 10.0 * $weight_kg ) + ( 6.25 * $height_cm ) - ( 5.0 * $age ) - 161.0;
            				}

            				$sex_flag   = ( 'male' === $bmi_sex_raw ) ? 1 : 0;
            				$bf_percent = ( 1.20 * $bmi ) + ( 0.23 * $age ) - ( 10.8 * $sex_flag ) - 5.4;
            			}
            		}
            	}
            }

            return array(
                'section_url'    => $section_url,
                'calc_expr'      => $calc_expr,
                'calc_error'     => $calc_error,
                'calc_result'    => $calc_result,
                'calc_display'   => $calc_display,
                'sex_labels'     => $sex_labels,
                'bmi_sex_raw'    => $bmi_sex_raw,
                'bmi_height_raw' => $bmi_height_raw,
                'bmi_weight_raw' => $bmi_weight_raw,
                'bmi_age_raw'    => $bmi_age_raw,
                'bmi_error'      => $bmi_error,
                'bmi_value'      => $bmi_value,
                'bmi_category'   => $bmi_category,
                'bmi_range_min'  => $bmi_range_min,
                'bmi_range_max'  => $bmi_range_max,
                'bmr_value'      => $bmr_value,
                'bf_percent'     => $bf_percent,
            );
        }
    }
}
