/**
 * Regenerates docs/prosthesis-catalog.xlsx from catalog-seed-data.ts.
 *
 *   cd backend && npx ts-node --transpile-only prisma/export-prosthesis-catalog.ts
 */
import * as fs from 'fs';
import * as path from 'path';
import * as zlib from 'zlib';
import {
  PROSTHESIS_TYPES,
  PROSTHESIS_LABELS,
  WORKFLOW_STEP_LABELS,
  buildProsthesisStepCodes,
} from './catalog-seed-data';

const CATEGORY_LABELS: Record<string, string> = {
  crown: 'Crowns',
  indirect: 'Veneer/Inlay/Onlay/Overlay',
  implant: 'Implants',
  post_core: 'Post & core',
  removable: 'Removable',
  appliance: 'Appliances',
  digital: 'Digital',
};

const SUBCATEGORY_LABELS: Record<string, string> = {
  full_contour: 'Full contour',
  layered: 'Layered',
  veneer: 'Veneer',
  inlay: 'Inlay',
  onlay: 'Onlay',
  overlay: 'Overlay',
  complete_denture: 'Complete denture',
  partial_denture: 'Partial denture',
  night_guard: 'Night guard',
};

const STACK_RULES = [
  ['Slot', 'Rule'],
  [
    'restoration',
    'One per tooth: crown, veneer, inlay, onlay, or overlay.',
  ],
  ['implant', 'One implant type per tooth. Disabled when post & core is on the tooth.'],
  [
    'post_core',
    'One post & core. Disabled when a restoration or implant is already on the tooth.',
  ],
  [
    'arch',
    'Complete denture, overdenture, appliances, and digital use Upper/Lower/Both (UA/LA). The picker control remaps jobs (Both→Upper drops lower).',
  ],
  [
    'partial_denture',
    'Tooth-selected (FDI), not the arch labels. All selected teeth become one lab job.',
  ],
  [
    'screw_retained',
    'Implant that paints the crown. No crown suggestion slot; Crown and Indirect are disabled. Does not stack with another restoration.',
  ],
  [
    'crown_suggestion',
    'Implant or post & core (not screw-retained) shows a crown suggestion slot. A veneer/inlay/onlay/overlay hides it. After a crown is picked the slot stays filled so it can be changed.',
  ],
  [
    'connected',
    'Connected teeth that share a type are one lab job. An extra type on one unit of the bridge is its own job. Unconnected teeth stay separate even if they share a type.',
  ],
  ['two_crowns', 'Two crowns cannot stack.'],
];

function xmlEscape(value: string): string {
  return value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

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

function sheetXml(rows: string[][]): string {
  const cells = rows
    .map((row, r) => {
      const rowNum = r + 1;
      const inner = row
        .map((value, c) => {
          const ref = `${colLetter(c)}${rowNum}`;
          return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${xmlEscape(value)}</t></is></c>`;
        })
        .join('');
      return `<row r="${rowNum}">${inner}</row>`;
    })
    .join('');
  const dim = `A1:${colLetter(Math.max(0, ...(rows.map((r) => r.length - 1))))}${rows.length || 1}`;
  return (
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
    `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">` +
    `<dimension ref="${dim}"/>` +
    `<sheetData>${cells}</sheetData>` +
    `</worksheet>`
  );
}

const CRC_TABLE = (() => {
  const table = new Uint32Array(256);
  for (let i = 0; i < 256; i++) {
    let c = i;
    for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
    table[i] = c >>> 0;
  }
  return table;
})();

function crc32(buf: Buffer): number {
  let crc = 0xffffffff;
  for (let i = 0; i < buf.length; i++) {
    crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
  }
  return (crc ^ 0xffffffff) >>> 0;
}

function zipStore(files: Array<{ name: string; data: Buffer }>): Buffer {
  const locals: Buffer[] = [];
  const centrals: Buffer[] = [];
  let offset = 0;

  for (const file of files) {
    const name = Buffer.from(file.name, 'utf8');
    const compressed = zlib.deflateRawSync(file.data);
    const crc = crc32(file.data);
    const local = Buffer.alloc(30 + name.length);
    local.writeUInt32LE(0x04034b50, 0);
    local.writeUInt16LE(20, 4);
    local.writeUInt16LE(0, 6);
    local.writeUInt16LE(8, 8);
    local.writeUInt16LE(0, 10);
    local.writeUInt16LE(0, 12);
    local.writeUInt32LE(crc, 14);
    local.writeUInt32LE(compressed.length, 18);
    local.writeUInt32LE(file.data.length, 22);
    local.writeUInt16LE(name.length, 26);
    local.writeUInt16LE(0, 28);
    name.copy(local, 30);
    locals.push(local, compressed);

    const central = Buffer.alloc(46 + name.length);
    central.writeUInt32LE(0x02014b50, 0);
    central.writeUInt16LE(20, 4);
    central.writeUInt16LE(20, 6);
    central.writeUInt16LE(0, 8);
    central.writeUInt16LE(8, 10);
    central.writeUInt16LE(0, 12);
    central.writeUInt16LE(0, 14);
    central.writeUInt32LE(crc, 16);
    central.writeUInt32LE(compressed.length, 20);
    central.writeUInt32LE(file.data.length, 24);
    central.writeUInt16LE(name.length, 28);
    central.writeUInt16LE(0, 30);
    central.writeUInt16LE(0, 32);
    central.writeUInt16LE(0, 34);
    central.writeUInt16LE(0, 36);
    central.writeUInt32LE(0, 38);
    central.writeUInt32LE(offset, 42);
    name.copy(central, 46);
    centrals.push(central);

    offset += local.length + compressed.length;
  }

  const centralDir = Buffer.concat(centrals);
  const eocd = Buffer.alloc(22);
  eocd.writeUInt32LE(0x06054b50, 0);
  eocd.writeUInt16LE(0, 4);
  eocd.writeUInt16LE(0, 6);
  eocd.writeUInt16LE(files.length, 8);
  eocd.writeUInt16LE(files.length, 10);
  eocd.writeUInt32LE(centralDir.length, 12);
  eocd.writeUInt32LE(offset, 16);
  eocd.writeUInt16LE(0, 20);

  return Buffer.concat([...locals, centralDir, eocd]);
}

function buildWorkbook(): Buffer {
  const typeRows: string[][] = [
    [
      'category',
      'category_label',
      'subcategory',
      'subcategory_label',
      'code',
      'label_en',
      'label_fa',
      'label_nl',
      'chartRegion',
      'stackGroup',
      'active',
    ],
  ];
  for (const type of PROSTHESIS_TYPES) {
    const labels = PROSTHESIS_LABELS[type.code] ?? {};
    typeRows.push([
      type.category,
      CATEGORY_LABELS[type.category] ?? type.category,
      type.subcategory ?? '',
      type.subcategory ? SUBCATEGORY_LABELS[type.subcategory] ?? type.subcategory : '',
      type.code,
      labels.en ?? type.code,
      labels.fa ?? '',
      labels.nl ?? '',
      type.chartRegion,
      type.stackGroup,
      type.isActive === false ? 'false' : 'true',
    ]);
  }

  const stepRows: string[][] = [
    ['code', 'label_en', 'step_order', 'step_code', 'step_en', 'step_fa', 'step_nl'],
  ];
  for (const type of PROSTHESIS_TYPES) {
    if (type.isActive === false) continue;
    const labels = PROSTHESIS_LABELS[type.code] ?? {};
    const steps = buildProsthesisStepCodes(type);
    steps.forEach((stepCode, index) => {
      const stepLabels = WORKFLOW_STEP_LABELS[stepCode] ?? {};
      stepRows.push([
        type.code,
        labels.en ?? type.code,
        String(index + 1),
        stepCode,
        stepLabels.en ?? stepCode,
        stepLabels.fa ?? '',
        stepLabels.nl ?? '',
      ]);
    });
  }

  const contentTypes =
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
    `<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">` +
    `<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>` +
    `<Default Extension="xml" ContentType="application/xml"/>` +
    `<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>` +
    `<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>` +
    `<Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>` +
    `<Override PartName="/xl/worksheets/sheet3.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>` +
    `</Types>`;

  const rels =
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
    `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
    `<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>` +
    `</Relationships>`;

  const workbook =
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
    `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ` +
    `xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">` +
    `<sheets>` +
    `<sheet name="Types" sheetId="1" r:id="rId1"/>` +
    `<sheet name="Steps" sheetId="2" r:id="rId2"/>` +
    `<sheet name="Stack rules" sheetId="3" r:id="rId3"/>` +
    `</sheets>` +
    `</workbook>`;

  const workbookRels =
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
    `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
    `<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>` +
    `<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet2.xml"/>` +
    `<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet3.xml"/>` +
    `</Relationships>`;

  return zipStore([
    { name: '[Content_Types].xml', data: Buffer.from(contentTypes, 'utf8') },
    { name: '_rels/.rels', data: Buffer.from(rels, 'utf8') },
    { name: 'xl/workbook.xml', data: Buffer.from(workbook, 'utf8') },
    { name: 'xl/_rels/workbook.xml.rels', data: Buffer.from(workbookRels, 'utf8') },
    { name: 'xl/worksheets/sheet1.xml', data: Buffer.from(sheetXml(typeRows), 'utf8') },
    { name: 'xl/worksheets/sheet2.xml', data: Buffer.from(sheetXml(stepRows), 'utf8') },
    { name: 'xl/worksheets/sheet3.xml', data: Buffer.from(sheetXml(STACK_RULES), 'utf8') },
  ]);
}

const outPath = path.resolve(__dirname, '../../docs/prosthesis-catalog.xlsx');
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, buildWorkbook());
console.log(`Wrote ${outPath}`);
