{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "xlsx-workbook",
  "title": "XLSX workbook model",
  "description": "Sparse compact workbook helpers for the XLSX viewer: cell lookup, metadata projection, column labels, and bounded source caching.",
  "files": [
    {
      "path": "registry/new-york-v4/lib/xlsx-workbook.ts",
      "content": "export interface CompactSheet {\n  name: string;\n  rowCount: number;\n  columnCount: number;\n  /** Sorted row-major indexes for non-empty cells only. */\n  cellIndexes: Uint32Array;\n  /** Length cellIndexes.length + 1; cell i's text is text.slice(textOffsets[i], textOffsets[i + 1]). */\n  textOffsets: Uint32Array;\n  /** Length cellIndexes.length; 1 = numeric/date. */\n  numericFlags: Uint8Array;\n  /** All non-empty display texts concatenated in row-major order. */\n  text: string;\n}\n\nexport interface XlsxCell {\n  /** Display text: the workbook's formatted value when available. */\n  text: string;\n  /** Right-align numbers and dates. */\n  numeric: boolean;\n}\n\nexport interface XlsxSheetMeta {\n  name: string;\n  rowCount: number;\n  columnCount: number;\n  nonEmptyCellCount: number;\n}\n\nexport interface XlsxSource {\n  sheets: XlsxSheetMeta[];\n  getCell(sheetIndex: number, rowIndex: number, columnIndex: number): XlsxCell;\n  dispose?: () => void;\n  estimatedByteSize?: number;\n}\n\nexport interface XlsxCacheOptions {\n  maxEntries?: number;\n  maxBytes?: number;\n}\n\nexport interface XlsxSheetChangeResult {\n  accepted: boolean;\n  changed: boolean;\n  sheetIndex: number;\n}\n\ninterface CacheEntry {\n  promise: Promise<XlsxSource>;\n  source?: XlsxSource;\n  bytes: number;\n}\n\nexport const EMPTY_XLSX_CELL: XlsxCell = { text: \"\", numeric: false };\nconst MAX_COMPACT_CELL_INDEX = 0xffffffff;\n\nexport function resolveXlsxSheetChange({\n  activeSheet,\n  requestedSheet,\n  sheetCount,\n}: {\n  activeSheet: number;\n  requestedSheet: number;\n  sheetCount?: number | null;\n}): XlsxSheetChangeResult {\n  if (!Number.isSafeInteger(requestedSheet) || requestedSheet < 0) {\n    return { accepted: false, changed: false, sheetIndex: activeSheet };\n  }\n  if (\n    sheetCount != null &&\n    (!Number.isSafeInteger(sheetCount) ||\n      sheetCount < 0 ||\n      requestedSheet >= sheetCount)\n  ) {\n    return { accepted: false, changed: false, sheetIndex: activeSheet };\n  }\n  return {\n    accepted: true,\n    changed: requestedSheet !== activeSheet,\n    sheetIndex: requestedSheet,\n  };\n}\n\n/** Spreadsheet column label: 0 -> A, 25 -> Z, 26 -> AA. */\nexport function xlsxColumnLabel(index: number): string {\n  if (!Number.isSafeInteger(index) || index < 0) return \"\";\n  let i = Math.floor(index) + 1;\n  let label = \"\";\n  while (i > 0) {\n    const m = (i - 1) % 26;\n    label = String.fromCharCode(65 + m) + label;\n    i = Math.floor((i - 1) / 26);\n  }\n  return label;\n}\n\nexport function compactSheetByteSize(sheet: CompactSheet): number {\n  return (\n    sheet.text.length * 2 +\n    sheet.cellIndexes.byteLength +\n    sheet.textOffsets.byteLength +\n    sheet.numericFlags.byteLength\n  );\n}\n\nexport function estimateXlsxSourceBytes(source: XlsxSource): number {\n  if (\n    source.estimatedByteSize != null &&\n    Number.isFinite(source.estimatedByteSize) &&\n    source.estimatedByteSize >= 0\n  ) {\n    return source.estimatedByteSize;\n  }\n  return source.sheets.reduce(\n    (sum, sheet) =>\n      sum +\n      sheet.name.length * 2 +\n      // Metadata overhead is intentionally approximate; compact sheet buffers\n      // dominate real workbook memory.\n      32,\n    0,\n  );\n}\n\nexport function estimateCompactWorkbookBytes(sheets: CompactSheet[]): number {\n  return sheets.reduce((sum, sheet) => sum + compactSheetByteSize(sheet), 0);\n}\n\nexport function getCompactSheetCell(\n  sheet: CompactSheet | undefined,\n  rowIndex: number,\n  columnIndex: number,\n): XlsxCell {\n  if (\n    !sheet ||\n    !Number.isInteger(rowIndex) ||\n    !Number.isInteger(columnIndex) ||\n    rowIndex < 0 ||\n    columnIndex < 0 ||\n    rowIndex >= sheet.rowCount ||\n    columnIndex >= sheet.columnCount\n  ) {\n    return EMPTY_XLSX_CELL;\n  }\n\n  const cellIndex = rowIndex * sheet.columnCount + columnIndex;\n  const sparseIndex = binarySearchUint32(sheet.cellIndexes, cellIndex);\n  if (sparseIndex < 0) return EMPTY_XLSX_CELL;\n\n  const start = sheet.textOffsets[sparseIndex];\n  const end = sheet.textOffsets[sparseIndex + 1];\n  if (\n    !Number.isSafeInteger(start) ||\n    !Number.isSafeInteger(end) ||\n    start < 0 ||\n    end < start ||\n    end > sheet.text.length\n  ) {\n    return EMPTY_XLSX_CELL;\n  }\n  if (start === end) return EMPTY_XLSX_CELL;\n\n  return {\n    text: sheet.text.slice(start, end),\n    numeric: sheet.numericFlags[sparseIndex] === 1,\n  };\n}\n\nexport function buildXlsxSourceFromCompact(\n  compact: CompactSheet[],\n): XlsxSource {\n  const sheets: XlsxSheetMeta[] = compact.map((sheet) => ({\n    name: sheet.name,\n    rowCount: sheet.rowCount,\n    columnCount: sheet.columnCount,\n    nonEmptyCellCount: sheet.cellIndexes.length,\n  }));\n\n  return {\n    sheets,\n    estimatedByteSize: estimateCompactWorkbookBytes(compact),\n    getCell: (sheetIndex: number, rowIndex: number, columnIndex: number) =>\n      getCompactSheetCell(compact[sheetIndex], rowIndex, columnIndex),\n  };\n}\n\nexport function createCompactSheet(input: {\n  name: string;\n  rowCount: number;\n  columnCount: number;\n  entries: Array<{ cellIndex: number; text: string; numeric?: boolean }>;\n}): CompactSheet {\n  const rowCount = normalizeCompactDimension(input.rowCount);\n  const columnCount = normalizeCompactDimension(input.columnCount);\n  const cellCapacity = rowCount * columnCount;\n  const sorted = input.entries\n    .filter(\n      (entry) =>\n        entry.text !== \"\" &&\n        Number.isSafeInteger(entry.cellIndex) &&\n        entry.cellIndex >= 0 &&\n        entry.cellIndex <= MAX_COMPACT_CELL_INDEX &&\n        entry.cellIndex < cellCapacity,\n    )\n    .sort((a, b) => a.cellIndex - b.cellIndex);\n  const deduped: typeof sorted = [];\n  for (const entry of sorted) {\n    const previous = deduped[deduped.length - 1];\n    if (previous?.cellIndex === entry.cellIndex) {\n      deduped[deduped.length - 1] = entry;\n    } else {\n      deduped.push(entry);\n    }\n  }\n\n  const cellIndexes = new Uint32Array(deduped.length);\n  const textOffsets = new Uint32Array(deduped.length + 1);\n  const numericFlags = new Uint8Array(deduped.length);\n  const parts: string[] = [];\n  let pos = 0;\n\n  for (let i = 0; i < deduped.length; i++) {\n    const entry = deduped[i];\n    cellIndexes[i] = entry.cellIndex;\n    textOffsets[i] = pos;\n    parts.push(entry.text);\n    pos += entry.text.length;\n    if (entry.numeric) numericFlags[i] = 1;\n  }\n  textOffsets[deduped.length] = pos;\n\n  return {\n    name: input.name,\n    rowCount,\n    columnCount,\n    cellIndexes,\n    textOffsets,\n    numericFlags,\n    text: parts.join(\"\"),\n  };\n}\n\nexport class XlsxSourceCache {\n  private readonly maxEntries: number;\n  private readonly maxBytes: number;\n  private readonly entries = new Map<string, CacheEntry>();\n\n  constructor(options: XlsxCacheOptions = {}) {\n    this.maxEntries = options.maxEntries ?? 4;\n    this.maxBytes = options.maxBytes ?? 96 * 1024 * 1024;\n  }\n\n  get(loadKey: string, load: () => Promise<XlsxSource>): Promise<XlsxSource> {\n    const existing = this.entries.get(loadKey);\n    if (existing) {\n      this.entries.delete(loadKey);\n      this.entries.set(loadKey, existing);\n      return existing.promise;\n    }\n\n    const entry: CacheEntry = {\n      bytes: 0,\n      promise: Promise.resolve()\n        .then(load)\n        .then(\n          (source) => {\n            if (this.entries.get(loadKey) !== entry) {\n              source.dispose?.();\n              return source;\n            }\n            entry.source = source;\n            entry.bytes = estimateXlsxSourceBytes(source);\n            this.evict();\n            return source;\n          },\n          (error) => {\n            if (this.entries.get(loadKey) === entry)\n              this.entries.delete(loadKey);\n            throw error;\n          },\n        ),\n    };\n\n    this.entries.set(loadKey, entry);\n    this.evict();\n    return entry.promise;\n  }\n\n  setResolvedForTest(loadKey: string, source: XlsxSource, bytes = 1): void {\n    const entry: CacheEntry = {\n      source,\n      bytes,\n      promise: Promise.resolve(source),\n    };\n    this.entries.set(loadKey, entry);\n    this.evict();\n  }\n\n  has(loadKey: string): boolean {\n    return this.entries.has(loadKey);\n  }\n\n  clear(): void {\n    for (const entry of this.entries.values()) entry.source?.dispose?.();\n    this.entries.clear();\n  }\n\n  size(): number {\n    return this.entries.size;\n  }\n\n  private evict(): void {\n    let bytes = 0;\n    for (const entry of this.entries.values()) bytes += entry.bytes;\n\n    if (this.maxBytes > 0 && bytes > this.maxBytes) {\n      for (const [key, entry] of this.entries) {\n        if (bytes <= this.maxBytes) break;\n        if (!entry.source) continue;\n        this.entries.delete(key);\n        bytes -= entry.bytes;\n        entry.source.dispose?.();\n      }\n    }\n\n    for (const [key, entry] of this.entries) {\n      if (this.entries.size <= this.maxEntries) break;\n      this.entries.delete(key);\n      bytes -= entry.bytes;\n      entry.source?.dispose?.();\n    }\n  }\n}\n\nfunction binarySearchUint32(items: Uint32Array, target: number): number {\n  let lo = 0;\n  let hi = items.length - 1;\n  while (lo <= hi) {\n    const mid = (lo + hi) >>> 1;\n    const value = items[mid];\n    if (value === target) return mid;\n    if (value < target) lo = mid + 1;\n    else hi = mid - 1;\n  }\n  return -1;\n}\n\nfunction normalizeCompactDimension(value: number) {\n  return Number.isSafeInteger(value) && value > 0 ? value : 0;\n}\n",
      "type": "registry:lib",
      "target": "@lib/xlsx-workbook.ts"
    }
  ],
  "type": "registry:lib"
}