import * as XLSX from 'xlsx-js-style';

export type ExcelCellValue = string | number | boolean | Date | null | undefined;

export interface StyledExcelColumn<T = any> {
  title: string;
  key?: string;
  width?: number;
  minWidth?: number;
  maxWidth?: number;
  align?: 'left' | 'center' | 'right';
  numFmt?: string;
  render: (row: T, rowIndex: number) => ExcelCellValue;
}

export interface StyledExcelExportOptions<T = any> {
  filename: string;
  sheetName: string;
  title?: string;
  metadataRows?: ExcelCellValue[][];
  summaryRows?: ExcelCellValue[][];
  columns: StyledExcelColumn<T>[];
  rows: T[];
}

const BLACK_BORDER = {
  top: { style: 'thin', color: { rgb: '000000' } },
  right: { style: 'thin', color: { rgb: '000000' } },
  bottom: { style: 'thin', color: { rgb: '000000' } },
  left: { style: 'thin', color: { rgb: '000000' } },
};

const BASE_ALIGNMENT = {
  vertical: 'center',
  wrapText: true,
};

const TITLE_STYLE = {
  font: { bold: true, sz: 16, color: { rgb: 'FFFFFF' } },
  fill: { fgColor: { rgb: '111827' } },
  alignment: { ...BASE_ALIGNMENT, horizontal: 'center' },
  border: BLACK_BORDER,
};

const SECTION_LABEL_STYLE = {
  font: { bold: true, color: { rgb: '111827' } },
  fill: { fgColor: { rgb: 'F3F4F6' } },
  alignment: { ...BASE_ALIGNMENT, horizontal: 'center' },
  border: BLACK_BORDER,
};

const SECTION_VALUE_STYLE = {
  font: { color: { rgb: '111827' } },
  fill: { fgColor: { rgb: 'FFFFFF' } },
  alignment: { ...BASE_ALIGNMENT, horizontal: 'left' },
  border: BLACK_BORDER,
};

const HEADER_STYLE = {
  font: { bold: true, color: { rgb: 'FFFFFF' } },
  fill: { fgColor: { rgb: '374151' } },
  alignment: { ...BASE_ALIGNMENT, horizontal: 'center' },
  border: BLACK_BORDER,
};

const BODY_STYLE = {
  font: { color: { rgb: '111827' } },
  fill: { fgColor: { rgb: 'FFFFFF' } },
  alignment: { ...BASE_ALIGNMENT, horizontal: 'left' },
  border: BLACK_BORDER,
};

function normalizeCellValue(value: ExcelCellValue): ExcelCellValue {
  if (value === null || value === undefined) return '';
  return value;
}

function visualLength(value: ExcelCellValue): number {
  if (value === null || value === undefined) return 0;
  const text = value instanceof Date ? value.toISOString() : String(value);
  const lines = text.split(/\r?\n/);
  return Math.max(...lines.map((line) => Array.from(line).reduce((len, ch) => len + (ch.charCodeAt(0) > 255 ? 2 : 1), 0)), 0);
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(max, Math.max(min, value));
}

function estimateColumnWidths(rows: ExcelCellValue[][], columns: StyledExcelColumn[]): { wch: number }[] {
  const totalColumns = Math.max(columns.length, ...rows.map((row) => row.length));
  return Array.from({ length: totalColumns }, (_, index) => {
    const config = columns[index];
    if (config?.width) return { wch: config.width };

    const maxLength = rows.reduce<number>((max, row) => Math.max(max, visualLength(row[index])), visualLength(config?.title));
    const minWidth = config?.minWidth ?? 10;
    const maxWidth = config?.maxWidth ?? 42;
    return { wch: clamp(Math.ceil(maxLength * 1.15) + 2, minWidth, maxWidth) };
  });
}

function estimateRowHeight(row: ExcelCellValue[], colWidths: { wch: number }[], baseHeight = 20): number {
  const maxLines = row.reduce<number>((max, cell, index) => {
    const width = Math.max(8, colWidths[index]?.wch || 12);
    const text = cell === null || cell === undefined ? '' : String(cell);
    const explicitLines = text.split(/\r?\n/);
    const wrappedLines = explicitLines.reduce((sum, line) => sum + Math.max(1, Math.ceil(visualLength(line) / Math.max(8, width - 2))), 0);
    return Math.max(max, wrappedLines);
  }, 1);

  return clamp(baseHeight + (maxLines - 1) * 16, baseHeight, 120);
}

function applyCellStyle(cell: any, style: any, numFmt?: string): void {
  cell.s = {
    ...style,
    alignment: { ...style.alignment },
    border: BLACK_BORDER,
  };
  if (numFmt) cell.z = numFmt;
}

function isNumericCell(value: ExcelCellValue): boolean {
  return typeof value === 'number' && Number.isFinite(value);
}

export function exportStyledExcel<T>(options: StyledExcelExportOptions<T>): void {
  const { filename, sheetName, title, metadataRows = [], summaryRows = [], columns, rows } = options;
  const totalColumns = Math.max(1, columns.length);
  const aoa: ExcelCellValue[][] = [];
  let titleRowIndex = -1;
  let headerRowIndex = -1;

  if (title) {
    titleRowIndex = aoa.length;
    aoa.push([title, ...Array.from({ length: totalColumns - 1 }, () => '')]);
  }

  metadataRows.forEach((row) => aoa.push(row.map(normalizeCellValue)));
  summaryRows.forEach((row) => aoa.push(row.map(normalizeCellValue)));

  if (metadataRows.length || summaryRows.length) {
    aoa.push(Array.from({ length: totalColumns }, () => ''));
  }

  headerRowIndex = aoa.length;
  aoa.push(columns.map((column) => column.title));
  rows.forEach((row, rowIndex) => {
    aoa.push(columns.map((column) => normalizeCellValue(column.render(row, rowIndex))));
  });

  const worksheet = XLSX.utils.aoa_to_sheet(aoa);
  const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1:A1');
  const colWidths = estimateColumnWidths(aoa, columns);

  worksheet['!cols'] = colWidths;
  worksheet['!rows'] = aoa.map((row, index) => {
    if (index === titleRowIndex) return { hpt: 34 };
    if (index === headerRowIndex) return { hpt: 28 };
    if (row.every((cell) => cell === '')) return { hpt: 10 };
    return { hpt: estimateRowHeight(row, colWidths) };
  });

  if (titleRowIndex >= 0 && totalColumns > 1) {
    worksheet['!merges'] = worksheet['!merges'] || [];
    worksheet['!merges'].push({
      s: { r: titleRowIndex, c: 0 },
      e: { r: titleRowIndex, c: totalColumns - 1 },
    });
  }

  worksheet['!autofilter'] = {
    ref: XLSX.utils.encode_range({
      s: { r: headerRowIndex, c: 0 },
      e: { r: Math.max(headerRowIndex, aoa.length - 1), c: totalColumns - 1 },
    }),
  };

  for (let rowIndex = range.s.r; rowIndex <= range.e.r; rowIndex += 1) {
    for (let colIndex = range.s.c; colIndex <= range.e.c; colIndex += 1) {
      const address = XLSX.utils.encode_cell({ r: rowIndex, c: colIndex });
      const cell = worksheet[address] || { t: 's', v: '' };
      worksheet[address] = cell;

      if (rowIndex === titleRowIndex) {
        applyCellStyle(cell, TITLE_STYLE);
        continue;
      }

      if (rowIndex === headerRowIndex) {
        applyCellStyle(cell, HEADER_STYLE);
        continue;
      }

      if (rowIndex < headerRowIndex) {
        applyCellStyle(cell, colIndex === 0 ? SECTION_LABEL_STYLE : SECTION_VALUE_STYLE, isNumericCell(cell.v) ? '#,##0.00' : undefined);
        continue;
      }

      const column = columns[colIndex];
      const align = column?.align || (isNumericCell(cell.v) ? 'right' : 'left');
      applyCellStyle(cell, {
        ...BODY_STYLE,
        alignment: { ...BASE_ALIGNMENT, horizontal: align },
      }, column?.numFmt || (isNumericCell(cell.v) ? '#,##0.00' : undefined));
    }
  }

  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
  XLSX.writeFile(workbook, filename);
}
