<?php

namespace Utils;

use Typecho\Common;
use Typecho\Db;
use Typecho\I18n;
use Typecho\Plugin;
use Typecho\Widget;
use Widget\Base\Options as BaseOptions;
use Widget\Options;
use Widget\Plugins\Edit;
use Widget\Security;
use Widget\Service;

/**
 * 插件帮手将默认出现在所有的typecho发行版中.
 * 因此你可以放心使用它的功能, 以方便你的插件安装在用户的系统里.
 *
 * @package Helper
 * @author qining
 * @version 1.0.0
 * @link http://typecho.org
 */
class Helper
{
    /**
     * 获取Security对象
     *
     * @return Security
     */
    public static function security(): Security
    {
        return Security::alloc();
    }

    /**
     * 根据ID获取单个Widget对象
     *
     * @param string $table 表名, 支持 contents, comments, metas, users
     * @param int $pkId
     * @return Widget|null
     */
    public static function widgetById(string $table, int $pkId): ?Widget
    {
        $table = ucfirst($table);
        if (!in_array($table, ['Contents', 'Comments', 'Metas', 'Users'])) {
            return null;
        }

        $keys = [
            'Contents' => 'cid',
            'Comments' => 'coid',
            'Metas'    => 'mid',
            'Users'    => 'uid'
        ];

        $className = '\Widget\Base\\' . $table;

        $key = $keys[$table];
        $db = Db::get();
        $widget = Widget::widget($className . '@' . $pkId);

        $db->fetchRow(
            $widget->select()->where("{$key} = ?", $pkId)->limit(1),
            [$widget, 'push']
        );

        return $widget;
    }

    /**
     * 请求异步服务
     *
     * @param $method
     * @param $params
     */
    public static function requestService($method, ... $params)
    {
        Service::alloc()->requestService($method, ... $params);
    }

    /**
     * 强行删除某个插件
     *
     * @param string $pluginName 插件名称
     */
    public static function removePlugin(string $pluginName)
    {
        try {
            /** 获取插件入口 */
            [$pluginFileName, $className] = Plugin::portal(
                $pluginName,
                __TYPECHO_ROOT_DIR__ . '/' . __TYPECHO_PLUGIN_DIR__
            );

            /** 获取已启用插件 */
            $plugins = Plugin::export();
            $activatedPlugins = $plugins['activated'];

            /** 载入插件 */
            require_once $pluginFileName;

            /** 判断实例化是否成功 */
            if (
                !isset($activatedPlugins[$pluginName]) || !class_exists($className)
                || !method_exists($className, 'deactivate')
            ) {
                throw new Widget\Exception(_t('无法禁用插件'), 500);
            }

            call_user_func([$className, 'deactivate']);
        } catch (\Exception $e) {
            //nothing to do
        }

        $db = Db::get();

        try {
            Plugin::deactivate($pluginName);
            self::setOption('plugins', Plugin::export());
        } catch (Plugin\Exception $e) {
            //nothing to do
        }

        $db->query($db->delete('table.options')->where('name = ?', 'plugin:' . $pluginName));
    }

    /**
     * 导入语言项
     *
     * @param string $domain
     */
    public static function lang(string $domain)
    {
        $currentLang = I18n::getLang();
        if ($currentLang) {
            $currentLang = basename($currentLang);
            $fileName = dirname(__FILE__) . '/' . $domain . '/lang/' . $currentLang;
            if (file_exists($fileName)) {
                I18n::addLang($fileName);
            }
        }
    }

    /**
     * 获取Options对象
     *
     * @return Options
     */
    public static function options(): Options
    {
        return Options::alloc();
    }

    /**
     * @param string $name
     * @param $value
     * @return int
     */
    public static function setOption(string $name, $value): int
    {
        $options = self::options();
        $options->{$name} = $value;

        return BaseOptions::alloc()->update(
            ['value' => is_array($value) ? json_encode($value) : $value],
            Db::get()->sql()->where('name = ?', $name)
        );
    }

    /**
     * 增加路由
     *
     * @param string $name 路由名称
     * @param string $url 路由路径
     * @param string $widget 组件名称
     * @param string|null $action 组件动作
     * @param string|null $after 在某个路由后面
     * @return integer
     */
    public static function addRoute(
        string $name,
        string $url,
        string $widget,
        ?string $action = null,
        ?string $after = null
    ): int {
        $routingTable = self::options()->routingTable;
        if (isset($routingTable[0])) {
            unset($routingTable[0]);
        }

        $pos = 0;
        foreach ($routingTable as $key => $val) {
            $pos++;

            if ($key == $after) {
                break;
            }
        }

        $pre = array_slice($routingTable, 0, $pos);
        $next = array_slice($routingTable, $pos);

        $routingTable = array_merge($pre, [
            $name => [
                'url'    => $url,
                'widget' => $widget,
                'action' => $action
            ]
        ], $next);

        return self::setOption('routingTable', $routingTable);
    }

    /**
     * 移除路由
     *
     * @param string $name 路由名称
     * @return integer
     */
    public static function removeRoute(string $name): int
    {
        $routingTable = self::options()->routingTable;
        if (isset($routingTable[0])) {
            unset($routingTable[0]);
        }

        unset($routingTable[$name]);
        return self::setOption('routingTable', $routingTable);
    }

    /**
     * 增加action扩展
     *
     * @param string $actionName 需要扩展的action名称
     * @param string $widgetName 需要扩展的widget名称
     * @return integer
     */
    public static function addAction(string $actionName, string $widgetName): int
    {
        $actionTable = self::options()->actionTable;
        $actionTable = empty($actionTable) ? [] : $actionTable;
        $actionTable[$actionName] = $widgetName;

        return self::setOption('actionTable', $actionTable);
    }

    /**
     * 删除action扩展
     *
     * @param string $actionName
     * @return int
     */
    public static function removeAction(string $actionName): int
    {
        $actionTable = self::options()->actionTable;
        $actionTable = empty($actionTable) ? [] : $actionTable;

        if (isset($actionTable[$actionName])) {
            unset($actionTable[$actionName]);
            reset($actionTable);
        }

        return self::setOption('actionTable', $actionTable);
    }

    /**
     * 增加一个菜单
     *
     * @param string $menuName 菜单名
     * @return integer
     */
    public static function addMenu(string $menuName): int
    {
        $panelTable = self::options()->panelTable;
        $panelTable['parent'] = empty($panelTable['parent']) ? [] : $panelTable['parent'];
        $panelTable['parent'][] = $menuName;

        self::setOption('panelTable', $panelTable);

        end($panelTable['parent']);
        return key($panelTable['parent']) + 10;
    }

    /**
     * 移除一个菜单
     *
     * @param string $menuName 菜单名
     * @return integer
     */
    public static function removeMenu(string $menuName): int
    {
        $panelTable = self::options()->panelTable;
        $panelTable['parent'] = empty($panelTable['parent']) ? [] : $panelTable['parent'];

        if (false !== ($index = array_search($menuName, $panelTable['parent']))) {
            unset($panelTable['parent'][$index]);
        }

        self::setOption('panelTable', $panelTable);

        return $index + 10;
    }

    /**
     * 增加一个面板
     *
     * @param integer $index 菜单索引
     * @param string $fileName 文件名称
     * @param string $title 面板标题
     * @param string $subTitle 面板副标题
     * @param string $level 进入权限
     * @param boolean $hidden 是否隐藏
     * @param string $addLink 新增项目链接, 会显示在页面标题之后
     * @return integer
     */
    public static function addPanel(
        int $index,
        string $fileName,
        string $title,
        string $subTitle,
        string $level,
        bool $hidden = false,
        string $addLink = ''
    ): int {
        $panelTable = self::options()->panelTable;
        $panelTable['child'] = empty($panelTable['child']) ? [] : $panelTable['child'];
        $panelTable['child'][$index] = empty($panelTable['child'][$index]) ? [] : $panelTable['child'][$index];
        $fileName = urlencode(trim($fileName, '/'));
        $panelTable['child'][$index][]
            = [$title, $subTitle, 'extending.php?panel=' . $fileName, $level, $hidden, $addLink];

        $panelTable['file'] = empty($panelTable['file']) ? [] : $panelTable['file'];
        $panelTable['file'][] = $fileName;
        $panelTable['file'] = array_unique($panelTable['file']);

        self::setOption('panelTable', $panelTable);

        end($panelTable['child'][$index]);
        return key($panelTable['child'][$index]);
    }

    /**
     * 移除一个面板
     *
     * @param integer $index 菜单索引
     * @param string $fileName 文件名称
     * @return integer
     */
    public static function removePanel(int $index, string $fileName): int
    {
        $panelTable = self::options()->panelTable;
        $panelTable['child'] = empty($panelTable['child']) ? [] : $panelTable['child'];
        $panelTable['child'][$index] = empty($panelTable['child'][$index]) ? [] : $panelTable['child'][$index];
        $panelTable['file'] = empty($panelTable['file']) ? [] : $panelTable['file'];
        $fileName = urlencode(trim($fileName, '/'));

        if (false !== ($key = array_search($fileName, $panelTable['file']))) {
            unset($panelTable['file'][$key]);
        }

        $return = 0;
        foreach ($panelTable['child'][$index] as $key => $val) {
            if ($val[2] == 'extending.php?panel=' . $fileName) {
                unset($panelTable['child'][$index][$key]);
                $return = $key;
            }
        }

        self::setOption('panelTable', $panelTable);
        return $return;
    }

    /**
     * 获取面板url
     *
     * @param string $fileName
     * @return string
     */
    public static function url(string $fileName): string
    {
        return Common::url('extending.php?panel=' . (trim($fileName, '/')), self::options()->adminUrl);
    }

    /**
     * 手动配置插件变量
     *
     * @param mixed $pluginName 插件名称
     * @param array $settings 变量键值对
     * @param bool $isPersonal . (default: false) 是否为私人变量
     */
    public static function configPlugin($pluginName, array $settings, bool $isPersonal = false)
    {
        if (empty($settings)) {
            return;
        }

        Edit::configPlugin($pluginName, $settings, $isPersonal);
    }

    /**
     * 评论回复按钮
     *
     * @access public
     * @param string $theId 评论元素id
     * @param integer $coid 评论id
     * @param string $word 按钮文字
     * @param string $formId 表单id
     * @param integer $style 样式类型
     * @return void
     */
    public static function replyLink(
        string $theId,
        int $coid,
        string $word = 'Reply',
        string $formId = 'respond',
        int $style = 2
    ) {
        if (self::options()->commentsThreaded) {
            echo '<a href="#' . $formId . '" rel="nofollow" onclick="return typechoAddCommentReply(\'' .
                $theId . '\', ' . $coid . ', \'' . $formId . '\', ' . $style . ');">' . $word . '</a>';
        }
    }

    /**
     * 评论取消按钮
     *
     * @param string $word 按钮文字
     * @param string $formId 表单id
     */
    public static function cancelCommentReplyLink(string $word = 'Cancel', string $formId = 'respond')
    {
        if (self::options()->commentsThreaded) {
            echo '<a href="#' . $formId . '" rel="nofollow" onclick="return typechoCancelCommentReply(\'' .
                $formId . '\');">' . $word . '</a>';
        }
    }

    /**
     * 评论回复js脚本
     */
    public static function threadedCommentsScript()
    {
        if (self::options()->commentsThreaded) {
            echo
            <<<EOF
<script type="text/javascript">
var typechoAddCommentReply = function (cid, coid, cfid, style) {
    var _ce = document.getElementById(cid), _cp = _ce.parentNode;
    var _cf = document.getElementById(cfid);

    var _pi = document.getElementById('comment-parent');
    if (null == _pi) {
        _pi = document.createElement('input');
        _pi.setAttribute('type', 'hidden');
        _pi.setAttribute('name', 'parent');
        _pi.setAttribute('id', 'comment-parent');

        var _form = 'form' == _cf.tagName ? _cf : _cf.getElementsByTagName('form')[0];

        _form.appendChild(_pi);
    }
    _pi.setAttribute('value', coid);

    if (null == document.getElementById('comment-form-place-holder')) {
        var _cfh = document.createElement('div');
        _cfh.setAttribute('id', 'comment-form-place-holder');
        _cf.parentNode.insertBefore(_cfh, _cf);
    }

    1 == style ? (null == _ce.nextSibling ? _cp.appendChild(_cf)
    : _cp.insertBefore(_cf, _ce.nextSibling)) : _ce.appendChild(_cf);

    return false;
};

var typechoCancelCommentReply = function (cfid) {
    var _cf = document.getElementById(cfid),
    _cfh = document.getElementById('comment-form-place-holder');

    var _pi = document.getElementById('comment-parent');
    if (null != _pi) {
        _pi.parentNode.removeChild(_pi);
    }

    if (null == _cfh) {
        return true;
    }

    _cfh.parentNode.insertBefore(_cf, _cfh);
    return false;
};
</script>
EOF;
        }
    }
}
