<?php

use Acms\Services\Category\CategoryRepository;
use Acms\Services\Facades\Application as App;
use Acms\Services\Facades\Category;

class ACMS_POST_Category_Update extends ACMS_POST
{
    private function repository(): CategoryRepository
    {
        /** @var CategoryRepository $repository */
        $repository = App::make('category.repository');
        return $repository;
    }

    protected function isScopeShared(): bool
    {
        return $this->repository()->hasEntryInOtherBlog((int) CID, BID);
    }

    /**
     * 親候補が自カテゴリー自身でないか（自己参照の検知）
     *
     * @param int $categoryId 自カテゴリー
     * @param int $parentCategoryId 親候補
     * @return bool 自身でなければ true（バリデート OK）
     */
    protected function parentIsNotSelf(int $categoryId, int $parentCategoryId): bool
    {
        return $categoryId !== $parentCategoryId;
    }

    /**
     * 親候補が自カテゴリーの子孫でないか（循環参照の検知）
     *
     * @param int $categoryId 自カテゴリー
     * @param int $parentCategoryId 親候補
     * @return bool 子孫でなければ true（バリデート OK）
     */
    protected function parentIsNotOwnDescendant(int $categoryId, int $parentCategoryId): bool
    {
        if ($parentCategoryId <= 0) {
            return true;
        }
        return !$this->repository()->isDescendantOf($parentCategoryId, $categoryId, BID);
    }

    /**
     * status を open に変更しようとしているとき、祖先カテゴリーに close があれば invalid
     *
     * @param mixed $status 入力された status 値
     */
    protected function isStatusOpenSafe($status): bool
    {
        if ($status !== 'open') {
            return true;
        }
        /** @var int|null $categoryId */
        $categoryId = CID;
        if ($categoryId === null || $categoryId < 1) {
            return true;
        }
        return !$this->repository()->hasClosedAncestor($categoryId, BID);
    }

    public function post()
    {
        $workflowData = null;
        $categoryId = (int) CID;
        $Category = $this->extract('category');
        $Field = $this->extract('field', new ACMS_Validator());

        if (sessionWithEnterpriseAdministration()) {
            $workflowData = $this->extractWorkflow();
        }

        $this->validate($Category, $Field);

        if ($this->Post->isValidAll()) {
            $name   = $Category->get('name');
            $code   = $Category->get('code');
            $scope  = $Category->get('scope');
            $parent = $Category->get('parent');

            $parentStatus = ACMS_RAM::categoryStatus($parent);
            if ($parentStatus !== null && $parentStatus !== '' && $parentStatus !== 'open') {
                $Category->set('status', $parentStatus);
            }

            $status = $Category->get('status');
            $blogId = BID;
            $repository = $this->repository();
            $boundary = $repository->findBoundary($categoryId);
            if ($boundary !== null) {
                $descendantsColumns = ['category_scope' => $scope];
                if ($status !== 'open') {
                    $descendantsColumns['category_status'] = $status;
                }
                $repository->updateDescendants(
                    $blogId,
                    $boundary['left'],
                    $boundary['right'],
                    $descendantsColumns
                );
            }

            $configSetId = $Category->get('config_set_id') ?: null;
            $themeSetId = $Category->get('theme_set_id') ?: null;
            $editorSetId = $Category->get('editor_set_id') ?: null;

            if ($configSetId === null || $configSetId === '') {
                $Category->set('config_set_scope', 'local');
            }
            if ($themeSetId === null || $themeSetId === '') {
                $Category->set('theme_set_scope', 'local');
            }
            if ($editorSetId === null || $editorSetId === '') {
                $Category->set('editor_set_scope', 'local');
            }

            $repository->update($categoryId, [
                'category_status' => $status,
                'category_name' => $name,
                'category_scope' => $scope,
                'category_indexing' => $Category->get('indexing'),
                'category_code' => $code,
                'category_config_set_id' => $configSetId,
                'category_config_set_scope' => $Category->get('config_set_scope', 'local'),
                'category_theme_set_id' => $themeSetId,
                'category_theme_set_scope' => $Category->get('theme_set_scope', 'local'),
                'category_editor_set_id' => $editorSetId,
                'category_editor_set_scope' => $Category->get('editor_set_scope', 'local'),
            ]);
            Common::saveField('cid', $categoryId, $Field);
            Common::saveFulltext('cid', $categoryId, Common::loadCategoryFulltext($categoryId));
            //----------
            // geometry
            $this->saveGeometry('cid', $categoryId, $this->extract('geometry'));
            $repository->changeParent($categoryId, (int) $parent, $blogId);

            //----------
            // workflow
            if ($workflowData !== null) {
                $this->saveWorkflow($workflowData, BID, $categoryId);
            }
            Cache::flush('temp');
            AcmsLogger::info('「' . ACMS_RAM::categoryName($categoryId) . '」カテゴリーの更新をしました', [
                'status' => $status,
                'name' => $name,
                'scope' => $scope,
                'indexing' => $Category->get('indexing'),
                'code' => $code,
                'configSetId' => $configSetId,
                'themeSetId' => $themeSetId,
                'editorSetId' => $editorSetId,
                'field' => $Field,
            ]);
        } else {
            AcmsLogger::info('「' . ACMS_RAM::categoryName($categoryId) . '」カテゴリーの更新に失敗しました', [
                'category' => $Category->_aryV,
                'field' => $Field->_aryV,
            ]);
        }
        $Category->setField('id', $categoryId);

        $this->Post->set('edit', 'update');

        return $this->Post;
    }

    /**
     *  バリデート
     *
     * @param \Field_Validation $Category
     * @param \Field_Validation $Field
     */
    protected function validate(
        \Field_Validation $Category,
        \Field_Validation $Field
    ) {
        $Category->setMethod('name', 'required');
        $Category->setMethod('name', 'maxlength', '255');
        $Category->setMethod('code', 'required');
        $Category->setMethod('code', 'maxlength', '255');
        $Category->setMethod('code', 'double', [$Category->get('scope'), $Category->get('parent'), CID]);
        $Category->setMethod('code', 'reserved', !isReserved($Category->get('code')));
        if ($Category->get('code') !== '') {
            $Category->setMethod('code', 'string', isValidCode($Category->get('code')));
        }
        $Category->setMethod('status', 'required');
        $Category->setMethod('status', 'in', ['open', 'close', 'secret']);
        $Category->setMethod('status', 'status', $this->isStatusOpenSafe($Category->get('status')));
        $Category->setMethod('scope', 'required');
        $Category->setMethod('indexing', 'required');
        $Category->setMethod('indexing', 'in', ['on', 'off']);
        $Category->setMethod('config_set_id', 'value', $this->checkConfigSetScope($Category->get('config_set_id')));
        $Category->setMethod('config_set_scope', 'in', ['local', 'global']);
        $Category->setMethod('theme_set_id', 'value', $this->checkConfigSetScope($Category->get('theme_set_id')));
        $Category->setMethod('theme_set_scope', 'in', ['local', 'global']);
        $Category->setMethod('editor_set_id', 'value', $this->checkConfigSetScope($Category->get('editor_set_id')));
        $Category->setMethod('editor_set_scope', 'in', ['local', 'global']);
        $Category->setMethod('category', 'operable', $this->isOperable());
        // scopeをlocalに変更しようとしたとき、すでに子ブログのエントリーが登録されていればinvalid
        if ($Category->get('scope') === 'local') {
            $Category->setMethod(
                'scope',
                'shared',
                !$this->isScopeShared()
            );
        }
        // parentを指定時に、scopeがparentと同じに設定されていなければinvalid
        $pid = intval($Category->get('parent'));
        if ($pid > 0) {
            $Category->setMethod(
                'scope',
                'tree',
                $Category->get('scope') === ACMS_RAM::categoryScope($pid)
            );
        }
        // 自分自身を親カテゴリーに設定しようとするとinvalid
        // CID が無いリクエストは isOperable() が false を返し全体で invalid になるが、その判定は
        // ここでは短絡しないため、native int 引数の TypeError を避けるためキャストしておく。
        $Category->setMethod('parent', 'isSelf', $this->parentIsNotSelf((int) CID, $pid));
        // 子カテゴリーをparentに設定しようとするとinvalid
        $Category->setMethod('parent', 'isChild', $this->parentIsNotOwnDescendant((int) CID, $pid));

        $Category->validate(new \Acms\Services\Validator\Category());
        $Field->validate(new ACMS_Validator());
    }

    /**
     * カテゴリーの更新が可能なユーザーかどうか
     *
     */
    protected function isOperable(): bool
    {
        /** @var int<1, max>|null $categoryId */
        $categoryId = CID;
        if (is_null($categoryId)) {
            return false;
        }

        if (Category::canUpdate(BID)) {
            return true;
        }

        if ($this->shortcutAuthorization()) {
            return true;
        }

        return false;
    }

    /**
     *  ショートカットによる認可チェック
     *
     * @return bool
     */
    protected function shortcutAuthorization(): bool
    {
        return Auth::checkShortcut(['cid' => CID]);
    }
}
