const { pool, query } = require('../../utils/db')

// Raw SQL for the cliloc table. `shard_clilocs` is IMPORT-OWNED: `replaceAll`
// empties and refills it inside one transaction, and nothing else in the
// codebase writes to it. No foreign keys, consistent with every other shard_*
// table.

const BATCH = 1000

/**
 * Replace the entire cliloc table in one transaction.
 *
 * All-or-nothing on purpose: a failed reload must leave the previous table
 * intact rather than a half-loaded one, because a partially-imported cliloc
 * table is indistinguishable from a complete one to anyone reading it — you
 * would just see some items named and some not, which is also what "no table at
 * all" looks like.
 *
 * `DELETE`, not `TRUNCATE` — `TRUNCATE` is DDL in MariaDB and implicitly
 * commits, which would defeat exactly that guarantee. (The same trap the spawn
 * atlas import documents; at ~123k rows `DELETE` is still well under a second.)
 */
async function replaceAll(entries, meta) {
  const conn = await pool.getConnection()
  try {
    await conn.beginTransaction()
    await conn.query('DELETE FROM shard_clilocs')

    // Blank entries are dropped rather than stored. Roughly HALF of a real
    // cliloc table is empty strings — ids the client reserves and never uses —
    // and a row that resolves to no name is indistinguishable from no row at
    // all to every caller. Dropping them halves the table (123,490 → ~67,500)
    // and, more importantly, makes the binary and text imports converge on
    // identical content: the binary format carries the blanks explicitly and a
    // text export may or may not, depending on the tool.
    //
    // Later duplicates win. Merging across sources already happened upstream in
    // `readCliloc`, so in practice this collapses nothing — it is kept because
    // the plain format permits a repeated id WITHIN one file and the client's
    // own loader resolves it the same way (its dictionary assignment
    // overwrites). Without it, a file the game itself would load happily would
    // fail the batch insert on a primary-key collision.
    const byNumber = new Map()
    let blank = 0
    for (const entry of entries) {
      if (!Number.isInteger(entry.number)) continue
      if (String(entry.text ?? '').trim() === '') {
        blank++
        continue
      }
      byNumber.set(entry.number, entry)
    }

    const rows = [...byNumber.values()].map((e) => [e.number, e.flag ?? 0, e.text])
    for (let i = 0; i < rows.length; i += BATCH) {
      await conn.batch('INSERT INTO shard_clilocs (number, flag, text) VALUES (?,?,?)', rows.slice(i, i + BATCH))
    }

    await conn.query(
      'INSERT INTO shard_cliloc_meta (id, payload) VALUES (1, ?) ' +
        'ON DUPLICATE KEY UPDATE payload = VALUES(payload), imported_at = CURRENT_TIMESTAMP',
      [JSON.stringify({ ...meta, count: rows.length })],
    )

    await conn.commit()
    return { count: rows.length, blank, duplicates: entries.length - blank - rows.length }
  } catch (err) {
    await conn.rollback().catch(() => {})
    throw err
  } finally {
    conn.release()
  }
}

async function getMeta() {
  const rows = await query('SELECT payload, imported_at FROM shard_cliloc_meta WHERE id = 1')
  if (rows.length === 0) return null
  const payload = typeof rows[0].payload === 'string' ? JSON.parse(rows[0].payload) : rows[0].payload
  return { ...payload, importedAt: rows[0].imported_at }
}

/**
 * Look up a batch of ids.
 *
 * Batched rather than one-at-a-time because every caller has a LIST: a character
 * sheet resolves a dozen equipment ids at once, and a page of marketplace
 * listings resolves fifty. `IN (...)` with generated placeholders keeps it one
 * round trip and one parameterized statement.
 */
async function lookup(numbers) {
  if (!Array.isArray(numbers) || numbers.length === 0) return []
  const ids = [...new Set(numbers.filter((n) => Number.isInteger(n)))]
  if (ids.length === 0) return []
  const placeholders = ids.map(() => '?').join(',')
  return query(`SELECT number, text FROM shard_clilocs WHERE number IN (${placeholders})`, ids)
}

async function count() {
  const rows = await query('SELECT COUNT(*) AS n FROM shard_clilocs')
  return Number(rows[0]?.n) || 0
}

module.exports = {
  replaceAll,
  getMeta,
  lookup,
  count,
}
