<?php

namespace Limb_Chatbot\Includes\Integrations\WPML;

use Limb_Chatbot\Includes\Factories\Widget_Config_Factory;

/**
 * WPML integration for chatbot dynamic strings.
 */
class WPML {
	private const STRING_DOMAIN = 'limb-chatbot';

	/**
	 * Init integration hooks.
	 *
	 * @return void
	 */
	public function init(): void {
		add_action( 'init', [ $this, 'register_dynamic_strings' ], 20 );
		add_filter( 'lbaic_translate_dynamic_string', [ $this, 'filter_translate_dynamic_string' ], 10, 3 );
		add_filter( 'lbaic_translate_widget_config', [ $this, 'filter_translate_widget_config' ], 10, 3 );
	}

	/**
	 * Register all user-managed chatbot strings in WPML String Translation.
	 *
	 * @return void
	 */
	public function register_dynamic_strings(): void {
		if ( ! self::is_active() ) {
			return;
		}

		$this->register_chatbot_settings_strings();
		$this->register_widget_strings();
		$this->register_action_callback_strings();
		$this->register_parameter_strings();
	}

	/**
	 * Whether WPML String Translation API is available.
	 *
	 * @return bool
	 */
	public static function is_active(): bool {
		return has_action( 'wpml_register_single_string' ) || function_exists( 'icl_register_string' );
	}

	/**
	 * Translate a single dynamic string.
	 *
	 * @param string $name String key in WPML.
	 * @param mixed  $value String value.
	 *
	 * @return mixed
	 */
	public static function translate_string( string $name, $value ) {
		if ( ! is_string( $value ) || '' === trim( $value ) || ! self::is_active() ) {
			return $value;
		}

		$translated = apply_filters( 'wpml_translate_single_string', $value, self::STRING_DOMAIN, $name );

		return is_string( $translated ) ? $translated : $value;
	}

	/**
	 * Translate action callback text.
	 *
	 * @param int    $action_id Action ID.
	 * @param mixed  $value Source value.
	 *
	 * @return mixed
	 */
	public static function translate_action_callback_text( int $action_id, $value ) {
		return self::translate_string( sprintf( 'action.%d.callback_text', $action_id ), $value );
	}

	/**
	 * Translate parameter field with ID/action+name fallback.
	 *
	 * @param int    $parameter_id Parameter ID.
	 * @param int    $action_id Action ID.
	 * @param string $parameter_name Parameter name.
	 * @param string $field Field slug.
	 * @param mixed  $value Source value.
	 *
	 * @return mixed
	 */
	public static function translate_parameter_field( int $parameter_id, int $action_id, string $parameter_name, string $field, $value ) {
		$translated = self::translate_string( sprintf( 'parameter.%d.%s', $parameter_id, $field ), $value );
		if ( $translated !== $value ) {
			return $translated;
		}

		if ( $action_id > 0 && '' !== trim( $parameter_name ) ) {
			return self::translate_string(
				sprintf( 'parameter.a%d.%s.%s', $action_id, sanitize_key( $parameter_name ), $field ),
				$value
			);
		}

		return $value;
	}

	/**
	 * Plugin-level dynamic string translation adapter.
	 *
	 * @param mixed  $value Source value.
	 * @param string $key Canonical translation key.
	 * @param array  $context Optional context payload.
	 *
	 * @return mixed
	 */
	public function filter_translate_dynamic_string( $value, string $key, array $context = [] ) {
		if ( ! self::is_active() ) {
			return $value;
		}

		if ( isset( $context['scope'] ) && 'parameter' === $context['scope'] ) {
			$parameter_id   = isset( $context['parameter_id'] ) ? (int) $context['parameter_id'] : 0;
			$action_id      = isset( $context['action_id'] ) ? (int) $context['action_id'] : 0;
			$parameter_name = isset( $context['parameter_name'] ) ? (string) $context['parameter_name'] : '';
			$field          = isset( $context['field'] ) ? (string) $context['field'] : '';

			return self::translate_parameter_field( $parameter_id, $action_id, $parameter_name, $field, $value );
		}

		if ( isset( $context['scope'] ) && 'action_callback_text' === $context['scope'] ) {
			$action_id = isset( $context['action_id'] ) ? (int) $context['action_id'] : 0;

			return self::translate_action_callback_text( $action_id, $value );
		}

		return self::translate_string( $key, $value );
	}

	/**
	 * Plugin-level widget config translation adapter.
	 *
	 * @param mixed  $config Widget config.
	 * @param string $widget_ref Widget UUID or id.
	 * @param string $widget_type Widget type key.
	 *
	 * @return mixed
	 */
	public function filter_translate_widget_config( $config, string $widget_ref, string $widget_type ) {
		if ( ! self::is_active() || ! is_array( $config ) ) {
			return $config;
		}

		return self::translate_widget_config_strings( $widget_ref, $widget_type, $config );
	}

	/**
	 * Translate all strings in a nested array/object-like structure.
	 *
	 * @param string $base_name Base name used for stable keys.
	 * @param mixed  $value Value to translate recursively.
	 *
	 * @return mixed
	 */
	public static function register_string( string $name, $value ): void {
		if ( ! is_string( $value ) || '' === trim( $value ) || ! self::is_active() ) {
			return;
		}

		do_action( 'wpml_register_single_string', self::STRING_DOMAIN, $name, $value );
	}

	/**
	 * Register frontend chatbot settings strings.
	 *
	 * @return void
	 */
	private function register_chatbot_settings_strings(): void {
		self::register_string( 'chatbot.title', get_option( 'lbaic.utilities.chatbot.title', '' ) );
	}

	/**
	 * Register strings from widget config payload.
	 *
	 * @return void
	 */
	private function register_widget_strings(): void {
		global $wpdb;

		$table_name = $wpdb->prefix . 'lbaic_widgets';
		$rows       = $wpdb->get_results( "SELECT id, uuid, type, config FROM {$table_name}", ARRAY_A );
		if ( ! is_array( $rows ) ) {
			return;
		}

		foreach ( $rows as $row ) {
			$widget_ref = ! empty( $row['uuid'] ) ? (string) $row['uuid'] : (string) ( $row['id'] ?? 'unknown' );
			$widget_type = isset( $row['type'] ) ? sanitize_key( (string) $row['type'] ) : '';
			$config_raw = $row['config'] ?? '';
			$config     = is_string( $config_raw ) ? json_decode( $config_raw, true ) : [];
			if ( ! is_array( $config ) ) {
				continue;
			}

			self::register_widget_config_strings( $widget_ref, $widget_type, $config );
		}
	}

	/**
	 * Register only textual widget config fields.
	 *
	 * @param string               $widget_ref Widget UUID/id.
	 * @param string               $widget_type Widget type key.
	 * @param array<string, mixed> $config Widget config.
	 *
	 * @return void
	 */
	public static function register_widget_config_strings( string $widget_ref, string $widget_type, array $config ): void {
		foreach ( self::resolve_widget_translatable_descriptors( $widget_type ) as $descriptor ) {
			$path = isset( $descriptor['path'] ) ? (string) $descriptor['path'] : '';
			$type = isset( $descriptor['type'] ) ? (string) $descriptor['type'] : 'string';
			if ( '' === $path ) {
				continue;
			}

			foreach ( self::resolve_path_targets( $config, $path ) as $target ) {
				$target_key   = self::widget_string_key( $widget_ref, $target['path'] );
				$target_value = $target['value'];
				if ( 'rich_blocks' === $type && is_array( $target_value ) ) {
					self::register_rich_blocks_text( $target_key, $target_value );
					continue;
				}
				self::register_string( $target_key, $target_value );
			}
		}
	}

	/**
	 * Translate only textual widget config fields.
	 *
	 * @param string               $widget_ref Widget UUID/id.
	 * @param string               $widget_type Widget type key.
	 * @param array<string, mixed> $config Widget config.
	 *
	 * @return array<string, mixed>
	 */
	public static function translate_widget_config_strings( string $widget_ref, string $widget_type, array $config ): array {
		foreach ( self::resolve_widget_translatable_descriptors( $widget_type ) as $descriptor ) {
			$path = isset( $descriptor['path'] ) ? (string) $descriptor['path'] : '';
			$type = isset( $descriptor['type'] ) ? (string) $descriptor['type'] : 'string';
			if ( '' === $path ) {
				continue;
			}

			foreach ( self::resolve_path_targets( $config, $path ) as $target ) {
				$target_key = self::widget_string_key( $widget_ref, $target['path'] );
				if ( 'rich_blocks' === $type && is_array( $target['value'] ) ) {
					self::set_path_value( $config, $target['path'], self::translate_rich_blocks_text( $target_key, $target['value'] ) );
					continue;
				}
				self::set_path_value( $config, $target['path'], self::translate_string( $target_key, $target['value'] ) );
			}
		}

		return $config;
	}

	/**
	 * Resolve declared translatable fields from widget config class.
	 *
	 * @param string $widget_type Widget type key.
	 *
	 * @return array<int,array{path:string,type:string}>
	 */
	private static function resolve_widget_translatable_descriptors( string $widget_type ): array {
		$config_handler = ( new Widget_Config_Factory() )->make( $widget_type );
		if ( ! method_exists( $config_handler, 'get_translatable_fields' ) ) {
			return [];
		}

		$fields = $config_handler->get_translatable_fields();

		return is_array( $fields ) ? $fields : [];
	}

	/**
	 * Resolve path targets from nested config, supporting `*`.
	 *
	 * @param array<string,mixed> $config Widget config.
	 * @param string $path Dot path.
	 *
	 * @return array<int,array{path:string,value:mixed}>
	 */
	private static function resolve_path_targets( array $config, string $path ): array {
		$segments = array_values( array_filter( explode( '.', $path ), 'strlen' ) );
		if ( empty( $segments ) ) {
			return [];
		}

		return self::resolve_path_targets_recursive( $config, $segments, '' );
	}

	/**
	 * @param mixed $current
	 * @param string[] $segments
	 * @param string $resolved_path
	 *
	 * @return array<int,array{path:string,value:mixed}>
	 */
	private static function resolve_path_targets_recursive( $current, array $segments, string $resolved_path ): array {
		if ( empty( $segments ) ) {
			return [
				[
					'path'  => ltrim( $resolved_path, '.' ),
					'value' => $current,
				],
			];
		}

		$segment = array_shift( $segments );
		if ( '*' === $segment ) {
			if ( ! is_array( $current ) ) {
				return [];
			}
			$results = [];
			foreach ( $current as $index => $item ) {
				$results = array_merge(
					$results,
					self::resolve_path_targets_recursive( $item, $segments, $resolved_path . '.' . (string) $index )
				);
			}

			return $results;
		}

		if ( ! is_array( $current ) || ! array_key_exists( $segment, $current ) ) {
			return [];
		}

		return self::resolve_path_targets_recursive( $current[ $segment ], $segments, $resolved_path . '.' . $segment );
	}

	/**
	 * Set value in nested array by dot path.
	 *
	 * @param array<string,mixed> $data
	 * @param string $path
	 * @param mixed $value
	 *
	 * @return void
	 */
	private static function set_path_value( array &$data, string $path, $value ): void {
		$segments = array_values( array_filter( explode( '.', $path ), 'strlen' ) );
		if ( empty( $segments ) ) {
			return;
		}

		$ref = &$data;
		foreach ( $segments as $segment ) {
			if ( ! is_array( $ref ) || ! array_key_exists( $segment, $ref ) ) {
				return;
			}
			$ref = &$ref[ $segment ];
		}
		$ref = $value;
	}

	/**
	 * Register text nodes in rich blocks.
	 *
	 * @param string $base_name Base key.
	 * @param array  $blocks Rich block array.
	 *
	 * @return void
	 */
	private static function register_rich_blocks_text( string $base_name, array $blocks ): void {
		foreach ( $blocks as $block_index => $block ) {
			if ( ! is_array( $block ) || ! isset( $block['text'] ) || ! is_array( $block['text'] ) ) {
				continue;
			}
			self::register_string( sprintf( '%s.%d.text.value', $base_name, (int) $block_index ), $block['text']['value'] ?? '' );
		}
	}

	/**
	 * Translate text nodes in rich blocks.
	 *
	 * @param string $base_name Base key.
	 * @param array  $blocks Rich block array.
	 *
	 * @return array
	 */
	private static function translate_rich_blocks_text( string $base_name, array $blocks ): array {
		foreach ( $blocks as $block_index => $block ) {
			if ( ! is_array( $block ) || ! isset( $block['text'] ) || ! is_array( $block['text'] ) ) {
				continue;
			}
			$blocks[ $block_index ]['text']['value'] = self::translate_string(
				sprintf( '%s.%d.text.value', $base_name, (int) $block_index ),
				$block['text']['value'] ?? ''
			);
		}

		return $blocks;
	}

	/**
	 * Build widget string key.
	 *
	 * @param string $widget_ref Widget UUID/id.
	 * @param string $path Widget field path.
	 *
	 * @return string
	 */
	private static function widget_string_key( string $widget_ref, string $path ): string {
		return sprintf( 'widget.%s.%s', $widget_ref, $path );
	}

	/**
	 * Register action callback text only.
	 *
	 * @return void
	 */
	private function register_action_callback_strings(): void {
		global $wpdb;

		$table_name = $wpdb->prefix . 'lbaic_actions';
		$rows       = $wpdb->get_results( "SELECT id, name, config FROM {$table_name}", ARRAY_A );
		if ( ! is_array( $rows ) ) {
			return;
		}

		foreach ( $rows as $row ) {
			$action_id = (int) ( $row['id'] ?? 0 );
			if ( $action_id <= 0 ) {
				continue;
			}

			$config_raw = $row['config'] ?? '';
			$config     = is_string( $config_raw ) ? json_decode( $config_raw, true ) : [];
			if ( is_array( $config ) && isset( $config['callback_text'] ) ) {
				self::register_string( sprintf( 'action.%d.callback_text', $action_id ), $config['callback_text'] );
			}
		}
	}

	/**
	 * Register strings from action parameters.
	 *
	 * @return void
	 */
	private function register_parameter_strings(): void {
		global $wpdb;

		$table_name = $wpdb->prefix . 'lbaic_parameters';
		$rows       = $wpdb->get_results(
			"SELECT id, action_id, name, label, question, default_value, placeholder, suggestions, config FROM {$table_name}",
			ARRAY_A
		);
		if ( ! is_array( $rows ) ) {
			return;
		}

		foreach ( $rows as $row ) {
			$parameter_id = (int) ( $row['id'] ?? 0 );
			if ( $parameter_id <= 0 ) {
				continue;
			}

			self::register_string( sprintf( 'parameter.%d.label', $parameter_id ), $row['label'] ?? '' );
			self::register_string( sprintf( 'parameter.%d.question', $parameter_id ), $row['question'] ?? '' );
			self::register_string( sprintf( 'parameter.%d.default', $parameter_id ), $row['default_value'] ?? '' );
			self::register_string( sprintf( 'parameter.%d.placeholder', $parameter_id ), $row['placeholder'] ?? '' );
			$action_id      = isset( $row['action_id'] ) ? (int) $row['action_id'] : 0;
			$parameter_name = isset( $row['name'] ) ? sanitize_key( (string) $row['name'] ) : '';

			$suggestions_raw = $row['suggestions'] ?? '';
			$suggestions     = is_string( $suggestions_raw ) ? json_decode( $suggestions_raw, true ) : [];
			if ( is_array( $suggestions ) ) {
				foreach ( $suggestions as $index => $suggestion ) {
					self::register_string( sprintf( 'parameter.%d.suggestion_%d', $parameter_id, (int) $index ), $suggestion );
				}
			}

			$config_raw = $row['config'] ?? '';
			$config     = is_string( $config_raw ) ? json_decode( $config_raw, true ) : [];
			if ( is_array( $config ) && isset( $config['options'] ) && is_array( $config['options'] ) ) {
				foreach ( $config['options'] as $index => $option ) {
					if ( ! is_array( $option ) ) {
						continue;
					}
					self::register_string( sprintf( 'parameter.%d.option_%d.label', $parameter_id, (int) $index ), $option['label'] ?? '' );
					self::register_string( sprintf( 'parameter.%d.option_%d.tooltip', $parameter_id, (int) $index ), $option['tooltip'] ?? '' );
				}
			}
		}
	}
}
