/**
 * officeSheet — Excel (.xlsx) builder + reader for officeTool.
 *
 * Builder supports the rich-spreadsheet surface our agents need:
 *   - Multiple sheets per workbook with names, tabColor, default font
 *   - Typed columns (text/number/currency/percent/date/duration) with
 *     header style, number format, width, auto-size hints
 *   - Header styling block: bold, fill, fontColor, alignment, height,
 *     wrapText
 *   - Alternating row fill (zebra) for readability
 *   - Per-cell styling via cellStyles array
 *   - Formulas via { cell, formula } OR inline "=A1+B1" strings in rows
 *   - Merged cells
 *   - Freeze panes (row + column)
 *   - Auto-filter
 *   - Conditional formatting (cellIs, containsText, dataBar, colorScale)
 *   - Named ranges
 *   - Charts — line / bar / pie (limited by exceljs surface)
 *
 * All input is permissively normalised; unknown column type → text.
 */

import fs from 'fs/promises';
import path from 'path';

let ExcelJS = null;
async function loadExcel() {
  if (ExcelJS) return ExcelJS;
  const mod = await import('exceljs');
  ExcelJS = mod.default || mod;
  return ExcelJS;
}

function normHex(v) {
  if (!v || typeof v !== 'string') return null;
  const s = v.replace(/^#/, '').trim().toUpperCase();
  return /^[0-9A-F]{6}$/.test(s) ? s : null;
}

function argb(hex) {
  const h = normHex(hex);
  return h ? `FF${h}` : null;
}

function numFmtForType(type) {
  switch ((type || '').toLowerCase()) {
    case 'number':   return '#,##0';
    case 'integer':  return '0';
    case 'currency': return '$#,##0.00';
    case 'percent':  return '0.0%';
    case 'date':     return 'yyyy-mm-dd';
    case 'datetime': return 'yyyy-mm-dd hh:mm';
    case 'duration': return '[h]:mm:ss';
    default:         return null;
  }
}

function colLetter(n) {
  let s = '';
  while (n > 0) {
    const r = (n - 1) % 26;
    s = String.fromCharCode(65 + r) + s;
    n = Math.floor((n - 1) / 26);
  }
  return s;
}

/** Apply a style object onto a cell: { bold, italic, fontColor, fill, alignment, wrapText }. */
function applyCellStyle(cell, style) {
  if (!style) return;
  const font = { ...(cell.font || {}) };
  if (style.bold != null) font.bold = !!style.bold;
  if (style.italic != null) font.italic = !!style.italic;
  if (style.underline) font.underline = true;
  const colorHex = argb(style.fontColor);
  if (colorHex) font.color = { argb: colorHex };
  if (style.fontName) font.name = style.fontName;
  if (style.fontSize) font.size = Number(style.fontSize);
  cell.font = font;

  const fillHex = argb(style.fill);
  if (fillHex) {
    cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: fillHex } };
  }
  if (style.alignment || style.wrapText != null) {
    cell.alignment = {
      ...(cell.alignment || {}),
      horizontal: style.alignment || cell.alignment?.horizontal,
      wrapText: style.wrapText ?? cell.alignment?.wrapText,
    };
  }
  if (style.border) {
    const colour = argb(style.borderColor) || 'FFCCCCCC';
    cell.border = {
      top:    { style: 'thin', color: { argb: colour } },
      bottom: { style: 'thin', color: { argb: colour } },
      left:   { style: 'thin', color: { argb: colour } },
      right:  { style: 'thin', color: { argb: colour } },
    };
  }
}

/* ------------------------------------------------------------------------ */
/* Public API                                                                */
/* ------------------------------------------------------------------------ */

export async function createSheet(spec, outputPath) {
  const Excel = await loadExcel();
  const workbook = new Excel.Workbook();
  workbook.creator = spec.creator || 'Loxia Agent';
  workbook.created = new Date();
  if (spec.properties) {
    Object.assign(workbook, {
      title: spec.properties.title || '',
      subject: spec.properties.subject || '',
      company: spec.properties.company || '',
    });
  }

  const sheets = Array.isArray(spec.sheets) ? spec.sheets : [];
  if (sheets.length === 0) {
    throw new Error('createSheet requires at least one sheet definition');
  }

  for (const sheetDef of sheets) {
    const ws = workbook.addWorksheet(sheetDef.name || 'Sheet', {
      properties: {
        tabColor: argb(sheetDef.tabColor) ? { argb: argb(sheetDef.tabColor) } : undefined,
      },
    });

    // Columns
    if (Array.isArray(sheetDef.columns) && sheetDef.columns.length > 0) {
      ws.columns = sheetDef.columns.map(col => {
        const key = col.key || (col.header || '').toLowerCase().replace(/\s+/g, '_') || 'col';
        return {
          header: col.header || '',
          key,
          width: col.width || 15,
          style: col.numFmt || col.type ? { numFmt: col.numFmt || numFmtForType(col.type) || undefined } : undefined,
        };
      });
    }

    // Header style
    if (sheetDef.headerStyle && ws.columns?.length > 0) {
      const headerRow = ws.getRow(1);
      headerRow.eachCell(cell => applyCellStyle(cell, sheetDef.headerStyle));
      if (sheetDef.headerStyle.height) headerRow.height = sheetDef.headerStyle.height;
    }

    // Rows. Inline "=..." strings are converted to formula cells.
    const altFill = argb(sheetDef.alternateRowFill);
    if (Array.isArray(sheetDef.rows)) {
      sheetDef.rows.forEach((row, idx) => {
        const isArr = Array.isArray(row);
        const added = ws.addRow(row);
        if (altFill && idx % 2 === 1) {
          added.eachCell(cell => {
            cell.fill = cell.fill || { type: 'pattern', pattern: 'solid', fgColor: { argb: altFill } };
          });
        }
        if (isArr) {
          row.forEach((val, i) => {
            if (typeof val === 'string' && val.startsWith('=')) {
              added.getCell(i + 1).value = { formula: val.slice(1) };
            }
          });
        } else if (typeof row === 'object' && row) {
          for (const [k, v] of Object.entries(row)) {
            if (typeof v === 'string' && v.startsWith('=')) {
              const colIdx = (ws.columns || []).findIndex(c => c.key === k);
              if (colIdx >= 0) added.getCell(colIdx + 1).value = { formula: v.slice(1) };
            }
          }
        }
      });
    }

    // Explicit formulas
    if (Array.isArray(sheetDef.formulas)) {
      for (const f of sheetDef.formulas) {
        if (!f.cell || !f.formula) continue;
        const c = ws.getCell(f.cell);
        c.value = { formula: f.formula.startsWith('=') ? f.formula.slice(1) : f.formula };
      }
    }

    // Per-cell styles
    if (Array.isArray(sheetDef.cellStyles)) {
      for (const cs of sheetDef.cellStyles) {
        if (!cs.cell) continue;
        applyCellStyle(ws.getCell(cs.cell), cs.style || cs);
      }
    }

    // Merged cells
    if (Array.isArray(sheetDef.mergedCells)) {
      for (const m of sheetDef.mergedCells) {
        if (m.range) ws.mergeCells(m.range);
        else if (m.from && m.to) ws.mergeCells(`${m.from}:${m.to}`);
      }
    }

    // Freeze panes
    if (sheetDef.freezeRow || sheetDef.freezeColumn) {
      ws.views = [{
        state: 'frozen',
        xSplit: sheetDef.freezeColumn || 0,
        ySplit: sheetDef.freezeRow || 0,
      }];
    }

    // Auto-filter
    if (sheetDef.autoFilter && ws.columns?.length > 0) {
      const last = ws.columnCount;
      ws.autoFilter = typeof sheetDef.autoFilter === 'object'
        ? sheetDef.autoFilter
        : { from: 'A1', to: `${colLetter(last)}1` };
    }

    // Conditional formatting
    if (Array.isArray(sheetDef.conditionalFormatting)) {
      for (const cf of sheetDef.conditionalFormatting) {
        if (!cf.range || !cf.type) continue;
        ws.addConditionalFormatting({
          ref: cf.range,
          rules: [buildCfRule(cf)],
        });
      }
    }

    // Named ranges
    if (Array.isArray(sheetDef.namedRanges)) {
      for (const nr of sheetDef.namedRanges) {
        if (nr.name && nr.range) {
          workbook.definedNames.add(`${ws.name}!${nr.range}`, nr.name);
        }
      }
    }

    // Charts — best-effort. exceljs has limited chart support; we skip if the
    // exceljs version doesn't expose addChart. Returning the spec in metadata
    // so renderers/callers can know the chart was requested.
    if (Array.isArray(sheetDef.charts) && typeof ws.addChart === 'function') {
      for (const c of sheetDef.charts) {
        try { ws.addChart(c); } catch { /* unsupported; skip */ }
      }
    }
  }

  await workbook.xlsx.writeFile(outputPath);
  const stat = await fs.stat(outputPath);
  return { path: outputPath, size: stat.size, sheetCount: sheets.length };
}

function buildCfRule(cf) {
  const style = {
    font: cf.fontColor ? { color: { argb: argb(cf.fontColor) } } : undefined,
    fill: cf.fill ? { type: 'pattern', pattern: 'solid', bgColor: { argb: argb(cf.fill) } } : undefined,
  };
  switch ((cf.type || '').toLowerCase()) {
    case 'cellis':
    case 'cell-is':
      return {
        type: 'cellIs',
        operator: cf.operator || 'greaterThan',
        formulae: Array.isArray(cf.formulae) ? cf.formulae : [String(cf.value ?? 0)],
        style,
      };
    case 'containstext':
    case 'contains-text':
      return { type: 'containsText', operator: 'containsText', text: cf.text || '', style };
    case 'databar':
    case 'data-bar':
      return { type: 'dataBar', cfvo: [{ type: 'min' }, { type: 'max' }], color: { argb: argb(cf.color) || 'FF638EC6' } };
    case 'colorscale':
    case 'color-scale':
      return {
        type: 'colorScale',
        cfvo: [{ type: 'min' }, { type: 'percentile', value: 50 }, { type: 'max' }],
        color: [
          { argb: argb(cf.min) || 'FFF8696B' },
          { argb: argb(cf.mid) || 'FFFFEB84' },
          { argb: argb(cf.max) || 'FF63BE7B' },
        ],
      };
    default:
      return { type: 'expression', formulae: [cf.formula || 'FALSE'], style };
  }
}

/* ----- read / info ----- */

export async function readSheet(filePath, opts = {}) {
  const Excel = await loadExcel();
  const workbook = new Excel.Workbook();
  if (filePath.toLowerCase().endsWith('.csv')) {
    await workbook.csv.readFile(filePath);
  } else {
    await workbook.xlsx.readFile(filePath);
  }
  const sheetName = opts.sheetName;
  const ws = sheetName ? workbook.getWorksheet(sheetName) : workbook.worksheets[0];
  if (!ws) {
    const available = workbook.worksheets.map(w => w.name);
    throw new Error(`Sheet not found${sheetName ? `: ${sheetName}` : ''}. Available: ${available.join(', ') || '(none)'}`);
  }
  const totalRows = ws.actualRowCount || ws.rowCount;
  const startRow = Math.max(1, opts.startRow || 1);
  const endRow = opts.endRow ? Math.min(opts.endRow, totalRows) : totalRows;
  const rows = [];
  for (let r = startRow; r <= endRow; r++) {
    const row = ws.getRow(r);
    const out = [];
    for (let c = 1; c <= ws.columnCount; c++) {
      const cell = row.getCell(c);
      if (opts.includeFormulas && cell.formula) out.push(`=${cell.formula}`);
      else out.push(cell.value ?? '');
    }
    rows.push(out);
  }
  return { sheetName: ws.name, rows, startRow, endRow, totalRows };
}

export async function sheetInfo(filePath) {
  const Excel = await loadExcel();
  const workbook = new Excel.Workbook();
  if (filePath.toLowerCase().endsWith('.csv')) {
    await workbook.csv.readFile(filePath);
  } else {
    await workbook.xlsx.readFile(filePath);
  }
  const sheets = [];
  workbook.eachSheet(ws => {
    sheets.push({
      name: ws.name,
      rowCount: ws.actualRowCount || ws.rowCount,
      columnCount: ws.columnCount,
    });
  });
  const stat = await fs.stat(filePath);
  return {
    fileName: path.basename(filePath),
    fileSize: stat.size,
    modified: stat.mtime.toISOString(),
    sheetCount: sheets.length,
    sheets,
  };
}
