/**
 * dxfAdapter — convert dxf-parser (MIT) output into the parseResult shape that
 * Viewer2D.load() consumes, so the existing renderer (bulge arcs, dimension
 * block geometry, arrowhead blocks, text alignment) is reused unchanged for DXF.
 *
 * Why an adapter and not dxf-parser's handles directly: dxf-parser exposes the
 * raw DXF handle graph where a BLOCK entity's handle differs from the BLOCK_RECORD
 * handle its child entities are owned by, and INSERT/DIMENSION reference their
 * block by NAME, not handle. Viewer2D matches blocks by handle. So we discard the
 * incoherent handles and synthesize one consistent integer handle PER BLOCK NAME:
 *   - block table entry handle      = {value: H}
 *   - every child entity ownerHandle = {value: H}
 *   - INSERT.blockHeaderHandle       = {value: H of insert.name}
 *   - DIMENSION.blockHeaderHandle    = {value: H of dim.block}
 * Model-space entities are owned by the synthetic '*MODEL_SPACE' handle so
 * Viewer2D's space-handle filter keeps them visible.
 *
 * Units: dxf-parser gives ARC/ELLIPSE angles in radians (pass through) but
 * INSERT/TEXT/MTEXT rotation in DEGREES → converted to radians here.
 *
 * Not supported by dxf-parser 1.1.x: HATCH (no handler) → hatches are absent.
 */
const DEG2RAD = Math.PI / 180;

const vec = (p) => (p ? { x: p.x, y: p.y, z: p.z || 0 } : p);

/** Map one dxf-parser entity → Viewer2D field names. Returns a shallow copy. */
function adaptEntity(e, nameToH) {
  const o = { ...e };
  delete o.color; // dxf-parser's `color` is a 24-bit RGB int; let colorIndex/layer drive ACI
  const t = (e.type || '').toUpperCase();

  switch (t) {
    case 'LINE':
      if (e.vertices?.length >= 2) { o.start = vec(e.vertices[0]); o.end = vec(e.vertices[1]); }
      break;

    case 'POLYLINE':
    case 'LWPOLYLINE':
      o.type = 'LWPOLYLINE';
      o.points = (e.vertices || []).map(vec);
      o.bulges = (e.vertices || []).map((v) => v.bulge || 0);
      o.closed = !!e.shape || ((e.flags || 0) & 1) === 1;
      break;

    case 'ARC':
    case 'CIRCLE':
      o.center = vec(e.center); // radius + (radian) angles already match
      break;

    case 'ELLIPSE':
      o.center = vec(e.center);
      o.majorAxis = vec(e.majorAxisEndPoint);
      o.ratio = e.axisRatio;
      o.startParam = e.startAngle;
      o.endParam = e.endAngle;
      break;

    case 'POINT':
      o.position = vec(e.position);
      break;

    case 'SOLID':
    case '3DFACE': {
      const p = e.points || [];
      if (p[0]) o.corner1 = vec(p[0]);
      if (p[1]) o.corner2 = vec(p[1]);
      if (p[2]) o.corner3 = vec(p[2]);
      if (p[3]) o.corner4 = vec(p[3]);
      o.type = 'SOLID';
      break;
    }

    case 'TEXT':
    case 'ATTRIB':
    case 'ATTDEF':
      o.insertionPoint = vec(e.startPoint || e.position);
      o.alignmentPt = vec(e.endPoint);
      o.rotationAngle = (e.rotation || 0) * DEG2RAD;
      o.horizAlignment = e.halign || 0;
      o.vertAlignment = e.valign || 0;
      o.textHeight = e.textHeight ?? e.height;
      break;

    case 'MTEXT':
      o.insertionPoint = vec(e.position);
      o.textHeight = e.height ?? e.textHeight;
      if (e.directionVector) o.xAxisDir = vec(e.directionVector);
      else if (e.rotation != null) o.xAxisDir = { x: Math.cos(e.rotation * DEG2RAD), y: Math.sin(e.rotation * DEG2RAD) };
      break;

    case 'INSERT': {
      o.insertionPoint = vec(e.position);
      o.scaleX = e.xScale ?? 1;
      o.scaleY = e.yScale ?? 1;
      o.rotation = (e.rotation || 0) * DEG2RAD;
      const h = nameToH.get(e.name);
      if (h != null) o.blockHeaderHandle = { value: h };
      break;
    }

    case 'DIMENSION': {
      const h = nameToH.get(e.block);
      if (h != null) o.blockHeaderHandle = { value: h };
      break;
    }

    default:
      break;
  }
  return o;
}

/**
 * Convert a full dxf-parser result → Viewer2D parseResult.
 * @param {object} dxf  output of `new DxfParser().parseSync(text)`
 * @returns {{version:string, vars:object, entities:object[], tables:{blocks:object[], layers:object[]}}}
 */
export function dxfToParseResult(dxf) {
  const blocks = dxf.blocks || {};
  const names = Object.keys(blocks);
  const nameToH = new Map();
  names.forEach((n, i) => nameToH.set(n, i + 1)); // 1-based synthetic handles

  const msName = names.find((n) => /model_space/i.test(n)) ?? names.find((n) => /model/i.test(n));
  const msH = msName != null ? nameToH.get(msName) : 0;

  const entities = [];
  // Model-space entities (top-level array) → owned by the synthetic *MODEL_SPACE handle.
  for (const e of (dxf.entities || [])) {
    const o = adaptEntity(e, nameToH);
    o.ownerHandle = { value: msH };
    entities.push(o);
  }
  // Block-definition entities → owned by their block's synthetic handle.
  for (const n of names) {
    const H = nameToH.get(n);
    for (const e of (blocks[n].entities || [])) {
      const o = adaptEntity(e, nameToH);
      o.ownerHandle = { value: H };
      entities.push(o);
    }
  }

  const blockTable = names.map((n) => ({
    name: n,
    handle: { value: nameToH.get(n) },
    basePoint: vec(blocks[n].position) || { x: 0, y: 0, z: 0 },
  }));

  const layerSrc = dxf.tables?.layer?.layers || {};
  const layerTable = Object.values(layerSrc).map((l) => ({ name: l.name, colorIndex: l.colorIndex }));

  return {
    version: 'dxf',
    vars: dxf.header || {},
    entities,
    tables: { blocks: blockTable, layers: layerTable },
  };
}
