<?php

namespace App\Models\Objects;

use App\Models\Objects\Values\BooleanValue;
use App\Models\Objects\Values\DateValue;
use App\Models\Objects\Values\DocumentValue;
use App\Models\Objects\Values\FloatValue;
use App\Models\Objects\Values\HtmlValue;
use App\Models\Objects\Values\ImageValue;
use App\Models\Objects\Values\IntegerValue;
use App\Models\Objects\Values\RelationValue;
use App\Models\Objects\Values\StringValue;
use App\Models\Objects\Values\TextValue;
use App\Models\Pages\Page;
use App\Models\Registries\Entry;
use App\Models\User;
use App\Support\UuidScopeTrait;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;

class NirObject extends Model {
    use UuidScopeTrait, SoftDeletes;

    protected $table = 'objects';

    protected $dates = [
    ];

    protected $fillable = [
        'uuid',
        'type_id',
        'owner_id',
        'name'
    ];

    protected $hidden = [
        'id'
    ];


    public function entries(): MorphToMany {
        return $this->morphedByMany(Entry::class, 'objectable');
    }

    public function pages(): MorphToMany {
        return $this->morphedByMany(Page::class, 'objectable');
    }

    public function objects(): MorphToMany {
        return $this->morphToMany(NirObject::class, 'objectable');
    }

    public function objectables($related): MorphToMany {
        return $this->morphedByMany($related, 'objectable')->withPivot(['ord', 'group']);
    }

    public function type(): BelongsTo {
        return $this->belongsTo(ObjectType::class);
    }

    public function owner(): BelongsTo {
        return $this->belongsTo(User::class);
    }

    public function properties(): HasMany {
        return $this->type->groups()->with('fields');
    }

    public function groups(): HasMany {
        return $this->type->groups();
    }

    public function stringValues(): HasMany {
        return $this->hasMany(StringValue::class, 'object_id');
    }
    public function textValues(): HasMany {
        return $this->hasMany(TextValue::class, 'object_id');
    }
    public function htmlValues(): HasMany {
        return $this->hasMany(HtmlValue::class, 'object_id');
    }
    public function integerValues(): HasMany {
        return $this->hasMany(IntegerValue::class, 'object_id');
    }
    public function floatValues(): HasMany {
        return $this->hasMany(FloatValue::class, 'object_id');
    }
    public function booleanValues(): HasMany {
        return $this->hasMany(BooleanValue::class, 'object_id');
    }
    public function dateValues(): HasMany {
        return $this->hasMany(DateValue::class, 'object_id');
    }
    public function relationValues(): HasMany {
        return $this->hasMany(RelationValue::class, 'object_id');
    }
    public function documentValues(): HasMany {
        return $this->hasMany(DocumentValue::class, 'object_id');
    }
    public function imageValues(): HasMany {
        return $this->hasMany(ImageValue::class, 'object_id');
    }


    public function scopeApplyFilters(Builder $query, array $filters): Builder {
        collect($filters)->each(function($value, $prop) use($query) {
            if ($field = Field::byUuidOrName($prop)->first()) $field->applyFilter($query, $value);
        });
        return $query;
    }

    public function scopeApplyOrders(Builder $query, $orders): Builder {
        collect($orders)->each(function($dir, $prop) use($query) {
            if ($field = Field::byUuidOrName($prop)->first()) $field->applyOrder($query, $dir);
        });
        return $query;
    }


    public function getValuesAttribute(): array {
        $result = [];
        $this->properties->map(function($group) use(&$result) {
            $group->fields->map(function($field) use(&$result) {
                 $result[$field->name] = $this->getValue($field->name);
            });
        });
        return $result;
    }


    public function getValue($fieldName) {
        return ($field = $this->type->getField($fieldName)) ? $field->getValue($this->id) : null;
    }

    public function value($fieldName) {
        $result = null;
        if ($field = $this->type->getField($fieldName)) {
            $result = $field->getValue($this->id);
            if ($result && !$field->multiple) $result = $result->first();
        }
        return $result;
    }

    public function setValues(array $values): Collection {
        return collect($values)->map(function($value, $fieldName) {
            return $this->setValue($fieldName, $value);
        });
    }

    public function setValue($fieldName, $value) {
        return ($field = $this->type->getField($fieldName)) ? $field->setValue($this->id, $value) : null;
    }

    public function addValue($fieldName, $value) {
        return ($field = $this->type->getField($fieldName)) ? $field->addValue($this->id, $value) : null;
    }


    public function clone() {
        $clone = $this->type->objects()->create(['name' => $this->name, 'owner_id' => $this->owner_id]);
        $this->properties->each(function($group) use($clone) {
            $group->fields->each(function($field) use($clone) {
                $clone->setValue($field->name, $this->getValue($field->name));
            });
        });
        return $clone;
    }

    public function currentGroup($related) {
        return ($res = $this->objectables($related)->first()) ? $res->pivot->group : 'default';
    }
    public function currentOrd($related) {
        return ($res = $this->objectables($related)->first()) ? $res->pivot->ord : null;
    }

    public function applySearchFilter(Builder $query, $search) {
        $query->whereHas('stringValues', function($query) use($search) {
            $query->where('value', 'like', "%{$search}%");
        })->orWhereHas('textValues', function($query) use($search) {
            $query->where('value', 'like', "%{$search}%");
        });
    }


    public function fakeValues() {
        $this->type->fields()->get()->each(function($field) {
            $field->fakeValue($this->id);
        });
    }
}