import { downloadBlob, downloadText } from '../downloadFile';
import { Citation } from '../wsFrames';
import {
  buildExportFilename,
  extractMarkdownTables,
  parseMarkdownBlocks,
  stripMarkdown,
  type MdBlock,
} from './markdownUtils';

export type ExportFormat = 'pdf' | 'csv' | 'xlsx' | 'txt';

export type ExportTurn = {
  role: 'user' | 'assistant';
  message: string;
  timestamp?: string | Date | null;
  citations?: Citation[];
};

export type ExportScope = 'message' | 'conversation';

export type ExportOptions = {
  format: ExportFormat;
  scope: ExportScope;
  title: string;
  turns: ExportTurn[];
};

function yieldToUi(): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, 0));
}

function formatTs(value?: string | Date | null): string {
  if (!value) return '';
  try {
    return new Date(value).toISOString();
  } catch {
    return String(value);
  }
}

function roleLabel(role: ExportTurn['role']): string {
  return role === 'user' ? 'User' : 'Assistant';
}

function citationFootnotes(citations?: Citation[]): string {
  if (!citations?.length) return '';
  return citations
    .map((c) => `[${c.index}] ${c.title}${c.url ? ` — ${c.url}` : ''}`)
    .join('\n');
}

export async function exportChat(options: ExportOptions): Promise<void> {
  const { format, scope, title, turns } = options;
  const filename = buildExportFilename(title, format);
  const exportedAt = new Date().toISOString();

  // Yield so large conversations don't freeze the main thread (#97).
  await yieldToUi();

  switch (format) {
    case 'txt':
      downloadText(buildTxt(title, turns, scope), filename);
      break;
    case 'csv':
      await exportCsv(title, turns, scope, filename);
      break;
    case 'xlsx':
      await exportXlsx(title, turns, scope, filename);
      break;
    case 'pdf':
      await exportPdf(title, turns, exportedAt, filename);
      break;
    default:
      throw new Error(`Unsupported export format: ${format}`);
  }
}

function buildTxt(title: string, turns: ExportTurn[], scope: ExportScope): string {
  if (scope === 'message' && turns.length === 1) {
    const turn = turns[0];
    const body = stripMarkdown(turn.message);
    const notes = citationFootnotes(turn.citations);
    return notes ? `${body}\n\nSources:\n${notes}` : body;
  }
  const parts = [`# ${title}`, ''];
  for (const turn of turns) {
    const ts = formatTs(turn.timestamp);
    parts.push(`[${roleLabel(turn.role)}${ts ? ` · ${ts}` : ''}]`);
    parts.push(stripMarkdown(turn.message));
    const notes = citationFootnotes(turn.citations);
    if (notes) {
      parts.push('Sources:');
      parts.push(notes);
    }
    parts.push('');
  }
  return parts.join('\n');
}

function turnsAsRows(turns: ExportTurn[]): string[][] {
  const header = ['timestamp', 'role', 'message', 'citations'];
  const rows = turns.map((turn) => [
    formatTs(turn.timestamp),
    roleLabel(turn.role),
    stripMarkdown(turn.message),
    citationFootnotes(turn.citations).replace(/\n/g, ' | '),
  ]);
  return [header, ...rows];
}

async function exportCsv(
  _title: string,
  turns: ExportTurn[],
  scope: ExportScope,
  filename: string,
): Promise<void> {
  const Papa = (await import('papaparse')).default;
  let matrix: string[][];

  if (scope === 'message' && turns.length === 1) {
    const tables = extractMarkdownTables(turns[0].message);
    if (tables.length > 0) {
      const table = tables[0];
      matrix = [table.headers, ...table.rows];
    } else {
      matrix = turnsAsRows(turns);
    }
  } else {
    matrix = turnsAsRows(turns);
  }

  const csv = Papa.unparse(matrix);
  downloadText(csv, filename, 'text/csv;charset=utf-8');
}

async function exportXlsx(
  _title: string,
  turns: ExportTurn[],
  scope: ExportScope,
  filename: string,
): Promise<void> {
  const XLSX = await import('xlsx');
  const workbook = XLSX.utils.book_new();

  if (scope === 'message' && turns.length === 1) {
    const tables = extractMarkdownTables(turns[0].message);
    if (tables.length > 0) {
      tables.forEach((table, idx) => {
        const sheet = XLSX.utils.aoa_to_sheet([table.headers, ...table.rows]);
        XLSX.utils.book_append_sheet(workbook, sheet, `Table ${idx + 1}`);
      });
    } else {
      const sheet = XLSX.utils.aoa_to_sheet(turnsAsRows(turns));
      XLSX.utils.book_append_sheet(workbook, sheet, 'Messages');
    }
  } else {
    const sheet = XLSX.utils.aoa_to_sheet(turnsAsRows(turns));
    XLSX.utils.book_append_sheet(workbook, sheet, 'Messages');
  }

  const arrayBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
  downloadBlob(
    new Blob([arrayBuffer], {
      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    }),
    filename,
  );
}

function blocksToPdfContent(blocks: MdBlock[]): unknown[] {
  const content: unknown[] = [];
  for (const block of blocks) {
    switch (block.type) {
      case 'heading':
        content.push({
          text: block.text,
          style: `h${Math.min(block.level, 3)}`,
          margin: [0, 8, 0, 4],
        });
        break;
      case 'paragraph':
        content.push({ text: block.text, margin: [0, 2, 0, 6] });
        break;
      case 'code':
        content.push({
          text: block.text,
          fontSize: 9,
          preserveLeadingSpaces: true,
          margin: [0, 4, 0, 8],
          background: '#f5f5f5',
        });
        break;
      case 'list':
        content.push(
          block.ordered
            ? { ol: block.items, margin: [0, 2, 0, 6] }
            : { ul: block.items, margin: [0, 2, 0, 6] },
        );
        break;
      case 'table':
        content.push({
          table: {
            headerRows: 1,
            widths: block.headers.map(() => '*'),
            body: [
              block.headers.map((h) => ({ text: h, bold: true })),
              ...block.rows.map((row) =>
                block.headers.map((_, col) => row[col] ?? ''),
              ),
            ],
          },
          margin: [0, 4, 0, 8],
        });
        break;
      default:
        break;
    }
  }
  return content;
}

async function exportPdf(
  title: string,
  turns: ExportTurn[],
  exportedAt: string,
  filename: string,
): Promise<void> {
  const pdfMakeModule = await import('pdfmake/build/pdfmake');
  const pdfFonts = await import('pdfmake/build/vfs_fonts');
  const pdfMake = pdfMakeModule.default || pdfMakeModule;
  // vfs_fonts may export vfs on default or as pdfMake.vfs
  const vfs =
    (pdfFonts as { pdfMake?: { vfs?: unknown }; default?: { pdfMake?: { vfs?: unknown } } })
      .pdfMake?.vfs ||
    (pdfFonts as { default?: { pdfMake?: { vfs?: unknown } } }).default?.pdfMake?.vfs ||
    (pdfFonts as { default?: unknown }).default;
  if (vfs) {
    (pdfMake as { vfs?: unknown }).vfs = vfs;
  }

  const content: unknown[] = [
    { text: title, style: 'title' },
    { text: `Exported ${exportedAt}`, style: 'meta', margin: [0, 0, 0, 16] },
  ];

  for (let i = 0; i < turns.length; i += 1) {
    if (i > 0 && i % 20 === 0) await yieldToUi();
    const turn = turns[i];
    if (turns.length > 1) {
      content.push({
        text: `${roleLabel(turn.role)}${turn.timestamp ? ` · ${formatTs(turn.timestamp)}` : ''}`,
        style: 'role',
        margin: [0, 12, 0, 4],
      });
    }
    content.push(...blocksToPdfContent(parseMarkdownBlocks(turn.message)));
    if (turn.citations?.length) {
      content.push({ text: 'Sources', style: 'h3', margin: [0, 8, 0, 4] });
      content.push({
        ol: turn.citations
          .slice()
          .sort((a, b) => a.index - b.index)
          .map((c) => `${c.title}${c.url ? ` (${c.url})` : ''}`),
      });
    }
  }

  const docDefinition = {
    content,
    styles: {
      title: { fontSize: 18, bold: true },
      meta: { fontSize: 9, color: '#666666' },
      role: { fontSize: 11, bold: true, color: '#333333' },
      h1: { fontSize: 16, bold: true },
      h2: { fontSize: 14, bold: true },
      h3: { fontSize: 12, bold: true },
    },
    defaultStyle: { fontSize: 11 },
    header: {
      text: `${title} · ${exportedAt}`,
      fontSize: 8,
      color: '#888888',
      margin: [40, 20, 40, 0] as [number, number, number, number],
    },
  };

  await new Promise<void>((resolve, reject) => {
    try {
      // pdfmake typings vary across 0.2/0.3 builds; keep runtime call flexible.
      const pdf = (pdfMake as { createPdf: (def: unknown) => { getBlob: (cb: (blob: Blob) => void) => void } }).createPdf(
        docDefinition,
      );
      pdf.getBlob((blob: Blob) => {
        downloadBlob(blob, filename);
        resolve();
      });
    } catch (err) {
      reject(err);
    }
  });
}

/** Decide CSV/XLSX matrix strategy — exported for unit tests. */
export function selectTabularStrategy(
  scope: ExportScope,
  messageMarkdown: string,
): 'tables' | 'turns' {
  if (scope === 'conversation') return 'turns';
  return extractMarkdownTables(messageMarkdown).length > 0 ? 'tables' : 'turns';
}
