"""Read saved DXF entities, not exporter memory. No SVG rasterization involved."""
from pathlib import Path
from collections import Counter
import json,math,datetime,hashlib
import ezdxf
from ezdxf import bbox
ROOT=Path(__file__).resolve().parent
cases=json.loads((ROOT/'fixtures.json').read_text());results=[];records=[]
for case in cases:
 source=ROOT/(case['id']+'.dxf');doc=ezdxf.readfile(source);entities=list(doc.modelspace());lines=[];arcs=[];open_paths=0;polyrecords=[]
 for entity in entities:
  if entity.dxftype()!='LWPOLYLINE':raise ValueError('Unexpected DXF entity: '+entity.dxftype())
  vertices=list(entity.get_points('xyb'));closed=bool(entity.closed);open_paths+=int(not closed);segments=[]
  for i in range(len(vertices) if closed else len(vertices)-1):
   x,y,b=vertices[i];xx,yy,_=vertices[(i+1)%len(vertices)];a=(float(x),float(y));z=(float(xx),float(yy))
   # Exact duplicate LINE segments, direction independent; not a near-overlap tolerance test.
   if b==0:lines.append(tuple(sorted((a,z))));kind='line'
   else:arcs.append({'start':a,'end':z,'bulge':float(b)});kind='circular_arc'
   segments.append({'start':a,'end':z,'bulge':float(b),'kind':kind})
  polyrecords.append({'handle':entity.dxf.handle,'closed_flag':closed,'vertices_xyb':[list(v) for v in vertices],'segments':segments})
 counts=Counter(lines);duplicate_lines=sum(n-1 for n in counts.values());extent=bbox.extents(entities,fast=False);width=float(extent.extmax.x-extent.extmin.x);height=float(extent.extmax.y-extent.extmin.y)
 incidence=Counter()
 for poly in polyrecords:
  for segment in poly['segments']:
   incidence[tuple(segment['start'])]+=1;incidence[tuple(segment['end'])]+=1
 bad_ends=[]
 for poly in polyrecords:
  if poly['closed_flag']:continue
  for v in [poly['vertices_xyb'][0],poly['vertices_xyb'][-1]]:bad_ends.append({'xy':v[:2],'incident_edge_count':incidence[tuple(v[:2])]})
 virtual=[v for e in entities for v in e.virtual_entities()];radii=[float(v.dxf.radius) for v in virtual if v.dxftype()=='ARC']
 result={'id':case['id'],'open_paths':open_paths,'duplicate_lines':duplicate_lines,'decoded_width_mm':width,'decoded_height_mm':height,'width_deviation_mm':width-case['width_mm'],'height_deviation_mm':height-case['height_mm'],'header_INSUNITS':int(doc.header.get('$INSUNITS',0)),'unit':'mm' if doc.header.get('$INSUNITS')==4 else 'not specified','polyline_count':len(entities),'circular_arc_segments':len(arcs),'decoded_arc_radii_mm':radii,'straight_segments':len(lines),'bytes':source.stat().st_size,'sha256':hashlib.sha256(source.read_bytes()).hexdigest(),'measured_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'dxf_auditor_errors':len(doc.audit().errors),'open_endpoint_incidence':bad_ends}
 results.append(result);records.append({'id':case['id'],'entities':polyrecords,'exact_line_segment_counts':[{'endpoints':list(k),'copies':v} for k,v in counts.items()],'arcs':arcs,'extent_min':list(extent.extmin),'extent_max':list(extent.extmax)})
(ROOT/'measurements.json').write_text(json.dumps(results,indent=2));(ROOT/'decoded-entities.json').write_text(json.dumps(records,indent=2));print(json.dumps(results,indent=2))
