<?php
/**
 * Custom Fields import/export.
 *
 * @package CustomFields
 * @since 1.2.0
 */

namespace CustomFields;

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

/**
 * Handles export and import of field-group definitions.
 */
final class ImportExport {

	private const SOURCE = 'wp-powersuite-custom-fields';

	/**
	 * Maximum accepted import payload size in bytes (1 MB).
	 */
	public const MAX_IMPORT_BYTES = 1048576;

	/**
	 * Maximum JSON nesting depth accepted on import.
	 */
	public const MAX_JSON_DEPTH = 32;

	/**
	 * Maximum field groups accepted from one import payload.
	 */
	public const MAX_IMPORT_GROUPS = 100;

	private Storage $storage;

	private Validator $validator;

	public function __construct( Storage $storage, Validator $validator ) {
		$this->storage   = $storage;
		$this->validator = $validator;
	}

	/**
	 * Build an export payload for one or all field groups.
	 *
	 * @param string|null $key Specific key, or null for all.
	 * @return array<string, mixed>
	 */
	public function export( ?string $key = null ): array {
		$definitions = $this->storage->all();

		if ( $key !== null ) {
			$definitions = isset( $definitions[ $key ] ) ? [ $key => $definitions[ $key ] ] : [];
		}

		return [
			'schema'       => Config::SCHEMA_VERSION,
			'source'       => self::SOURCE,
			'generated'    => gmdate( 'Y-m-d H:i:s' ),
			'field_groups' => array_values( $definitions ),
		];
	}

	/**
	 * Encode an export payload as pretty JSON.
	 *
	 * @param string|null $key Specific key, or null for all.
	 */
	public function exportJson( ?string $key = null ): string {
		return (string) wp_json_encode( $this->export( $key ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
	}

	/**
	 * Import definitions from a JSON string.
	 *
	 * @param string $json               Raw JSON.
	 * @param bool   $overwrite          Whether to overwrite existing keys.
	 * @param bool   $import_as_disabled Whether to force imported groups disabled.
	 * @return array{imported: int, skipped: array<int, string>, errors: array<int, string>}
	 */
	public function import( string $json, bool $overwrite, bool $import_as_disabled ): array {
		$result = [
			'imported' => 0,
			'skipped'  => [],
			'errors'   => [],
		];

		$json = self::normalizeJson( $json );
		if ( $json === '' ) {
			$result['errors'][] = __( 'No import data was provided.', 'wppowersuite-lite' );
			return $result;
		}

		if ( strlen( $json ) > self::MAX_IMPORT_BYTES ) {
			$result['errors'][] = __( 'The import data is too large (limit: 1 MB).', 'wppowersuite-lite' );
			return $result;
		}

		$data = json_decode( $json, true, self::MAX_JSON_DEPTH );
		if ( ! is_array( $data ) || json_last_error() !== JSON_ERROR_NONE ) {
			$result['errors'][] = __( 'The import data is not valid JSON.', 'wppowersuite-lite' );
			return $result;
		}

		$entries = $this->extractEntries( $data );
		if ( $entries === [] ) {
			$result['errors'][] = __( 'No field group definitions were found in the import data.', 'wppowersuite-lite' );
			return $result;
		}

		$extra_entries = max( 0, count( $entries ) - self::MAX_IMPORT_GROUPS );
		if ( $extra_entries > 0 ) {
			$entries = array_slice( $entries, 0, self::MAX_IMPORT_GROUPS );
			$result['skipped'][] = sprintf(
				/* translators: %d: number of skipped field groups. */
				_n(
					'%d field group was skipped because the import limit was reached.',
					'%d field groups were skipped because the import limit was reached.',
					$extra_entries,
					'wppowersuite-lite'
				),
				$extra_entries
			);
		}

		foreach ( $entries as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$entry = $this->normalizeExternalEntry( $entry );
			$key = $this->validator->sanitizeKey( (string) ( $entry['key'] ?? '' ) );
			if ( $key === '' ) {
				$key = $this->uniqueImportKey( (string) ( $entry['title'] ?? 'imported_group' ) );
			}

			if ( $key === '' ) {
				$result['errors'][] = __( 'Skipped an entry with a missing or invalid key.', 'wppowersuite-lite' );
				continue;
			}

			$already_exists = $this->storage->exists( $key );
			if ( $already_exists && ! $overwrite ) {
				$result['skipped'][] = $key;
				continue;
			}

			$existing   = $already_exists ? $this->storage->get( $key ) : null;
			$definition = $this->validator->buildDefinition( array_merge( $entry, [ 'key' => $key ] ), $existing );

			if ( $import_as_disabled ) {
				$definition['enabled'] = false;
			}

			$errors = $this->validator->validateDefinition( $definition, $already_exists, $already_exists ? $key : null );
			if ( $errors !== [] ) {
				$result['errors'][] = sprintf(
					/* translators: 1: group key, 2: error message. */
					__( '“%1$s” was skipped: %2$s', 'wppowersuite-lite' ),
					$key,
					implode( ' ', $errors )
				);
				continue;
			}

			$this->storage->save( $key, $definition );
			++$result['imported'];
		}

		return $result;
	}

	/**
	 * Normalize a raw JSON string: trim whitespace and strip a UTF-8 BOM.
	 *
	 * @param string $json Raw JSON.
	 */
	public static function normalizeJson( string $json ): string {
		$json = trim( $json );
		if ( str_starts_with( $json, "\xEF\xBB\xBF" ) ) {
			$json = substr( $json, 3 );
		}

		return trim( $json );
	}

	/**
	 * Read and validate an uploaded JSON import file.
	 *
	 * @param array<string, mixed> $file One entry from $_FILES.
	 * @return array{json: string, error: string} JSON contents, or an error message.
	 */
	public static function readUploadedFile( array $file ): array {
		$tmp_name = isset( $file['tmp_name'] ) ? (string) $file['tmp_name'] : '';
		$name     = isset( $file['name'] ) ? (string) $file['name'] : '';
		$err_code = isset( $file['error'] ) ? (int) $file['error'] : UPLOAD_ERR_NO_FILE;

		if ( $err_code === UPLOAD_ERR_NO_FILE || $tmp_name === '' ) {
			return [
				'json'  => '',
				'error' => '',
			];
		}

		if ( $err_code !== UPLOAD_ERR_OK || ! is_uploaded_file( $tmp_name ) ) {
			return [
				'json'  => '',
				'error' => __( 'The import file could not be uploaded. Please try again.', 'wppowersuite-lite' ),
			];
		}

		$size = (int) ( $file['size'] ?? 0 );
		if ( $size <= 0 || $size > self::MAX_IMPORT_BYTES ) {
			return [
				'json'  => '',
				'error' => __( 'The import file is empty or too large (limit: 1 MB).', 'wppowersuite-lite' ),
			];
		}

		$filetype = wp_check_filetype( $name, [ 'json' => 'application/json' ] );
		if ( $filetype['ext'] !== 'json' ) {
			return [
				'json'  => '',
				'error' => __( 'Only .json files can be imported.', 'wppowersuite-lite' ),
			];
		}

		$contents = file_get_contents( $tmp_name ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown -- validated local upload.
		if ( ! is_string( $contents ) || $contents === '' ) {
			return [
				'json'  => '',
				'error' => __( 'The import file could not be read.', 'wppowersuite-lite' ),
			];
		}

		return [
			'json'  => $contents,
			'error' => '',
		];
	}

	/**
	 * @param array<string, mixed>|array<int, mixed> $data Decoded JSON.
	 * @return array<int, mixed>
	 */
	private function extractEntries( array $data ): array {
		if ( isset( $data['field_groups'] ) && is_array( $data['field_groups'] ) ) {
			return array_values( $data['field_groups'] );
		}

		if ( array_is_list( $data ) ) {
			return $data;
		}

		if ( isset( $data['key'] ) ) {
			return [ $data ];
		}

		return [];
	}

	/**
	 * Translate common external JSON setting names into this module's schema.
	 *
	 * @param array<string, mixed> $entry Raw field group.
	 * @return array<string, mixed>
	 */
	private function normalizeExternalEntry( array $entry ): array {
		if ( isset( $entry['active'] ) && ! isset( $entry['enabled'] ) ) {
			$entry['enabled'] = ! empty( $entry['active'] );
		}

		if ( isset( $entry['position'] ) ) {
			$entry['position'] = $this->normalizePosition( (string) $entry['position'] );
		}

		if ( isset( $entry['location'] ) && ! isset( $entry['location_rules'] ) && is_array( $entry['location'] ) && array_is_list( $entry['location'] ) ) {
			$entry['location_rules'] = $this->normalizeLocationRules( $entry['location'] );
			unset( $entry['location'] );
		}

		if ( isset( $entry['fields'] ) && is_array( $entry['fields'] ) ) {
			$entry['fields'] = $this->normalizeExternalFields( $entry['fields'] );
		}

		return $entry;
	}

	/**
	 * @param array<int|string, mixed> $fields Raw field definitions.
	 * @return array<int, mixed>
	 */
	private function normalizeExternalFields( array $fields ): array {
		$normalized = [];

		foreach ( $fields as $field ) {
			if ( is_array( $field ) ) {
				$normalized[] = $this->normalizeExternalField( $field );
			}
		}

		return $normalized;
	}

	/**
	 * @param array<string, mixed> $field Raw field definition.
	 * @return array<string, mixed>
	 */
	private function normalizeExternalField( array $field ): array {
		$type = isset( $field['type'] ) ? (string) $field['type'] : '';
		$type = $this->normalizeFieldType( $type );
		if ( $type !== '' ) {
			$field['type'] = $type;
		}

		if ( isset( $field['default_value'] ) && ! isset( $field['default'] ) ) {
			$field['default'] = $field['default_value'];
		}

		if ( isset( $field['return_format'] ) && ! isset( $field['return'] ) ) {
			$field['return'] = $field['return_format'];
		}

		if ( $type === 'nav_menu' && isset( $field['save_format'] ) && ! isset( $field['return'] ) ) {
			$field['return'] = $field['save_format'];
		}

		if ( isset( $field['default_to_current_date'] ) && ! isset( $field['default_current'] ) ) {
			$field['default_current'] = $field['default_to_current_date'];
		}

		if ( isset( $field['prepend'] ) && ! isset( $field['before'] ) ) {
			$field['before'] = $field['prepend'];
		}

		if ( isset( $field['append'] ) && ! isset( $field['after'] ) ) {
			$field['after'] = $field['append'];
		}

		if ( isset( $field['library'] ) && ! isset( $field['media_library'] ) ) {
			$field['media_library'] = $field['library'];
		}
		if ( isset( $field['media_library'] ) && $field['media_library'] === 'uploadedTo' ) {
			$field['media_library'] = 'uploaded_to';
		}

		if ( isset( $field['wrapper'] ) && is_array( $field['wrapper'] ) && isset( $field['wrapper']['width'] ) && ! isset( $field['width'] ) ) {
			$field['width'] = $this->normalizeWrapperWidth( $field['wrapper']['width'] );
		}

		if ( isset( $field['conditional_logic'] ) && ! isset( $field['conditional'] ) ) {
			$field['conditional'] = $this->normalizeConditionalLogic( $field['conditional_logic'] );
		}

		if ( isset( $field['role'] ) && ! isset( $field['user_roles'] ) ) {
			$field['user_roles'] = $this->normalizeListSetting( $field['role'] );
		}

		if ( isset( $field['preview_size'] ) ) {
			if ( $type === 'image' && ! isset( $field['image_preview_size'] ) ) {
				$field['image_preview_size'] = $field['preview_size'];
			}
			if ( $type === 'gallery' && ! isset( $field['gallery_preview_size'] ) ) {
				$field['gallery_preview_size'] = $field['preview_size'];
			}
		}

		if ( isset( $field['post_type'] ) && ! isset( $field['post_types'] ) ) {
			$field['post_types'] = $this->normalizeListSetting( $field['post_type'] );
		}

		if ( isset( $field['post_status'] ) && ! isset( $field['post_statuses'] ) ) {
			$field['post_statuses'] = $this->normalizeListSetting( $field['post_status'] );
		}

		if ( $type === 'toggle' ) {
			if ( isset( $field['message'] ) && ! isset( $field['toggle_message'] ) ) {
				$field['toggle_message'] = $field['message'];
			}
			if ( isset( $field['ui'] ) && ! isset( $field['toggle_ui'] ) ) {
				$field['toggle_ui'] = $field['ui'];
			}
			if ( isset( $field['ui_on_text'] ) && ! isset( $field['toggle_on_text'] ) ) {
				$field['toggle_on_text'] = $field['ui_on_text'];
			}
			if ( isset( $field['ui_off_text'] ) && ! isset( $field['toggle_off_text'] ) ) {
				$field['toggle_off_text'] = $field['ui_off_text'];
			}
		}

		if ( $type === 'oembed' ) {
			if ( isset( $field['width'] ) && ! isset( $field['oembed_width'] ) ) {
				$field['oembed_width'] = $field['width'];
			}
			if ( isset( $field['height'] ) && ! isset( $field['oembed_height'] ) ) {
				$field['oembed_height'] = $field['height'];
			}
			unset( $field['width'] );
		}

		if ( $type === 'google_map' ) {
			if ( isset( $field['center_lat'] ) && ! isset( $field['map_center_lat'] ) ) {
				$field['map_center_lat'] = $field['center_lat'];
			}
			if ( isset( $field['center_lng'] ) && ! isset( $field['map_center_lng'] ) ) {
				$field['map_center_lng'] = $field['center_lng'];
			}
			if ( isset( $field['zoom'] ) && ! isset( $field['map_zoom'] ) ) {
				$field['map_zoom'] = $field['zoom'];
			}
			if ( isset( $field['height'] ) && ! isset( $field['map_height'] ) ) {
				$field['map_height'] = $field['height'];
			}
		}

		if ( $type === 'tab' ) {
			if ( isset( $field['placement'] ) && ! isset( $field['tab_placement'] ) ) {
				$field['tab_placement'] = $field['placement'];
			}
			if ( isset( $field['endpoint'] ) && ! isset( $field['tab_endpoint'] ) ) {
				$field['tab_endpoint'] = $field['endpoint'];
			}
			if ( isset( $field['selected'] ) && ! isset( $field['tab_selected'] ) ) {
				$field['tab_selected'] = $field['selected'];
			}
		}

		if ( $type === 'accordion' ) {
			if ( isset( $field['open'] ) && ! isset( $field['accordion_open'] ) ) {
				$field['accordion_open'] = $field['open'];
			}
			if ( isset( $field['multi_expand'] ) && ! isset( $field['accordion_multi_expand'] ) ) {
				$field['accordion_multi_expand'] = $field['multi_expand'];
			}
			if ( isset( $field['endpoint'] ) && ! isset( $field['accordion_endpoint'] ) ) {
				$field['accordion_endpoint'] = $field['endpoint'];
			}
		}

		if ( in_array( $type, [ 'group', 'repeater' ], true ) && isset( $field['layout'] ) && ! isset( $field['nested_layout'] ) ) {
			$field['nested_layout'] = $field['layout'];
		}

		if ( $type === 'clone' ) {
			if ( isset( $field['clone'] ) && ! isset( $field['clone_source'] ) ) {
				$field['clone_source'] = $field['clone'];
			}
			if ( isset( $field['display'] ) && ! isset( $field['clone_display'] ) ) {
				$field['clone_display'] = $field['display'];
			}
			if ( isset( $field['layout'] ) && ! isset( $field['clone_layout'] ) ) {
				$field['clone_layout'] = $field['layout'];
			}
			if ( isset( $field['prefix_label'] ) && ! isset( $field['clone_prefix_label'] ) ) {
				$field['clone_prefix_label'] = $field['prefix_label'];
			}
			if ( isset( $field['prefix_name'] ) && ! isset( $field['clone_prefix_name'] ) ) {
				$field['clone_prefix_name'] = $field['prefix_name'];
			}
		}

		if ( $type === 'relationship' ) {
			if ( isset( $field['filters'] ) && ! isset( $field['relationship_filters'] ) ) {
				$field['relationship_filters'] = $this->normalizeListSetting( $field['filters'] );
			}
			if ( isset( $field['elements'] ) && ! isset( $field['relationship_elements'] ) ) {
				$field['relationship_elements'] = $this->normalizeListSetting( $field['elements'] );
			}
		}

		if ( $type === 'taxonomy' ) {
			$type          = 'term';
			$field['type'] = 'term';
		}

		if ( $type === 'term' ) {
			if ( isset( $field['field_type'] ) && ! isset( $field['taxonomy_field_type'] ) ) {
				$field['taxonomy_field_type'] = $field['field_type'];
			}
			if ( isset( $field['save_terms'] ) && ! isset( $field['taxonomy_save_terms'] ) ) {
				$field['taxonomy_save_terms'] = $field['save_terms'];
			}
			if ( isset( $field['load_terms'] ) && ! isset( $field['taxonomy_load_terms'] ) ) {
				$field['taxonomy_load_terms'] = $field['load_terms'];
			}
			if ( isset( $field['add_term'] ) && ! isset( $field['taxonomy_add_terms'] ) ) {
				$field['taxonomy_add_terms'] = $field['add_term'];
			}
		} elseif ( in_array( $type, [ 'post_object', 'relationship', 'page_link' ], true ) && isset( $field['taxonomy'] ) && ! isset( $field['taxonomy_terms'] ) ) {
			$field['taxonomy_terms'] = $this->normalizeTaxonomyTerms( $this->normalizeListSetting( $field['taxonomy'] ) );
		}

		if ( isset( $field['sub_fields'] ) && is_array( $field['sub_fields'] ) ) {
			$field['sub_fields'] = $this->normalizeExternalFields( $field['sub_fields'] );
		}

		if ( isset( $field['layouts'] ) && is_array( $field['layouts'] ) ) {
			foreach ( $field['layouts'] as $layout_key => $layout ) {
				if ( is_array( $layout ) && isset( $layout['sub_fields'] ) && is_array( $layout['sub_fields'] ) ) {
					$layout['sub_fields']           = $this->normalizeExternalFields( $layout['sub_fields'] );
					$field['layouts'][ $layout_key ] = $layout;
				}
			}
		}

		return $field;
	}

	private function normalizeFieldType( string $type ): string {
		$type = sanitize_key( $type );
		$aliases = [
			'button_group'     => 'button_group',
			'color_picker'     => 'color',
			'date_picker'      => 'date',
			'date_time_picker' => 'datetime',
			'flexible_content' => 'flexible_content',
			'google_map'       => 'google_map',
			'page_link'        => 'page_link',
			'post_object'      => 'post_object',
			'taxonomy'         => 'term',
			'textarea'         => 'textarea',
			'time_picker'      => 'time',
			'true_false'       => 'toggle',
			'wysiwyg'          => 'wysiwyg',
		];

		return $aliases[ $type ] ?? $type;
	}

	private function normalizeWrapperWidth( mixed $value ): string {
		$width = trim( (string) $value );
		if ( $width === '' ) {
			return '';
		}

		$width = rtrim( $width, '%' );
		if ( ! is_numeric( $width ) ) {
			return '';
		}

		$width = (float) $width;
		if ( $width >= 95 ) {
			return '100';
		}
		if ( $width >= 70 ) {
			return '75';
		}
		if ( $width >= 45 ) {
			return '50';
		}
		if ( $width >= 30 ) {
			return '33';
		}

		return '25';
	}

	/**
	 * Convert external conditional_logic groups into this module's flat model.
	 *
	 * External formats store OR groups, with each group containing AND rules. This builder
	 * stores either one flat AND set or one flat OR set, so a single group maps
	 * exactly and multiple groups map to a conservative ANY list.
	 *
	 * @param mixed $raw Raw external conditional_logic value.
	 * @return array{enabled: bool, match: string, rules: array<int, array<string, string>>}
	 */
	private function normalizeConditionalLogic( mixed $raw ): array {
		if ( empty( $raw ) || ! is_array( $raw ) ) {
			return [
				'enabled' => false,
				'match'   => 'all',
				'rules'   => [],
			];
		}

		$rules       = [];
		$group_count = 0;
		foreach ( $raw as $group ) {
			if ( ! is_array( $group ) ) {
				continue;
			}
			++$group_count;
			foreach ( $group as $rule ) {
				if ( count( $rules ) >= Config::MAX_CONDITIONAL_RULES ) {
					break 2;
				}
				if ( ! is_array( $rule ) ) {
					continue;
				}
				$field_key = $this->validator->sanitizeKey( (string) ( $rule['field'] ?? '' ) );
				if ( $field_key === '' ) {
					continue;
				}
				$operator = (string) ( $rule['operator'] ?? '==' );
				if ( ! array_key_exists( $operator, Config::conditionalOperators() ) ) {
					$operator = '==';
				}
				$value = $rule['value'] ?? '';
				if ( is_array( $value ) ) {
					$value = implode( ',', array_map( 'strval', $value ) );
				}
				$rules[] = [
					'field'    => $field_key,
					'operator' => $operator,
					'value'    => sanitize_text_field( (string) $value ),
				];
			}
		}

		return [
			'enabled' => $rules !== [],
			'match'   => $group_count > 1 ? 'any' : 'all',
			'rules'   => $rules,
		];
	}

	/**
	 * @param mixed $value Raw scalar/list value.
	 * @return array<int, string>
	 */
	private function normalizeListSetting( mixed $value ): array {
		$items = is_array( $value ) ? $value : [ $value ];
		$out   = [];

		foreach ( $items as $item ) {
			$item = trim( sanitize_text_field( (string) $item ) );
			if ( $item !== '' ) {
				$out[] = $item;
			}
		}

		return array_values( array_unique( $out ) );
	}

	/**
	 * Convert external taxonomy filter strings such as category:news to term IDs.
	 *
	 * @param array<int, string> $values Raw taxonomy term identifiers.
	 * @return array<int, int>
	 */
	private function normalizeTaxonomyTerms( array $values ): array {
		$out = [];

		foreach ( $values as $value ) {
			if ( is_numeric( $value ) ) {
				$term_id = absint( $value );
				if ( $term_id > 0 ) {
					$out[] = $term_id;
				}
				continue;
			}

			$parts    = explode( ':', $value, 2 );
			$taxonomy = sanitize_key( (string) ( $parts[0] ?? '' ) );
			$slug     = sanitize_title( (string) ( $parts[1] ?? '' ) );
			if ( $taxonomy === '' || $slug === '' || ! taxonomy_exists( $taxonomy ) ) {
				continue;
			}

			$term = get_term_by( 'slug', $slug, $taxonomy );
			if ( $term instanceof \WP_Term ) {
				$out[] = (int) $term->term_id;
			}
		}

		return array_values( array_unique( array_filter( $out ) ) );
	}

	private function normalizePosition( string $position ): string {
		$position = sanitize_key( $position );
		if ( in_array( $position, [ 'acf_after_title', 'after_title' ], true ) ) {
			return 'after_title';
		}
		if ( $position === 'side' ) {
			return 'side';
		}

		return 'normal';
	}

	/**
	 * @param array<int, mixed> $groups External location groups.
	 * @return array{screen: string, match: string, rules: array<int, array<string, string>>}
	 */
	private function normalizeLocationRules( array $groups ): array {
		$rules = [];

		foreach ( $groups as $group ) {
			if ( ! is_array( $group ) ) {
				continue;
			}
			foreach ( $group as $rule ) {
				if ( ! is_array( $rule ) ) {
					continue;
				}
				$param = sanitize_key( (string) ( $rule['param'] ?? '' ) );
				if ( $param === 'current_user_role' ) {
					$param = 'user_role';
				}
				if ( ! array_key_exists( $param, Config::locationRuleParams() ) ) {
					continue;
				}
				$operator = (string) ( $rule['operator'] ?? '==' );
				if ( ! array_key_exists( $operator, Config::locationRuleOperatorsForParam( $param ) ) ) {
					$operator = '==';
				}
				$value = sanitize_text_field( (string) ( $rule['value'] ?? '' ) );
				if ( $value === '' ) {
					continue;
				}
				$rules[] = [
					'param'    => $param,
					'operator' => $operator,
					'value'    => $value,
				];

				if ( count( $rules ) >= Config::MAX_LOCATION_RULES ) {
					break 2;
				}
			}
		}

		return LocationRules::normalizeScreen(
			[
				'screen' => 'post',
				'match'  => count( $groups ) > 1 ? 'any' : 'all',
				'rules'  => $rules,
			]
		);
	}

	/**
	 * Generate a unique import key from a title when the payload key collides.
	 *
	 * @param string $title Source title or base key.
	 */
	private function uniqueImportKey( string $title ): string {
		return KeyGenerator::generateGroupKey( $title, array_keys( $this->storage->all() ) );
	}
}
