<?php

namespace App\Libs\Backoffice\Generic;

use App\Libs\Backoffice\Generic\Actions\BaseAction;
use App\Libs\Backoffice\Generic\Fields\CheckBoxField;
use App\Libs\Backoffice\Generic\Fields\DatePickerField;
use App\Libs\Backoffice\Generic\Fields\SelectBoxField;
use App\Libs\Backoffice\Generic\Fields\TextBoxField;
use App\Libs\Contracts\Backoffice\DuplicatableField;
use App\Libs\Contracts\Backoffice\ExternalField;
use App\Libs\Contracts\Backoffice\ListableField;
use App\Libs\Contracts\Backoffice\SearchableField;
use App\Libs\Contracts\Backoffice\VersionableField;
use App\Libs\Exception\InvalidConfigurationException;
use App\Libs\Exception\InvalidPermissionException;
use App\Libs\Facades\Localization;
use App\Libs\Facades\RequestContext;
use App\Rules\PublishOn;
use ContentVersion;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Website;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use WebsiteObject;
use WebsiteObjectLanguage;
use Exception;

class ContentType implements Arrayable, Jsonable
{
    const SHAREABLE_NONE = "none";
    const SHAREABLE_SHARE = "share";
    const SHAREABLE_GLOBAL = "global";

    protected $type;
    protected $group;
    protected $modelType;
    protected $label = "";
    protected $labelSingular = "";
    protected $template = false;
    protected $templates = false;
    protected $hierarchic = false;
    protected $order = false;
    protected $showSlug = false;
    protected $showUri = false;
    protected $shareable = "share";
    protected $websites = array();
    protected $fields;
    protected $actions;
    protected $baseUri = false;
    protected $parent = false;
    protected $search = false;
    protected $typeFilterField = false;
    protected $showId = false;
    protected $filters;
    protected $subTypes = null;
    protected $defaultSort = null;

    public function __construct($config)
    {

        if (empty($config)) {
            throw new InvalidConfigurationException("You must provide a configuration");
        }

        if (is_string($config)) {
            $config = config("backoffice.generic.{$config}");
        }

        if (empty($config) || !is_array($config)) {
            throw new InvalidConfigurationException("You must provide a configuration");
        }

        $this->_initialize($config);
    }

    protected function _initialize($config)
    {
        if (empty($config["type"])) {
            throw new InvalidConfigurationException("The configuration is missing type");
        }

        if (empty($config["model"]) || !class_exists($config["model"])) {
            throw new InvalidConfigurationException("The configuration is missing a valid model");
        }

        if (empty($config["fields"])) {
            throw new InvalidConfigurationException("The configuration is missing fields");
        }

        $backendUser = Auth::guard('admin')->user();

        if (!empty($backendUser)) {
            if (!$backendUser->hasPermission("backend.{$config["type"]}.view")) {
                throw new InvalidPermissionException("You don't have permissions to view this content type");
            }
        }

        $this->modelType = $config["model"];
        $this->type = $config["type"];
        $this->label = $config["label"];
        $this->labelSingular = $config["label_singular"];

        if (empty($config["group"])) {
            $this->group = trans('backend.content');
        } else {
            $this->group = $config["group"];
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        $this->fields = new FieldCollection();
        $this->actions = new ActionCollection();

        if (!empty($config["actions"]) && !empty($backendUser)) {
            foreach ($config["actions"] as $action) {
                $actionType = ucfirst($action);
                $class = "App\\Libs\\Backoffice\\Generic\\Actions\\{$actionType}Action";
                $class = new $class();
                if (!empty($backendUser)) {
                    $permission = $class->getPermission();
                    if (!empty($permission) && !$backendUser->hasPermission("backend.content.{$this->type}.{$permission}")) {
                        continue;
                    }
                }
                $this->actions->push(new $class());
            }
        }

        foreach ($config["fields"] as $fieldConfig) {
            if (empty($fieldConfig["type"])) {
                throw new InvalidConfigurationException("There are fields without a type in the configuration");
            }
            $fieldType = ucfirst($fieldConfig["type"]);
            $class = "App\\Libs\\Backoffice\\Generic\\Fields\\{$fieldType}Field";
            $this->fields->push(new $class($fieldConfig, $this));
        }
    }

    public function userHasPermission($permission)
    {
        $user = Auth::guard('admin')->user();
        if (empty($user)) {
            return false;
        }

        return $user->hasPermission("backend.content.{$this->getType()}.{$permission}");
    }

    public function getType()
    {
        return $this->type;
    }

    public function setType($type)
    {
        $this->type = $type;
    }

    public function getModelType()
    {
        return $this->modelType;
    }

    public function setModelType($modelType)
    {
        $this->modelType = $modelType;
    }

    public function getLabel()
    {
        return $this->label;
    }

    public function setLabel($label)
    {
        $this->label = $label;
    }

    public function getSingularLabel()
    {
        return $this->labelSingular;
    }

    public function setSingularLabel($labelSingular)
    {
        $this->labelSingular = $labelSingular;
    }

    public function getFields()
    {
        return $this->fields;
    }

    public function isHierarchic()
    {
        return $this->hierarchic;
    }

    public function setHierarchic($hierarchic = false)
    {
        $this->hierarchic = $hierarchic;
    }

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

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

    public function getShowSlug()
    {
        return $this->showSlug;
    }

    public function setShowSlug($showSlug = false)
    {
        $this->showSlug = $showSlug;
    }

    public function getShareable()
    {
        return $this->shareable;
    }

    public function setShareable($shareable)
    {
        $this->shareable = $shareable;
    }

    public function getShowUri()
    {
        return $this->showUri;
    }

    public function getShowId()
    {
        return $this->showId;
    }

    public function setShowUri($showUri = false)
    {
        $this->showUri = $showUri;
    }

    public function hasTemplate()
    {
        return $this->template;
    }

    public function setTemplate($template = false)
    {
        $this->template = $template;
    }

    public function getTemplates($websiteId = false)
    {
        if (empty($this->templates) || empty($websiteId)) {
            return $this->templates;
        }

        if (isset($this->templates[$websiteId])) {
            return $this->templates[$websiteId];
        }
    }

    public function getActions($actionType = null)
    {
        if (empty($actionType)) {
            return $this->actions;
        } else {
            return $this->actions->getActionsByActionType($actionType);
        }
    }

    public function hasActions($actionType = null)
    {
        $actions = $this->getActions($actionType);
        return !$actions->isEmpty();
    }

    public function getWebsites()
    {
        return $this->websites;
    }

    public function setWebsites($websites = array())
    {
        $this->websites = $websites;
    }

    public function getFilters()
    {
        return $this->filters;
    }

    public function getSubTypes()
    {
        return $this->subTypes;
    }

    public function setFilters($filters)
    {
        $this->filters = $filters;
    }

    public function hasWebsite($websiteId)
    {
        if (empty($this->websites)) {
            return true;
        }

        return in_array($websiteId, $this->websites);
    }

    public function getGroup()
    {
        return $this->group;
    }

    public function setGroup($group)
    {
        if (empty($group)) {
            $this->group = trans('backend.content');
        } else {
            $this->group = $group;
        }
    }

    public function getBaseUri($websiteId = false)
    {
        if ($websiteId === false || empty($this->baseUri)) {
            return $this->baseUri;
        }

        if (!empty($this->baseUri[$websiteId])) {
            return $this->baseUri[$websiteId];
        } else {
            return false;
        }
    }

    public function setBaseUri($baseUri)
    {
        $this->baseUri = $baseUri;
    }

    public function getDefaultSort()
    {
        return $this->defaultSort;
    }

    public function getParent()
    {
        return $this->parent;
    }

    public function setParent($parent)
    {
        $this->parent = $parent;
    }

    public function getTypeFilterField()
    {
        return $this->typeFilterField;
    }

    public function setTypeFilterField($typeFilterField)
    {
        $this->typeFilterField = $typeFilterField;
    }

    public function isWebsiteSearchable($websiteId = false)
    {
        if (empty($this->search)) {
            return false;
        }
        if (!$websiteId) {
            return true;
        }
        return array_key_exists($websiteId, $this->search);
    }

    public function getSearchParentRelationships($websiteId = false) {
        $search = $this->getSearch($websiteId);
        return array_key_exists('parentRelationships', $search) ? $search['parentRelationships'] : null;
    }

    public function getSearchRelations($websiteId) {
        $search = $this->getSearch($websiteId);
        return array_key_exists('relations', $search) ? $search['relations'] : null;
    }

    public function getSearchIncludes($websiteId) {
        $search = $this->getSearch($websiteId);
        return array_key_exists('include', $search) ? $search['include'] : null;
    }

    public function getSearchDisplay($websiteId) {
        $search = $this->getSearch($websiteId);
        return array_key_exists('display', $search) ? $search['display'] : null;
    }

    public function getSearchFilter($websiteId) {
        $search = $this->getSearch($websiteId);
        return array_key_exists('filter', $search) ? $search['filter'] : null;
    }

    public function getSearchFields($websiteId) {
        $search = $this->getSearch($websiteId);
        return [
            'primary' => array_key_exists('primary', $search) ? $search['primary'] : [],
            'secondary' => array_key_exists('secondary', $search) ? $search['secondary'] : []
        ];
    }

    public function getSearchLists($websiteId) {
        $search = $this->getSearch($websiteId);
        return array_key_exists('lists', $search) ? $search['lists'] : null;
    }

    public function toList($page = null, $itemsPerPage = null, $sort = null, $searchPhrase = null, $subtype = null)
    {

        if ($this->isHierarchic()) {
            if (empty($sort) && empty($searchPhrase)) {
                return $this->_toHierarchicList($page, $itemsPerPage, $subtype);
            }
        }

        return $this->_toFlatList($page, $itemsPerPage, $sort, $searchPhrase, $subtype);
    }

    public function toStructuredList()
    {
        $items = $this->_toHierarchicList(null, null, null);
        $list = array();

        foreach ($items as $item) {
            $list[$item->id] = $item;
        }
        return $this->_structureList($list);
    }

    private function _structureList($listItems, $root = null)
    {
        $return = array();
        foreach ($listItems as $id => $item) {
            if ($item->getActiveWebsiteEntry()->parent_id == $root) {
                unset($listItems[$id]);
                $item->children = $this->_structureList($listItems, $item->getActiveWebsiteEntry()->id);
                $return[] = $item;
            }
        }
        return $return;
    }

    protected function _toFlatList($page, $itemsPerPage, $sort, $searchPhrase, $subtype)
    {
        Paginator::currentPageResolver(function () use ($page) {
            return $page;
        });

        $fields = $this->fields->getListableFields();
        $hasLanguage = !$fields->getMultiLanguageFields()->isEmpty();

        $sortColumn = null;
        $orderField = null;
        $sortOrder = null;


        if (empty($sort) && !$this->defaultSort && $this->isOrderable() && !$this->isHierarchic()) {
            $sort = array("position" => "asc");
        } elseif (empty($sort) && $this->defaultSort) {
            $sort = $this->defaultSort;
        } elseif (empty($sort)) {
            $sort = ['id' => 'DESC'];
        }

        $sortColumn = head(array_keys($sort));
        $sortOrder = $sort[$sortColumn];
        $sortColumn = str_replace("relation.", "", $sortColumn);

        if (!in_array($sortColumn, ["slug", "uri", "status", "is_shared", "position", "id", 'created_at', 'updated_at'])) {
            $orderField = $fields->find($sortColumn);

            if (empty($orderField)) {
                throw new InvalidConfigurationException("The order field is not part of this content type list fields");
            }
        }

        $modelType = $this->modelType;

        $queryBuilder = $modelType::query();
        $model = $queryBuilder->getModel();

        $websiteEntryRelationship = $model->websites();
        $websiteEntryModel = $websiteEntryRelationship->getRelated();
        $websiteEntryFK = $websiteEntryRelationship->getQualifiedForeignKeyName();

        $queryBuilder->join($websiteEntryModel->getTable(),
            function ($join) use ($model, $websiteEntryFK, $websiteEntryRelationship, $modelType) {
                $join->on($model->getQualifiedKeyName(), "=", $websiteEntryFK)
                    ->where($websiteEntryRelationship->getMorphType(), "=", $modelType);
            });

        $queryBuilder->where("{$websiteEntryModel->getTable()}.website_id", "=", Localization::getWebsite()->id);

        if ($hasLanguage) {
            $languageRelationship = $model->languages();
            $languageModel = $languageRelationship->getRelated();
            $languageFK = $languageRelationship->getQualifiedForeignKeyName();
            $queryBuilder->join($languageModel->getTable(), $model->getQualifiedKeyName(), "=", $languageFK);
            $queryBuilder->where("{$languageModel->getTable()}.language_id", "=", Localization::getActiveLocale());
        }
        if (!empty($sortColumn)) {
            if (in_array($sortColumn, ['id', 'created_at', 'updated_at'])) {
                $modelTable = $model->getTable();
                $queryBuilder->orderBy("$modelTable.$sortColumn", $sortOrder);
            } else if (!empty($orderField)) {
                $orderField->buildOrder($queryBuilder, $sortOrder);
            } else {
                $queryBuilder->orderBy("{$websiteEntryModel->getTable()}.{$sortColumn}", $sortOrder);
            }
        }

        $queryBuilder->addSelect($model->getQualifiedKeyName());

        $queryBuilder->addSelect($websiteEntryModel->getTable().".status");
        $queryBuilder->addSelect($websiteEntryModel->getTable().".is_shared");

        if ($this->getShowUri()) {
            $queryBuilder->addSelect($websiteEntryModel->getTable().".uri");
        }

        if ($this->getShowSlug()) {
            $queryBuilder->addSelect($websiteEntryModel->getTable().".slug");
        }

        foreach ($fields as $field) {
            if ($field instanceof ListableField) {
                $field->buildSelect($queryBuilder);
            }
        }
        if (!empty($searchPhrase)) {
            $queryBuilder->where(function ($query) use ($fields, $searchPhrase) {
                foreach ($fields as $field) {
                    if ($field instanceof ListableField) {
                        $field->buildWhere($query, $searchPhrase);
                    }
                }
            });

            if (is_numeric($searchPhrase)) {
                $table = $queryBuilder->getModel()->getTable();
                $langTable = $queryBuilder->getModel()->languages()->getRelated()->getTable();
                $queryBuilder->orWhere("{$table}.id", $searchPhrase * 1);
                $queryBuilder->where("$langTable.language_id", Localization::getActiveLocale());
            }
        }

        if (!empty($subtype)) {
            $queryBuilder->where('website_objects.sub_type', $subtype);
        }

        if (empty($page) || empty($itemsPerPage)) {
            $rows = $queryBuilder->get();

            return $rows;
        } else {
            $paginator = $queryBuilder->paginate($itemsPerPage);

            return array(
                "total" => $paginator->total(),
                "current" => $paginator->currentPage(),
                "rowCount" => $itemsPerPage,
                "searchPhrase" => $searchPhrase,
                "sort" => $sort,
                "rows" => $paginator->getCollection()
            );
        }
    }

    protected function _toHierarchicList($page, $itemsPerPage, $subtype)
    {

        $fields = $this->fields->getListableFields();

        if (!$this->isOrderable()) {
            $hasLanguage = true;
        } else {
            $hasLanguage = !$fields->getMultiLanguageFields()->isEmpty();
        }

        $modelType = $this->modelType;

        $queryBuilder = $modelType::query();
        $model = $queryBuilder->getModel();

        $websiteEntryRelationship = $model->websites();


        $websiteEntryModel = $websiteEntryRelationship->getRelated();
        $websiteEntryFK = $websiteEntryRelationship->getQualifiedForeignKeyName();

        $queryBuilder->join($websiteEntryModel->getTable(),
            function ($join) use ($model, $websiteEntryFK, $websiteEntryRelationship, $modelType) {
                $join->on($model->getQualifiedKeyName(), "=", $websiteEntryFK)
                    ->where($websiteEntryRelationship->getMorphType(), "=", $modelType);
            });

        $queryBuilder->where("{$websiteEntryModel->getTable()}.website_id", "=", Localization::getWebsite()->id);

        if ($hasLanguage) {
            $languageRelationship = $model->languages();
            $languageModel = $languageRelationship->getRelated();
            $languageFK = $languageRelationship->getQualifiedForeignKeyName();
            $queryBuilder->join($languageModel->getTable(), $model->getQualifiedKeyName(), "=", $languageFK);
            $queryBuilder->where("{$languageModel->getTable()}.language_id", "=", Localization::getActiveLocale());
        }

        $queryBuilder->orderBy("{$websiteEntryModel->getTable()}.parent_id", "asc");

        if ($this->isOrderable()) {
            $queryBuilder->orderBy("{$websiteEntryModel->getTable()}.position", "asc");
        } else {
            $queryBuilder->orderBy("{$languageModel->getTable()}.title", "asc");
        }

        $queryBuilder->addSelect($model->getQualifiedKeyName());
        $queryBuilder->addSelect($websiteEntryModel->getTable().".parent_id");
        $queryBuilder->addSelect($websiteEntryModel->getTable().".template");
        $queryBuilder->addSelect($websiteEntryModel->getTable().".status");
        $queryBuilder->addSelect($websiteEntryModel->getTable().".is_shared");

        if ($this->getShowUri()) {
            $queryBuilder->addSelect($websiteEntryModel->getTable().".uri");
        }

        if ($this->getShowSlug()) {
            $queryBuilder->addSelect($websiteEntryModel->getTable().".slug");
        }

        if (!empty($subtype)) {
            $queryBuilder->where('website_objects.sub_type', $subtype);
        }

        foreach ($fields as $field) {
            if ($field instanceof ListableField) {
                $field->buildSelect($queryBuilder);
            }
        }

        $rows = $queryBuilder->get();

        $rows = new EloquentCollection($this->_buildHierarchicList($rows));

        if (empty($page) || empty($itemsPerPage)) {
            return $rows;
        } else {
            $offset = ($page - 1) * $itemsPerPage;

            return array(
                "total" => $rows->count(),
                "current" => (int) $page,
                "rowCount" => (int) $itemsPerPage,
                "rows" => $rows->slice($offset, $itemsPerPage)->values()
            );
        }

    }

    protected function _buildHierarchicList(
        EloquentCollection $oriItems,
        array $outItems = array(),
        $parent_id = null,
        $depth = 0
    ) {
        foreach ($oriItems as $item) {
            if ($item->getActiveWebsiteEntry()->parent_id == $parent_id) {
                $titlePrefix = "";
                for ($i = 0; $i < $depth; $i++) {
                    $titlePrefix .= "— ";
                }
                $item->clean_title = $item->title;
                $item->title = $titlePrefix.$item->title;
                array_push($outItems, $item);

                $outItems = $this->_buildHierarchicList($oriItems, $outItems, $item->getActiveWebsiteEntry()->id,
                    $depth + 1);

            } elseif ($item->getActiveWebsiteEntry()->parent_id > $parent_id) {
                break;
            }
        }

        return $outItems;
    }

    public function hasFormFields()
    {
        $fields = $this->fields->getMainFormFields();
        return !$fields->isEmpty();
    }

    public function toForm($model, $placement = "main")
    {
        $website = Localization::getWebsite();
        $fields = $this->fields->getFieldsByPlacement($placement);
        $form = "";
        foreach ($fields as $field) {
            if ($field instanceof ExternalField) {
                continue;
            }
            $parent = $field->getParent();
            $hidden = false;
            if (!empty($parent)) {
                $value = $this->getFieldValue($parent["name"], $model);
                if (!in_array($value, $parent["values"])) {
                    $hidden = true;
                }
            }

            if (!$field->websiteVisible($website->id)) {
                $hidden = true;
            }

            $form .= $field->toForm($model, $this->userHasPermission("create"), $hidden);
        }
        return $form;
    }

    public function toExternalForm($model, $placement = "main")
    {
        $fields = $this->fields->getExternalFields($placement);
        $form = "";
        foreach ($fields as $field) {
            $parent = $field->getParent();
            $hidden = false;
            if (!empty($parent)) {
                $value = $this->getFieldValue($parent["name"], $model);
                if (!in_array($value, $parent["values"])) {
                    $hidden = true;
                }
            }
            $form .= $field->toForm($model, $this->userHasPermission("create"), $hidden);
        }
        return $form;
    }

    public function getFieldValue($fieldName, $model)
    {
        $field = $this->fields->find($fieldName);
        return $field->getCurrentValue($model);
    }

    public function toVersionControl($model)
    {
        if (!$model->exists) {
            return "";
        }

        $versions = ContentVersion::with(array(
            'backendUser' => function ($query) {
                $query->withTrashed();
            }
        ))
            ->where('object_type', $this->modelType)
            ->where('object_id', $model->id)
            ->orderBy('created_at', 'desc')
            ->limit(20)
            ->get();

        if ($versions->isEmpty()) {
            return "";
        }

        $html = '<div class="version-control"><label>Content History</label>
            <a href="#" class="show-versions">show</a>
            <a href="#" class="hide-versions">hide</a>
            <div class="revisions">';

        foreach ($versions as $idx => $version) {
            if ($idx == 0) {
                $html .= '<div class="current-version">';
                $html .= $version->created_at.' - '.(isset($version->backendUser) ? $version->backendUser->name : 'unknown');
                $html .= ' <span>(latest)</span></div>';
            } else {
                $html .= '<a href="#" class="version" data-version_id="'.$version->id.'">';
                $html .= $version->created_at.' - '.(isset($version->backendUser) ? $version->backendUser->name : 'unknown');
                $html .= '</a><br>';
            }
        }

        $html .= '</div></div>';

        return $html;
    }

    public function toWebsiteForm($model)
    {
        $form = "";
        $website = Localization::getWebsite();
        $slug = new TextBoxField(array(
            "name" => 'lang_slug',
            'label' => 'slug',
            'multiLanguage' => true,
            'woMultilang' => true,
            "blocksMode" => "also",
        ));
        $slugInput = request()->old("slug");
        $hidden = false;
        if (!$model->exists && empty($slugInput)) {
            $hidden = true;
        }

        $form .= $slug->toForm($model, $this->userHasPermission("create"), $hidden);
        $status = new SelectBoxField(array(
            "name" => 'status',
            'label' => 'status',
            "list" => array(
                "draft" => "draft",
                "published" => "published"
            ),
            "defaultValue" => "draft",
            "blocksMode" => "also",
        ));

        if ($this->userHasPermission("create") &&
            $this->getShareable() != self::SHAREABLE_GLOBAL) {

            $hidden = false;
        } else {
            $hidden = true;
        }

        $form .= $status->toForm($model, true, $hidden);

        $hideScheduler = empty($this->getBaseUri()) && !$this->hasTemplate();

        $mainPublishDateField = new DatePickerField([
            'name'  => 'publish_on',
            'label' => 'Publish on',
            'inputType' => 'datetime',
            'info' => 'Date and time to switch state to published (if not already)',
            "blocksMode" => "also",
            'woMultilang' => true,
            'convertToLocalTimezone' => true
        ]);

        $form .= $mainPublishDateField->toForm($model, true, $hidden || $hideScheduler);

        $translationStatus = new SelectBoxField(array(
            "name" => 'lang_status',
            'label' => 'Translation Status',
            "list" => array(
                "draft" => "draft",
                "published" => "published"
            ),
            "defaultValueDefaultLanguage" => "draft",
            "defaultValue" => "draft",
            'multiLanguage' => true,
            'woMultilang' => true,
            "blocksMode" => "also"
        ));

        if ($this->userHasPermission("create") &&
            $this->getShareable() != self::SHAREABLE_GLOBAL) {
            $hidden = false;
        } else {
            $hidden = true;
        }

        $form .= $translationStatus->toForm($model, true, $hidden);

        $translationsPublishDateField = new DatePickerField([
            'name'  => 'lang_publish_on',
            'label' => 'Publish translation on',
            'inputType' => 'datetime',
            'info' => 'Date and time to switch translation state to published (if not already)',
            'multiLanguage' => true,
            "blocksMode" => "also",
            'woMultilang' => true,
            'convertToLocalTimezone' => true
        ]);

        $form .= $translationsPublishDateField->toForm($model, true, $hidden || $hideScheduler);

        if (!empty($this->subTypes)) {
            $options = [];
            foreach ($this->subTypes as $subtype => $data) {
                $options[$subtype] = $data['label'];
            }

            $subtypeField = new SelectBoxField(array(
                "name" => 'sub_type',
                'label' => 'Sub Type',
                "list" => $options,
                "defaultValue" => array_key_first($options),
                "blocksMode" => "also",
            ));
            $form .= $subtypeField->toForm($model, true, $hidden);
        }

        if ($this->template) {
            $templatesSelectList = array();
            $templates = array();

            if (!empty($this->templates) && isset($this->templates[$website->id])) {
                $templates = $this->templates[$website->id];
            }

            foreach ($templates as $template) {
                $templatesSelectList[$template['id']] = $template['name'];
            }

            $defaultValue = "";
            if (!empty($templates)) {
                $defaultValue = head($templates)['id'];
            }

            $templateSelect = new SelectBoxField(array(
                "name" => "template",
                "label" => "template",
                "list" => $templatesSelectList,
                "defaultValue" => $defaultValue,
                "blocksMode" => "also",
            ));
            $form .= $templateSelect->toForm($model, $this->userHasPermission("create"));

        }

        if ($this->getShareable() == self::SHAREABLE_SHARE) {
            $contentWebsites = $this->getWebsites();
            if (empty($contentWebsites)) {
                $websitesCount = Website::count();
            } elseif (count($contentWebsites) == 1) {
                $websitesCount = 1;
            } else {
                $websitesCount = count($contentWebsites);
            }

            if ($websitesCount > 1) {
                if (empty($model->websites) || $model->websites->count() < $websitesCount) {
                    if ($this->userHasPermission("create")) {
                        $availableWebsites = new CheckBoxField(array(
                            "name" => 'website_available', 'label' => 'Available to all websites?'
                        ));
                        $form .= $availableWebsites->toForm($model);
                    }
                }
            }
        }

        if ($model->exists && ($this->baseUri && isset($this->baseUri[$website->id]) || $this->hasTemplate())) {
            $languages = Localization::getSupportedLanguages();
            foreach ($languages as $language) {
                $style = $language->id == Localization::getDefaultLocale() ? ' style="display: inline;" ' : '';
                $form .= '<button class="share-link btn btn-secondary form-language-group form-language-' . $language->id
                    . '" ' . $style . ' data-action="share-link">Share Link (' .  $language->id . ')</button>';
            }


            $form .= '<a href="#" data-uri="'.$model->uri
                .'" target="_blank" class="preview-page btn btn-primary pull-right">Preview</a><div class="clearfix"></div>';
        }

        return $form;
    }

    public function toAddOnInformation($model)
    {
        if ($model->exists && $this->actions->getActionByType('info')) {
            return '<a href="/admin/content/type-info/' . $this->type . '/' . $model->id
                . '" class="btn content-type-more-info btn-xs btn-default" target="_blank">' . $this->type . ' Information</a>';
        }

        return '';
    }

    public function getValidation($copyDefaultLang = false)
    {
        $rules = array();
        $values = array();
        $labels = array();
        foreach ($this->fields as $field) {
            $validation = $field->getValidationData($copyDefaultLang);

            if (is_null($validation)) {
                continue;
            }

            $rules = array_merge($rules, $validation["rules"]);
            $values = array_merge($values, $validation["values"]);
            $labels = array_merge($labels, $validation["labels"]);
        }
        $languages = Localization::getSupportedLanguages();
        foreach ($languages as $lang) {
            $rules["lang_slug.{$lang->id}"] = "required";
        }
        $rules['status'] = "required|in:draft,published";
        $rules["publish_on"] = ["date", "nullable", "after:now", new PublishOn()];
        foreach ($languages as $lang) {
            $rules["lang_status.{$lang->id}"] = "required";
            $rules["lang_publish_on.{$lang->id}"] = ["date", "nullable", "after:now", new PublishOn($lang->id)];;
        }
        if (!empty($this->subTypes)) {
            $rules['sub_type'] = "required|in:" . implode(',', array_keys($this->subTypes));
        }

        if ($this->template) {
            $website = Localization::getWebsite();
            $templates = $this->getTemplates($website->id);
            $availTemplates = implode(',', array_keys($templates));
            $rules['template'] = "required|in:".$availTemplates;
        }
        $values["lang_slug"] = request()->input("lang_slug");
        $values["status"] = request()->input("status");
        $values["publish_on"] = request()->input("publish_on");
        $values["lang_status"] = request()->input("lang_status");
        $values["lang_publish_on"] = request()->input("lang_publish_on");
        $values["sub_type"] = request()->input("sub_type");
        $values["template"] = request()->input("template");
        $values["website_available"] = request()->input("website_available");
        $labels["slug"] = "slug";
        $labels["status"] = "status";
        $labels["lang_slug"] = "translation status";
        $labels["sub_type"] = "sub type";
        $labels["template"] = "template";

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

    public function getErrorLocales()
    {
        $errorLocales = array();
        $errors = RequestContext::get('errors');
        if (empty($errors)) {
            return $errorLocales;
        }

        $languages = Localization::getSupportedLanguages();

        foreach ($languages as $language) {
            $errorLanguage = Arr::where($errors->toArray(), function ($value, $key) use ($language) {
                return Str::endsWith($key, ".".$language->id) || Str::contains($key, ".{$language->id}.");
            });

            if (!empty($errorLanguage)) {
                $errorLocales[] = $language->id;
            }
        }

        return $errorLocales;

    }

    /*
    public function getScripts(){
        $scripts = array(
            'genericForm' => path_asset('path.backoffice.js_path') . '/view/backend-generic-form.js',
            'langPicker' => path_asset('path.backoffice.js_path') . '/view/backend-lang-tab-picker.js'
        );

        foreach($this->fields as $field){
            $script = $field->getScript();
            if(!is_null($script)){
                $scripts = array_merge($scripts, $script);
            }
        }

        return $scripts;
    }
    */

    public function hasExternalFields()
    {
        $externalFields = $this->fields->getExternalFields();
        if ($externalFields->isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    public function getSearch($websiteId = false)
    {
        if (!$websiteId) {
            return $this->search;
        }
        if (!is_array($this->search)) {
            return null;
        }
        return $this->search[$websiteId];
    }

    public function getSearchStructure($model)
    {
        if (!$this->isWebsiteSearchable() || $this->type == 'page' && $model->no_index) {
            return array();
        }

        $search = array();

        foreach ($this->search as $websiteId => $searchData) {
            $search[$websiteId] = array();

            $searchLevels = array("primary", "secondary");

            foreach ($searchLevels as $level) {
                if (!empty($searchData[$level])) {
                    $searchFields = $this->fields->getFieldsByNames($searchData[$level]);
                    foreach ($searchFields as $field) {

                        if (!($field instanceof SearchableField)) {
                            continue;
                        }
                        $fieldSearch = $field->getSearchStructure($model);

                        if (empty($fieldSearch)) {
                            continue;
                        }

                        foreach ($fieldSearch as $languageKey => $searchValue) {
                            if (!isset($search[$websiteId][$languageKey])) {
                                $search[$websiteId][$languageKey] = array();
                                $search[$websiteId][$languageKey]["content_type"] = $this->getType();
                                $search[$websiteId][$languageKey]["group_label"] = trans("backend.{$this->getType()}_plural");
                            }

                            if (!isset($search[$websiteId][$languageKey]["{$level}_search"])) {
                                $search[$websiteId][$languageKey]["{$level}_search"] = "";
                            }

                            $search[$websiteId][$languageKey]["{$level}_search"] = $search[$websiteId][$languageKey]["{$level}_search"]." ".$searchValue;
                        }
                    }
                }
            }

            if (!empty($searchData["display"])) {
                foreach ($searchData["display"] as $displayType => $fieldName) {
                    $field = $this->fields->find($fieldName);
                    if (empty($field) || !($field instanceof SearchableField)) {
                        continue;
                    }

                    $fieldSearch = $field->getSearchStructure($model, true);
                    if (empty($fieldSearch)) {
                        continue;
                    }

                    foreach ($fieldSearch as $languageKey => $searchValue) {
                        if (!isset($search[$websiteId][$languageKey])) {
                            $search[$websiteId][$languageKey] = array();
                            $search[$websiteId][$languageKey]["content_type"] = $this->getType();
                            $search[$websiteId][$languageKey]["group_label"] = trans("backend.{$this->getType()}_plural");
                        }

                        $search[$websiteId][$languageKey][$displayType] = $searchValue;
                    }
                }
            }


            if (empty($searchData["uri"])) {
                $websiteObject = $model->websites->findByWebsite($websiteId);

                if (!empty($websiteObject)) {
                    foreach ($search[$websiteId] as $languageKey => $searchValue) {
                        $woLang = $websiteObject->languages->findByLocale($languageKey);
                        if ($woLang && $woLang->lang_uri) {
                            $search[$websiteId][$languageKey]["uri"] = $woLang->lang_uri;
                        }
                    }
                }
            } else {
                foreach ($search[$websiteId] as $languageKey => $searchValue) {
                    if (is_array($searchData["uri"])) {
                        $searchUri = $searchData["uri"][$languageKey];
                    } else {
                        $searchUri = $searchData["uri"];
                    }

                    if (Str::contains($searchUri, "{type}") && isset($model->type)) {
                        if (isset($this->baseUri[$websiteId])) {
                            $baseUri = $this->baseUri[$websiteId][$model->type];
                            if (is_array($baseUri)) {
                                $searchUri = str_replace("{type}", $baseUri[$languageKey], $searchUri);
                            } else {
                                $searchUri = str_replace("{type}", $baseUri, $searchUri);
                            }
                        } else {
                            $searchUri = str_replace("{type}", $model->type, $searchUri);
                        }
                    }

                    if (Str::contains($searchUri, "{slug}")) {
                        $websiteObject = $model->websites->findByWebsite($websiteId);

                        if (!empty($websiteObject)) {
                            $woLang = $websiteObject->languages->findByLocale($languageKey);
                            $search[$websiteId][$languageKey]["uri"] = str_replace("{slug}", $woLang->lang_slug,
                                $searchUri);
                        }
                    } else {
                        $search[$websiteId][$languageKey]["uri"] = $searchUri;
                    }
                }
            }
        }
        return $search;
    }

    public function autoComplete($searchTerm)
    {
        $modelType = $this->modelType;
        $model = new $modelType();
        $query = $model->full()->where("title", 'like', "%".$searchTerm."%")
            ->select("id", "title", "id as object_id")
            ->orderBy("title", "asc")
            ->limit(10);
        $filters = $this->getFilters();

        foreach ($this->getFilters() as $filter) {
            if (is_null($filter["value"])) {
                $query->whereNull($filter["field"]);
            } else {
                $query->where($filter["field"], $filter["value"]);
            }
        }
        return $query->get();
    }

    public function toArray($onlyListable = false)
    {
        $website = Localization::getWebsite();
        $array = array(
            'type' => $this->getType(),
            'label' => $this->getLabel(),
            'labelSingular' => $this->getSingularLabel(),
            'hierarchic' => $this->isHierarchic(),
            'order' => $this->isOrderable(),
            'websites' => $this->getWebsites(),
            'templates' => $this->getTemplates($website->id),
            'actions' => array(
                "grid" => $this->actions->getActionsByActionType(BaseAction::TYPE_GRID)->toArray(),
                "row" => $this->actions->getActionsByActionType(BaseAction::TYPE_ROW)->toArray()
            ),
            'subTypes' => $this->subTypes,
            'fields' => array()
        );


        if ($onlyListable) {
            $fields = $this->fields->getListableFields();
        } else {
            $fields = $this->fields;
        }

        $array["fields"] = $fields->toArray();
        return $array;

    }

    public function toJson($options = 0)
    {
        return json_encode($this->toArray(), $options);
    }

    public function getVersion($model)
    {
        $version = array(
            'object_id' => $model->id,
            'object_type' => $this->modelType,
            'content_type' => $this->type,
            'fields' => "",
            'lang_fields' => ""
        );
        $fields = array();
        $langFields = array();
        foreach ($this->fields as $field) {
            if (!($field instanceof VersionableField)) {
                continue;
            }
            if (!$field->isVersionable()) {
                continue;
            }
            $fieldVersion = $field->getVersionStructure($model);
            if ($fieldVersion['multilanguage']) {
                $langFields[$field->getName()] = $fieldVersion['value'];
            } else {
                $fields[$field->getName()] = $fieldVersion['value'];
            }
        }
        $version['fields'] = serialize($fields);
        $version['lang_fields'] = serialize($langFields);
        return $version;
    }

    public function loadVersion($model, $version)
    {
        $fields = unserialize($version->fields);
        $langFields = unserialize($version->lang_fields);

        if (!$version) {
            return null;
        }

        foreach ($this->fields as $field) {
            if (!($field instanceof VersionableField)) {
                continue;
            }
            if (!$field->isVersionable()) {
                continue;
            }

            $field->loadVersion($model, $fields, $langFields);

        }
    }

    public function duplicate($sourceModel, $addInFields = [], $addInFieldsLang = [])
    {
        $modelType = $this->modelType;
        $model = new $modelType();

        $languages = Localization::getSupportedLanguages();

        $languageModels = $model->languages;
        $languageModelType = $model->languages()->getRelated();

        foreach ($languages as $lang) {
            $languageModel = new $languageModelType;
            $languageModel->language_id = $lang->id;
            foreach ($addInFieldsLang as $fieldName => $fieldValue) {
                $languageModel->$fieldName = $fieldValue;
            }
            $languageModels->add($languageModel);
        }

        DB::beginTransaction();

        try {
            foreach ($this->getFields() as $field) {
                if (!($field instanceof DuplicatableField) || $field->isRelationField() || $field instanceof ExternalField) {
                    continue;
                }

                $field->duplicate($sourceModel, $model);
            }
            $backendUser = Auth::guard('admin')->user();
            $model->backend_user_id = $backendUser->id;

            foreach ($addInFields as $fieldName => $value) {
                $model->$fieldName = $value;
            }

            $model->save();
            $model->languages()->saveMany($languageModels->all());

            // Duplicates relation fields
            $relationFields = $this->getFields()->getRelationFields();

            foreach ($relationFields as $field) {
                if ($field instanceof DuplicatableField) {
                    $field->duplicate($sourceModel, $model);
                }
            }

            DB::commit();
        } catch (Exception $e) {
            DB::rollback();
            Log::error($e);
            throw($e);
        }
        return $model;
    }

    public static function duplicateContent($contentType, $sourceModel, $status = 'draft', $addInFields = [], $addInFieldsLang = [], $positionOverride = null)
    {
        $model = $contentType->duplicate($sourceModel, $addInFields, $addInFieldsLang);

        $originalWo = WebsiteObject::find($sourceModel->website_object_id);

        $websiteObject = new WebsiteObject;
        $websiteObject->website_id = Localization::getWebsite()->id;
        $websiteObject->content_type = $contentType->getType();

        if (!$positionOverride && $contentType->isOrderable()) {
            $position = WebsiteObject::maxPosition(
                Localization::getWebsite()->id,
                get_class($model));
            if (empty($position)) {
                $position = 0;
            }
            $websiteObject->position = $position + 1;
        } else if ($positionOverride) {
            $websiteObject->position = $positionOverride;
        }

        $websiteObject->status = $status;
        $websiteObject->template = $originalWo->template;
        $websiteObject->sub_type = $originalWo->sub_type;

        $langs = Localization::getSupportedLanguages();
        foreach ($langs as $lang) {
            $woLang = new WebsiteObjectLanguage();
            $woLang->wo_language_id = $lang->id;
            $woLang->wo_website_id = $websiteObject->website_id;
            $woLang->lang_slug = $websiteObject->slug = UriHelper::generateSlug($originalWo->slug . '-copy', Localization::getWebsite(), $lang->id);

            if ($lang->id == Localization::getDefaultLocale()) {
                $websiteObject->slug = $woLang->lang_slug;
            }
            $websiteObject->languages->add($woLang);
        }
        if ($contentType->hasTemplate() ||
            $contentType->getBaseUri($websiteObject->website_id) != false) {
            UriHelper::updateUri($websiteObject, $model, $contentType, false);
        }
        $websiteObjects = $model->websites;
        $websiteObjects->add($websiteObject);
        $model->websites()->saveMany($websiteObjects->all());
        $websiteObject->languages()->saveMany($websiteObject->languages->all());

        // Copy related website objects
        $syncData = [];
        foreach ($originalWo->related as $relatedObject) {
            $syncData[$relatedObject->website_object_id] = [
                'type' => $relatedObject->pivot->type,
                'position' => $relatedObject->pivot->position
            ];
        }
        $websiteObject->related()->sync($syncData);

        return $model;
    }
}
