<?php

declare(strict_types=1);

/**
 * CSV 엑셀 내보내기 헬퍼
 *
 * UTF-8 BOM 포함으로 한글 엑셀 호환성 보장
 */

if (! function_exists('audit_export_log')) {
    /**
     * 엑셀/CSV 다운로드를 활동 로그에 기록 (누가·무엇을 다운로드).
     * 모든 export_* 함수 진입 시 호출 — 실패해도 다운로드에 영향 없음.
     */
    function audit_export_log(string $filename): void
    {
        try {
            helper('audit');
            audit_log('export', 'export', 0, null, ['파일' => $filename]);
        } catch (\Throwable $e) {
            // 무시: 로깅 실패가 다운로드를 막지 않도록
        }
    }
}

if (! function_exists('export_csv')) {
    /**
     * CSV 파일을 브라우저로 다운로드 전송
     *
     * @param string   $filename  파일명 (확장자 포함, 예: 'export.csv')
     * @param string[] $headers   컬럼 헤더 배열
     * @param array    $rows      데이터 행 배열 (각 행은 배열)
     */
    function export_csv(string $filename, array $headers, array $rows): void
    {
        audit_export_log($filename);
        // 파일명에 .csv 확장자 보장
        if (! str_ends_with($filename, '.csv')) {
            $filename .= '.csv';
        }

        $response = service('response');
        $response->setHeader('Content-Type', 'text/csv; charset=UTF-8');
        $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
        $response->setHeader('Pragma', 'no-cache');
        $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');

        // UTF-8 BOM (한글 엑셀 호환)
        $output = "\xEF\xBB\xBF";

        // 헤더 행
        $output .= csv_encode_row($headers);

        // 데이터 행
        foreach ($rows as $row) {
            $output .= csv_encode_row(array_values((array) $row));
        }

        $response->setBody($output);
        $response->send();
        exit;
    }
}

if (! function_exists('csv_encode_row')) {
    /**
     * 배열 한 행을 CSV 문자열로 변환
     *
     * @param array $fields
     * @return string
     */
    function csv_encode_row(array $fields): string
    {
        $escaped = [];
        foreach ($fields as $field) {
            $val = (string) ($field ?? '');
            // 쌍따옴표 이스케이프 및 감싸기
            if (str_contains($val, '"') || str_contains($val, ',') || str_contains($val, "\n") || str_contains($val, "\r")) {
                $val = '"' . str_replace('"', '""', $val) . '"';
            }
            $escaped[] = $val;
        }
        return implode(',', $escaped) . "\r\n";
    }
}

if (! function_exists('export_excel_2003_xml')) {
    /**
     * Excel 2003 XML(SpreadsheetML)로 브라우저 다운로드 (.xls 확장자, 별도 라이브러리 불필요)
     *
     * @param string   $filename   저장 파일명(확장자는 .xls로 정규화)
     * @param string   $sheetName  시트 이름(Excel 제한: 길이·일부 문자)
     * @param string[] $headers    컬럼 헤더
     * @param array    $rows       데이터 행(각 행은 배열, 값은 문자열로 출력)
     * @param array    $topRows    헤더 위에 추가할 제목/부제 행(각 행은 배열). 비우면 없음.
     */
    function export_excel_2003_xml(string $filename, string $sheetName, array $headers, array $rows, array $topRows = []): void
    {
        audit_export_log($filename);
        $filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xls';

        $safeSheet = str_replace(['/', '\\', '?', '*', '[', ']'], '', $sheetName);
        $safeSheet = function_exists('mb_substr')
            ? mb_substr($safeSheet, 0, 31, 'UTF-8')
            : substr($safeSheet, 0, 31);

        $esc = static function (mixed $v): string {
            return htmlspecialchars((string) ($v ?? ''), ENT_XML1 | ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        };

        $parts = [];
        $parts[] = '<?xml version="1.0" encoding="UTF-8"?>';
        $parts[] = '<?mso-application progid="Excel.Sheet"?>';
        $parts[] = '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">';
        $parts[] = '<Worksheet ss:Name="' . $esc($safeSheet) . '">';
        $parts[] = '<Table>';

        // 상단 제목/부제 행(있을 때만)
        foreach ($topRows as $topRow) {
            $parts[] = '<Row>';
            foreach (array_values((array) $topRow) as $cell) {
                $parts[] = '<Cell><Data ss:Type="String">' . $esc($cell) . '</Data></Cell>';
            }
            $parts[] = '</Row>';
        }

        $parts[] = '<Row>';
        foreach ($headers as $h) {
            $parts[] = '<Cell><Data ss:Type="String">' . $esc($h) . '</Data></Cell>';
        }
        $parts[] = '</Row>';

        foreach ($rows as $row) {
            $parts[] = '<Row>';
            foreach (array_values((array) $row) as $cell) {
                $parts[] = '<Cell><Data ss:Type="String">' . $esc($cell) . '</Data></Cell>';
            }
            $parts[] = '</Row>';
        }

        $parts[] = '</Table>';
        $parts[] = '</Worksheet>';
        $parts[] = '</Workbook>';

        $output = implode('', $parts);

        $response = service('response');
        $response->setHeader('Content-Type', 'application/vnd.ms-excel; charset=UTF-8');
        $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
        $response->setHeader('Pragma', 'no-cache');
        $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
        $response->setBody($output);
        $response->send();
        exit;
    }
}

if (! function_exists('export_excel_2003_xml_workbook')) {
    /**
     * Excel 2003 XML — 다중 시트, 인쇄 미리보기와 유사한 헤더·줄바꿈·열 너비
     *
     * @param string $filename 저장 파일명
     * @param list<array{name: string, headers: list<string>, rows: list<list<string>>, col_widths?: list<int>}> $sheets
     */
    function export_excel_2003_xml_workbook(string $filename, array $sheets): void
    {
        audit_export_log($filename);
        $filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xls';

        $esc = static function (mixed $v): string {
            return htmlspecialchars((string) ($v ?? ''), ENT_XML1 | ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        };

        $safeSheetName = static function (string $name) use ($esc): string {
            $safe = str_replace(['/', '\\', '?', '*', '[', ']', ':'], '', $name);
            $safe = function_exists('mb_substr') ? mb_substr($safe, 0, 31, 'UTF-8') : substr($safe, 0, 31);

            return $esc($safe !== '' ? $safe : 'Sheet');
        };

        $parts   = [];
        $parts[] = '<?xml version="1.0" encoding="UTF-8"?>';
        $parts[] = '<?mso-application progid="Excel.Sheet"?>';
        $parts[] = '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">';
        $parts[] = '<Styles>';
        $parts[] = '<Style ss:ID="Default"><Alignment ss:Vertical="Top" ss:WrapText="1" ss:Horizontal="Left"/><Font ss:FontName="맑은 고딕" x:CharSet="129" ss:Size="9"/></Style>';
        $parts[] = '<Style ss:ID="Header"><Font ss:Bold="1" ss:Size="9" ss:FontName="맑은 고딕" x:CharSet="129"/><Interior ss:Color="#F3F4F6" ss:Pattern="Solid"/><Alignment ss:Horizontal="Left" ss:Vertical="Center" ss:WrapText="1"/><Borders><Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/></Borders></Style>';
        $parts[] = '</Styles>';

        foreach ($sheets as $sheet) {
            $sheetName = $safeSheetName((string) ($sheet['name'] ?? 'Sheet'));
            $headers   = array_values((array) ($sheet['headers'] ?? []));
            $rows      = (array) ($sheet['rows'] ?? []);
            $colWidths = array_values((array) ($sheet['col_widths'] ?? []));

            $parts[] = '<Worksheet ss:Name="' . $sheetName . '">';
            $parts[] = '<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"><PageSetup><Layout x:Orientation="Landscape"/></PageSetup></WorksheetOptions>';
            $parts[] = '<Table>';

            $colCount = max(count($headers), 1);
            for ($i = 0; $i < $colCount; $i++) {
                $px     = (int) ($colWidths[$i] ?? 72);
                $width  = max(48, min(280, $px));
                $excelW = round($width / 6.5, 1);
                $parts[] = '<Column ss:Index="' . ($i + 1) . '" ss:AutoFitWidth="0" ss:Width="' . $excelW . '"/>';
            }

            $parts[] = '<Row ss:StyleID="Header">';
            foreach ($headers as $h) {
                $parts[] = '<Cell ss:StyleID="Header"><Data ss:Type="String">' . $esc($h) . '</Data></Cell>';
            }
            $parts[] = '</Row>';

            foreach ($rows as $row) {
                $parts[] = '<Row>';
                foreach (array_values((array) $row) as $cell) {
                    $parts[] = '<Cell ss:StyleID="Default"><Data ss:Type="String">' . $esc($cell) . '</Data></Cell>';
                }
                $parts[] = '</Row>';
            }

            $parts[] = '</Table>';
            $parts[] = '</Worksheet>';
        }

        $parts[] = '</Workbook>';

        $output = implode('', $parts);

        $response = service('response');
        $response->setHeader('Content-Type', 'application/vnd.ms-excel; charset=UTF-8');
        $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
        $response->setHeader('Pragma', 'no-cache');
        $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
        $response->setBody($output);
        $response->send();
        exit;
    }
}

if (! function_exists('bag_flow_report_build_spreadsheet')) {
    /**
     * 기간별 봉투 수불 엑셀 통합문서 생성 (PhpSpreadsheet — 열 너비·병합 안정)
     *
     * @param list<array<string, mixed>> $reportRows
     * @param list<string>               $metaLines
     */
    function bag_flow_report_build_spreadsheet(
        string $lgName,
        string $title,
        array $metaLines,
        array $reportRows
    ): \PhpOffice\PhpSpreadsheet\Spreadsheet {
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $spreadsheet->getDefaultStyle()->getFont()->setName('맑은 고딕')->setSize(10);
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->setTitle('수불현황');
        $bodyFontSize = 10;

        $lastCol = 'N';
        $colWidths = [
            'A' => 22.0,
            'B' => 26.0,
            'C' => 12.0,
            'D' => 11.0,
            'E' => 11.0,
            'F' => 11.0,
            'G' => 12.0,
            'H' => 11.0,
            'I' => 12.0,
            'J' => 12.0,
            'K' => 12.0,
            'L' => 11.0,
            'M' => 12.0,
            'N' => 12.0,
        ];
        foreach ($colWidths as $col => $width) {
            $sheet->getColumnDimension($col)->setWidth($width);
            $sheet->getColumnDimension($col)->setAutoSize(false);
        }

        $r = 1;
        if ($lgName !== '') {
            $sheet->setCellValue("A{$r}", $lgName);
            $sheet->getStyle("A{$r}")->getFont()->setSize(10)->getColor()->setRGB('666666');
            $r++;
        }

        $sheet->setCellValue("A{$r}", $title);
        $sheet->mergeCells("A{$r}:{$lastCol}{$r}");
        $sheet->getStyle("A{$r}")->getFont()->setBold(true)->setSize($bodyFontSize);
        $r++;

        foreach ($metaLines as $line) {
            $sheet->setCellValue("A{$r}", $line);
            $sheet->mergeCells("A{$r}:{$lastCol}{$r}");
            $sheet->getStyle("A{$r}")->getFont()->setSize(10)->getColor()->setRGB('555555');
            $r++;
        }

        $r++;

        $h1 = $r;
        $h2 = $r + 1;

        $sheet->setCellValue("A{$h1}", '일자');
        $sheet->mergeCells("A{$h1}:A{$h2}");
        $sheet->setCellValue("B{$h1}", '품목');
        $sheet->mergeCells("B{$h1}:B{$h2}");
        $sheet->setCellValue("C{$h1}", '전일');
        $sheet->mergeCells("C{$h1}:C{$h2}");
        $sheet->setCellValue("D{$h1}", '입고');
        $sheet->mergeCells("D{$h1}:G{$h1}");
        $sheet->setCellValue("H{$h1}", '출고');
        $sheet->mergeCells("H{$h1}:M{$h1}");
        $sheet->setCellValue("N{$h1}", '잔량');
        $sheet->mergeCells("N{$h1}:N{$h2}");

        $subHeaders = ['입고', '반품', '기타', '입계', '판매', '일반', '무료', '반품', '기타', '출계'];
        foreach ($subHeaders as $i => $label) {
            $col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex(4 + $i);
            $sheet->setCellValue("{$col}{$h2}", $label);
        }

        $headerStyle = [
            'font'      => ['bold' => true, 'size' => $bodyFontSize],
            'alignment' => [
                'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
                'vertical'   => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
                'wrapText'   => false,
            ],
            'fill' => [
                'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
                'startColor' => ['rgb' => 'E9ECEF'],
            ],
            'borders' => [
                'bottom' => ['borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN],
            ],
        ];
        $sheet->getStyle("A{$h1}:{$lastCol}{$h2}")->applyFromArray($headerStyle);

        $dataRow = $h2 + 1;
        foreach ($reportRows as $row) {
            $rowType = (string) ($row['row_type'] ?? 'data');
            if (! in_array($rowType, ['data', 'subtotal', 'grand'], true)) {
                continue;
            }

            $sheet->fromArray([
                (string) ($row['date'] ?? ''),
                (string) ($row['item_name'] ?? ''),
                (int) ($row['prev_stock'] ?? 0),
                (int) ($row['recv_in'] ?? 0),
                (int) ($row['recv_return'] ?? 0),
                (int) ($row['recv_misc'] ?? 0),
                (int) ($row['recv_total'] ?? 0),
                (int) ($row['out_sale'] ?? 0),
                (int) ($row['out_issue_gen'] ?? 0),
                (int) ($row['out_issue_free'] ?? 0),
                (int) ($row['out_return'] ?? 0),
                (int) ($row['out_misc'] ?? 0),
                (int) ($row['out_total'] ?? 0),
                (int) ($row['balance'] ?? 0),
            ], null, "A{$dataRow}", true);

            $sheet->getStyle("C{$dataRow}:{$lastCol}{$dataRow}")
                ->getNumberFormat()
                ->setFormatCode('#,##0');
            $sheet->getStyle("C{$dataRow}:{$lastCol}{$dataRow}")
                ->getAlignment()
                ->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT);
            $sheet->getStyle("A{$dataRow}:B{$dataRow}")
                ->getAlignment()
                ->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT)
                ->setWrapText(false);
            $sheet->getStyle("A{$dataRow}:{$lastCol}{$dataRow}")
                ->getFont()
                ->setSize($bodyFontSize);

            if (in_array($rowType, ['subtotal', 'grand'], true)) {
                $sheet->getStyle("A{$dataRow}:{$lastCol}{$dataRow}")->applyFromArray([
                    'font' => ['bold' => true, 'size' => $bodyFontSize],
                    'fill' => [
                        'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
                        'startColor' => ['rgb' => 'FFF8E1'],
                    ],
                ]);
            }

            $dataRow++;
        }

        if ($dataRow > $h2 + 1) {
            $sheet->getStyle('A' . ($h2 + 1) . ':' . $lastCol . ($dataRow - 1))
                ->getBorders()
                ->getAllBorders()
                ->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_HAIR);
        }

        $sheet->getPageSetup()->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT);
        $sheet->getPageSetup()->setPaperSize(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::PAPERSIZE_A4);
        $sheet->getPageSetup()->setFitToWidth(1);
        $sheet->getPageSetup()->setFitToHeight(0);

        return $spreadsheet;
    }
}

if (! function_exists('export_bag_flow_report_excel')) {
    /**
     * 기간별 봉투 수불 (/bag/flow) — 인쇄와 동일한 헤더·2단 표 (xlsx, PhpSpreadsheet)
     *
     * @param list<array<string, mixed>> $reportRows
     * @param list<string>               $metaLines
     */
    function export_bag_flow_report_excel(
        string $filename,
        string $lgName,
        string $title,
        array $metaLines,
        array $reportRows
    ): void {
        audit_export_log($filename);
        $baseName = preg_replace('/\.[^.]+$/u', '', $filename);
        $baseName = preg_replace('/[^\p{L}\p{N}_\-]+/u', '_', $baseName) ?? 'bag_flow';
        $baseName = trim($baseName, '_') !== '' ? trim($baseName, '_') : 'bag_flow';
        $filename = $baseName . '.xlsx';

        $spreadsheet = bag_flow_report_build_spreadsheet($lgName, $title, $metaLines, $reportRows);
        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);

        ob_start();
        try {
            $writer->save('php://output');
        } catch (\Throwable $e) {
            ob_end_clean();
            $spreadsheet->disconnectWorksheets();
            unset($spreadsheet);
            throw $e;
        }
        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet);
        $output = ob_get_clean();
        if ($output === false) {
            $output = '';
        }

        $response = service('response');
        $response->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        $asciiName = preg_replace('/[^\x20-\x7E]+/', '_', $filename) ?? 'bag_flow.xlsx';
        $response->setHeader(
            'Content-Disposition',
            'attachment; filename="' . $asciiName . '"; filename*=UTF-8\'\'' . rawurlencode($filename)
        );
        $response->setHeader('Pragma', 'no-cache');
        $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
        $response->setBody($output);
        $response->send();
        exit;
    }
}

if (! function_exists('export_xlsx')) {
    /**
     * Office Open XML(.xlsx) 브라우저 다운로드 (PhpSpreadsheet)
     *
     * @param string   $filename   저장 파일명(확장자는 .xlsx로 정규화)
     * @param string   $sheetName  시트 이름(Excel 제한: 길이·일부 문자)
     * @param string[] $headers    컬럼 헤더
     * @param array    $rows       데이터 행(각 행은 배열)
     */
    function export_xlsx(string $filename, string $sheetName, array $headers, array $rows): void
    {
        audit_export_log($filename);
        $filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xlsx';

        $safeSheet = str_replace(['/', '\\', '?', '*', '[', ']'], '', $sheetName);
        $safeSheet = function_exists('mb_substr')
            ? mb_substr($safeSheet, 0, 31, 'UTF-8')
            : substr($safeSheet, 0, 31);
        if ($safeSheet === '') {
            $safeSheet = 'Sheet1';
        }

        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->setTitle($safeSheet);

        $data = [array_map(static fn ($v): string => (string) ($v ?? ''), array_values($headers))];
        foreach ($rows as $row) {
            $data[] = array_map(static fn ($v): string => (string) ($v ?? ''), array_values((array) $row));
        }
        $sheet->fromArray($data, null, 'A1', true);

        $headerCount = max(1, count($headers));
        $rowCount = max(1, count($data));
        $lastCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($headerCount);
        $fullRange = 'A1:' . $lastCol . $rowCount;

        // 헤더/데이터 모두 좌측 정렬(요구사항)
        $sheet->getStyle($fullRange)->getAlignment()->setHorizontal(
            \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT
        );

        // 가독성을 위해 기본 열 너비를 넓게 지정
        for ($i = 1; $i <= $headerCount; $i++) {
            $col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i);
            $sheet->getColumnDimension($col)->setWidth(22);
        }

        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
        ob_start();
        try {
            $writer->save('php://output');
        } catch (\Throwable $e) {
            ob_end_clean();
            $spreadsheet->disconnectWorksheets();
            unset($spreadsheet);
            throw $e;
        }
        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet);
        $output = ob_get_clean();
        if ($output === false) {
            $output = '';
        }

        $response = service('response');
        $response->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
        $response->setHeader('Pragma', 'no-cache');
        $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
        $response->setBody($output);
        $response->send();
        exit;
    }
}
