<?php

namespace App\Libs\Backoffice\Generic\Fields;

use App\Libs\Backoffice\Generic\ContentType;
use App\Libs\Contracts\Backoffice\DuplicatableField;
use App\Libs\Contracts\Backoffice\SavableField;
use App\Libs\Exception\InvalidConfigurationException;
use App\Libs\Facades\ContentTypes;
use App\Libs\Facades\Localization;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use RuntimeException;
use stdClass;
use App\Models\WebsiteObject;

class MultiplePickerField extends BaseField implements SavableField, DuplicatableField
{
    protected $order = false;
    protected $objectTypes = false;
    protected $maxSize = false;
    protected $longList = false;
    protected $minSize = 0;
    protected $websiteFiltered = false;
    protected $allowInsert = false;
    protected $duplicateRelated = false;

    public function __construct($config, $contentType = null)
    {
        unset($config["inList"]);
        unset($config["search"]);
        unset($config["sort"]);

        parent::__construct($config, $contentType);

        $this->relationField = true;

        if (empty($config["objectTypes"])) {
            throw new InvalidConfigurationException("The {$this->name} configuration is missing the object types");
        }

        $this->objectTypes = $config["objectTypes"];

        if (!empty($config["websiteFiltered"])) {
            $this->websiteFiltered = $config["websiteFiltered"];
        }

        if ($this->websiteFiltered === false && count($this->objectTypes) > 1) {
            throw new InvalidConfigurationException("The {$this->name} multi picker field cannot have more than one object type if it is not multi website");
        }

        if (!empty($config["order"])) {
            $this->order = $config["order"];
        }

        if (!empty($config["maxSize"])) {
            $this->maxSize = $config["maxSize"];
        }

        if (!empty($config["minSize"])) {
            $this->minSize = $config["minSize"];
        }

        if (!empty($config["duplicateRelated"])) {
            $this->duplicateRelated = $config["duplicateRelated"];
        }

        if (!empty($config["allowInsert"]) && !$this->isMultipleTypes()) {
            $this->allowInsert = $config["allowInsert"];
        }

        if (!empty($config["longList"])) {
            $this->longList = $config["longList"];
        }

        if ($this->multiLanguage && !$this->longList) {
            throw new InvalidConfigurationException("The {$this->name} multi picker field multilang mode is only supported for longlist and no website mode");
        }
    }

    public function isOrderable()
    {
        return $this->order;
    }

    public function setOrderable($order)
    {
        $this->order = $order;
    }

    public function getObjectTypes()
    {
        return $this->objectTypes;
    }

    public function getObjectTypesModelsType($websiteId = null)
    {
        $modelsType = array();
        foreach ($this->objectTypes as $objectType) {
            $contentType = ContentTypes::find($objectType);
            if (is_null($websiteId) || $contentType->hasWebsite($websiteId)) {
                $modelsType[] = $contentType->getModelType();
            }
        }
        return $modelsType;
    }

    public function getContentTypes($websiteId = null)
    {
        $contentTypes = array();
        foreach ($this->objectTypes as $objectType) {
            $contentType = ContentTypes::find($objectType);
            if (is_null($websiteId) || $contentType->hasWebsite($websiteId)) {
                $contentTypes[] = $contentType;
            }
        }
        return $contentTypes;
    }

    public function setObjectTypes($objectTypes = array())
    {
        $this->objectTypes = $objectTypes;
    }

    public function isMultipleTypes()
    {
        return !empty($this->objectTypes) && count($this->objectTypes) > 1;
    }

    public function setWebsiteFiltered($websiteFiltered)
    {
        $this->websiteFiltered = $websiteFiltered;
    }

    public function isWebsiteFiltered()
    {
        return $this->websiteFiltered;
    }

    public function getMaxSize()
    {
        return $this->maxSize;
    }

    public function setMaxSize($maxSize)
    {
        $this->maxSize = $maxSize;
    }

    public function getMinSize()
    {
        return $this->minSize;
    }

    public function setMinSize($minSize = 0)
    {
        $this->minSize = $minSize;
    }

    public function getAllowInsert()
    {
        return $this->allowInsert;
    }

    public function setAllowInsert($allowInsert)
    {
        $this->allowInsert = $allowInsert;
    }

    public function isLongList()
    {
        return $this->longList;
    }

    public function setLongList($longList)
    {
        $this->longList = $longList;
    }

    public function getValidation()
    {
        $rules = "array";

        if ($this->minSize > 0) {
            $rules .= "|required|min:".$this->minSize;
        } else {
            $rules .= "|nullable";
        }

        if ($this->maxSize > 0) {
            $rules .= "|max:".$this->maxSize;
        }

        return $rules;
    }

    public function toForm($model, $enabled = true, $hidden = false)
    {
        if (!$this->isLongList()) {
            if ($this->isWebsiteFiltered()) {
                return $this->_toFormWebsite($model, $enabled, $hidden);
            } else {
                return $this->_toFormNoWebsite($model, $enabled, $hidden);
            }
        } else {
            if ($this->isWebsiteFiltered()) {
                return $this->_toFormLongListWebsite($model, $enabled, $hidden);
            } else {
                return $this->_toFormLongListNoWebsite($model, $enabled, $hidden);
            }
        }
    }

    public function _toFormWebsite($model, $enabled, $hidden)
    {
        if (empty($this->objectTypes)) {
            return "";
        }
        $websiteId = Localization::getWebsite()->id;

        $websiteModel = new WebsiteObject();
        $relation = $websiteModel->related();

        $modelsTypes = $this->getObjectTypesModelsType($websiteId);


        if (empty($modelsTypes)) {
            return "";
        }

        $query = WebsiteObject::with("objectable")
            ->whereIn("object_type", $modelsTypes)
            ->where("website_id", $websiteId);

        $objects = $query->get();

        $fullList = array();

        foreach ($this->getContentTypes($websiteId) as $contentType) {

            $fullList[$contentType->getModelType()] = array(
                "label" => ucfirst($contentType->getLabel()),
                "list" => array()
            );
        }

        foreach ($objects as $object) {
            $fullList[$object->object_type]["list"][] = (object) array(
                "id" => $object->id,
                "title" => $object->objectable->title,
                "object_id" => $object->objectable->id
            );
        }

        foreach ($fullList as $listType) {
            usort($listType["list"], function ($firstItem, $secondItem) {
                if ($firstItem->title == $secondItem->title) {
                    return 0;
                }

                return ($firstItem->title < $secondItem->title) ? -1 : 1;
            });

        }

        $postData = request()->old($this->getName());
        $pickedList = array();
        if (!empty($postData)) {
            $pickedList = json_decode($postData);
        } elseif ($model->exists) {
            $websiteObjectModel = $model->getActiveWebsiteEntry();

            $query = DB::table($relation->getTable())
                ->where($relation->getQualifiedForeignPivotKeyName(), '=', $websiteObjectModel->id)
                ->select($relation->getRelatedPivotKeyName()." as id");

            if ($this->isOrderable()) {
                $query->orderBy($relation->getTable().".position", "asc");
            }

            $pickedList = $query->get();
        }

        return view(
            "admin.generic.components.forms.multiplepicker",
            array(
                "id" => $this->getName(),
                "name" => $this->getName(),
                "errorKey" => $this->getName(),
                "label" => $this->getLabel(),
                "order" => $this->isOrderable(),
                "maxSize" => $this->getMaxSize(),
                "fullList" => $fullList,
                "pickedList" => $pickedList,
                "multipleTypes" => $this->isMultipleTypes(),
                "hide" => $hidden,
                "enabled" => $enabled,
                "parentHidden" => ($hidden && !empty($this->parent)),
                "parent" => $this->getParent(),
                "allowInsert" => $this->getAllowInsert(),
                "websiteFiltered" => $this->isWebsiteFiltered(),
                "info" => $this->getInfo(),
                "blocksMode" => $this->blocksMode
            ));
    }

    public function _toFormLonglistWebsite($model, $enabled, $hidden)
    {
        if (empty($this->objectTypes)) {
            return "";
        }
        $websiteId = Localization::getWebsite()->id;

        $websiteModel = new WebsiteObject();
        $relation = $websiteModel->related();

        $modelsTypes = $this->getObjectTypesModelsType($websiteId);


        if (empty($modelsTypes)) {
            return "";
        }

        $postData = request()->old($this->getName());
        $pickedList = array();
        if (!empty($postData)) {
            $pickedList = json_decode($postData);
        } elseif ($model->exists) {
            $objs = $model->related($this->name);

            foreach($objs as $pickedObj) {
                $obj = new stdClass();
                $obj->id = $pickedObj->id;
                $obj->object_id = $pickedObj->website_object_id;
                $obj->type = $pickedObj->content_type;
                $obj->title = $pickedObj->title;
                $pickedList[] = $obj;
            }
        }
        return view(
            "admin.generic.components.forms.multiplepickersearch",
            array(
                "id" => $this->getName(),
                "name" => $this->getName(),
                "fieldName" => $this->getName(),
                "errorKey" => $this->getName(),
                "label" => $this->getLabel(),
                "order" => $this->isOrderable(),
                "maxSize" => $this->getMaxSize(),
                "pickedList" => $pickedList,
                "multipleTypes" => $this->isMultipleTypes(),
                "contentType" => null,
                "hide" => $hidden,
                "enabled" => $enabled,
                "parentHidden" => ($hidden && !empty($this->parent)),
                "parent" => $this->getParent(),
                "allowInsert" => false,
                "websiteFiltered" => $this->isWebsiteFiltered(),
                "info" => $this->getInfo(),
                "searchUrl" => url("admin/content/auto-complete/".$this->contentType->getType()."/".$this->name),
                "blocksMode" => $this->blocksMode,
                "objectTypes" => $this->objectTypes
            ));
    }

    public function _toFormLongListNoWebsite($model, $enabled, $hidden)
    {
        $fieldName = $this->getName();
        $contentType = head($this->getContentTypes());
        $relationshipName = $this->name;
        $relationship = $model->$relationshipName();
        $relationTableName = $relationship->getTable();
        $relationModel = $relationship->getRelated();
        $html = '';

        if ($this->isMultiLanguage()) {
            $languages = Localization::getSupportedLanguages();
            $defaultLang = Localization::getDefaultLocale();
            $postData = request()->old($fieldName);

            foreach ($languages as $language) {
                $name = $fieldName."[{$language->id}]";
                $id = $fieldName."_{$language->id}";
                $errorKey = $fieldName.".".$language->id;

                $query = $relationModel->full()
                    ->select("id", "title", "id as object_id", "content_type as type")
                    ->join($relationTableName, $relationModel->getTable().'.'.$relationship->getRelatedKeyName(), "=",
                        $relationship->getQualifiedRelatedPivotKeyName())
                    ->where($relationship->getTable().'.language_id', $language->id);

                if ($this->isOrderable()) {
                    $query->orderBy("{$relationTableName}.position", "asc");
                } else {
                    $query->orderBy("title", "asc");
                }

                if (!empty($postData)) {
                    $langPostData = json_decode($postData[$language->id], true);
                }

                if (!empty($langPostData) && is_array($langPostData)) {
                    $pickedList = $langPostData;
                } else if ($model->exists) {
                    $query->where($relationship->getQualifiedForeignPivotKeyName(), '=', $model->id);
                    $pickedList = $query->get();
                } else {
                    $pickedList = $relationModel->newCollection();
                }

                $ct = [
                    'type' => $contentType->getType(),
                    'label' => ucfirst($contentType->getSingularLabel()),
                ];

                $html .= view(
                    "admin.generic.components.forms.multiplepickersearch",
                    array(
                        "id" => $id,
                        "name" => $name,
                        "fieldName" => $fieldName,
                        "errorKey" => $errorKey,
                        "label" => $this->getLabel(),
                        "order" => $this->isOrderable(),
                        "maxSize" => $this->getMaxSize(),
                        "pickedList" => $pickedList,
                        "multipleTypes" => false,
                        "contentType" => $ct,
                        "hide" => ($hidden || $language->id != $defaultLang),
                        "enabled" => $enabled,
                        "parentHidden" => ($hidden && !empty($this->parent)),
                        "parent" => $this->getParent(),
                        "allowInsert" => $this->getAllowInsert(),
                        "websiteFiltered" => $this->isWebsiteFiltered(),
                        "info" => $this->getInfo(),
                        "searchUrl" => url("admin/content/auto-complete/".$this->contentType->getType()."/".$this->name),
                        "blocksMode" => $this->blocksMode,
                        "objectTypes" => $this->objectTypes,
                        "language" => $language->id,
                        "defaultLang" => $defaultLang,
                        "languages" => $languages
                    ));
                }
        } else {
            $query = $relationModel->full()
                ->select("id", "title", "id as object_id", "content_type as type")
                ->join($relationTableName, $relationModel->getTable().'.'.$relationship->getRelatedKeyName(), "=",
                    $relationship->getQualifiedRelatedPivotKeyName());

            if ($this->isOrderable()) {
                $query->orderBy("{$relationTableName}.position", "asc");
            } else {
                $query->orderBy("title", "asc");
            }

            $postData = request()->old($this->getName());

            if (!empty($postData)) {
                $postData = json_decode($postData, true);
            }

            if (!empty($postData) && is_array($postData)) {
                $pickedList = $postData;
            } else if ($model->exists) {
                $query->where($relationship->getQualifiedForeignPivotKeyName(), '=', $model->id);
                $pickedList = $query->get();
            } else {
                $pickedList = $relationModel->newCollection();
            }

            $ct = [
                'type' => $contentType->getType(),
                'label' => ucfirst($contentType->getSingularLabel()),
            ];

            return view(
                "admin.generic.components.forms.multiplepickersearch",
                array(
                    "id" => $this->getName(),
                    "name" => $this->getName(),
                    "fieldName" => $this->getName(),
                    "errorKey" => $this->getName(),
                    "label" => $this->getLabel(),
                    "order" => $this->isOrderable(),
                    "maxSize" => $this->getMaxSize(),
                    "pickedList" => $pickedList,
                    "multipleTypes" => false,
                    "contentType" => $ct,
                    "hide" => $hidden,
                    "enabled" => $enabled,
                    "parentHidden" => ($hidden && !empty($this->parent)),
                    "parent" => $this->getParent(),
                    "allowInsert" => $this->getAllowInsert(),
                    "websiteFiltered" => $this->isWebsiteFiltered(),
                    "info" => $this->getInfo(),
                    "searchUrl" => url("admin/content/auto-complete/".$this->contentType->getType()."/".$this->name),
                    "blocksMode" => $this->blocksMode,
                    "objectTypes" => $this->objectTypes
                ));
        }
        return $html;
    }

    public function _toFormNoWebsite($model, $enabled, $hidden)
    {
        $contentType = head($this->getContentTypes());

        $relationModelType = $contentType->getModelType();
        $relationModel = new $relationModelType;

        $query = $relationModel->full()
            ->select("id", "title", "id as object_id")->orderBy("title", "asc");
        $fullList = $query->get();

        $relationName = $this->getName();
        $relation = $model->$relationName();

        $fullList = array(
            $contentType->getType() =>
                array(
                    "label" => ucfirst($contentType->getSingularLabel()),
                    "list" => $fullList
                )
        );

        $postData = request()->old($this->getName());
        $pickedList = array();
        if (!empty($postData)) {
            $pickedList = json_decode($postData);
        } elseif ($model->exists) {
            $query = DB::table($relation->getTable())
                ->where($relation->getQualifiedForeignPivotKeyName(), '=', $model->id)
                ->select($relation->getQualifiedRelatedPivotKeyName()." as id");

            if ($this->isOrderable()) {
                $query->orderBy($relation->getTable().".position", "asc");
            }

            $pickedList = $query->get();
        }

        return view(
            "admin.generic.components.forms.multiplepicker",
            array(
                "id" => $this->getName(),
                "name" => $this->getName(),
                "errorKey" => $this->getName(),
                "label" => $this->getLabel(),
                "order" => $this->isOrderable(),
                "maxSize" => $this->getMaxSize(),
                "fullList" => $fullList,
                "pickedList" => $pickedList,
                "multipleTypes" => $this->isMultipleTypes(),
                "hide" => $hidden,
                "enabled" => $enabled,
                "parentHidden" => ($hidden && !empty($this->parent)),
                "parent" => $this->getParent(),
                "allowInsert" => $this->getAllowInsert(),
                "websiteFiltered" => $this->isWebsiteFiltered(),
                "info" => $this->getInfo(),
                "blocksMode" => $this->blocksMode,
            ));
    }

    public function getValidationData($copyAllLang = false)
    {
        $contentTypes = $this->getContentTypes(Localization::getWebsite()->id);
        if ($this->isWebsiteFiltered() && empty($contentTypes)) {
            return null;
        }

        $validation = array(
            "rules" => array(),
            "values" => array(),
            "labels" => array()
        );

        $fieldName = $this->getName();
        $value = request()->input($fieldName);

        if ($this->isMultiLanguage()) {
            foreach ($value as $lang => $langValue) {
                $value[$lang] = json_decode($langValue, true);
            }

            if ($copyAllLang) {
                $defaultLocale = Localization::getDefaultLocale();
                $allLanguages = Localization::getAllLanguages();
                if (!array_key_exists($defaultLocale, $value)) {
                    throw new InvalidConfigurationException("The current website doesn't have the backoffice default language");
                }

                foreach ($allLanguages as $languages) {
                    $locale = $languages->id;
                    if ($locale == $defaultLocale) {
                        continue;
                    }

                    if (empty($value[$locale])) {
                        $value[$locale] = $value[$defaultLocale];
                    }
                }
            }
        } else {
            $value = json_decode($value, true);
        }

        $rules = $this->getValidation();

        if (!empty($rules)) {
            $validation["rules"][$fieldName] = $this->getValidation();
            $validation["labels"][$fieldName] = $this->getLabel();
        }
        $validation["values"][$fieldName] = $value;

        return $validation;
    }

    public function save($model, $values)
    {
        if ($this->isWebsiteFiltered()) {
            return $this->_saveWebsite($model, $values);
        } else {
            return $this->_saveNoWebsite($model, $values);
        }
    }

    protected function _saveNoWebsite($model, $values)
    {
        if ($this->isMultiLanguage()) {
            $this->_saveMultipleLanguages($model, $values);
        } else {
            $this->_saveSingleLanguage($model, $values);
        }
    }

    protected function _saveSingleLanguage($model, $values)
    {
        $relationName = $this->getName();
        $relation = $model->$relationName();

        $ids = array_column($values, 'id');

        if ($this->isOrderable()) {
            $syncData = array();
            $position = 1;
            foreach ($ids as $id) {
                $syncData[$id] = array("position" => $position);
                $position = $position + 1;
            }
            $model->$relationName()->sync($syncData);
        } else {
            $model->$relationName()->sync($ids);
        }
    }

    protected function _saveMultipleLanguages($model, $values)
    {
        $relationName = $this->getName();
        $relation = $model->$relationName();

        $languages = Localization::getSupportedLanguages();
        foreach ($languages as $language) {
            $langValues = $values[$language->id];

            $ids = array_column($langValues, 'id');

            $syncData = array();
            $position = 1;

            foreach ($ids as $id) {
                $syncData[$id] = array("language_id" => $language->id);
                $syncData[$id]["position"] = $position;
                $position = $position + 1;
            }

            $model->$relationName()->wherePivot("language_id", $language->id)->sync($syncData);
        }
    }

    protected function _saveWebsite($model, $values)
    {
        if (empty($this->objectTypes)) {
            return;
        }

        $type = $this->getName();
        $websiteObject = $model->getActiveWebsiteEntry();
        $relation = $websiteObject->related();

        $ids = array_column($values, 'id');

        if ($this->isOrderable()) {
            $syncData = array();
            $position = 1;
            foreach ($ids as $id) {
                $syncData[$id] = array("position" => $position, "type" => $type);
                $position = $position + 1;
            }
            $relation->wherePivot("type", $type)->sync($syncData);
        } else {
            $syncData = array();
            foreach ($ids as $id) {
                $syncData[$id] = array("type" => $type);
            }
            $relation->wherePivot("type", $type)->sync($syncData);
        }
    }

    public function toArray()
    {
        $array = parent::toArray();
        $array["maxSize"] = $this->maxSize;
        $array["minSize"] = $this->minSize;
        return $array;
    }

    public static function getScript()
    {
        return array(
            "multiplePicker" =>
                path_asset('path.backoffice.js_path').'/view/backend-multiple-picker.js',
            path_asset('path.backoffice.js_path').'/view/backend-multiple-picker-search.js'
        );

    }

    public function duplicate($sourceModel, $model)
    {
        $relationName = $this->getName();

        if ($this->isWebsiteFiltered()) {
            // If is website filtered, this is a related field, it will be copied after website object creation

            /*
            if (empty($this->objectTypes)) {
                return;
            }

            $type = $this->getName();
            $websiteObject = $sourceModel->getActiveWebsiteEntry();
            $relation = $websiteObject->related();

            $ids = Arr::flatten($values);

            if ($this->isOrderable()) {
                $syncData = array();
                $position = 1;
                foreach ($ids as $id) {
                    $syncData[$id] = array("position" => $position, "type" => $type);
                    $position = $position + 1;
                }
                $relation->wherePivot("type", $type)->sync($syncData);
            } else {
                $syncData = array();
                foreach ($ids as $id) {
                    $syncData[$id] = array("type" => $type);
                }
                $relation->wherePivot("type", $type)->sync($syncData);
            }
            */
        } else {
            $syncData = [];
            $contentType = null;

            $itemsRel = $sourceModel->$relationName();
            $relatedKey = $itemsRel->getRelatedPivotKeyName();

            if ($this->isOrderable()) {
                $pivotTable = $itemsRel->getTable();

                if ($pivotTable) {
                    $items = $itemsRel->orderBy("{$pivotTable}.position");
                } else {
                    $items = $itemsRel->orderBy('position');
                }
            }

            $items = $itemsRel->get();
            $baseItems = [];
            $contentType = ContentTypes::find($this->objectTypes[0]);

            foreach ($items as $item) {
                if (key_exists($item->id, $baseItems)) {
                    continue;
                }
                if ($this->duplicateRelated) {
                    $baseItems[$item->id] = ContentType::duplicateContent($contentType, $item);
                } else {
                    $baseItems[$item->id] = $item;
                }
            }

            if ($this->isMultiLanguage()) {
                $langs = Localization::getAllLanguages();

                foreach ($langs as $lang) {
                    if ($this->isOrderable()) {
                        $langItems = $sourceModel->$relationName()->where("language_id", $lang->id)->orderBy('position')->get();
                    } else {
                        $langItems = $sourceModel->$relationName()->where("language_id", $lang->id)->get();
                    }

                    foreach ($langItems as $i => $item) {
                        $itemId = $baseItems[$item->id]->id;
                        if ($this->isOrderable()) {
                            $syncData[] = [$relatedKey => $itemId, 'position' => $i, 'language_id' => $lang->id];
                        } else {
                            $syncData[] = [$relatedKey => $itemId, 'language_id' => $lang->id];
                        }
                    }
                }
            } else {
                foreach ($itemsRel->get() as $i => $item) {
                    if ($this->isOrderable()) {
                        $syncData[$baseItems[$item->id]->id] = ['position' => $i];
                    } else {
                        $syncData[$baseItems[$item->id]->id] = [];
                    }
                }
            }

            $model->$relationName()->sync($syncData);
        }
    }
}


