<?php

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

	#[AllowDynamicProperties]
	class WS_Form_Field extends WS_Form_Core {

		public $id;
		public $form_id;
		public $section_id;
		public $section_id_from;
		public $new_lookup;
		public $type;
		public $label;
		public $meta;

		public $table_name;

 		public $field_type_config_cache;

		const DB_INSERT = 'label,type,user_id,date_added,date_updated,sort_index,section_id';
		const DB_UPDATE = 'label,user_id,date_updated,sort_index';
		const DB_SELECT = 'label,type,date_updated,sort_index,id';

		public function __construct() {

			global $wpdb;

			$this->form_id = 0;
			$this->section_id = 0;
			$this->id = 0;
			$this->new_lookup = array();
			$this->new_lookup['field'] = array();
			$this->type = '';
			$this->label = '';
			$this->meta = array();

			$this->table_name = sprintf('%s%sfield', $wpdb->prefix, WS_FORM_DB_TABLE_PREFIX);
		}

		// Create field
		public function db_create($next_sibling_id = 0) {

			// User capability check
			WS_Form_Common::user_must('edit_form');

			// Check section ID
			self::db_check_section_id();

			// Check field type is licensed
			if(!self::db_check_licensed($this->type)) { return false; }

			// Get sort_index
			$sort_index = self::db_object_sort_index_get($this->table_name, 'section_id', $this->section_id, $next_sibling_id);

			// Process label
			if($this->label === '') {

				$field_type_config = self::db_field_type_config($this->type);
				if(isset($field_type_config['label_default'])) {

					// Use label configured in config
					$this->label = $field_type_config['label_default'];

				} else {

					// Use fallback label (in case label_default is not specified in the config data)
					$this->label = __('Field', 'ws-form');
				}
			}

			// Sanitize label
			self::sanitize_label($this->label);

			global $wpdb;

			// Add field
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom database table
			$insert_result = $wpdb->insert(
				"{$wpdb->prefix}wsf_field",
				array(
					'label' => $this->label,
					'type' => $this->type,
					'user_id' => get_current_user_id(),
					'date_added' => WS_Form_Common::get_mysql_date(),
					'date_updated' => WS_Form_Common::get_mysql_date(),
					'sort_index' => $sort_index,
					'section_id' => $this->section_id,
				),
				array( '%s', '%s', '%d', '%s', '%s', '%d', '%d' )
			);

			if($insert_result === false) { 
				parent::db_wpdb_handle_error(__('Error adding field', 'ws-form')); 
			}

			// Get inserted ID
			$this->id = $wpdb->insert_id;

			// Build meta data array
			$meta_keys = WS_Form_Config::get_meta_keys();
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$meta_keys = apply_filters('wsf_form_create_meta_keys', $meta_keys);
			$meta_values = array(

				'section_id' => $this->section_id
			);
			$meta_data = self::build_meta_data($field_type_config, $meta_keys, $meta_values);
			$meta_data = (object) array_merge($meta_data, (array) $this->meta);

			// Check for section_repeatable_section_id
			if(isset($meta_data->section_repeatable_section_id)) {

				// Read section
				$ws_form_section = new WS_Form_Section();
				$ws_form_section->id = $this->section_id;
				$section = $ws_form_section->db_read();

				$section_repeatable = WS_Form_Common::get_object_meta_value($section, 'section_repeatable', false);

				// If it is not enabled, we should not assign this field to that section
				if(!$section_repeatable) {

					$meta_data->section_repeatable_section_id = '';
				}
			}

			// Build meta data
			$field_meta = new WS_Form_Meta();
			$field_meta->object = 'field';
			$field_meta->parent_id = $this->id;
			$field_meta->db_update_from_object($meta_data);

			return $this->id;
		}

		// Read record to object
		public function db_read($get_meta = true, $bypass_user_capability_check = false) {

			// User capability check
			WS_Form_Common::user_must('read_form', $bypass_user_capability_check);

			self::db_check_id();

			global $wpdb;

			// Get field types
			$field_types = WS_Form_Config::get_field_types_flat();

			// Get row
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$field_array = $wpdb->get_row($wpdb->prepare(

				"SELECT label,type,date_updated,sort_index,id FROM {$wpdb->prefix}wsf_field WHERE id = %d LIMIT 1;",
				$this->id
			), 'ARRAY_A');
			if(is_null($field_array)) { return false; }

			// Skip unlicensed field types
			if(!isset($field_types[$field_array['type']])) { return false; }

			foreach($field_array as $key => $value) {

				$this->{$key} = $value;
			}

			if($get_meta) {

				// Read meta
				$field_meta = new WS_Form_Meta();
				$field_meta->object = 'field';
				$field_meta->parent_id = $this->id;
				$metas = $field_meta->db_read_all($bypass_user_capability_check);
				$field_array['meta'] = $this->meta = $metas;
			}

			// Convert into object
			$field_object = json_decode(wp_json_encode($field_array));

			// Return array
			return $field_object;
		}

		// Read record to object (Published)
		public function db_read_published($bypass_user_capability_check = false) {

			// User capability check
			WS_Form_Common::user_must('read_form', $bypass_user_capability_check);

			self::db_check_id();
			self::db_check_form_id();

			$ws_form_form = new WS_Form_Form();
			$ws_form_form->id = $this->form_id;
			$form_object = $ws_form_form->db_read_published();

 			// Form fields
			$fields = WS_Form_Common::get_fields_from_form($form_object, true);

			// Get field
			if(isset($fields[$this->id])) {

				return $fields[$this->id];

			} else {

				parent::db_throw_error(__('Invalid field ID', 'ws-form'));
			}
		}

		// Check if record exists
		public function db_check() {

			// User capability check
			WS_Form_Common::user_must('read_form');

			global $wpdb;

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$return_array = $wpdb->get_row($wpdb->prepare(

				"SELECT id FROM {$wpdb->prefix}wsf_field WHERE id = %d LIMIT 1;",
				$this->id
			), 'ARRAY_A');
			return !is_null($return_array);
		}

		// Read records
		public function db_read_all($get_meta = true, $checksum = false, $bypass_user_capability_check = false) {

			// User capability check
			WS_Form_Common::user_must('read_form', $bypass_user_capability_check);

			self::db_check_section_id();

			global $wpdb;

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$fields = $wpdb->get_results($wpdb->prepare(

				"SELECT label,type,date_updated,sort_index,id FROM {$wpdb->prefix}wsf_field WHERE section_id = %d ORDER BY sort_index",
				$this->section_id
			), 'ARRAY_A');

			if($fields) {

				// Get field types
				$field_types = WS_Form_Config::get_field_types_flat();

				foreach($fields as $key => $field) {

					// Skip unlicensed field types
					if(!isset($field_types[$field['type']])) { unset($fields[$key]); continue; }

					// Get meta data for each field
					if($get_meta) {

						$field_meta = new WS_Form_Meta();
						$field_meta->object = 'field';
						$field_meta->parent_id = $field['id'];
						$metas = $field_meta->db_read_all($bypass_user_capability_check);
						$fields[$key]['meta'] = $metas;

						// Checksum - Exclude data from checksum calculation
						if($checksum) {

							if(isset($fields[$key]['date_updated'])) { unset($fields[$key]['date_updated']); }
							if(isset($fields[$key]['meta']->data_source_last_api_error)) { unset($fields[$key]['meta']->data_source_last_api_error); }
						}
					}
				}

				// Reset keys in case one was removed because a field was not licensed
				$fields = array_values($fields);

				return $fields;

			} else {

				return [];
			}
		}

		// Data source processing
		public function data_source_process(&$field) {

			// Field types
			$field_types = WS_Form_Config::get_field_types_flat();

			// Get field_config
			$field_config = $field_types[$field->type];

			$data_source = isset($field_config['data_source']) ? $field_config['data_source'] : false;
			if(
				($data_source === false) ||
				!isset($data_source['id'])
			) {

				return false;
			}

			// Get meta key
			$meta_key = $data_source['id'];

			// Get meta keys if not set
			$meta_keys = WS_Form_Config::get_meta_keys();

			if(!isset($meta_keys[$meta_key])) { return false; }

			$meta_key_config = $meta_keys[$meta_key];

			// Check if data source enabled
			$data_source_enabled = isset($meta_key_config['data_source']) ? $meta_key_config['data_source'] : false;

			if(!$data_source_enabled) { return false; }

			// Check if data source ID is set
			$data_source_id = WS_Form_Common::get_object_meta_value($field, 'data_source_id', '');

			if(
				($data_source_id === '') ||
				!isset(WS_Form_Data_Source::$data_sources[$data_source_id]) ||
				!method_exists(WS_Form_Data_Source::$data_sources[$data_source_id], 'get_data_source_meta_keys')
			) {
				return false;
			}

			$data_source = WS_Form_Data_Source::$data_sources[$data_source_id];

			// Get meta keys
			$meta_keys = $data_source->config_meta_keys();

			// Get data source meta keys
			$data_source_meta_keys = $data_source->get_data_source_meta_keys();

			// Configure
			foreach($data_source_meta_keys as $data_source_meta_key) {

				$meta_value_default = isset($meta_keys[$data_source_meta_key]['default']) ? $meta_keys[$data_source_meta_key]['default'] : false;

				$data_source->{$data_source_meta_key} = WS_Form_Common::get_object_meta_value($field, $data_source_meta_key, $meta_value_default);
			}

			// Get existing meta_value
			$meta_value = WS_Form_Common::get_object_meta_value($field, $meta_key, false);

			// Get replacement meta_value
			$get_return = $data_source->get(false, $field->id, 1, $meta_key, $meta_value, true);	// true = form_parse to ignore paging

			// Error checking
			if(!(isset($get_return['error']) && $get_return['error'])) {

				// Set meta_key
				$field->meta->{$meta_key} = $get_return['meta_value'];

				// Check if data source ID is set
				$data_source_last_api_error = WS_Form_Common::get_object_meta_value($field, 'data_source_last_api_error', '');

				// Clear last_api_error
				if($data_source_last_api_error !== '') {

					$ws_form_field = new WS_Form_Field();
					$ws_form_field->id = $field->id;
					$ws_form_field->db_last_api_error_clear();
				}
			}
		}

		// Delete
		public function db_delete($repair = true) {

			// User capability check
			WS_Form_Common::user_must('edit_form');

			self::db_check_id();

			global $wpdb;

			// Delete field
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$delete_result = $wpdb->delete(
				"{$wpdb->prefix}wsf_field",
				array( 'id' => $this->id ),
				array( '%d' )
			);

			if($delete_result === false) { 
				parent::db_wpdb_handle_error(__('Error deleting field', 'ws-form')); 
			}

			// Delete meta
			$ws_form_meta = new WS_Form_Meta();
			$ws_form_meta->object = 'field';
			$ws_form_meta->parent_id = $this->id;
			$ws_form_meta->db_delete_by_object();

			// Repair conditional, actions and meta data to remove references to this deleted field
			if($repair) {

				self::db_check_form_id();

				$ws_form_form = new WS_Form_Form();
				$ws_form_form->id = $this->form_id;
				$ws_form_form->new_lookup['field'][$this->id] = '';
				$ws_form_form->db_action_repair();
				$ws_form_form->db_meta_repair();
			}

			return true;
		}

		// Delete all fields in section
		public function db_delete_by_section($repair = true) {
			
			// User capability check
			WS_Form_Common::user_must('edit_form');

			self::db_check_section_id();

			global $wpdb;

			if($repair) {

				self::db_check_form_id();

				$ws_form_form = new WS_Form_Form();
				$ws_form_form->id = $this->form_id;
			}

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$fields = $wpdb->get_results($wpdb->prepare(

				"SELECT label,type,date_updated,sort_index,id FROM {$wpdb->prefix}wsf_field WHERE section_id = %d",
				$this->section_id
			), 'ARRAY_A');

			if($fields) {

				foreach($fields as $key => $field) {

					// Delete field
					$this->id = $field['id'];
					self::db_delete(false);

					if($repair) {

						$ws_form_form->new_lookup['field'][$this->id] = '';
					}
				}
			}

			// Repair conditional, actions and meta data to remove references to these deleted fields
			if($repair) {

				$ws_form_form->db_action_repair();
				$ws_form_form->db_meta_repair();
			}

			return true;
		}

		// Clone - All
		public function db_clone_all($section_id_copy_to) {

			// User capability check
			WS_Form_Common::user_must('edit_form');

			global $wpdb;

			// Get field types
			$field_types = WS_Form_Config::get_field_types_flat();

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$fields = $wpdb->get_results($wpdb->prepare(

				"SELECT label,type,date_updated,sort_index,id FROM {$wpdb->prefix}wsf_field WHERE section_id = %d ORDER BY sort_index",
				$this->section_id
			), 'ARRAY_A');

			if($fields) {

				foreach($fields as $key => $field) {

					// Read data required for copying
					$this->id = $field['id'];
					$this->label = $field['label'];
					$this->type = $field['type'];
					$this->sort_index = $field['sort_index'];
					$this->section_id = $section_id_copy_to;

					// Check for multiple = false field types
					if(!isset($field_types[$this->type])) { continue; }
					$multiple = (isset($field_types[$this->type]['multiple'])) ? $field_types[$this->type]['multiple'] : true;
					if(!$multiple) { continue; }

					self::db_clone(false);
				}
			}

			// Repair new section
			$ws_form_form = new WS_Form_Form();
			$ws_form_form->id = $this->form_id;
			$ws_form_form->new_lookup['field'] = $this->new_lookup['field'];
			$ws_form_form->db_meta_repair(false, array($section_id_copy_to));
		}

		// Clone
		public function db_clone($single_field_clone = true) {

			// User capability check
			WS_Form_Common::user_must('edit_form');

			global $wpdb;

			// Clone field
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom database table
			$insert_result = $wpdb->insert(
				"{$wpdb->prefix}wsf_field",
				array(
					'label' => $this->label,
					'type' => $this->type,
					'user_id' => get_current_user_id(),
					'date_added' => WS_Form_Common::get_mysql_date(),
					'date_updated' => WS_Form_Common::get_mysql_date(),
					'sort_index' => $this->sort_index,
					'section_id' => $this->section_id,
				),
				array( '%s', '%s', '%d', '%s', '%s', '%d', '%d' )
			);

			if($insert_result === false) { 
				parent::db_wpdb_handle_error(__('Error cloning field', 'ws-form')); 
			}

			// Get new field ID
			$field_id_new = $wpdb->insert_id;

			// Clone meta data
			$ws_form_meta = new WS_Form_Meta();
			$ws_form_meta->object = 'field';
			$ws_form_meta->parent_id = $this->id;
			$ws_form_meta->db_clone_all($field_id_new, $single_field_clone);

			// Add to lookup
			$this->new_lookup['field'][$this->id] = $field_id_new;

			return $field_id_new;
		}

		// Get checksum of current form and store it to database
		public function db_checksum() {

			// Get form ID
			self::db_check_form_id();

			// Calculate new form checksum
			$ws_form_form = new WS_Form_Form();
			$ws_form_form->id = $this->form_id;
			$checksum = $ws_form_form->db_checksum();

			return $checksum;
		}

		// Push field from array
		public function db_update_from_object($field_object, $new = false, $replace_meta = false) {

			// User capability check
			WS_Form_Common::user_must('edit_form');

			// Check field type is licensed
			if(!isset($field_object->type)) { return false; }
			if(!self::db_check_licensed($field_object->type)) { return false; }

			// Check for field ID in $field_object
			if(isset($field_object->id) && !$new) {

				// Set field ID
				$this->id = absint($field_object->id);

				// Set section ID of field object (Required in case field moves)
				if(!empty($this->section_id)) {

					$field_object->section_id = $this->section_id;
				}
			}
			if($new) {

				$this->id = 0;
				$field_object_id_old = (isset($field_object->id)) ? $field_object->id : 0;		// Do not convert to in, some imported forms require strings for keys (e.g. '1.3')
				if(isset($field_object->id)) { unset($field_object->id); }
			}

			// Check for label
			if(!isset($field_object->label)) {

				self::db_read();
				$field_object->label = $this->label;
			}
			if($field_object->label === '') {

				$field_type_config = self::db_field_type_config($field_object->type);
				if(isset($field_type_config['label_default'])) {

					// Use label configured in config
					$field_object->label = $field_type_config['label_default'];

				} else {

					// Use fallback label (in case label_default is not specified in the config data)
					$field_object->label = __('Field', 'ws-form');
				}
			}

			// Get DB_UPDATE
			$db_update = self::DB_UPDATE;

			// Add section ID if it needs to be updated
			if(!empty($field_object->section_id)) {

				$db_update .= ',section_id';
			}

			// Update / Insert
			$this->id = parent::db_update_insert($this->table_name, $db_update, self::DB_INSERT, $field_object, 'field', $this->id);
			if($new) {

				if($field_object_id_old) { $this->new_lookup['field'][$field_object_id_old] = $this->id; }
				if(
					isset($field_object->meta) &&
					isset($field_object->meta->parent_id) &&
					$field_object->meta->parent_id &&
					!isset($this->new_lookup['field'][$field_object->meta->parent_id])
				) {

					$this->new_lookup['field'][$field_object->meta->parent_id] = $this->id;
				}
			}

			// Base meta for new records
			if(!isset($field_object->meta) || !is_object($field_object->meta)) { $field_object->meta = new stdClass(); }
			if($new) {

				$field_type_config = self::db_field_type_config($field_object->type);
				$meta_keys = WS_Form_Config::get_meta_keys();
				$meta_data_array = self::build_meta_data($field_type_config, $meta_keys);
				$field_object->meta = (object) array_merge($meta_data_array, (array) $field_object->meta);
			}

			// Update meta
			if(isset($field_object->meta)) {

				$ws_form_meta = new WS_Form_Meta();
				$ws_form_meta->object = 'field';
				$ws_form_meta->parent_id = $this->id;
				$ws_form_meta->db_update_from_object($field_object->meta, $this->new_lookup['field'], false, $replace_meta);
			}

			$this->previous_id = $this->id;

			return $this->id;
		}

		// Push all fields from array
		public function db_update_from_array($fields, $new, $replace_meta = false) {

			// User capability check
			WS_Form_Common::user_must('edit_form');

			self::db_check_section_id();

			global $wpdb;

			// Change date_updated to null for all records
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$wpdb->update($this->table_name, array('date_updated' => null), array('section_id' => $this->section_id));

			foreach($fields as $field) {

				self::db_update_from_object($field, $new, $replace_meta);
			}

			// Delete any fields that were not updated
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$wpdb->delete($this->table_name, array('date_updated' => null, 'section_id' => $this->section_id));

			return true;
		}

		// Get section ID
		public function db_get_section_id() {

			// User capability check
			WS_Form_Common::user_must('read_form');

			if($this->id == 0) { parent::db_throw_error(__('Field ID is zero, cannot get section ID', 'ws-form')); }

			global $wpdb;

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom database table
			$section_id = $wpdb->get_var($wpdb->prepare(

				"SELECT section_id FROM {$wpdb->prefix}wsf_field WHERE id = %d LIMIT 1;",
				$this->id
			));
			if(is_null($section_id)) { parent::db_wpdb_handle_error(__('Error getting section ID', 'ws-form')); }

			return $section_id;
		}

		// Check form_id
		public function db_check_form_id() {

			if(absint($this->form_id) === 0) { parent::db_throw_error(__('Invalid form ID (WS_Form_Field | db_check_form_id)', 'ws-form')); }
			return true;
		}

		// Check section_id
		public function db_check_section_id() {

			if(absint($this->section_id) === 0) { parent::db_throw_error(__('Invalid section ID (WS_Form_Field | db_check_section_id)', 'ws-form')); }
			return true;
		}

		// Check section_id from
		public function db_check_section_id_from() {

			if(absint($this->section_id_from) === 0) { parent::db_throw_error(__('Invalid section ID (WS_Form_Field | db_check_section_id_from)', 'ws-form')); }
			return true;
		}

		// Check id
		public function db_check_id() {

			if(absint($this->id) === 0) { parent::db_throw_error(__('Invalid field ID (WS_Form_Field | db_check_id)', 'ws-form')); }
		}

		// Check type
		public function db_field_type_config($field_type) {

			if(isset($this->field_type_config_cache[$field_type])) {

				// Retrieve field type config from cache
				return $this->field_type_config_cache[$field_type];
			}

			$field_types = WS_Form_Config::get_field_types();
			foreach($field_types as $field_group => $types) {

				if(isset($types['types'][$field_type])) {

					// Cache field type config
					$this->field_type_config_cache[$field_type] = $types['types'][$field_type];

					return $types['types'][$field_type];
				}
			}
			return false;
		}

		// Check licensed
		public function db_check_licensed($type) {

			$field_types = WS_Form_Config::get_field_types();
			foreach($field_types as $section => $types) {

				if(isset($types['types'][$type])) {

					if(
						isset($types['types'][$type]['pro_required']) &&
						$types['types'][$type]['pro_required']
					) {
						return false;
					} else {
						return true;
					}
				}
			}
			return false;
		}

		// Get field label
		public function db_get_label() {

			// User capability check
			WS_Form_Common::user_must('read_form');

			return parent::db_object_get_label($this->table_name, $this->id);
		}

		// Clear last API error meta data
		public function db_last_api_error_clear() {

			// Build new meta array
			$meta_array = array('data_source_last_api_error' => '');

			// Save new meta value
			$ws_form_meta = new WS_Form_Meta();
			$ws_form_meta->parent_id = $this->id;
			$ws_form_meta->object = 'field';
			$ws_form_meta->db_update_from_array($meta_array, false, true);
		}

		// Is valid
		public function is_valid($field_object) {

			return (

				is_object($field_object) &&
				property_exists($field_object, 'id') &&
				property_exists($field_object, 'label') &&
				property_exists($field_object, 'type') &&
				property_exists($field_object, 'meta')
			);
		}
	}
