/**
 * Client-side .xlsx export with shared styling (headers, money format, alignment).
 * @see ./EXCEL_EXPORT_STANDARD.md — conventions for new Excel exports
 */
import * as XLSX from "xlsx-js-style";

/** Light grey header background + bold text (exported by xlsx-js-style). */
const HEADER_CELL_STYLE: XLSX.CellStyle = {
  font: { bold: true, color: { rgb: "000000" } },
  fill: {
    patternType: "solid",
    fgColor: { rgb: "D9D9D9" },
  },
  alignment: { vertical: "center", horizontal: "center", wrapText: true },
};

function applyHeaderRowStyle(
  ws: XLSX.WorkSheet,
  columnCount: number
): void {
  for (let colIdx = 0; colIdx < columnCount; colIdx++) {
    const cellRef = XLSX.utils.encode_cell({ r: 0, c: colIdx });
    const cell = ws[cellRef];
    if (cell) {
      cell.s = { ...HEADER_CELL_STYLE };
    }
  }
}

/** Headers for money columns (GRN report & similar): thousands separator in Excel. */
const MONEY_COLUMN_HEADER =
  /金額|單價|Amount|Unit Price|Total Amount|Total Amount \/ 金額|Amount \/ 金額|Unit Price \/ 單價/i;

/** Quantity / numeric columns (right-align with amounts). */
const QTY_COLUMN_HEADER = /Qty|數量|Demand/i;

function isNumericDataColumnHeader(h: string): boolean {
  return MONEY_COLUMN_HEADER.test(h) || QTY_COLUMN_HEADER.test(h);
}

/**
 * Apply Excel number format `#,##0.00` to money columns so values show with comma separators.
 * Handles numeric cells and pre-formatted strings like "1,234.56".
 */
function applyMoneyColumnNumberFormats(ws: XLSX.WorkSheet, headerLabels: string[]): void {
  const moneyColIdx = new Set<number>();
  headerLabels.forEach((h, c) => {
    if (MONEY_COLUMN_HEADER.test(h)) moneyColIdx.add(c);
  });
  if (moneyColIdx.size === 0 || !ws["!ref"]) return;

  const range = XLSX.utils.decode_range(ws["!ref"]);
  for (let r = range.s.r + 1; r <= range.e.r; r++) {
    moneyColIdx.forEach((c) => {
      const cellRef = XLSX.utils.encode_cell({ r, c });
      const cell = ws[cellRef];
      if (!cell) return;

      if (typeof cell.v === "number" && Number.isFinite(cell.v)) {
        cell.t = "n";
        cell.z = "#,##0.00";
        return;
      }
      if (typeof cell.v === "string") {
        const cleaned = cell.v.replace(/,/g, "").trim();
        if (cleaned === "") return;
        const n = Number.parseFloat(cleaned);
        if (!Number.isNaN(n)) {
          cell.v = n;
          cell.t = "n";
          cell.z = "#,##0.00";
        }
      }
    });
  }
}

/** Right-align amount / quantity column headers and data (merge with existing header fill). */
function applyNumericColumnRightAlign(
  ws: XLSX.WorkSheet,
  headerLabels: string[]
): void {
  const numericColIdx = new Set<number>();
  headerLabels.forEach((h, c) => {
    if (isNumericDataColumnHeader(h)) numericColIdx.add(c);
  });
  if (numericColIdx.size === 0 || !ws["!ref"]) return;

  const range = XLSX.utils.decode_range(ws["!ref"]);
  for (let r = range.s.r; r <= range.e.r; r++) {
    numericColIdx.forEach((c) => {
      const cellRef = XLSX.utils.encode_cell({ r, c });
      const cell = ws[cellRef];
      if (!cell) return;
      const prev = (cell.s || {}) as XLSX.CellStyle;
      cell.s = {
        ...prev,
        font: prev.font,
        fill: prev.fill,
        border: prev.border,
        numFmt: prev.numFmt,
        alignment: {
          ...prev.alignment,
          horizontal: "right",
          vertical: "center",
          wrapText: prev.alignment?.wrapText ?? true,
        },
      };
    });
  }
}

/**
 * Export an array of row objects to a .xlsx file and trigger download.
 * @param rows Array of objects (keys become column headers)
 * @param filename Download filename (without .xlsx)
 * @param sheetName Optional sheet name (default "Sheet1")
 */
export function exportChartToXlsx(
  rows: Record<string, unknown>[],
  filename: string,
  sheetName = "Sheet1"
): void {
  if (rows.length === 0) {
    const ws = XLSX.utils.aoa_to_sheet([[]]);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, sheetName);
    XLSX.writeFile(wb, `${filename}.xlsx`);
    return;
  }
  const ws = XLSX.utils.json_to_sheet(rows);

  // Auto-set column widths based on header length (simple heuristic).
  const header = Object.keys(rows[0] ?? {});
  if (header.length > 0) {
    ws["!cols"] = header.map((h) => ({
      // Basic width: header length + padding, minimum 12
      wch: Math.max(12, h.length + 4),
    }));

    applyHeaderRowStyle(ws, header.length);
    applyMoneyColumnNumberFormats(ws, header);
    applyNumericColumnRightAlign(ws, header);
  }

  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, sheetName);
  XLSX.writeFile(wb, `${filename}.xlsx`);
}

export type MultiSheetSpec = { name: string; rows: Record<string, unknown>[] };

/**
 * Export multiple worksheets in one .xlsx file.
 * Sheet names are truncated to 31 characters (Excel limit).
 */
export function exportMultiSheetToXlsx(
  sheets: MultiSheetSpec[],
  filename: string
): void {
  const wb = XLSX.utils.book_new();
  for (const { name, rows } of sheets) {
    const safeName = name.slice(0, 31);
    let ws: ReturnType<typeof XLSX.utils.json_to_sheet>;
    if (rows.length === 0) {
      ws = XLSX.utils.aoa_to_sheet([[]]);
    } else {
      ws = XLSX.utils.json_to_sheet(rows);
      const header = Object.keys(rows[0] ?? {});
      if (header.length > 0) {
        ws["!cols"] = header.map((h) => ({
          wch: Math.max(12, h.length + 4),
        }));
        applyHeaderRowStyle(ws, header.length);
        applyMoneyColumnNumberFormats(ws, header);
        applyNumericColumnRightAlign(ws, header);
      }
    }
    XLSX.utils.book_append_sheet(wb, ws, safeName);
  }
  XLSX.writeFile(wb, `${filename}.xlsx`);
}
