<?php

declare(strict_types=1);
/**
 * +----------------------------------------------------------------------
 * | ThinkAdmin Plugin for ThinkAdmin
 * +----------------------------------------------------------------------
 * | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
 * +----------------------------------------------------------------------
 * | 官方网站: https://thinkadmin.top
 * +----------------------------------------------------------------------
 * | 开源协议 ( https://mit-license.org )
 * | 免责声明 ( https://thinkadmin.top/disclaimer )
 * | 会员特权 ( https://thinkadmin.top/vip-introduce )
 * +----------------------------------------------------------------------
 * | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
 * | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
 * +----------------------------------------------------------------------
 */

namespace think\admin\service;

use Symfony\Component\Process\Process;
use think\admin\Exception;
use think\admin\extend\CodeExtend;
use think\admin\Library;
use think\admin\Service;

/**
 * 系统进程管理服务
 * @class ProcessService
 */
class ProcessService extends Service
{
    /**
     * 静态兼容处理.
     * @return array
     * @throws Exception
     */
    public static function __callStatic(string $method, array $arguments)
    {
        if ($method === 'thinkCreate') {
            return self::thinkExec(...$arguments);
        }
        throw new Exception("method not exists: ProcessService::{$method}()");
    }

    /**
     * 生成 PHP 指令.
     */
    public static function php(string $args = ''): string
    {
        return ModuleService::getPhpExec() . ' ' . $args;
    }

    /**
     * 生成 Think 指令.
     * @param string $args 指令参数
     * @param bool $simple 仅返回内容
     */
    public static function think(string $args = '', bool $simple = false): string
    {
        $command = syspath('think') . ' ' . $args;
        return $simple ? $command : self::php($command);
    }

    /**
     * 生成 Composer 指令.
     * @param string $args 参数
     */
    public static function composer(string $args = ''): string
    {
        static $comExec;
        if (empty($comExec)) {
            $comExec = ModuleService::getRunVar('com');
            $comExec = self::isFile($comExec) ? self::php($comExec) : 'composer';
        }
        $root = Library::$sapp->getRootPath();
        return "{$comExec} -d {$root} {$args}";
    }

    /**
     * 创建 Think 进程.
     * @param string $args 执行参数
     * @param int $usleep 延时等待
     * @param bool $doQuery 查询进程
     */
    public static function thinkExec(string $args, int $usleep = 0, bool $doQuery = false): array
    {
        static::create(static::think($args), $usleep);
        return $doQuery ? static::query(static::think($args, true)) : [];
    }

    /**
     * 检查 Think 进程.
     * @param string $args 执行参数
     */
    public static function thinkQuery(string $args): array
    {
        return static::query(static::think($args, true));
    }

    /**
     * 创建异步进程.
     * @param string $command 任务指令
     * @param int $usleep 延时毫米
     */
    public static function create(string $command, int $usleep = 0)
    {
        if (static::isWin()) {
            static::exec(__DIR__ . "/bin/console.exe {$command}");
        } else {
            static::exec("{$command} > /dev/null 2>&1 &");
        }
        $usleep > 0 && usleep($usleep);
    }

    /**
     * 查询进程列表.
     * @param string $cmd 任务指令
     * @param string $name 进程名称
     */
    public static function query(string $cmd, string $name = 'php.exe'): array
    {
        $list = [];
        if (static::isWin()) {
            $lines = static::exec("wmic process where name=\"{$name}\" get processid,CommandLine", true);
            foreach ($lines as $line) {
                if (is_numeric(stripos($line, $cmd))) {
                    $attr = explode(' ', trim(preg_replace('#\s+#', ' ', $line)));
                    $list[] = ['pid' => array_pop($attr), 'cmd' => join(' ', $attr)];
                }
            }
        } else {
            $lines = static::exec("ps ax|grep -v grep|grep \"{$cmd}\"", true);
            foreach ($lines as $line) {
                if (is_numeric(stripos($line, $cmd))) {
                    $attr = explode(' ', trim(preg_replace('#\s+#', ' ', $line)));
                    [$pid] = [array_shift($attr), array_shift($attr), array_shift($attr), array_shift($attr)];
                    $list[] = ['pid' => $pid, 'cmd' => join(' ', $attr)];
                }
            }
        }
        return $list;
    }

    /**
     * 关闭指定进程.
     * @param int $pid 进程号
     */
    public static function close(int $pid): bool
    {
        if (static::isWin()) {
            static::exec("wmic process {$pid} call terminate");
        } else {
            static::exec("kill -9 {$pid}");
        }
        return true;
    }

    /**
     * 立即执行指令.
     * @param string $command 执行指令
     * @param bool $outarr 返回数组
     * @param ?callable $callable 逐行处理
     * @return array|string
     */
    public static function exec(string $command, bool $outarr = false, ?callable $callable = null)
    {
        $process = Process::fromShellCommandline($command)->setWorkingDirectory(Library::$sapp->getRootPath());
        $process->run(is_callable($callable) ? static function ($type, $text) use ($callable, $process) {
            call_user_func($callable, $process, $type, trim(CodeExtend::text2utf8($text))) === true && $process->stop();
        } : null);
        $output = str_replace("\r\n", "\n", CodeExtend::text2utf8($process->getOutput()));
        return $outarr ? explode("\n", $output) : trim($output);
    }

    /**
     * 输出命令行消息.
     * @param string $message 输出内容
     * @param int $backline 回退行数
     */
    public static function message(string $message, int $backline = 0)
    {
        while ($backline-- > 0) {
            $message = "\033[1A\r\033[K{$message}";
        }
        print_r($message . PHP_EOL);
    }

    /**
     * 判断系统类型 WINDOWS.
     */
    public static function isWin(): bool
    {
        return PATH_SEPARATOR === ';';
    }

    /**
     * 判断系统类型 UNIX.
     */
    public static function isUnix(): bool
    {
        return PATH_SEPARATOR !== ';';
    }

    /**
     * 检查文件是否存在.
     * @param string $file 文件路径
     */
    public static function isFile(string $file): bool
    {
        try {
            return $file !== '' && is_file($file);
        } catch (\Error|\Exception $exception) {
            try {
                if (self::isWin()) {
                    return self::exec("if exist \"{$file}\" echo 1") === '1';
                }
                return self::exec("if [ -f \"{$file}\" ];then echo 1;fi") === '1';
            } catch (\Error|\Exception $exception) {
                return false;
            }
        }
    }
}