// Rows in, printed pages out.
//
// This is the half of the generator a preview cannot check. Two things live
// here for that reason: the CSV columns, which are a contract with whoever
// builds the file, and the PAGE ORDER of a two-sided card, which decides
// whether card 3's back lands on card 3 or on card 4. Both are the kind of
// wrong you only find once the stock is used up.
//
// Nothing here knows how a code is drawn or how big a label is. That is
// utils/codes.js and the component's CSS respectively.

// Rendering thousands of codes locks the tab, so stop at a number a person
// would actually feed a label printer in one run. The cap counts CARDS, not
// pages - a two-sided run of 500 is 1000 pages and that is fine.
export const MAX_LABELS = 500
export const MAX_COPIES = 1000

const CONTENT_HEADERS = ['content', 'text', 'data', 'value', 'url', 'qr']
const LABEL_HEADERS = ['label', 'name', 'caption', 'description']
const COPIES_HEADERS = ['copies', 'qty', 'quantity', 'count']
const BACK_HEADERS = ['back', 'backcontent', 'reverse']
const BACKLABEL_HEADERS = ['backlabel', 'backcaption', 'backname']

/** Split one CSV line, honoring quoted fields and "" escapes. */
export function splitCsvLine(line) {
  const fields = []
  let current = ''
  let inQuotes = false
  for (let i = 0; i < line.length; i++) {
    const char = line[i]
    if (inQuotes) {
      if (char === '"' && line[i + 1] === '"') { current += '"'; i++ }
      else if (char === '"') { inQuotes = false }
      else { current += char }
    } else if (char === '"') {
      inQuotes = true
    } else if (char === ',') {
      fields.push(current); current = ''
    } else {
      current += char
    }
  }
  fields.push(current)
  return fields.map(field => field.trim())
}

/**
 * Parse the label CSV into rows.
 *
 * Columns: content (required), label, copies, back, backlabel. A header row is
 * optional; without one the order is the order above. back/backlabel are only
 * read when the back side is set to take its own content per row.
 */
export function parseCsv(text) {
  const lines = String(text || '').split(/\r?\n/).filter(line => line.trim() !== '')
  if (!lines.length) return []

  let columns = { content: 0, label: 1, copies: 2, back: 3, backlabel: 4 }
  let start = 0
  const first = splitCsvLine(lines[0]).map(field => field.toLowerCase())
  if (first.some(field => CONTENT_HEADERS.includes(field))) {
    // Named header: map by name so column order does not matter.
    const indexOf = names => first.findIndex(field => names.includes(field))
    columns = {
      content: indexOf(CONTENT_HEADERS),
      label: indexOf(LABEL_HEADERS),
      copies: indexOf(COPIES_HEADERS),
      back: indexOf(BACK_HEADERS),
      backlabel: indexOf(BACKLABEL_HEADERS),
    }
    start = 1
  }

  const at = (fields, index) => (index >= 0 ? (fields[index] || '') : '')

  const rows = []
  for (let i = start; i < lines.length; i++) {
    const fields = splitCsvLine(lines[i])
    const content = at(fields, columns.content)
    if (!content) continue
    const copies = columns.copies >= 0 ? parseInt(fields[columns.copies], 10) : 1
    rows.push({
      content,
      label: at(fields, columns.label),
      copies: Number.isFinite(copies) && copies > 0 ? Math.min(copies, MAX_COPIES) : 1,
      back: at(fields, columns.back),
      backlabel: at(fields, columns.backlabel),
    })
  }
  return rows
}

/** The back page belonging to one row, per the chosen back source. */
function backPageFor(row, back) {
  const fallback = back.text || ''
  switch (back.source) {
    // The same payload again, so either face of a card scans. Worth it on a
    // badge that can end up in a holder either way round.
    case 'code':
      return { content: row.content, label: row.label || '', side: 'back' }
    // Per-row back: the CSV carries it. A row that left it empty still gets a
    // page, because a card printer counts pages and a missing one shifts every
    // back after it onto the wrong front.
    case 'column':
      return { content: row.back || '', label: row.backlabel || fallback, side: 'back' }
    case 'blank':
      return { content: '', label: '', side: 'back' }
    case 'text':
    default:
      return { content: '', label: fallback, side: 'back' }
  }
}

/**
 * Expand rows by their copies count and, when a back side is enabled, pair
 * each card with its back page.
 *
 * `back.order` is the one that matters on the floor:
 *   interleave - front, back, front, back. What a duplex card printer's driver
 *                expects: it takes pages two at a time.
 *   grouped    - every front, then every back. For a single-sided printer,
 *                where the stack comes out, gets flipped and goes back in.
 * Getting this backwards prints readable cards with the wrong names on the
 * back, which is worse than a jam because nothing looks broken.
 */
export function buildPages(rows, options = {}) {
  const { back = {}, max = MAX_LABELS } = options
  const fronts = []
  const backs = []
  let dropped = 0

  for (const row of rows || []) {
    const wanted = Number.isFinite(row.copies) && row.copies > 0
      ? Math.min(row.copies, MAX_COPIES) : 1
    for (let i = 0; i < wanted; i++) {
      if (fronts.length >= max) { dropped += 1; continue }
      fronts.push({ content: row.content, label: row.label || '', side: 'front' })
      if (back.enabled) backs.push(backPageFor(row, back))
    }
  }

  let pages = fronts
  if (back.enabled) {
    pages = back.order === 'grouped'
      ? [...fronts, ...backs]
      : fronts.flatMap((front, index) => [front, backs[index]])
  }

  return { pages, fronts, backs, dropped }
}
