<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\Dataset;
use Limb_Chatbot\Includes\Data_Objects\File;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Limb_Chatbot;
use ReflectionClass;

/**
 * Class Helper
 *
 * Provides utility functions for string manipulation, file processing,
 * debugging, error formatting, and reflection-based operations.
 *
 * @package Limb_Chatbot\Includes\Services
 * @since 1.0.0
 */
class Helper {

	/**
	 * Converts an underscore string to CamelCase.
	 *
	 * @param  string  $string  The input string.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function underscore_to_camelcase( $string ) {
		$words = explode( '_', $string );

		return ucfirst( implode( '', array_map( 'ucfirst', $words ) ) );
	}

	/**
	 * Converts a camelCase string to underscore_case.
	 *
	 * @param  string  $string  The input string.
	 * @param  string  $separator  The separator character.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function camelcase_to_underscore( $string, $separator = '_' ) {
		// Use a regular expression to replace camel case with underscore
		$output = preg_replace_callback( '/([a-z])([A-Z])/', function ( $matches ) use ( $separator ) {
			return $matches[1] . $separator . strtolower( $matches[2] );
		}, $string );

		return strtolower( $output );
	}

	public static function text_to_underscore( $string ) {
		$array = explode( ' ', trim( $string ) );
		$array = array_map( function ( $item ) {
			return strtolower( $item );
		}, $array );

		return implode( '_', $array );
	}

	/**
	 * Converts underscore to hyphen.
	 *
	 * @param  string  $string  Input string.
	 * @param  bool  $lowercase  Whether to convert to lowercase.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function underscore_to_hyphen( $string, $lowercase = false ) {
		$string = str_replace( '_', '-', $string );

		return $lowercase ? strtolower( $string ) : $string;
	}

	/**
	 * Converts hyphen to underscore.
	 *
	 * @param  string  $string  Input string.
	 * @param  bool  $lowercase  Whether to convert to lowercase.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function hyphen_to_underscore( $string, $lowercase = false ) {
		$string = str_replace( '-', '_', $string );

		return $lowercase ? strtolower( $string ) : $string;
	}

	/**
	 * Converts a custom Exception or fallback into WP_Error.
	 *
	 * @param  mixed  $e  Exception or other error data.
	 *
	 * @return \WP_Error
	 * @since 1.0.0
	 *
	 */
	public static function get_wp_error( $e, $message = '' ) {
		if ( $e instanceof Exception ) {
			return new \WP_Error( $e->get_error_code(), $e->getMessage(), [ 'status' => $e->get_http_status(), 'details' => $e->get_error_data() ] );
		}
		$message = ! empty( $message ) ? $message : __( 'Technical error', 'limb-chatbot' );

		return new \WP_Error( 'technical_error', $message, [ 'status' => 400 ] );
	}

	/**
	 * Gets short class name.
	 *
	 * @param  string|object  $className  Class name or object.
	 * @param  bool  $lowercase  Whether to return in lowercase.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function get_class_shortname( $className, $lowercase = false ) {
		if ( is_object( $className ) ) {
			$reflection = new ReflectionClass( $className );
			$name       = $reflection->getShortName();
		} else {
			$className = is_object( $className ) ? get_class( $className ) : $className;
			$className = explode( '\\', $className );
			$name      = end( $className );
		}
		if ( $lowercase ) {
			$name = strtolower( $name );
		}

		return $name;
	}

	/**
	 * Gets the namespace from a fully qualified class name.
	 *
	 * @param  string|object  $class  The class.
	 *
	 * @return string|null
	 * @since 1.0.0
	 *
	 */
	public static function get_namespace_name( $class ): ?string {
		try {
			return ( new ReflectionClass( $class ) )->getNamespaceName();
		} catch ( \Exception $e ) {
			self::log( $e, __METHOD__ );

			return null;
		}
	}

	/**
	 * Logs plugin debug output if debug mode is enabled.
	 *
	 * @param  mixed  $data  The data to log.
	 * @param  string  $key  Optional label.
	 *
	 * @return void
	 * @since 1.0.0
	 *
	 */
	public static function log( $data, $key = 'log' ): void {
		if ( Limb_Chatbot()->get_debug() ) {
			$key = (string) $key;
			if ( $data instanceof Exception ) {
				$data_to_log = $data->get_error_data();
			} else {
				$data_to_log = $data;
			}
			if ( $data instanceof \Exception ) {
				$message = $data->getMessage() . ':  ';
			}
			error_log( Limb_Chatbot()->get_plugin_name() . '-debug: [' . $key . ']: ' . ( $message ?? '' ) . print_r( $data_to_log, true ) );
		}
	}

	/**
	 * Generates a UUID.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function get_uuid() {
		return wp_generate_uuid4();
	}

	/**
	 * Determine if the site is running in local or live mode.
	 *
	 * @since 1.0.9
	 * @return string 'local' or 'live'
	 */
	public static function get_site_mode() {
		$site_url = home_url();

		// Check for common local development indicators
		$local_indicators = array(
			'.local',
			'.localhost',
			'localhost',
			'127.0.0.1',
			'::1',
		);

		foreach ( $local_indicators as $indicator ) {
			if ( stripos( $site_url, $indicator ) !== false ) {
				return 'local';
			}
		}

		return 'live';
	}

	/**
	 * Generates a random string using WordPress password generator.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function get_random_string() {
		return wp_generate_password( 10, false );
	}

	/**
	 * Tries to decode a string as JSON, or return original value.
	 *
	 * @param  string  $string  Input string.
	 * @param  bool  $assoc  Whether to return associative array.
	 *
	 * @return mixed
	 * @since 1.0.0
	 *
	 */
	public static function maybe_json_decode( $string, $assoc = true ) {
		if ( ! is_string( $string ) ) {
			return $string;
		}
		$decoded = json_decode( $string, $assoc );
		if ( json_last_error() == JSON_ERROR_NONE ) {
			return $decoded;
		} else {
			return $string;
		}
	}

	/**
	 * Validates a URL with protocol and filter.
	 *
	 * @param  string  $url  URL to validate.
	 *
	 * @return bool
	 * @since 1.0.0
	 *
	 */
	public static function is_valid_url( $url ) {
		if ( ! str_starts_with( $url, 'http://' ) && ! str_starts_with( $url, 'https://' ) ) {
			return false;
		}
		if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Validates a base64 image string with file signature.
	 *
	 * @param  string  $base64  The base64-encoded image.
	 *
	 * @return bool
	 * @since 1.0.0
	 *
	 */
	public static function is_valid_base64_image( $base64 ) {
		// Check if the string starts with the correct data URL format
		if ( preg_match( '/^data:image\/(\w+);base64,/', $base64, $matches ) ) {
			// Extract the actual base64 data part
			$data = substr( $base64, strpos( $base64, ',' ) + 1 );
			// Check if the remaining string has valid base64 characters and optional padding
			if ( preg_match( '/^[A-Za-z0-9+\/]*={0,2}$/', $data ) ) {
				// Decode a portion to check for image signatures
				$decodedData = base64_decode( $data, true );
				if ( $decodedData === false ) {
					return false; // Decoding failed
				}
				// Check image type based on signature
				$imageType = strtolower( $matches[1] );
				switch ( $imageType ) {
					case 'png':
						return str_starts_with( $decodedData, "\x89PNG\r\n\x1a\n" );
					case 'jpeg':
						return str_starts_with( $decodedData, "\xFF\xD8\xFF" ); // JPEG signature
					case 'gif':
						return str_starts_with( $decodedData, "GIF89a" ) || str_starts_with( $decodedData, "GIF87a" ); // GIF signature
					// Add more image formats as needed
					default:
						return false; // Unsupported image type
				}
			}
		}

		return false; // Invalid format
	}

	/**
	 * Decodes a file response and returns as base64 image.
	 *
	 * @param  mixed  $response  The HTTP response.
	 *
	 * @return string|false
	 * @since 1.0.0
	 *
	 */
	public static function decode_file_from_rest_response( $response ) {
		$body      = wp_remote_retrieve_body( $response );
		$mime_type = wp_remote_retrieve_header( $response, 'content-type' );
		if ( ! empty( $mime_type ) && ! is_wp_error( $mime_type ) && $body ) {
			$base64_image = base64_encode( $body );

			return 'data:' . $mime_type . ';base64,' . $base64_image;
		}

		return false;
	}

	/**
	 * Checks if a string is probably JSON.
	 *
	 * @param  string  $string  Input string.
	 *
	 * @return bool
	 * @since 1.0.0
	 *
	 */
	public static function is_probable_json( $string ) {
		$string = trim( $string );
		if ( empty( $string ) ) {
			return false;
		}
		// Quick check: does it start with { or [ and end with } or ]?
		if ( ! ( ( ( $string[0] === '{' || $string[0] === '[' )
		           && ( $string[ strlen( $string ) - 1 ] === '}' || $string[ strlen( $string ) - 1 ] === ']' ) ) ) ) {
			return false;
		}
		$bracketCount = 0;
		$inQuotes     = false;
		$escaped      = false;
		for ( $i = 0; $i < strlen( $string ); $i ++ ) {
			$char = $string[ $i ];
			if ( $char === '"' && ! $escaped ) {
				// Toggle quotes, unless it's an escaped quote
				$inQuotes = ! $inQuotes;
			}
			// Manage escape sequences so we don't get tripped by escaped quotes
			$escaped = ( $char === '\\' ) && ! $escaped;
			// Only process brackets if not inside quotes
			if ( ! $inQuotes ) {
				if ( $char === '{' || $char === '[' ) {
					$bracketCount ++;
				} elseif ( $char === '}' || $char === ']' ) {
					$bracketCount --;
					// If at any point we have more closing brackets, it's invalid
					if ( $bracketCount < 0 ) {
						return false;
					}
				}
			}
		}

		// Finally, all brackets should be balanced (i.e., count should be zero)
		return $bracketCount === 0 && ! $inQuotes;
	}

	/**
	 * Downloads image by URL and returns base64-encoded image data URI.
	 *
	 * @param  string  $url  The image URL.
	 *
	 * @return string|null
	 * @since 1.0.0
	 *
	 */
	public static function convert_uri_to_base64( $url ) {
		if ( ! empty( $url ) && filter_var( $url, FILTER_VALIDATE_URL ) ) {
			$response  = wp_remote_get( $url );
			$body      = wp_remote_retrieve_body( $response );
			$mime_type = wp_remote_retrieve_header( $response, 'content-type' );
			if ( ! empty( $mime_type ) && ! is_wp_error( $mime_type ) && $body ) {
				$base64_image = base64_encode( $body );

				return 'data:' . $mime_type . ';base64,' . $base64_image;
			}
		}

		return null;
	}

	/**
	 * File move
	 *
	 * @param string $from From
	 * @param string $to To
	 *
	 * @return bool
	 */
	public static function file_move( $from, $to ) {
		try {
			global $wp_filesystem;

			if ( ! function_exists( 'WP_Filesystem' ) ) {
				// Instead of direct include, use WordPress's proper loading
				if ( ! function_exists( 'get_filesystem_method' ) ) {
					return false; // WordPress not fully loaded
				}
			}

			WP_Filesystem();

			if ( $wp_filesystem->exists( $from ) ) {
				return $wp_filesystem->move( $from, $to );
			}

			return false;
		} catch ( \Exception $e ) {
			return false;
		}
	}

	/**
	 * Extracts MIME type from base64 image string.
	 *
	 * @param  string  $base64_data  Base64 input.
	 *
	 * @return string|null
	 * @since 1.0.0
	 *
	 */
	public static function get_mime_type_from_base64( $base64_data ) {
		if ( preg_match( '/^data:(\w+\/\w+);base64,/', $base64_data, $type ) ) {
			return $type[1]; // MIME type from the base64 header
		}

		return null;
	}

	/**
	 * Checks if MIME type is an image type.
	 *
	 * @param  string  $mime_type  MIME type.
	 *
	 * @return bool
	 * @since 1.0.0
	 *
	 */
	public static function is_image_mime_type( string $mime_type ) {
		return in_array( $mime_type, [
			'image/jpeg',
			'image/png',
			'image/gif',
			'image/webp',
			'image/svg+xml',
			'image/bmp',
			'image/tiff',
			'image/x-icon'
		], true );
	}

	/**
	 * Gets the value of a protected/private property from an object.
	 *
	 * @param  object  $instance  The object instance.
	 * @param  string  $property_name  Property name.
	 *
	 * @return mixed|null
	 * @since 1.0.0
	 *
	 */
	public static function get_instance_property_value( $instance, string $property_name ) {
		try {
			$reflection = new ReflectionClass( $instance );
			$property   = $reflection->getProperty( $property_name );
			$property->setAccessible( true );

			return $property->getValue( $instance );
		} catch ( \Exception $e ) {
			return null;
		}
	}

	/**
	 * Casts value to given type name.
	 *
	 * @param  string  $type  Type name.
	 * @param  mixed  $value  Value to cast.
	 *
	 * @return mixed
	 * @since 1.0.0
	 *
	 */
	public static function cast_value( string $type, $value ) {
		switch ( $type ) {
			case 'int':
			case 'integer':
				return (int) $value;
			case 'float':
				return (float) $value;
			case 'string':
				return (string) $value;
			case 'bool':
			case 'boolean':
				return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
			case 'array':
				return (array) $value;
			case 'object':
				return (object) $value;
			case 'Limb_Chatbot\Includes\Services\Widget_Item_Collection':
			case 'Limb_Chatbot\Includes\Services\Widget_Collection':
			case 'Limb_Chatbot\Includes\Services\Collection':
				return $value;
			default:
				return null;
			// throw new Exception("Unsupported type: {$type}");
		}
	}

	/**
	 * Loads plugin configuration from /configs/{file}.php and nested keys.
	 *
	 * @param  string  $config  Dot-style config path (e.g. `file/key1/key2`).
	 *
	 * @return mixed|null
	 * @since 1.0.0
	 *
	 */
	public static function config( string $config ) {
		$config = explode( '/', trim( $config ) );
		if ( empty( $config[0] ) ) {
			return null;
		}
		$file_path = rtrim( Limb_Chatbot::get_instance()->get_plugin_dir_path(), '/' ) . "/configs/{$config[0]}.php";
		if ( ! file_exists( $file_path ) ) {
			return null;
		}
		$res = include $file_path;
		foreach ( array_slice( $config, 1 ) as $key ) {
			if ( ! is_array( $res ) || ! array_key_exists( $key, $res ) ) {
				return null;
			}
			$res = $res[ $key ];
		}

		return $res;
	}

	/**
	 * Generates a random string with alphanumeric characters.
	 *
	 * @param  int  $length  String length.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function random_string( $length = 10 ) {
		return wp_generate_password( $length, false, false );
	}

	/**
	 * Gets normalized type name for a value.
	 *
	 * @param  mixed  $value  The value to evaluate.
	 *
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public static function gettype( $value ): string {
		if ( is_int( $value ) ) {
			return 'int';
		}
		if ( is_bool( $value ) ) {
			return 'bool';
		}
		if ( is_float( $value ) ) {
			return 'float';
		}
		if ( is_string( $value ) ) {
			return 'string';
		}
		if ( is_array( $value ) ) {
			return 'array';
		}
		if ( is_object( $value ) ) {
			return 'object';
		}

		return gettype( $value );
	}

	/**
	 * Checks if array is associative.
	 *
	 * @param  array  $arr  Input array.
	 *
	 * @return bool
	 * @since 1.0.0
	 *
	 */
	public static function is_assoc( array $arr ): bool {
		return array_keys( $arr ) !== range( 0, count( $arr ) - 1 );
	}

	/**
	 * Gets the full upload path for a given relative file path.
	 *
	 * @param  string  $file_path  Relative file path.
	 *
	 * @return string|null
	 * @since 1.0.0
	 *
	 */
	public static function get_wp_uploaded_file_dir( $file_path ): ?string {
		return trailingslashit( wp_upload_dir()['basedir'] ) . $file_path ?? null;
	}

	/**
	 * @param $file
	 * @param  string  $to
	 *
	 * @return bool
	 */
	public static function wp_handle_limb_upload( $file, string $to ): bool {
		// Check if we're in a REST API context and load required functions
		if ( ! function_exists( 'wp_handle_upload' ) ) {
			if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
				// This is acceptable for REST API context as per WordPress guidelines
				require_once( ABSPATH . 'wp-admin/includes/file.php' );
			} else {
				return false;
			}
		}

		$upload = wp_handle_upload( $file, [ 'test_form' => false ] );
		if ( ! empty( $upload['file'] ) ) {
			return self::file_move( $upload['file'], $to );
		} else {
			return false;
		}
	}

	public static function resolve_source_object( $source_type, $source, ?Dataset $dataset = null ) {
		switch ( $source_type ) {
			case 'post':
			case 'cpt':
				return get_post( $source );
			case 'term':
				return get_term( $source );
			case 'text':
			case 'q_a':
			case 'url':
				return $source;
			case 'file':
				return File::find_by_uuid( $source );
			default:
				return null;
		}
	}



	/**
	 * Check if value matches expected type.
	 *
	 * @param  mixed   $value Value to check
	 * @param  string  $type  Expected type
	 *
	 * @return bool True if type matches
	 * @since 1.0.0
	 */
	public static function check_type( $value, string $type ): bool {
		switch ( $type ) {
			case 'string':
				return is_string( $value );
			case 'integer':
				return is_int( $value ) || ( is_string( $value ) && is_numeric( $value ) );
			case 'boolean':
				return is_bool( $value ) || in_array( $value, [ '0', '1', 0, 1 ], true );
			case 'array':
				return is_array( $value );
			default:
				return true;
		}
	}

	public static function is_local() {
		if ( function_exists( 'wp_get_environment_type' ) ) {
			$env = wp_get_environment_type();

			if ( $env === 'local' || $env === 'development' ) {
				return true;
			}
		}

		// 2) Environment variables (used by Bedrock, Docker, CI setups …)
		$envVars = [
			getenv( 'WP_ENV' ),
			getenv( 'APP_ENV' ),
			getenv( 'ENVIRONMENT' ),
			getenv( 'WP_ENVIRONMENT_TYPE' ),
		];

		foreach ( $envVars as $var ) {
			if ( in_array( $var, [ 'local', 'development', 'dev' ], true ) ) {
				return true;
			}
		}

		// 3) Hostname/IP based heuristics (fallback only)
		$host   = $_SERVER['HTTP_HOST'] ?? '';
		$remote = $_SERVER['REMOTE_ADDR'] ?? '';

		$local_ips  = [ '127.0.0.1', '::1' ];
		$local_tlds = [ '.test', '.local', '.localhost', '.loc' ];

		if ( in_array( $remote, $local_ips, true ) ) {
			return true;
		}

		foreach ( $local_tlds as $tld ) {
			if ( str_ends_with( $host, $tld ) ) {
				return true;
			}
		}

		return false;
	}

	public static function generate_unique_id(int $length = 10): string
	{
		// Clamp length between 1 and 10
		$length = max(1, min(10, $length));

		$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
		$maxIndex   = strlen($characters) - 1;
		$result     = '';

		for ($i = 0; $i < $length; $i++) {
			$result .= $characters[random_int(0, $maxIndex)];
		}

		return $result;
	}
}
