<?php
// 这是系统自动生成的公共文件

if (!function_exists('xlsx_reader')) {
    /**
     * 读取Excel数据
     * @param string $file Excel文件路径
     * @param int $initial_row 初始行
     * @param array $keys_map 列 -> 键 例：['A' => 'name', 'B' => 'age', 'C' => 'sex']
     * @return array
     */
    function xlsx_reader(string $file, int $initial_row = 1, array $keys_map = [], bool $read_only = false): array
    {
        $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
        if ($read_only) {
            // 只读模式，不读取公式，样式等等，减少内存消耗
            $reader->setReadDataOnly(true);
        }

        // 读取文件
        $spreadsheet = $reader->load($file);

        // 获取活动sheet
        $sheet = $spreadsheet->getActiveSheet();

        // 获取最大行
        $max_row = $sheet->getHighestRow();

        // 获取最大列
        $max_col = $sheet->getHighestColumn();

        $xlsx_data = [];
        $first_col = ord('A');
        $last_col  = ord($max_col);
        for ($row = $initial_row; $row <= $max_row; $row++) {
            for ($col = $first_col; $col <= $last_col; $col++) {
                $key = $col_chr = chr($col);
                if (!empty($keys_map)) {
                    $key = $keys_map[$col_chr] ?? $col_chr;
                }
                $xlsx_data[$row][$key] = $sheet->getCell($col_chr . $row)->getValue();
            }
        }

        return $xlsx_data;
    }
}

if (!function_exists('xlsx_stream_reader')) {
    /**
     * 读取Excel数据
     * @param string $file Excel文件路径
     * @param int $initial_row 初始行
     * @param array $keys_map 列 -> 键 例：['A' => 'name', 'B' => 'age', 'C' => 'sex']
     * @return Generator
     */
    function xlsx_stream_reader(string $file, int $initial_row = 1, array $keys_map = [], bool $read_only = false): Generator
    {
        $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
        if ($read_only) {
            // 只读模式，不读取公式，样式等等，减少内存消耗
            $reader->setReadDataOnly(true);
        }

        // 读取文件
        $spreadsheet = $reader->load($file);

        // 获取活动sheet
        $sheet = $spreadsheet->getActiveSheet();

        // 获取最大行
        $max_row = $sheet->getHighestRow();

        // 获取最大列
        $max_col = $sheet->getHighestColumn();

        $xlsx_row = [];
        $first_col = ord('A');
        $last_col  = ord($max_col);
        for ($row = $initial_row; $row <= $max_row; $row++) {
            for ($col = $first_col; $col <= $last_col; $col++) {
                $key = $col_chr = chr($col);
                if (!empty($keys_map)) {
                    $key = $keys_map[$col_chr] ?? $col_chr;
                }
                $xlsx_row[$key] = $sheet->getCell($col_chr . $row)->getValue();
            }
            yield $xlsx_row;
        }

        // 主动销毁对象以释放内存
        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet);
    }
}

if (!function_exists('xlsx_writer')) {
    /**
     * 构建Excel数据
     * @param array $data 数据 例：[['name'=>'张三','age'=>18, 'sex'=>'男']]
     * @param array $schema 表头信息 例：['name' => '姓名', 'age' => '年龄', 'sex' => '性别']
     * @param string $filename 下载文件名称 默认为YmdHis时间
     * @param string $output 输出方式 php://output 或 文件路径
     * @return \think\Response
     */
    function xlsx_writer(array|\think\model\Collection|Generator $data, array $schema, string $filename = '', string $output = 'php://output'): \think\Response
    {
        // 获取Spreadsheet对象
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();

        // 写入表头
        $title = array_values($schema);
        $title_col = 'A';
        foreach ($title as $value) {
            // 单元格内容写入
            $sheet->setCellValue($title_col . '1', $value);
            $title_col++;
        }

        // 写入数据
        $row = 2;
        $keys = array_keys($schema);
        foreach ($data as $item) {
            $data_col = 'A';
            foreach ($keys as $key) {
                $sheet->setCellValue($data_col . $row, $item[$key] ?? '');
                $data_col++;
            }
            $row++;
        }

        flush();
        ob_flush();
        if ($filename == '') $filename = date('YmdHis');
        $filename = urlencode($filename);
        // 文件写入缓冲区
        \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx')->save($output);
        // 从缓冲区获取导出文件，并通过Response返回（解决thinkphp框架的Response无法获取设置的header问题）
        $file_data = ob_get_clean();
        $response  = \think\Response::create($file_data)->header([
            'Access-Control-Expose-Headers' => 'Content-Disposition',
            'Content-Type'                  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8',
            'Content-Disposition'           => 'attachment;filename=' . $filename . '.xlsx',
            'Cache-Control'                 => 'max-age=0'
        ]);
        return $response;
    }
}

if (!function_exists('xlsx_download_xaccel')) {
    /**
     * 构建Excel数据（采用Nginx Sendfile方式）
     *
     * Nginx中需配置
     * location /download/ {
     *     internal; # 关键：禁止外部直接访问这个路径
     *     alias /tmp/; # 使用 alias，将 URI 映射到你存放文件的真实目录
     * }
     *
     * 注意：避免临时文件造成磁盘堆积，需依赖系统层定时清理（如 systemd-tmpfiles / cron），PHP 侧不做删除
     *      如：# 每分钟执行一次，删除 /tmp 下 10 分钟前创建的 xlsx_ 临时文件
     *      * * * * * find /tmp -maxdepth 1 -name 'ow_xlsx_*' -type f -mmin +10 -delete
     *
     * @param array|\think\model\Collection|Generator $data 数据 例：[['name'=>'张三','age'=>18, 'sex'=>'男']]
     * @param array $schema 表头信息 例：['name' => '姓名', 'age' => '年龄', 'sex' => '性别']
     * @param string $filename 下载文件名称 默认为YmdHis时间
     * @return \think\Response
     */
    function xlsx_download_xaccel(array|\think\model\Collection|Generator $data, array $schema, string $filename = ''): \think\Response
    {
        // 生成到临时文件，避免将整个文件内容写入输出缓冲区
        $tmp_file = tempnam(sys_get_temp_dir(), 'ow_xlsx_');
        if (false === $tmp_file) {
            throw new \RuntimeException('创建临时文件失败');
        }
        // tempnam 默认 0600，需放开读权限，否则 Nginx 与 PHP-FPM 用户不同时无法读取
        chmod($tmp_file, 0644);

        // 获取Spreadsheet对象
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet       = $spreadsheet->getActiveSheet();

        // 写入表头
        $title_col = 'A';
        foreach (array_values($schema) as $value) {
            $sheet->setCellValue($title_col . '1', $value);
            $title_col++;
        }

        // 写入数据
        $row  = 2;
        $keys = array_keys($schema);
        foreach ($data as $item) {
            $data_col = 'A';
            foreach ($keys as $key) {
                $sheet->setCellValue($data_col . $row, $item[$key] ?? '');
                $data_col++;
            }
            $row++;
        }

        // 写入临时文件，不预计算公式以降低耗时与内存
        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
        $writer->setPreCalculateFormulas(false);
        $writer->save($tmp_file);

        // 释放Spreadsheet内存
        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet, $writer, $sheet);

        if ('' == $filename) $filename = date('YmdHis');
        $filename = urlencode($filename);

        $response = \think\Response::create()
            ->header([
                'Access-Control-Expose-Headers' => 'Content-Disposition',
                'Content-Type'                  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8',
                'Content-Disposition'           => 'attachment;filename=' . $filename . '.xlsx',
                'Cache-Control'                 => 'max-age=0',
                'Content-Length'                => filesize($tmp_file),
                'X-Accel-Redirect'              => '/download/' . basename($tmp_file), // Nginx
            ]);

        return $response;
    }
}

if (!function_exists('strtobytes')) {

    /**
     * 字符串转字节
     * @param string $str_size 字符串 例：'1MB' '2GB'
     * @return int
     */
    function strtobytes(string $str_size): int
    {
        preg_match('/(\d+)(\w+)/', $str_size, $matches);
        if (!isset($matches[1])) {
            throw new \Exception('请确认size大小');
        }
        if (!isset($matches[2])) {
            throw new \Exception('请确认单位');
        }

        $size = intval($matches[1]);
        $unit = $matches[2];
        switch (strtolower($unit)) {
            case 'kb':
                $size *= 1024;
                break;
            case 'mb':
                $size *= 1024 * 1024;
                break;
            case 'gb':
                $size *= 1024 * 1024 * 1024;
                break;
            case 'tb':
                $size *= 1024 * 1024 * 1024 * 1024;
            default:
                throw new \Exception('不支持[' . $unit . ']单位');
        }
        return $size;
    }
}
