<?php

namespace App\Models\Pages;

use App\Models\Asset;
use App\Models\Objects\Field;
use App\Models\Publications\Publication;
use App\Models\Registries\Registry;
use App\Models\User;
use App\Support\HasObjectsTrait;
use App\Support\RelationValuesTrait;
use App\Support\UuidScopeTrait;
use DragonCode\Support\Facades\Helpers\Str;
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 Page extends Model {
    use UuidScopeTrait, SoftDeletes, HasObjectsTrait, RelationValuesTrait;

    protected $dates = [
    ];

    protected $fillable = [
        'uuid',
        'parent_id',
        'picture_id',
        'slug',
        'type',
        'name',
        'title',
        'description',
        'keywords',
        'h1',
        'ord'
    ];

    protected $hidden = [
        'id'
    ];


    public function parent(): BelongsTo {
        return $this->belongsTo(Page::class, 'parent_id');
    }

    public function children(): HasMany {
        return $this->hasMany(Page::class, 'parent_id')->orderBy('ord');
    }

    public function siblings(): HasMany {
        return $this->hasMany(Page::class, 'parent_id', 'parent_id')->where('id', '!=', $this->id);
    }

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

    public function sections(): MorphToMany {
        return $this->objects()->wherePivot('group', '=', 'sections');
    }

    public function sidebars(): MorphToMany {
        return $this->objects()->wherePivot('group', '=', 'sidebars');
    }

    public function publications(): HasMany {
        return $this->hasMany(Publication::class);
    }

    public function registries(): HasMany {
        return $this->hasMany(Registry::class);
    }


    public function scopeBySlug($query, $slug) {
        $query->where(['slug' => $slug]);
    }

    public function scopeNthParentSlug($query, $nth, $slug) {
        $query->whereHas(implode('.', array_fill(0, $nth, 'parent')), function($query) use($slug) {
            $query->bySlug($slug);
        });
    }


    public function getLinkAttribute(): string {
        return '/' . $this->parents->reverse()->push($this)->pluck('slug')->filter(function($v) {return $v;})->implode('/');
    }

    public function getParentsAttribute(): Collection {
        $page = $this;
        $result = collect([]);
        while ($page = $page->parent) $result->push($page);
        return $result;
    }

    public function getParsedTypeAttribute(): array {
        return ['name' => $this->type, 'title' => PageType::TITLES[$this->type] ?? null];
    }

    public function getRegistryAttribute(): Model {
        return $this->registries()->firstOrCreate();
    }



    public function isEditable(User $user): bool {
        return $user->isModerator && $user->membership()->whereHas('objects', function($query) {
                Field::applyFilters($query, collect(['types' => 'company-member-properties', 'moderate-pages' => $this->parents->add($this)->pluck('uuid')->all()]));
            })->exists();
    }

    public function addSection($typeName, $ord = null): ?Model {
        return $this->createObject($typeName, $ord, 'sections');
    }

    public function addSidebar($typeName = 'page-sidebar', $ord = null): ?Model {
        return $this->createObject($typeName, $ord, 'sidebars');
    }


    public function checkConflictedProps($props = ['name', 'slug']) {
        collect($props)->each(function($prop) {
            $this->checkConflictedProp($prop);
        });
    }
    public function checkConflictedProp($prop): ?bool {
        if ($val = $this->$prop) {
            $i = 1;
            while($this->siblings()->where([$prop => $val])->exists()) {
                $val = "{$this->$prop} (копия {$i})";
                if ($prop === 'slug') $val = Str::slug($val);
                $i++;
            }
            return ($this->$prop !== $val) ? $this->update([$prop => $val]) : null;
        } else return null;
    }


    public function clone(?Page $parent = null, $recursive = false): Model {
        $model = $this->replicate(['uuid']);
        if ($parent) $model->parent_id = $parent->id;
        $model->push();
        $model->copyObjectsFromObjectable($this);
        $model->checkConflictedProps();
        if ($recursive) $this->children->each(function($child) use($model) {
            $child->clone($model, true);
        });
        return $model;
    }


    public function move($ord, ?Page $parent = null) {
        $prevParent = $this->parent()->withTrashed()->first();
        if (($parent->id ?? 0) === ($prevParent->id ?? 0)) {
            ($ord > $this->ord) ? $this->moveSet('backward', $this->ord, $ord, $parent) : $this->moveSet('forward', $ord, $this->ord, $parent);
        } else $this->moveSet('forward', $ord, null, $parent);
        $this->update(['parent_id' => $parent->id ?? 0, 'ord' => $ord]);
        $this->trimIndexes([$prevParent->id ?? 0, $parent->id ?? 0]);
        $this->checkConflictedProps();
    }
    public function moveSet($dir = 'forward', $ordFrom = null, $ordTo = null, ?Page $parent = null) {
        $query = Page::query()->where(['parent_id' => $parent->id ?? 0])->withTrashed()->orderBy('ord');
        if ($ordFrom !== null) $query->where('ord', '>=', $ordFrom);
        if ($ordTo !== null) $query->where('ord', '<=', $ordTo);
        $query->get()->each(function($page) use($dir) {
            $page->update(['ord' => ($dir === 'forward') ? ($page->ord + 1) : ($page->ord - 1)]);
        });
    }
    public function trimIndexes($parentIds) {
        collect(is_array($parentIds) ? $parentIds : [$parentIds])->unique()->each(function($parentId) {
            Page::query()->where(['parent_id' => $parentId])->withTrashed()->orderBy('ord')->orderBy('id')->get()->each(function($page, $index) {
                if ($page->ord !== $index) $page->update(['ord' => $index]);
            });
        });
    }

    public function getMaxOrd(): int {
        $res = $this->siblings()->withTrashed()->max('ord');
        return ($res !== null) ? ($res + 1) : 0;
    }


    public static function byUrl($url) {
        if ($url === '/') return Page::query()->where(['parent_id' => 0, 'slug' => ''])->first();
        elseif ($url = trim($url, '/ ')) {
            $query = self::query();
            collect(explode('/', $url))->reverse()->values()->each(function($slug, $index) use($query) {
                if ($slug !== '') {
                    $index ? $query->nthParentSlug($index, $slug) : $query->bySlug($slug);
                }
            });
            return $query->first();
        }
        return null;
    }


    public static function root($withTrashed = false) {
        $query = self::query()->where(['parent_id' => 0])->orderBy('ord');
        if ($withTrashed) $query->withTrashed();
        return $query->get();
    }

}
