using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
using WinRT.Interop;
using Logger = Sonex.Client.Helpers.Logger;

namespace Sonex.Client.Controls.DataGridView;

// Odpowiedzialnosc pliku: eksport danych gridu do formatow CSV/XLSX/TSV/HTML.

public sealed partial class DataGridView
{
    public enum DataGridExportFormat
    {
        Csv,
        Xlsx,
        Tsv,
        Html
    }

    public enum DataGridExportRowScope
    {
        FilteredRows,
        SelectedRows
    }

    public enum DataGridExportColumnScope
    {
        VisibleColumns,
        SelectedColumns
    }

    // Opcje eksportu pozwalaja zdecydowac m.in. o zakresie danych, formacie i nazwie pliku.
    public sealed class DataGridExportOptions
    {
        public bool IncludeHeaders { get; set; } = true;
        public DataGridExportRowScope RowScope { get; set; } = DataGridExportRowScope.FilteredRows;
        public DataGridExportColumnScope ColumnScope { get; set; } = DataGridExportColumnScope.VisibleColumns;
        public bool FallbackToFilteredRowsWhenNoSelection { get; set; } = true;
        public bool FallbackToVisibleColumnsWhenNoSelection { get; set; } = true;
        public string CsvDelimiter { get; set; } = ",";
        public string? FileNamePrefix { get; set; }
        public string? NavigationName { get; set; }
        public bool OverwriteFile { get; set; } = true;
        public bool XlsxUseFormatting { get; set; } = true;
    }

    private sealed class ExportSnapshot
    {
        // Snapshot stabilizuje dane eksportu, aby nie zależeć od zmian UI w trakcie zapisu.
        public required List<Column> Columns { get; init; }
        public required List<int> RowIndexes { get; init; }
    }

    public async Task<string?> Export(DataGridExportFormat format, string filePath, DataGridExportOptions? options = null)
    {
        try
        {
            return await ExportCore(format, filePath, options ?? new DataGridExportOptions());
        }
        catch (Exception ex)
        {
            LogExportException(nameof(Export), ex);
            return null;
        }
    }

    public async Task<string?> ExportWithDialog(DataGridExportFormat format, DataGridExportOptions? options = null)
    {
        try
        {
            var effectiveOptions = options ?? new DataGridExportOptions();
            var suggestedFileName = BuildAutoFileName(format, effectiveOptions);

            var filePath = await PickSavePathAsync(format, suggestedFileName);
            if (string.IsNullOrWhiteSpace(filePath))
            {
                return null;
            }

            return await ExportCore(format, filePath, effectiveOptions);
        }
        catch (Exception ex)
        {
            LogExportException(nameof(ExportWithDialog), ex);
            return null;
        }
    }

    public async Task<string?> ExportAutoName(DataGridExportFormat format, string targetFolderPath, DataGridExportOptions? options = null)
    {
        if (string.IsNullOrWhiteSpace(targetFolderPath))
        {
            return null;
        }

        try
        {
            var effectiveOptions = options ?? new DataGridExportOptions();
            var fileName = BuildAutoFileName(format, effectiveOptions);
            var filePath = Path.Combine(targetFolderPath, fileName);

            return await ExportCore(format, filePath, effectiveOptions);
        }
        catch (Exception ex)
        {
            LogExportException(nameof(ExportAutoName), ex);
            return null;
        }
    }

    private async Task<string?> ExportCore(DataGridExportFormat format, string filePath, DataGridExportOptions options)
    {
        try
        {
            // Najpierw zbieramy dane wejściowe, potem uruchamiamy writer konkretnego formatu.
            var snapshot = CreateExportSnapshot(options);
            if (snapshot.Columns.Count == 0)
            {
                return null;
            }

            var outputPath = NormalizeOutputPath(filePath, format, options.OverwriteFile);
            if (string.IsNullOrWhiteSpace(outputPath))
            {
                return null;
            }

            var directory = Path.GetDirectoryName(outputPath);
            if (!string.IsNullOrWhiteSpace(directory))
            {
                Directory.CreateDirectory(directory);
            }

            switch (format)
            {
                case DataGridExportFormat.Csv:
                    await WriteDelimitedAsync(outputPath, snapshot, options.IncludeHeaders, options.CsvDelimiter);
                    break;
                case DataGridExportFormat.Tsv:
                    await WriteDelimitedAsync(outputPath, snapshot, options.IncludeHeaders, "\t");
                    break;
                case DataGridExportFormat.Html:
                    await WriteHtmlAsync(outputPath, snapshot, options.IncludeHeaders);
                    break;
                case DataGridExportFormat.Xlsx:
                    await WriteXlsxAsync(outputPath, snapshot, options.IncludeHeaders, options.XlsxUseFormatting);
                    break;
                default:
                    return null;
            }

            return outputPath;
        }
        catch (Exception ex)
        {
            LogExportException(nameof(ExportCore), ex);
            return null;
        }
    }

    private ExportSnapshot CreateExportSnapshot(DataGridExportOptions options)
    {
        // Wspólna warstwa wyboru wierszy/kolumn używana przez wszystkie formaty.
        var columns = GetColumnsForExport(options);
        var rows = GetRowsForExport(options);

        return new ExportSnapshot
        {
            Columns = columns,
            RowIndexes = rows
        };
    }

    private List<Column> GetColumnsForExport(DataGridExportOptions options)
    {
        var visibleColumns = _Columns
            .Where(c => !c.IsHiden)
            .OrderBy(c => c.Index)
            .ToList();

        if (options.ColumnScope != DataGridExportColumnScope.SelectedColumns)
        {
            return visibleColumns;
        }

        var selectedColumns = visibleColumns.Where(c => c.isSelected).ToList();
        if (selectedColumns.Count > 0)
        {
            return selectedColumns;
        }

        return options.FallbackToVisibleColumnsWhenNoSelection ? visibleColumns : selectedColumns;
    }

    private List<int> GetRowsForExport(DataGridExportOptions options)
    {
        var maxRows = _ItemsSourceFiltred?.Count ?? 0;
        if (maxRows == 0)
        {
            return new List<int>();
        }

        if (options.RowScope != DataGridExportRowScope.SelectedRows)
        {
            return Enumerable.Range(0, maxRows).ToList();
        }

        var selectedRows = _SelectedRows?.ToList() ?? new List<int>();
        selectedRows = selectedRows.Where(i => i >= 0 && i < maxRows).OrderBy(i => i).ToList();
        if (selectedRows.Count > 0)
        {
            return selectedRows;
        }

        return options.FallbackToFilteredRowsWhenNoSelection
            ? Enumerable.Range(0, maxRows).ToList()
            : selectedRows;
    }

    private async Task WriteDelimitedAsync(string outputPath, ExportSnapshot snapshot, bool includeHeaders, string delimiter)
    {
        if (string.IsNullOrEmpty(delimiter))
        {
            delimiter = ",";
        }

        var sb = new StringBuilder(capacity: Math.Max(1024, snapshot.RowIndexes.Count * Math.Max(1, snapshot.Columns.Count) * 10));

        if (includeHeaders)
        {
            AppendDelimitedRow(
                sb,
                snapshot.Columns.Select(c => string.IsNullOrWhiteSpace(c.Title) ? c.Name : c.Title),
                delimiter);
        }

        foreach (var rowIndex in snapshot.RowIndexes)
        {
            var values = snapshot.Columns.Select(c => GetExportValue(rowIndex, c) ?? string.Empty);
            AppendDelimitedRow(sb, values, delimiter);
        }

        await File.WriteAllTextAsync(outputPath, sb.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
    }

    private static void AppendDelimitedRow(StringBuilder sb, IEnumerable<string> values, string delimiter)
    {
        var first = true;
        foreach (var value in values)
        {
            if (!first)
            {
                sb.Append(delimiter);
            }

            var normalized = delimiter == "\t" ? NormalizeTsvCell(value ?? string.Empty) : (value ?? string.Empty);
            sb.Append(EscapeDelimitedCell(normalized, delimiter));
            first = false;
        }

        sb.Append("\r\n");
    }

    private static string EscapeDelimitedCell(string value, string delimiter)
    {
        var shouldQuote =
            value.Contains('"')
            || value.Contains('\r')
            || value.Contains('\n')
            || (!string.IsNullOrEmpty(delimiter) && value.Contains(delimiter));

        if (!shouldQuote)
        {
            return value;
        }

        return "\"" + value.Replace("\"", "\"\"") + "\"";
    }

    private async Task WriteHtmlAsync(string outputPath, ExportSnapshot snapshot, bool includeHeaders)
    {
        var sb = new StringBuilder(capacity: Math.Max(2048, snapshot.RowIndexes.Count * Math.Max(1, snapshot.Columns.Count) * 24));
        sb.AppendLine("<!DOCTYPE html>");
        sb.AppendLine("<html lang=\"pl\">");
        sb.AppendLine("<head>");
        sb.AppendLine("  <meta charset=\"utf-8\" />");
        sb.AppendLine("  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />");
        sb.AppendLine("  <title>DataGrid Export</title>");
        sb.AppendLine("  <style>");
        sb.AppendLine("    body { font-family: 'Segoe UI', Arial, sans-serif; margin: 16px; color: #000000; background: #ffffff; }");
        sb.AppendLine("    h3 { margin: 0 0 12px 0; font-size: 16px; font-weight: 600; }");
        sb.AppendLine("    .table-wrap { max-height: 75vh; overflow: auto; border: 1px solid #E3E3E3; border-radius: 6px; scroll-behavior: smooth; }");
        sb.AppendLine("    table { width: 100%; border-collapse: collapse; background: #ffffff; }");
        sb.AppendLine("    th, td { border: 1px solid #E3E3E3; padding: 8px 10px; font-size: 12px; text-align: left; white-space: nowrap; }");
        sb.AppendLine("    thead th { position: sticky; top: 0; z-index: 2; background: #F9F9F9; color: #626262; font-weight: 400; text-align: left; text-indent: 8px; border-bottom: 2px solid #FFD700FF; }");
        sb.AppendLine("    td.cell-left-indent { text-indent: 8px; }");
        sb.AppendLine("    td.cell-right { text-align: right; }");
        sb.AppendLine("    td.cell-center { text-align: center; }");
        sb.AppendLine("    tbody tr:nth-child(even) td { background: #FCFCFC; }");
        sb.AppendLine("    tbody tr:hover td { background: #F7F7F7; }");
        sb.AppendLine("    .table-wrap td, .table-wrap th { cursor: pointer; user-select: text; }");
        sb.AppendLine("    .selected-cell { outline: 2px solid #2563EB; outline-offset: -2px; }");
        sb.AppendLine("    @media print {");
        sb.AppendLine("      body { margin: 0; }");
        sb.AppendLine("      .table-wrap { max-height: none !important; overflow: visible !important; border: none; border-radius: 0; }");
        sb.AppendLine("      table { width: 100%; border-collapse: collapse; }");
        sb.AppendLine("      thead { display: table-header-group !important; }");
        sb.AppendLine("      tbody { display: table-row-group !important; }");
        sb.AppendLine("      tfoot { display: table-footer-group !important; }");
        sb.AppendLine("      thead th { position: static !important; top: auto !important; z-index: auto !important; }");
        sb.AppendLine("      tr { break-inside: avoid-page; page-break-inside: avoid; }");
        sb.AppendLine("      td, th { break-inside: avoid-page; page-break-inside: avoid; }");
        sb.AppendLine("    }");
        sb.AppendLine("  </style>");
        sb.AppendLine("</head>");
        sb.AppendLine("<body>");
        sb.AppendLine($"  <h3>Data export ({WebUtility.HtmlEncode(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture))})</h3>");
        sb.AppendLine("  <div class=\"table-wrap\" id=\"tableWrap\">");
        sb.AppendLine("    <table>");

        if (includeHeaders)
        {
            sb.AppendLine("    <thead><tr>");
            foreach (var column in snapshot.Columns)
            {
                var title = string.IsNullOrWhiteSpace(column.Title) ? column.Name : column.Title;
                sb.Append("      <th>").Append(WebUtility.HtmlEncode(title)).AppendLine("</th>");
            }

            sb.AppendLine("    </tr></thead>");
        }

        sb.AppendLine("    <tbody>");
        foreach (var rowIndex in snapshot.RowIndexes)
        {
            sb.AppendLine("      <tr>");
            foreach (var column in snapshot.Columns)
            {
                var value = GetExportValue(rowIndex, column) ?? string.Empty;
                var cellClass = GetHtmlCellClass(column);
                if (string.IsNullOrEmpty(cellClass))
                {
                    sb.Append("        <td>").Append(WebUtility.HtmlEncode(value)).AppendLine("</td>");
                }
                else
                {
                    sb.Append("        <td class=\"")
                      .Append(cellClass)
                      .Append("\">")
                      .Append(WebUtility.HtmlEncode(value))
                      .AppendLine("</td>");
                }
            }

            sb.AppendLine("      </tr>");
        }

        sb.AppendLine("    </tbody>");
        sb.AppendLine("    </table>");
        sb.AppendLine("  </div>");
        sb.AppendLine("  <script>");
        sb.AppendLine("    const wrap = document.getElementById('tableWrap');");
        sb.AppendLine("    let selectedCell = null;");
        sb.AppendLine("    function setSelectedCell(cell) {");
        sb.AppendLine("      if (selectedCell) { selectedCell.classList.remove('selected-cell'); }");
        sb.AppendLine("      selectedCell = cell;");
        sb.AppendLine("      if (selectedCell) { selectedCell.classList.add('selected-cell'); }");
        sb.AppendLine("    }");
        sb.AppendLine("    async function copySelectedCellText() {");
        sb.AppendLine("      if (!selectedCell) { return; }");
        sb.AppendLine("      const text = (selectedCell.innerText || selectedCell.textContent || '').trim();");
        sb.AppendLine("      if (text.length === 0) { return; }");
        sb.AppendLine("      try {");
        sb.AppendLine("        if (navigator.clipboard && navigator.clipboard.writeText) {");
        sb.AppendLine("          await navigator.clipboard.writeText(text);");
        sb.AppendLine("          return;");
        sb.AppendLine("        }");
        sb.AppendLine("      } catch (_) { }");
        sb.AppendLine("      const temp = document.createElement('textarea');");
        sb.AppendLine("      temp.value = text;");
        sb.AppendLine("      temp.setAttribute('readonly', 'readonly');");
        sb.AppendLine("      temp.style.position = 'fixed';");
        sb.AppendLine("      temp.style.opacity = '0';");
        sb.AppendLine("      document.body.appendChild(temp);");
        sb.AppendLine("      temp.select();");
        sb.AppendLine("      document.execCommand('copy');");
        sb.AppendLine("      document.body.removeChild(temp);");
        sb.AppendLine("    }");
        sb.AppendLine("    wrap.addEventListener('click', (e) => {");
        sb.AppendLine("      const cell = e.target.closest('td, th');");
        sb.AppendLine("      if (!cell || !wrap.contains(cell)) { return; }");
        sb.AppendLine("      setSelectedCell(cell);");
        sb.AppendLine("    });");
        sb.AppendLine("    wrap.addEventListener('dblclick', () => {");
        sb.AppendLine("      copySelectedCellText();");
        sb.AppendLine("    });");
        sb.AppendLine("    window.addEventListener('keydown', (e) => {");
        sb.AppendLine("      const isCopy = (e.ctrlKey || e.metaKey) && (e.key === 'c' || e.key === 'C');");
        sb.AppendLine("      if (!isCopy || !selectedCell) { return; }");
        sb.AppendLine("      e.preventDefault();");
        sb.AppendLine("      copySelectedCellText();");
        sb.AppendLine("    });");
        sb.AppendLine("  </script>");
        sb.AppendLine("</body>");
        sb.AppendLine("</html>");

        await File.WriteAllTextAsync(outputPath, sb.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
    }

    private async Task WriteXlsxAsync(string outputPath, ExportSnapshot snapshot, bool includeHeaders, bool withFormatting)
    {
        // XLSX generujemy ręcznie jako zestaw XML-i spakowanych do ZIP (format OpenXML).
        await Task.Run(() =>
        {
            using var stream = File.Create(outputPath);
            using var archive = new ZipArchive(stream, ZipArchiveMode.Create);

            var sheetXml = BuildWorksheetXml(snapshot, includeHeaders, withFormatting);

            WriteZipEntry(archive, "[Content_Types].xml", BuildContentTypesXml(withFormatting));
            WriteZipEntry(archive, "_rels/.rels", BuildRootRelationshipsXml());
            WriteZipEntry(archive, "xl/workbook.xml", BuildWorkbookXml());
            WriteZipEntry(archive, "xl/_rels/workbook.xml.rels", BuildWorkbookRelationshipsXml(withFormatting));
            if (withFormatting)
            {
                WriteZipEntry(archive, "xl/styles.xml", BuildStylesXml());
            }
            WriteZipEntry(archive, "xl/worksheets/sheet1.xml", sheetXml);
        });
    }

    private string BuildWorksheetXml(ExportSnapshot snapshot, bool includeHeaders, bool withFormatting)
    {
        var sb = new StringBuilder(capacity: Math.Max(4096, snapshot.RowIndexes.Count * Math.Max(1, snapshot.Columns.Count) * 36));
        sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
        sb.Append("<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">");
        AppendWorksheetViewAndFreeze(sb, snapshot, includeHeaders);
        AppendWorksheetColumns(sb, snapshot);
        sb.Append("<sheetData>");

        uint? headerStyleIndex = withFormatting ? 1u : null;
        uint? dataStyleIndex = withFormatting ? 2u : null;
        uint? dataLeftStyleIndex = withFormatting ? 3u : null;

        var rowNumber = 1;
        if (includeHeaders)
        {
            sb.Append("<row r=\"").Append(rowNumber).Append("\">");
            for (var colIndex = 0; colIndex < snapshot.Columns.Count; colIndex++)
            {
                var column = snapshot.Columns[colIndex];
                var header = string.IsNullOrWhiteSpace(column.Title) ? column.Name : column.Title;
                AppendInlineStringCellXml(sb, colIndex + 1, rowNumber, header, headerStyleIndex);
            }

            sb.Append("</row>");
            rowNumber++;
        }

        foreach (var sourceRowIndex in snapshot.RowIndexes)
        {
            sb.Append("<row r=\"").Append(rowNumber).Append("\">");
            for (var colIndex = 0; colIndex < snapshot.Columns.Count; colIndex++)
            {
                var column = snapshot.Columns[colIndex];
                var cellStyleIndex = dataStyleIndex;
                if (withFormatting && IsLeftAlignedColumn(column))
                {
                    cellStyleIndex = dataLeftStyleIndex;
                }

                AppendWorksheetDataCellXml(sb, colIndex + 1, rowNumber, sourceRowIndex, column, cellStyleIndex);
            }

            sb.Append("</row>");
            rowNumber++;
        }

        sb.Append("</sheetData>");
        if (includeHeaders && snapshot.Columns.Count > 0)
        {
            AppendAutoFilter(sb, snapshot.Columns.Count);
        }

        sb.Append("</worksheet>");
        return sb.ToString();
    }

    private static void AppendWorksheetViewAndFreeze(StringBuilder sb, ExportSnapshot snapshot, bool includeHeaders)
    {
        var freezeColumns = snapshot.Columns.TakeWhile(c => c.IsPinned).Count();
        var freezeRows = includeHeaders ? 1 : 0;

        sb.Append("<sheetViews><sheetView workbookViewId=\"0\">");
        if (freezeColumns > 0 || freezeRows > 0)
        {
            var topLeftCell = GetExcelCellReference(Math.Max(1, freezeColumns + 1), Math.Max(1, freezeRows + 1));
            sb.Append("<pane");
            if (freezeColumns > 0)
            {
                sb.Append(" xSplit=\"").Append(freezeColumns.ToString(CultureInfo.InvariantCulture)).Append("\"");
            }

            if (freezeRows > 0)
            {
                sb.Append(" ySplit=\"").Append(freezeRows.ToString(CultureInfo.InvariantCulture)).Append("\"");
            }

            var activePane = freezeColumns > 0 && freezeRows > 0
                ? "bottomRight"
                : freezeColumns > 0
                    ? "topRight"
                    : "bottomLeft";

            sb.Append(" topLeftCell=\"")
              .Append(topLeftCell)
              .Append("\" activePane=\"")
              .Append(activePane)
              .Append("\" state=\"frozen\"/>");
            sb.Append("<selection pane=\"")
              .Append(activePane)
              .Append("\" activeCell=\"")
              .Append(topLeftCell)
              .Append("\" sqref=\"")
              .Append(topLeftCell)
              .Append("\"/>");
        }

        sb.Append("</sheetView></sheetViews>");
    }

    private static void AppendWorksheetColumns(StringBuilder sb, ExportSnapshot snapshot)
    {
        sb.Append("<cols>");
        for (var i = 0; i < snapshot.Columns.Count; i++)
        {
            var col = snapshot.Columns[i];
            var pixels = col.Width > 0 ? col.Width : 88d;
            var excelWidth = ConvertPixelsToExcelWidth(pixels);
            var index = i + 1;

            sb.Append("<col min=\"")
              .Append(index)
              .Append("\" max=\"")
              .Append(index)
              .Append("\" width=\"")
              .Append(excelWidth.ToString("0.##", CultureInfo.InvariantCulture))
              .Append("\" customWidth=\"1\"/>");
        }

        sb.Append("</cols>");
    }

    private static double ConvertPixelsToExcelWidth(double pixels)
    {
        if (double.IsNaN(pixels) || double.IsInfinity(pixels) || pixels <= 0)
        {
            pixels = 88d;
        }

        var width = (pixels - 5d) / 7d;
        width = Math.Clamp(width, 1d, 255d);
        return Math.Round(width, 2);
    }

    private static void AppendAutoFilter(StringBuilder sb, int columnCount)
    {
        var endCol = GetExcelColumnName(columnCount);
        sb.Append("<autoFilter ref=\"A1:").Append(endCol).Append("1\"/>");
    }

    private void AppendWorksheetDataCellXml(StringBuilder sb, int excelColumnIndex, int excelRowIndex, int sourceRowIndex, Column column, uint? styleIndex)
    {
        var reference = GetExcelCellReference(excelColumnIndex, excelRowIndex);
        var raw = GetRawValue(sourceRowIndex, column);
        var styleAttr = GetStyleAttr(styleIndex);

        if (raw == null)
        {
            sb.Append("<c r=\"").Append(reference).Append("\"").Append(styleAttr).Append("/>");
            return;
        }

        if (raw is bool b)
        {
            sb.Append("<c r=\"").Append(reference).Append("\"").Append(styleAttr).Append(" t=\"b\"><v>").Append(b ? "1" : "0").Append("</v></c>");
            return;
        }

        if (TryConvertToInvariantNumber(raw, out var number))
        {
            sb.Append("<c r=\"").Append(reference).Append("\"").Append(styleAttr).Append("><v>").Append(number).Append("</v></c>");
            return;
        }

        var text = GetExportValue(sourceRowIndex, column) ?? string.Empty;
        AppendInlineStringCellXml(sb, excelColumnIndex, excelRowIndex, text, styleIndex);
    }

    private static void AppendInlineStringCellXml(StringBuilder sb, int excelColumnIndex, int excelRowIndex, string text, uint? styleIndex)
    {
        var reference = GetExcelCellReference(excelColumnIndex, excelRowIndex);
        var escaped = EscapeXml(text);
        var needsPreserve = text.Length > 0 && (char.IsWhiteSpace(text[0]) || char.IsWhiteSpace(text[^1]));
        var styleAttr = GetStyleAttr(styleIndex);

        sb.Append("<c r=\"").Append(reference).Append("\"").Append(styleAttr).Append(" t=\"inlineStr\"><is><t");
        if (needsPreserve)
        {
            sb.Append(" xml:space=\"preserve\"");
        }

        sb.Append(">").Append(escaped).Append("</t></is></c>");
    }

    private static string GetStyleAttr(uint? styleIndex)
    {
        return styleIndex.HasValue
            ? $" s=\"{styleIndex.Value.ToString(CultureInfo.InvariantCulture)}\""
            : string.Empty;
    }

    private static string EscapeXml(string text)
    {
        return System.Security.SecurityElement.Escape(text) ?? string.Empty;
    }

    private static bool TryConvertToInvariantNumber(object value, out string number)
    {
        switch (value)
        {
            case byte b8:
                number = b8.ToString(CultureInfo.InvariantCulture);
                return true;
            case sbyte sb8:
                number = sb8.ToString(CultureInfo.InvariantCulture);
                return true;
            case short s16:
                number = s16.ToString(CultureInfo.InvariantCulture);
                return true;
            case ushort u16:
                number = u16.ToString(CultureInfo.InvariantCulture);
                return true;
            case int i32:
                number = i32.ToString(CultureInfo.InvariantCulture);
                return true;
            case uint u32:
                number = u32.ToString(CultureInfo.InvariantCulture);
                return true;
            case long i64:
                number = i64.ToString(CultureInfo.InvariantCulture);
                return true;
            case ulong u64:
                number = u64.ToString(CultureInfo.InvariantCulture);
                return true;
            case float f32:
                number = f32.ToString("0.################", CultureInfo.InvariantCulture);
                return true;
            case double f64:
                number = f64.ToString("0.################", CultureInfo.InvariantCulture);
                return true;
            case decimal dec:
                number = dec.ToString("0.################", CultureInfo.InvariantCulture);
                return true;
            default:
                number = string.Empty;
                return false;
        }
    }

    private object? GetRawValue(int rowIndex, Column column)
    {
        if (_ItemsSourceFiltred == null || rowIndex < 0 || rowIndex >= _ItemsSourceFiltred.Count)
        {
            return null;
        }

        if (column.PropertyInfo == null)
        {
            return null;
        }

        var item = _ItemsSourceFiltred[rowIndex];
        return item == null ? null : column.PropertyInfo.GetValue(item);
    }

    private static string GetExcelCellReference(int columnIndex, int rowIndex)
    {
        return GetExcelColumnName(columnIndex) + rowIndex.ToString(CultureInfo.InvariantCulture);
    }

    private static string GetExcelColumnName(int columnIndex)
    {
        if (columnIndex <= 0)
        {
            return "A";
        }

        var sb = new StringBuilder();
        var n = columnIndex;
        while (n > 0)
        {
            n--;
            sb.Insert(0, (char)('A' + (n % 26)));
            n /= 26;
        }

        return sb.ToString();
    }

    private static bool IsLeftAlignedColumn(Column column)
    {
        return column.TextFormat?.HorizontalAlignment == Microsoft.Graphics.Canvas.Text.CanvasHorizontalAlignment.Left;
    }

    private static string GetHtmlCellClass(Column column)
    {
        var alignment = column.TextFormat?.HorizontalAlignment;
        return alignment switch
        {
            Microsoft.Graphics.Canvas.Text.CanvasHorizontalAlignment.Left => "cell-left-indent",
            Microsoft.Graphics.Canvas.Text.CanvasHorizontalAlignment.Right => "cell-right",
            Microsoft.Graphics.Canvas.Text.CanvasHorizontalAlignment.Center => "cell-center",
            _ => string.Empty
        };
    }

    private static string BuildContentTypesXml(bool withFormatting)
    {
        var sb = new StringBuilder();
        sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
        sb.Append("<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">");
        sb.Append("<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>");
        sb.Append("<Default Extension=\"xml\" ContentType=\"application/xml\"/>");
        sb.Append("<Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>");
        sb.Append("<Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>");
        if (withFormatting)
        {
            sb.Append("<Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/>");
        }

        sb.Append("</Types>");
        return sb.ToString();
    }

    private static string BuildRootRelationshipsXml()
    {
        return
            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
            "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">" +
            "<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>" +
            "</Relationships>";
    }

    private static string BuildWorkbookXml()
    {
        return
            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
            "<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" " +
            "xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">" +
            "<sheets><sheet name=\"Data\" sheetId=\"1\" r:id=\"rId1\"/></sheets>" +
            "</workbook>";
    }

    private static string BuildWorkbookRelationshipsXml(bool withFormatting)
    {
        var sb = new StringBuilder();
        sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
        sb.Append("<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
        sb.Append("<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet1.xml\"/>");
        if (withFormatting)
        {
            sb.Append("<Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>");
        }

        sb.Append("</Relationships>");
        return sb.ToString();
    }

    private static string BuildStylesXml()
    {
        return
            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
            "<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">" +
            "<fonts count=\"2\">" +
            "<font><sz val=\"11\"/><color rgb=\"FF000000\"/><name val=\"Segoe UI\"/><family val=\"2\"/></font>" +
            "<font><sz val=\"11\"/><color rgb=\"FF626262\"/><name val=\"Segoe UI\"/><family val=\"2\"/></font>" +
            "</fonts>" +
            "<fills count=\"3\">" +
            "<fill><patternFill patternType=\"none\"/></fill>" +
            "<fill><patternFill patternType=\"gray125\"/></fill>" +
            "<fill><patternFill patternType=\"solid\"><fgColor rgb=\"FFF9F9F9\"/><bgColor indexed=\"64\"/></patternFill></fill>" +
            "</fills>" +
            "<borders count=\"3\">" +
            "<border><left/><right/><top/><bottom/><diagonal/></border>" +
            "<border><left style=\"thin\"><color rgb=\"FFE3E3E3\"/></left><right style=\"thin\"><color rgb=\"FFE3E3E3\"/></right><top style=\"thin\"><color rgb=\"FFE3E3E3\"/></top><bottom style=\"thin\"><color rgb=\"FFE3E3E3\"/></bottom><diagonal/></border>" +
            "<border><left style=\"thin\"><color rgb=\"FFE3E3E3\"/></left><right style=\"thin\"><color rgb=\"FFE3E3E3\"/></right><top style=\"thin\"><color rgb=\"FFE3E3E3\"/></top><bottom style=\"medium\"><color rgb=\"FFFFD700\"/></bottom><diagonal/></border>" +
            "</borders>" +
            "<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>" +
            "<cellXfs count=\"4\">" +
            "<xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/>" +
            "<xf numFmtId=\"0\" fontId=\"1\" fillId=\"2\" borderId=\"2\" xfId=\"0\" applyFont=\"1\" applyFill=\"1\" applyBorder=\"1\" applyAlignment=\"1\"><alignment horizontal=\"left\" vertical=\"center\" indent=\"1\"/></xf>" +
            "<xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"1\" xfId=\"0\" applyBorder=\"1\"/>" +
            "<xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"1\" xfId=\"0\" applyBorder=\"1\" applyAlignment=\"1\"><alignment horizontal=\"left\" vertical=\"center\" indent=\"1\"/></xf></cellXfs>" +
            "<cellStyles count=\"1\"><cellStyle name=\"Normal\" xfId=\"0\" builtinId=\"0\"/></cellStyles>" +
            "</styleSheet>";
    }

    private static void WriteZipEntry(ZipArchive archive, string entryPath, string content)
    {
        var entry = archive.CreateEntry(entryPath, CompressionLevel.Fastest);
        using var stream = entry.Open();
        using var writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
        writer.Write(content);
    }

    private async Task<string?> PickSavePathAsync(DataGridExportFormat format, string suggestedFileName)
    {
        try
        {
            var extension = "." + GetFileExtension(format);
            var picker = new FileSavePicker
            {
                SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
                SuggestedFileName = Path.GetFileNameWithoutExtension(suggestedFileName)
            };

            picker.FileTypeChoices.Add(format.ToString().ToUpperInvariant(), new List<string> { extension });

            var hwnd = GetPickerWindowHandle();
            if (hwnd != IntPtr.Zero)
            {
                InitializeWithWindow.Initialize(picker, hwnd);
            }

            var file = await picker.PickSaveFileAsync();
            if (file == null)
            {
                return null;
            }

            return EnsureExtension(file.Path, extension);
        }
        catch (Exception ex)
        {
            LogExportException(nameof(PickSavePathAsync), ex);
            return null;
        }
    }

    private static string? NormalizeOutputPath(string filePath, DataGridExportFormat format, bool overwrite)
    {
        try
        {
            if (string.IsNullOrWhiteSpace(filePath))
            {
                return null;
            }

            var extension = "." + GetFileExtension(format);
            var normalized = EnsureExtension(filePath, extension);
            if (string.IsNullOrWhiteSpace(normalized))
            {
                return null;
            }

            if (overwrite || !File.Exists(normalized))
            {
                return normalized;
            }

            var folder = Path.GetDirectoryName(normalized) ?? string.Empty;
            var fileName = Path.GetFileNameWithoutExtension(normalized);
            var ext = Path.GetExtension(normalized);

            var suffix = 1;
            string candidate;
            do
            {
                candidate = Path.Combine(folder, $"{fileName}_{suffix}{ext}");
                suffix++;
            } while (File.Exists(candidate));

            return candidate;
        }
        catch (Exception ex)
        {
            LogExportException(nameof(NormalizeOutputPath), ex);
            return null;
        }
    }

    private string BuildAutoFileName(DataGridExportFormat format, DataGridExportOptions options)
    {
        var baseName = ResolveExportContextName(options.NavigationName, options.FileNamePrefix);
        var stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
        var ext = GetFileExtension(format);
        return $"{baseName}_{stamp}.{ext}";
    }

    private string ResolveExportContextName(string? navigationName, string? fileNamePrefix)
    {
        if (!string.IsNullOrWhiteSpace(fileNamePrefix))
        {
            return SanitizeFileName(fileNamePrefix);
        }

        if (!string.IsNullOrWhiteSpace(navigationName))
        {
            return SanitizeFileName(navigationName);
        }

        var page = FindAncestor<Page>(this);
        var pageName = page?.Frame?.SourcePageType?.Name ?? page?.GetType().Name;
        if (string.IsNullOrWhiteSpace(pageName))
        {
            pageName = "DataGridView";
        }

        if (pageName.EndsWith("Page", StringComparison.OrdinalIgnoreCase))
        {
            pageName = pageName[..^4];
        }

        return SanitizeFileName(pageName);
    }

    private static string SanitizeFileName(string fileName)
    {
        var result = fileName.Trim();
        foreach (var invalid in Path.GetInvalidFileNameChars())
        {
            result = result.Replace(invalid, '_');
        }

        while (result.Contains("  ", StringComparison.Ordinal))
        {
            result = result.Replace("  ", " ", StringComparison.Ordinal);
        }

        if (string.IsNullOrWhiteSpace(result))
        {
            return "DataGridView";
        }

        return result.Replace(' ', '_');
    }

    private static string EnsureExtension(string path, string extensionWithDot)
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            return string.Empty;
        }

        if (string.IsNullOrWhiteSpace(Path.GetExtension(path)))
        {
            return path + extensionWithDot;
        }

        return path;
    }

    private static string GetFileExtension(DataGridExportFormat format)
    {
        return format switch
        {
            DataGridExportFormat.Csv => "csv",
            DataGridExportFormat.Xlsx => "xlsx",
            DataGridExportFormat.Tsv => "tsv",
            DataGridExportFormat.Html => "html",
            _ => "txt"
        };
    }

    private static T? FindAncestor<T>(DependencyObject? start) where T : DependencyObject
    {
        var current = start;
        while (current != null)
        {
            if (current is T found)
            {
                return found;
            }

            current = VisualTreeHelper.GetParent(current);
        }

        return null;
    }

    private static IntPtr GetPickerWindowHandle()
    {
        try
        {
            var hwnd = GetActiveWindow();
            if (hwnd != IntPtr.Zero)
            {
                return hwnd;
            }

            return GetForegroundWindow();
        }
        catch (Exception ex)
        {
            LogExportException(nameof(GetPickerWindowHandle), ex);
            return IntPtr.Zero;
        }
    }

    private static void LogExportException(string context, Exception ex)
    {
        Logger.Error(
            ex,
            source: nameof(DataGridView),
            operation: $"Export.{context}");
    }

    [DllImport("user32.dll")]
    private static extern IntPtr GetActiveWindow();

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();
}
