<?php

namespace App\Traits;

use App\Rules\Base64FileValidation;
use App\Rules\CentralIntegrityRule;
use App\Rules\IntegrityRule;

trait HasStructuredData
{

     /**
     * Get the data of the instance
     *
     * @return array
     */
    public function getDataAttribute(): array
    {
        $data = $this->attributes['data'] ?? [];

        if (is_string($data)) {
            return json_decode($data, true) ?? [];
        }

        return $data;
    }

     /**
     * Get the structure class associated with the data class
     *
     * @return object
     */
    protected function getStructureClass(): object
    {
        $structureClass = static::class . 'Structure';
        if (!class_exists($structureClass)) {
            throw new \RuntimeException("Structure class $structureClass not found.");
        }

        return (new $structureClass())::currentStructure();
    }

     /**
     * Get the actual structure associated with the data(the structure column of teh structure class)
     *
     * @return array
     */
    protected function getStructure(): array
    {
        return $this->getStructureClass()->structure;
    }

     /**
     * Get a generated validation array for the data model based of it's structure
     *
     * @return array
     */
    public function getValidationArray(bool $for_update = false): array
    {
        $structure = $this->getStructure();
        $validation_array = [];

        foreach ($structure as $column_name => $column_data) {
            $validation_array[$column_name] = [];
            if ($for_update) {
                $validation_array[$column_name][] = 'sometimes';
            }

            if (isset($column_data['options'])) {
                $options = $column_data['options'];

                /** Making column required/nullable */
                if (isset($options['required']) && $options['required']) {
                    if (in_array($column_data['type'], ["foreign_key", "foreign_key_list"])) {
                        $relationship_column = $column_data['options']['relationship_column'];
                        $validation_array[$relationship_column][] = 'required';
                    } else {
                        $validation_array[$column_name][] = 'required';
                    }
                } else {
                    if (in_array($column_data['type'], ["foreign_key", "foreign_key_list"])) {
                        $relationship_column = $column_data['options']['relationship_column'];
                        $validation_array[$relationship_column][] = 'nullable';
                    } else {
                        $validation_array[$column_name][] = 'nullable';
                    }
                }

                /** Setting max value if applicable */
                if (isset($options['max'])) {
                    $validation_array[$column_name][] = 'max:' . $options['max'];
                }
            }

            /** Setting type validation */
            switch ($column_data['type']) {
                case 'string':
                case 'text':
                case 'email':
                case 'tel':
                    $validation_array[$column_name][] = 'string';
                    break;
                case 'integer':
                case 'decimal':
                    $validation_array[$column_name][] = 'numeric';
                    break;
                case 'time':
                    $validation_array[$column_name][] = 'date_format:H:i:s';
                    break;
                case 'date':
                    $validation_array[$column_name][] = 'date_format:Y-m-d';
                    break;
                case 'datetime':
                    $validation_array[$column_name][] = 'date_format:Y-m-d H:i:s';
                    break;
                case 'boolean':
                    $validation_array[$column_name][] = 'boolean';
                    break;
                case 'file':
                    $validation_array[$column_name][] = 'file';
                    break;
                case 'base64file':
                    $validation_array[$column_name][] = 'array';
                    $validation_array[$column_name][] = new Base64FileValidation();
                    break;
                case 'enum':
                    $validation_array[$column_name][] = 'array';
                    $validation_array[$column_name][] = 'min:1';
                    $validation_array[$column_name . '.*'][] = 'string';
                    $validation_array[$column_name . '.*'][] = 'in:' . implode(',', array_keys($column_data['options']['answers']));
                    break;
                case 'enum_single':
                    $validation_array[$column_name][] = 'string';
                    $validation_array[$column_name][] = 'in:' . implode(',', array_keys($column_data['options']['answers']));
                    break;
                case 'foreign_key':
                    $relationship_column = $column_data['options']['relationship_column'];
                    if ($for_update) {
                        $validation_array[$relationship_column][] = 'sometimes';
                    }
                    $validation_array[$relationship_column][] = 'numeric';
                    if ($column_data['options']['central']) {
                        $validation_array[$relationship_column][] = new CentralIntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    } else {
                        $validation_array[$relationship_column][] = new IntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    }
                    break;
                case 'foreign_key_list':
                    $relationship_column = $column_data['options']['relationship_column'];
                    $validation_array[$relationship_column][] = 'array';
                    $validation_array[$relationship_column][] = 'min:1';
                    if ($for_update) {
                        $validation_array[$relationship_column][] = 'sometimes';
                    }
                    $validation_array[$relationship_column . '.*'][] = 'numeric';
                    if ($column_data['options']['central']) {
                        $validation_array[$relationship_column . '.*'][] = new CentralIntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    } else {
                        $validation_array[$relationship_column . '.*'][] = new IntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    }
                    break;
                case 'list':
                    $validation_array[$column_name][] = 'array';
                    break;
            }
        }

        if (static::DATA_TYPE == "multi") {
            $multi_row_validation = [];
            foreach ($validation_array as $column_name => $column_rules) {
                $multi_row_validation["data.*.$column_name"] = $column_rules;
            }
            $validation_array = [
                "data" => "required|array",
            ];
            $validation_array += $multi_row_validation;
        }

        return $validation_array;
    }

     /**
     * Populate the data property with data
     *
     * @return void
     */
    public function populateData(array $data): void
    {
        $structureClass = $this->getStructureClass();
        $structure = $this->getStructure();
        $updated_data = $this->data;


        $is_multi_rows = static::DATA_TYPE == "multi";
        //populate multiple-row data for multi-row-data-type models
        if ($is_multi_rows) {
            $updated_data = [];
            foreach ($data['data'] as $row_data) {
                $row_updated_data = [];
                foreach ($row_data as $column => $value) {
                    if ($structureClass->hasColumn($column)) {
                        switch ($structure[$column]['type']) {
                            case 'file':
                                $row_updated_data[$column] = saveTenantFile($column);
                                break;
                            default:
                                $row_updated_data[$column] = $value;
                                break;
                        }
                    } elseif ($structureClass->hasForeign($column)) {
                        $row_updated_data[$column] = is_array($value) ? $value : (int) $value;
                    }
                }
                $updated_data[] = $row_updated_data;
            }
        } else {
            //populate  data for single-row-data-type models
            foreach ($data as $column => $value) {
                if ($structureClass->hasColumn($column)) {
                    switch ($structure[$column]['type']) {
                        case 'file':
                            $updated_data[$column] = saveTenantFile($column);
                            break;
                        default:
                            $updated_data[$column] = $value;
                            break;
                    }
                } elseif ($structureClass->hasForeign($column)) {
                    $updated_data[$column] = is_array($value) ? $value : (int) $value;
                }
            }
        }

        $this->setAttribute('data', $updated_data);
        $this->saveData();
    }

     /**
     * Save data property into database
     *
     * @return void
     */
    public function saveData(): void
    {
        $this->data = json_encode($this->data);
        $this->save();
    }
}
