<?php

namespace WPUM\Carbon_Fields\Datastore;

use WPUM\Carbon_Fields\Helper\Helper;
use WPUM\Carbon_Fields\Field\Field;
use WPUM\Carbon_Fields\Exception\Incorrect_Syntax_Exception;
/**
 * Key Value Datastore
 * Provides basic functionality to save in a key-value storage
 */
abstract class Key_Value_Datastore extends Datastore
{
    /**
     * Key Toolset for key generation and comparison utilities
     *
     * @var Key_Toolset
     */
    protected $key_toolset;
    /**
     * Initialize the datastore.
     */
    public function __construct()
    {
        $this->key_toolset = \WPUM\Carbon_Fields\Carbon_Fields::resolve('key_toolset');
        parent::__construct();
    }
    /**
     * Get array of ancestors (ordered top-bottom) with the field name appended to the end
     *
     * @param Field $field
     * @return array<string>
     */
    protected function get_full_hierarchy_for_field(Field $field)
    {
        $full_hierarchy = \array_merge($field->get_hierarchy(), array($field->get_base_name()));
        return $full_hierarchy;
    }
    /**
     * Get array of ancestor group indexes (ordered top-bottom)
     * Indexes show which entry/group this field belongs to in a Complex_Field
     *
     * @param Field $field
     * @return array<int>
     */
    protected function get_full_hierarchy_index_for_field(Field $field)
    {
        $hierarchy_index = $field->get_hierarchy_index();
        $full_hierarchy_index = !empty($hierarchy_index) ? $hierarchy_index : array();
        return $full_hierarchy_index;
    }
    /**
     * Convert a cascading storage array to a value tree array
     *
     * @see Internal Glossary in DEVELOPMENT.MD
     * @param array<stdClass> $storage_array
     * @return array
     */
    protected function cascading_storage_array_to_value_tree_array($storage_array)
    {
        $tree = array();
        $found_keepalive = \false;
        foreach ($storage_array as $row) {
            $parsed_storage_key = $this->key_toolset->parse_storage_key($row->key);
            if ($parsed_storage_key['property'] === $this->key_toolset->get_keepalive_property()) {
                $found_keepalive = \true;
                continue;
            }
            $level =& $tree;
            foreach ($parsed_storage_key['full_hierarchy'] as $i => $field_name) {
                $index = isset($parsed_storage_key['hierarchy_index'][$i]) ? $parsed_storage_key['hierarchy_index'][$i] : 0;
                if (!isset($level[$field_name])) {
                    $level[$field_name] = array();
                }
                $level =& $level[$field_name];
                if ($i < \count($parsed_storage_key['full_hierarchy']) - 1) {
                    if (!isset($level[$index])) {
                        $level[$index] = array();
                    }
                    $level =& $level[$index];
                } else {
                    if (!isset($level[$parsed_storage_key['value_index']])) {
                        $level[$parsed_storage_key['value_index']] = array();
                    }
                    $level =& $level[$parsed_storage_key['value_index']];
                    $level[$parsed_storage_key['property']] = $row->value;
                }
            }
            $level =& $tree;
        }
        Helper::ksort_recursive($tree);
        if (empty($tree) && !$found_keepalive) {
            return null;
        }
        return $tree;
    }
    /**
     * Convert a value set tree to a value tree for the specified field
     * (get a single value tree from the collection)
     *
     * @see Internal Glossary in DEVELOPMENT.MD
     * @param  array $value_tree_array
     * @param  Field $field
     * @return array
     */
    protected function value_tree_array_to_value_tree($value_tree_array, Field $field)
    {
        $value_tree = $value_tree_array;
        $hierarchy = $field->get_hierarchy();
        $hierarchy_index = $field->get_hierarchy_index();
        foreach ($hierarchy as $index => $parent_field) {
            $hierarchy_index_value = isset($hierarchy_index[$index]) ? $hierarchy_index[$index] : 0;
            if (isset($value_tree[$parent_field][$hierarchy_index_value])) {
                $value_tree = $value_tree[$parent_field][$hierarchy_index_value];
            }
        }
        if (isset($value_tree[$field->get_base_name()])) {
            $value_tree = $value_tree[$field->get_base_name()];
        }
        return $value_tree;
    }
    /**
     * Get a raw database query results array for a field
     *
     * @param Field $field The field to retrieve value for.
     * @param array $storage_key_patterns
     * @return array<stdClass> Array of {key, value} objects
     */
    protected abstract function get_storage_array(Field $field, $storage_key_patterns);
    /**
     * Get the field value(s)
     *
     * @param Field $field The field to get value(s) for
     * @return null|array<array>
     */
    public function load(Field $field)
    {
        $storage_key_patterns = $this->key_toolset->get_storage_key_getter_patterns($field->is_simple_root_field(), $this->get_full_hierarchy_for_field($field));
        $cascading_storage_array = $this->get_storage_array($field, $storage_key_patterns);
        $value_tree_array = $this->cascading_storage_array_to_value_tree_array($cascading_storage_array);
        if ($value_tree_array === null) {
            return $value_tree_array;
        }
        $value_tree = $this->value_tree_array_to_value_tree($value_tree_array, $field);
        return $value_tree;
    }
    /**
     * Save a single key-value pair to the database
     *
     * @param string $key
     * @param string $value
     */
    protected abstract function save_key_value_pair($key, $value);
    /**
     * Save the field value(s)
     *
     * @param Field $field The field to save.
     */
    public function save(Field $field)
    {
        $value_set = $field->get_full_value();
        if (empty($value_set) && $field->get_value_set()->keepalive()) {
            $storage_key = $this->key_toolset->get_storage_key($field->is_simple_root_field(), $this->get_full_hierarchy_for_field($field), $this->get_full_hierarchy_index_for_field($field), 0, $this->key_toolset->get_keepalive_property());
            $this->save_key_value_pair($storage_key, '');
        }
        foreach ($value_set as $value_group_index => $values) {
            foreach ($values as $property => $value) {
                $storage_key = $this->key_toolset->get_storage_key($field->is_simple_root_field(), $this->get_full_hierarchy_for_field($field), $this->get_full_hierarchy_index_for_field($field), $value_group_index, $property);
                $this->save_key_value_pair($storage_key, $value);
            }
        }
    }
}
