<?php

namespace Kylin\Excel\Export;

use Closure;
use Kylin\Excel\Concerns\FromArray;
use Kylin\Excel\Concerns\FromCollection;
use Kylin\Excel\Concerns\FromQuery;
use Kylin\Excel\Concerns\WithColumnFormatting;
use Kylin\Excel\Concerns\WithCustomStartCell;
use Kylin\Excel\Concerns\WithEvents;
use Kylin\Excel\Concerns\WithHeadings;
use Kylin\Excel\Concerns\WithMapping;
use Kylin\Excel\Concerns\WithTitle;
use Kylin\Excel\Events\AfterExport;
use Kylin\Excel\Events\AfterSheet;
use Kylin\Excel\Events\BeforeExport;
use Kylin\Excel\Events\BeforeSheet;
use Kylin\Excel\Events\EventDispatcher;
use Vtiful\Kernel\Excel as XlsWriterExcel;

/**
 * Excel导出处理类
 * 
 * 负责Excel文件的生成和数据导出处理
 * 支持多种数据源、标题行、数据映射、列格式化、事件触发等功能
 */
class Export
{
    /**
     * 导出对象
     * 
     * 实现了相应导出接口的对象，用于定义导出规则
     * 
     * @var object
     */
    protected object $exportable;

    /**
     * Excel文件路径
     * 
     * @var string
     */
    protected string $filePath;

    /**
     * 工作表名称
     * 
     * 默认为'Sheet1'
     * 
     * @var string
     */
    protected string $sheetName = 'Sheet1';

    /**
     * 导出数据数组
     * 
     * 存储要导出的数据
     * 
     * @var array
     */
    protected array $data = [];

    /**
     * 标题行数组
     * 
     * 存储Excel文件的标题行数据
     * 
     * @var array|null
     */
    protected ?array $headings = null;

    /**
     * 数据映射回调函数
     * 
     * 用于对每行数据进行转换处理
     * 
     * @var Closure|null
     */
    protected ?Closure $mapCallback = null;

    /**
     * 起始单元格
     * 
     * 数据写入的起始位置，默认为'A1'
     * 
     * @var string|null
     */
    protected ?string $startCell = 'A1';

    /**
     * 列格式化配置
     * 
     * 用于设置特定列的数字格式
     * 
     * @var array
     */
    protected array $columnFormats = [];

    /**
     * 工作表标题
     * 
     * 工作表的显示标题
     * 
     * @var string
     */
    protected string $title = 'Sheet1';

    /**
     * xlswriter实例
     * 
     * @var XlsWriterExcel
     */
    protected XlsWriterExcel $excel;

    /**
     * 文件句柄
     * 
     * xlswriter文件操作句柄
     * 
     * @var mixed
     */
    protected $fileHandle;

    /**
     * 事件调度器
     * 
     * @var EventDispatcher
     */
    protected EventDispatcher $eventDispatcher;

    /**
     * 构造函数
     * 
     * 初始化导出处理器并解析导出对象的配置
     * 
     * @param object $exportable 导出对象
     */
    public function __construct(object $exportable)
    {
        $this->exportable = $exportable;
        $this->eventDispatcher = new EventDispatcher();
        $this->resolveExportable();
    }

    /**
     * 解析导出对象配置
     * 
     * 根据导出对象实现的接口，提取相应的数据源和配置信息
     * 支持FromCollection、FromArray、FromQuery等数据源
     * 
     * @return void
     */
    protected function resolveExportable(): void
    {
        if ($this->exportable instanceof FromCollection) {
            $this->data = collect($this->exportable->collection())->toArray();
        } elseif ($this->exportable instanceof FromArray) {
            $this->data = $this->exportable->array();
        } elseif ($this->exportable instanceof FromQuery) {
            $this->data = $this->exportable->query()->get()->toArray();
        }

        if ($this->exportable instanceof WithHeadings) {
            $this->headings = $this->exportable->headings();
        }

        if ($this->exportable instanceof WithMapping) {
            $this->mapCallback = fn ($row) => $this->exportable->map($row);
        }

        if ($this->exportable instanceof WithCustomStartCell) {
            $this->startCell = $this->exportable->startCell();
        }

        if ($this->exportable instanceof WithTitle) {
            $this->title = $this->exportable->title();
            $this->sheetName = $this->title;
        }

        if ($this->exportable instanceof WithColumnFormatting) {
            $this->columnFormats = $this->exportable->columnFormats();
        }

        if ($this->exportable instanceof WithEvents) {
            $this->eventDispatcher->registerFromWithEvents($this->exportable);
        }
    }

    /**
     * 存储Excel文件
     * 
     * 将导出数据保存到指定路径
     * 
     * @param string $filePath 存储路径
     * @return bool 返回是否存储成功
     */
    public function store(string $filePath): bool
    {
        $this->filePath = $filePath;
        $directory = dirname($filePath);

        if (!is_dir($directory)) {
            mkdir($directory, 0755, true);
        }

        $this->fireEvent(BeforeExport::class);
        $this->createExcel($filePath);
        $this->fireEvent(BeforeSheet::class);
        $this->writeData();
        $this->fireEvent(AfterSheet::class);
        $this->fireEvent(AfterExport::class);

        return true;
    }

    /**
     * 下载Excel文件
     * 
     * 将导出数据转换为Excel文件并通过浏览器下载
     * 
     * @param string $fileName 下载文件名
     * @return void
     */
    public function download(string $fileName): void
    {
        $tempPath = sys_get_temp_dir() . '/' . uniqid('excel_', true) . '.xlsx';

        $this->fireEvent(BeforeExport::class);
        $this->createExcel($tempPath);
        $this->fireEvent(BeforeSheet::class);
        $this->writeData();
        $this->fireEvent(AfterSheet::class);
        $this->fireEvent(AfterExport::class);

        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        header('Content-Disposition: attachment;filename="' . $fileName . '"');
        header('Cache-Control: max-age=0');

        readfile($tempPath);
        unlink($tempPath);
        exit;
    }

    /**
     * 获取Excel原始内容
     * 
     * 将导出数据转换为Excel文件的原始二进制字符串
     * 适用于需要自定义响应的场景
     * 
     * @return string Excel文件的原始二进制内容
     */
    public function raw(): string
    {
        $tempPath = sys_get_temp_dir() . '/' . uniqid('excel_', true) . '.xlsx';

        $this->fireEvent(BeforeExport::class);
        $this->createExcel($tempPath);
        $this->fireEvent(BeforeSheet::class);
        $this->writeData();
        $this->fireEvent(AfterSheet::class);
        $this->fireEvent(AfterExport::class);

        $content = file_get_contents($tempPath);
        unlink($tempPath);

        return $content;
    }

    /**
     * 创建Excel文件
     * 
     * 使用xlswriter扩展创建Excel文件实例
     * 
     * @param string $filePath 文件路径
     * @return void
     */
    protected function createExcel(string $filePath): void
    {
        $config = ['path' => dirname($filePath)];
        $this->excel = new XlsWriterExcel($config);
        $this->fileHandle = $this->excel->fileName(basename($filePath), $this->sheetName);
    }

    /**
     * 写入数据
     * 
     * 将数据写入Excel文件
     * 包括标题行、数据内容和列格式化
     * 
     * @return void
     */
    protected function writeData(): void
    {
        $startRow = 1;
        $startCol = 0;

        if ($this->startCell) {
            $startCell = $this->parseCell($this->startCell);
            $startRow = $startCell['row'];
            $startCol = $startCell['col'];
        }

        $currentRow = $startRow;

        if ($this->headings) {
            $this->fileHandle->header($this->headings);
            $currentRow++;
        }

        if ($this->mapCallback) {
            $this->data = array_map($this->mapCallback, $this->data);
        }

        if (!empty($this->data)) {
            $this->fileHandle->data($this->data);
        }

        $this->applyColumnFormats();

        $this->fileHandle->output();
    }

    /**
     * 解析单元格坐标
     * 
     * 将Excel单元格坐标（如'A1'）转换为行列索引
     * 
     * @param string $cell 单元格坐标（如'A1'、'B2'等）
     * @return array 包含row和col索引的数组
     */
    protected function parseCell(string $cell): array
    {
        preg_match('/([A-Z]+)(\d+)/', strtoupper($cell), $matches);

        $col = 0;
        $colLetters = $matches[1] ?? 'A';
        $colLetters = str_split($colLetters);

        foreach ($colLetters as $letter) {
            $col = $col * 26 + (ord($letter) - ord('A') + 1);
        }

        return [
            'row' => (int)($matches[2] ?? 1),
            'col' => $col - 1,
        ];
    }

    /**
     * 应用列格式化
     * 
     * 为指定列应用数字格式
     * 如日期格式、货币格式等
     * 
     * @return void
     */
    protected function applyColumnFormats(): void
    {
        if (empty($this->columnFormats)) {
            return;
        }

        $format = new \Vtiful\Kernel\Format($this->fileHandle->getHandle());

        foreach ($this->columnFormats as $column => $formatCode) {
            $colIndex = $this->columnToIndex($column);
            $columnFormat = $format->number($formatCode);
        }
    }

    /**
     * 列字母转索引
     * 
     * 将Excel列字母（如'A'、'B'、'AA'等）转换为列索引
     * 
     * @param string $column 列字母
     * @return int 列索引（从0开始）
     */
    protected function columnToIndex(string $column): int
    {
        $column = strtoupper($column);
        $index = 0;
        $column = str_split($column);

        foreach ($column as $letter) {
            $index = $index * 26 + (ord($letter) - ord('A') + 1);
        }

        return $index - 1;
    }

    /**
     * 触发事件
     * 
     * @param string $eventClass 事件类名
     * @param array $payload 事件附加数据
     * @return void
     */
    protected function fireEvent(string $eventClass, array $payload = []): void
    {
        if (!$this->eventDispatcher->hasListeners($eventClass)) {
            return;
        }

        $event = new $eventClass($this->exportable, ...$payload);
        $this->eventDispatcher->dispatch($event);
    }

    /**
     * 获取工作表名称
     * 
     * 返回当前工作表的名称
     * 
     * @return string 工作表名称
     */
    public function getSheetName(): string
    {
        return $this->sheetName;
    }

    /**
     * 设置工作表名称
     * 
     * 自定义工作表的名称
     * 
     * @param string $sheetName 工作表名称
     * @return self 支持链式调用
     */
    public function setSheetName(string $sheetName): self
    {
        $this->sheetName = $sheetName;
        return $this;
    }
}
