#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FreeCAD Model Analyzer API - Parametrik Bounding Box ve Malzeme Ağırlık Hesaplayıcı
Web API ve CLI uyumlu analiz aracı - STEP İsim Parse Geliştirilmiş Versiyon
freecadcmd free.py freecad1.stp cikti.png
"""

import FreeCAD
import FreeCAD as App
import sys
import os
import json
import argparse
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, asdict
from pathlib import Path


# ============================================================================
# KONFİGÜRASYON SINIFLARI
# ============================================================================

@dataclass
class MaterialConfig:
    """Malzeme özellikleri"""
    name: str = "Aluminium"
    density_kg_dm3: float = 2.71  # Alüminyum: 2.71 kg/dm³
    symbol: str = "Al"


@dataclass
class AnalysisConfig:
    """Analiz ayarları"""
    decimal_precision: int = 4
    use_optimal_bbox: bool = True  # True: Optimal OBB, False: Axis-aligned
    calculate_inertia: bool = True
    name_search_depth: int = 10  # Parent zinciri derinliği
    parse_step_file: bool = True  # STEP dosyasını parse et


@dataclass
class OutputConfig:
    """Çıktı ayarları"""
    output_dir: str = "."
    save_json: bool = True
    save_detailed_debug: bool = True
    json_filename: Optional[str] = None  # None = otomatik
    include_name_candidates: bool = True  # İsim aday listesini dahil et


@dataclass
class UnitsConfig:
    """Birim ayarları"""
    length_unit: str = "mm"
    length_multiplier: float = 1.0
    area_unit: str = "mm²"
    area_multiplier: float = 1.0
    volume_unit: str = "mm³"
    volume_multiplier: float = 1.0
    weight_unit: str = "g"
    weight_multiplier: float = 1.0


# ============================================================================
# STEP DOSYA PARSER
# ============================================================================

class STEPParser:
    """STEP dosyasından metadata parse eden yardımcı sınıf"""
    
    def __init__(self, file_path: str):
        self.file_path = file_path
        self.content = ""
        self.entities = {}
        self.products = {}
        self.shapes = {}
        self.assemblies = {}
        
    def parse(self) -> Dict[str, any]:
        """STEP dosyasını parse et ve tüm entity'leri çıkar"""
        try:
            with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f:
                self.content = f.read()
            
            print(f"[STEP_PARSER] Dosya okundu: {len(self.content)} karakter")
            
            # Farklı entity tiplerini parse et
            self._parse_products()
            self._parse_shapes()
            self._parse_assemblies()
            self._parse_product_definitions()
            self._parse_shape_representations()
            
            # Tüm bulunan isimleri birleştir
            all_names = {}
            all_names.update(self.products)
            all_names.update(self.shapes)
            all_names.update(self.assemblies)
            
            print(f"[STEP_PARSER] Toplam {len(all_names)} isim bulundu")
            
            return {
                'all_names': all_names,
                'products': self.products,
                'shapes': self.shapes,
                'assemblies': self.assemblies,
                'total_entities': len(all_names)
            }
            
        except Exception as e:
            print(f"[STEP_PARSER ERROR] Parse hatası: {e}")
            return {'all_names': {}, 'products': {}, 'shapes': {}, 'assemblies': {}}
    
    def _parse_products(self):
        """PRODUCT entity'lerini parse et"""
        import re
        # Format: #123=PRODUCT('Part_Name','Part_Name','',(...))
        pattern = r"#(\d+)=PRODUCT\('([^']+)'"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name in matches:
            if name and name.strip():
                key = f"PRODUCT_{entity_id}"
                self.products[key] = name
                print(f"[STEP_PARSER] Product bulundu: {key} = {name}")
    
    def _parse_shapes(self):
        """SHAPE_REPRESENTATION entity'lerini parse et"""
        import re
        # Format: #456=SHAPE_REPRESENTATION('Shape_Name',(...))
        pattern = r"#(\d+)=SHAPE_REPRESENTATION\('([^']+)'"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name in matches:
            if name and name.strip():
                key = f"SHAPE_{entity_id}"
                self.shapes[key] = name
                print(f"[STEP_PARSER] Shape bulundu: {key} = {name}")
    
    def _parse_assemblies(self):
        """NEXT_ASSEMBLY_USAGE_OCCURRENCE entity'lerini parse et"""
        import re
        # Format: NEXT_ASSEMBLY_USAGE_OCCURRENCE('Assembly_Name',...)
        pattern = r"NEXT_ASSEMBLY_USAGE_OCCURRENCE\('([^']+)'"
        matches = re.findall(pattern, self.content)
        
        for idx, name in enumerate(matches):
            if name and name.strip():
                key = f"ASSEMBLY_{idx}"
                self.assemblies[key] = name
                print(f"[STEP_PARSER] Assembly bulundu: {key} = {name}")
    
    def _parse_product_definitions(self):
        """PRODUCT_DEFINITION entity'lerini parse et"""
        import re
        # Format: #789=PRODUCT_DEFINITION('id','description',#ref1,#ref2)
        pattern = r"#(\d+)=PRODUCT_DEFINITION\('([^']*)',\s*'([^']*)'"
        matches = re.findall(pattern, self.content)
        
        for entity_id, prod_id, description in matches:
            if description and description.strip():
                key = f"PROD_DEF_{entity_id}"
                self.products[key] = description
                print(f"[STEP_PARSER] Product Definition bulundu: {key} = {description}")
    
    def _parse_shape_representations(self):
        """ADVANCED_BREP_SHAPE_REPRESENTATION entity'lerini parse et"""
        import re
        # Format: #999=ADVANCED_BREP_SHAPE_REPRESENTATION('Name',(...))
        pattern = r"#(\d+)=ADVANCED_BREP_SHAPE_REPRESENTATION\('([^']+)'"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name in matches:
            if name and name.strip():
                key = f"BREP_{entity_id}"
                self.shapes[key] = name
                print(f"[STEP_PARSER] BREP Shape bulundu: {key} = {name}")


# ============================================================================
# FREECAD ANALİZ MOTORU
# ============================================================================

class FreeCADAnalyzer:
    """FreeCAD model analiz motoru"""

    def __init__(self,
                 material_cfg: MaterialConfig,
                 analysis_cfg: AnalysisConfig,
                 output_cfg: OutputConfig,
                 units_cfg: UnitsConfig):
        self.material_cfg = material_cfg
        self.analysis_cfg = analysis_cfg
        self.output_cfg = output_cfg
        self.units_cfg = units_cfg

        self.doc = None
        self.results: List[Dict] = []
        
        # STEP parser cache
        self.current_file_path: Optional[str] = None
        self.step_entity_names: Dict[str, str] = {}
        self.step_parser_data: Dict = {}

    def debug(self, msg: str, level: str = "INFO"):
        """Debug mesajı"""
        if self.output_cfg.save_detailed_debug or level in ["ERROR", "WARNING"]:
            print(f"[{level}] {msg}", flush=True)

    def around(self, value: float) -> float:
        """Sayıyı belirtilen hassasiyete yuvarla"""
        if self.analysis_cfg.decimal_precision == -1:
            return value
        return round(value, self.analysis_cfg.decimal_precision)

    def parse_step_file(self, file_path: str):
        """STEP dosyasını parse et ve cache'le"""
        if not self.analysis_cfg.parse_step_file:
            return
        
        try:
            print(f"\n[STEP_PARSE] STEP dosyası parse ediliyor...")
            parser = STEPParser(file_path)
            self.step_parser_data = parser.parse()
            self.step_entity_names = self.step_parser_data.get('all_names', {})
            
            print(f"[STEP_PARSE] {len(self.step_entity_names)} entity ismi bulundu")
            print(f"[STEP_PARSE] - Products: {len(self.step_parser_data.get('products', {}))}")
            print(f"[STEP_PARSE] - Shapes: {len(self.step_parser_data.get('shapes', {}))}")
            print(f"[STEP_PARSE] - Assemblies: {len(self.step_parser_data.get('assemblies', {}))}")
            
        except Exception as e:
            self.debug(f"STEP parse hatası: {e}", "WARNING")

    def find_object_name(self, obj) -> Tuple[str, str, Dict]:
        """
        Nesnenin gerçek ismini bul - GELİŞTİRİLMİŞ VERSİYON
        Returns: (real_name, source, candidates_dict)
        """
        print(f"\n[NAME_FINDER] ===== Nesne Özellikleri =====")
        print(f"[NAME_FINDER] Internal Name: {obj.Name if hasattr(obj, 'Name') else 'Yok'}")
        print(f"[NAME_FINDER] Label: {obj.Label if hasattr(obj, 'Label') else 'Yok'}")
        print(f"[NAME_FINDER] Label2: {obj.Label2 if hasattr(obj, 'Label2') else 'Yok'}")
        print(f"[NAME_FINDER] TypeId: {obj.TypeId if hasattr(obj, 'TypeId') else 'Yok'}")
        
        name_candidates = {}

        # 1. STEP DOSYASINDAN PARSE EDİLEN İSİMLER (EN ÖNCELİKLİ)
        if self.step_entity_names:
            print(f"[NAME_FINDER] STEP entity isimleri kontrol ediliyor...")
            
            # STEP entity'lerini adaylara ekle
            for key, value in self.step_entity_names.items():
                name_candidates[f'STEP.{key}'] = value
            
            # Obje ismini STEP entity'leriyle eşleştirmeye çalış
            obj_internal_name = obj.Name if hasattr(obj, 'Name') else ""
            
            # İnternal isim bazen STEP entity id'si içerir
            # Örn: "Shape001" -> STEP entity #001
            import re
            numbers = re.findall(r'\d+', obj_internal_name)
            if numbers:
                for num in numbers:
                    # STEP entity'lerde bu numarayı ara
                    matching_keys = [k for k in self.step_entity_names.keys() if num in k]
                    for key in matching_keys:
                        name_candidates[f'STEP.MATCHED.{key}'] = self.step_entity_names[key]
                        print(f"[NAME_FINDER] STEP eşleşme bulundu: {key} -> {self.step_entity_names[key]}")

        # 2. FREECAD LABEL ÖZELLİKLERİ
        if hasattr(obj, 'Label') and obj.Label:
            name_candidates['FreeCAD.Label'] = obj.Label
        if hasattr(obj, 'Label2') and obj.Label2:
            name_candidates['FreeCAD.Label2'] = obj.Label2
        if hasattr(obj, 'Name') and obj.Name:
            name_candidates['FreeCAD.Name'] = obj.Name
        if hasattr(obj, 'OldLabel') and obj.OldLabel:
            name_candidates['FreeCAD.OldLabel'] = obj.OldLabel
        if hasattr(obj, 'FullName') and obj.FullName:
            name_candidates['FreeCAD.FullName'] = obj.FullName

        # 3. SHAPE METADATASı
        if hasattr(obj, 'Shape'):
            try:
                if hasattr(obj.Shape, 'Tag'):
                    tag = str(obj.Shape.Tag)
                    name_candidates['Shape.Tag'] = tag
                    
                    # Tag'i STEP entity'leriyle eşleştir
                    matching_keys = [k for k in self.step_entity_names.keys() if tag in k]
                    for key in matching_keys:
                        name_candidates[f'STEP.TAG_MATCH.{key}'] = self.step_entity_names[key]
                
                # Solids içindeki taglar
                if hasattr(obj.Shape, 'Solids'):
                    for i, solid in enumerate(obj.Shape.Solids):
                        if hasattr(solid, 'Tag'):
                            name_candidates[f'Shape.Solid[{i}].Tag'] = str(solid.Tag)
            except Exception as e:
                print(f"[NAME_FINDER] Shape metadata hatası: {e}")

        # 4. LINKED OBJECT
        try:
            linked_obj = obj.getLinkedObject()
            if linked_obj and linked_obj != obj:
                if hasattr(linked_obj, 'Label') and linked_obj.Label:
                    name_candidates['LinkedObject.Label'] = linked_obj.Label
                if hasattr(linked_obj, 'Name') and linked_obj.Name:
                    name_candidates['LinkedObject.Name'] = linked_obj.Name
        except:
            pass

        # 5. PARENT ZİNCİRİ
        current_obj = obj
        parent_level = 0
        max_depth = self.analysis_cfg.name_search_depth

        while parent_level < max_depth:
            try:
                parent = current_obj.getParent()
                if parent and parent != current_obj:
                    parent_level += 1
                    if hasattr(parent, 'Label') and parent.Label:
                        name_candidates[f'Parent.Level{parent_level}.Label'] = parent.Label
                    if hasattr(parent, 'Name') and parent.Name:
                        name_candidates[f'Parent.Level{parent_level}.Name'] = parent.Name
                    if hasattr(parent, 'Label2') and parent.Label2:
                        name_candidates[f'Parent.Level{parent_level}.Label2'] = parent.Label2
                    current_obj = parent
                else:
                    break
            except:
                break

        # 6. PARENT GROUP
        try:
            parent_group = obj.getParentGroup()
            if parent_group:
                if hasattr(parent_group, 'Label') and parent_group.Label:
                    name_candidates['ParentGroup.Label'] = parent_group.Label
                if hasattr(parent_group, 'Name') and parent_group.Name:
                    name_candidates['ParentGroup.Name'] = parent_group.Name
        except:
            pass

        # 7. PARENTS (ÇOĞUL)
        try:
            if hasattr(obj, 'Parents') and obj.Parents:
                for idx, parent_tuple in enumerate(obj.Parents):
                    try:
                        parent_obj = parent_tuple[0] if isinstance(parent_tuple, tuple) else parent_tuple
                        if hasattr(parent_obj, 'Label') and parent_obj.Label:
                            name_candidates[f'Parents[{idx}].Label'] = parent_obj.Label
                    except:
                        pass
        except:
            pass

        # ÖNCELİK SIRALAMASI - GELİŞTİRİLMİŞ
        real_name = None
        source = None

        # Öncelik listesi (en önemliden en az önemliye)
        priority_order = [
            # 1. STEP dosyasından gelen eşleşmeler (EN ÖNCELİKLİ)
            'STEP.MATCHED.',
            'STEP.TAG_MATCH.',
            'STEP.PRODUCT_',
            'STEP.BREP_',
            'STEP.SHAPE_',
            'STEP.ASSEMBLY_',
            'STEP.PROD_DEF_',
            
            # 2. FreeCAD Label'ları
            'FreeCAD.Label',
            'FreeCAD.Label2',
            'FreeCAD.OldLabel',
            'LinkedObject.Label',
            
            # 3. Parent zinciri (en üstten)
            *[f'Parent.Level{i}.Label' for i in range(max_depth, 0, -1)],
            
            # 4. Diğer kaynaklar
            'ParentGroup.Label',
            'Parents[0].Label',
            'FreeCAD.FullName',
            'FreeCAD.Name',
            'Shape.Tag'
        ]

        for prefix in priority_order:
            # Prefix ile başlayan tüm adayları bul
            matching_keys = [k for k in name_candidates.keys() if k.startswith(prefix)]
            
            if matching_keys:
                # İlk eşleşeni al
                key = matching_keys[0]
                value = name_candidates[key]
                
                # Geçerli bir isim mi kontrol et
                if value and str(value).strip() and str(value).strip() not in ['', 'None', 'Unknown']:
                    real_name = str(value).strip()
                    source = key
                    print(f"[NAME_FINDER] İsim bulundu: {real_name} (kaynak: {source})")
                    break

        if not real_name:
            real_name = "Unknown"
            source = "None"
            print(f"[NAME_FINDER] İsim bulunamadı, 'Unknown' kullanılıyor")

        print(f"[NAME_FINDER] Toplam {len(name_candidates)} aday değerlendirildi")
        print(f"[NAME_FINDER] Seçilen: {real_name} (kaynak: {source})")

        return real_name, source, name_candidates

    def calculate_bounding_box(self, obj) -> Tuple[Dict, Dict]:
        """
        Bounding box hesapla
        Returns: (optimal_bbox, axis_aligned_bbox)
        """
        optimal_bbox = {}
        axis_aligned_bbox = {}

        # Axis-aligned bbox
        try:
            bb_aa = obj.Shape.BoundBox
            axis_aligned_bbox = {
                'x_length': self.around(bb_aa.XLength * self.units_cfg.length_multiplier),
                'y_length': self.around(bb_aa.YLength * self.units_cfg.length_multiplier),
                'z_length': self.around(bb_aa.ZLength * self.units_cfg.length_multiplier),
                'diagonal': self.around(bb_aa.DiagonalLength * self.units_cfg.length_multiplier),
                'x_min': self.around(bb_aa.XMin),
                'y_min': self.around(bb_aa.YMin),
                'z_min': self.around(bb_aa.ZMin),
                'x_max': self.around(bb_aa.XMax),
                'y_max': self.around(bb_aa.YMax),
                'z_max': self.around(bb_aa.ZMax),
                'type': 'axis_aligned',
                'unit': self.units_cfg.length_unit
            }
        except Exception as e:
            self.debug(f"Axis-aligned bbox hatası: {e}", "WARNING")

        # Optimal oriented bbox
        if self.analysis_cfg.use_optimal_bbox:
            try:
                import Part
                obb = obj.Shape.optimalBoundingBox()
                optimal_bbox = {
                    'x_length': self.around(obb.XLength * self.units_cfg.length_multiplier),
                    'y_length': self.around(obb.YLength * self.units_cfg.length_multiplier),
                    'z_length': self.around(obb.ZLength * self.units_cfg.length_multiplier),
                    'diagonal': self.around(obb.DiagonalLength * self.units_cfg.length_multiplier),
                    'x_min': self.around(obb.XMin),
                    'y_min': self.around(obb.YMin),
                    'z_min': self.around(obb.ZMin),
                    'x_max': self.around(obb.XMax),
                    'y_max': self.around(obb.YMax),
                    'z_max': self.around(obb.ZMax),
                    'type': 'optimal_oriented',
                    'unit': self.units_cfg.length_unit
                }
            except Exception as e:
                self.debug(f"Optimal bbox hatası: {e}", "WARNING")
                optimal_bbox = axis_aligned_bbox.copy()
                optimal_bbox['type'] = 'fallback_to_axis_aligned'

        return optimal_bbox, axis_aligned_bbox

    def analyze_object(self, obj) -> Optional[Dict]:
        """Tek bir nesneyi analiz et"""
        try:
            self.debug(f"Analiz ediliyor: {obj.Name}")

            # Shape kontrolü
            if not hasattr(obj, "Shape"):
                self.debug(f"Shape yok, atlanıyor: {obj.Name}", "WARNING")
                return None

            # İsim bul (GELİŞTİRİLMİŞ)
            real_name, name_source, name_candidates = self.find_object_name(obj)

            # Yüzey alanı
            try:
                surface = obj.Shape.Area * self.units_cfg.area_multiplier
            except:
                surface = 0.0

            # Hacim
            try:
                volume = obj.Shape.Volume * self.units_cfg.volume_multiplier
            except:
                volume = 0.0

            # Ağırlık hesaplama
            weight_g = ((volume * self.material_cfg.density_kg_dm3) * self.units_cfg.weight_multiplier) / 1000.0

            # Bounding box
            optimal_bbox, axis_aligned_bbox = self.calculate_bounding_box(obj)

            # Matrix of Inertia
            inertia = None
            if self.analysis_cfg.calculate_inertia:
                try:
                    matrix = obj.Shape.MatrixOfInertia
                    inertia = {
                        'raw': str(matrix),
                        'A11': matrix.A11, 'A12': matrix.A12, 'A13': matrix.A13, 'A14': matrix.A14,
                        'A21': matrix.A21, 'A22': matrix.A22, 'A23': matrix.A23, 'A24': matrix.A24,
                        'A31': matrix.A31, 'A32': matrix.A32, 'A33': matrix.A33, 'A34': matrix.A34,
                        'A41': matrix.A41, 'A42': matrix.A42, 'A43': matrix.A43, 'A44': matrix.A44,
                    }
                except:
                    inertia = {'error': 'Could not calculate inertia'}

            # Sonuç objesi
            result = {
                'object_name': real_name,
                'object_name_source': name_source,
                'object_name_internal': obj.Name if hasattr(obj, 'Name') else 'Unknown',
                'object_label': obj.Label if hasattr(obj, 'Label') else obj.Name,
                'object_type': obj.TypeId if hasattr(obj, 'TypeId') else 'Unknown',

                'bounding_box_optimal': optimal_bbox if optimal_bbox else axis_aligned_bbox,
                'bounding_box_axis_aligned': axis_aligned_bbox,

                'surface': {
                    'value': self.around(surface),
                    'unit': self.units_cfg.area_unit
                },

                'volume': {
                    'value': self.around(volume),
                    'unit': self.units_cfg.volume_unit,
                    'cm3': self.around(volume / 1000.0) if self.units_cfg.volume_unit == 'mm³' else None
                },

                'weight': {
                    'value': self.around(weight_g),
                    'unit': self.units_cfg.weight_unit,
                    'kg': self.around(weight_g / 1000.0),
                    'material': self.material_cfg.name,
                    'density_kg_dm3': self.material_cfg.density_kg_dm3
                }
            }

            # İsteğe bağlı alanlar
            if self.analysis_cfg.calculate_inertia:
                result['matrix_of_inertia'] = inertia

            if self.output_cfg.include_name_candidates:
                result['name_search_results'] = name_candidates

            return result

        except Exception as e:
            self.debug(f"Nesne analiz hatası ({obj.Name}): {e}", "ERROR")
            import traceback
            if self.output_cfg.save_detailed_debug:
                traceback.print_exc()
            return None

    def analyze_file(self, file_path: str) -> Dict:
        """STEP dosyasını analiz et - GELİŞTİRİLMİŞ"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"Dosya bulunamadı: {file_path}")

        # Dosya yolunu kaydet
        self.current_file_path = file_path
        
        # STEP dosyasını parse et (yeni özellik)
        self.parse_step_file(file_path)

        self.debug(f"Dosya açılıyor: {file_path}")

        try:
            # STEP dosyasını import et
            import Part
            self.doc = FreeCAD.newDocument("AnalysisDoc")
            Part.insert(file_path, self.doc.Name)

            objects = self.doc.Objects
            self.debug(f"Toplam {len(objects)} nesne bulundu")

            if not objects:
                raise ValueError("Dosyada nesne bulunamadı")

            # Her nesneyi analiz et
            results = []
            total_weight = 0.0

            for i, obj in enumerate(objects):
                self.debug(f"[{i+1}/{len(objects)}] {obj.Name}")
                result = self.analyze_object(obj)
                if result:
                    results.append(result)
                    total_weight += result['weight']['value']

            # Toplam özet
            summary = {
                'file_path': str(Path(file_path).absolute()),
                'file_name': Path(file_path).name,
                'total_objects_found': len(objects),
                'total_objects_analyzed': len(results),
                'step_entities_parsed': len(self.step_entity_names),
                'material': {
                    'name': self.material_cfg.name,
                    'density_kg_dm3': self.material_cfg.density_kg_dm3,
                    'symbol': self.material_cfg.symbol
                },
                'total_weight': {
                    'value': self.around(total_weight),
                    'unit': self.units_cfg.weight_unit,
                    'kg': self.around(total_weight / 1000.0)
                },
                'analysis_config': {
                    'decimal_precision': self.analysis_cfg.decimal_precision,
                    'use_optimal_bbox': self.analysis_cfg.use_optimal_bbox,
                    'calculate_inertia': self.analysis_cfg.calculate_inertia,
                    'parse_step_file': self.analysis_cfg.parse_step_file
                },
                'units': {
                    'length': self.units_cfg.length_unit,
                    'area': self.units_cfg.area_unit,
                    'volume': self.units_cfg.volume_unit,
                    'weight': self.units_cfg.weight_unit
                },
                'objects': results
            }
            
            # STEP parser sonuçlarını ekle
            if self.step_parser_data:
                summary['step_parser_summary'] = {
                    'total_entities': self.step_parser_data.get('total_entities', 0),
                    'products_found': len(self.step_parser_data.get('products', {})),
                    'shapes_found': len(self.step_parser_data.get('shapes', {})),
                    'assemblies_found': len(self.step_parser_data.get('assemblies', {}))
                }

            # JSON kaydet
            if self.output_cfg.save_json:
                json_path = self._save_json(file_path, summary)
                summary['json_output_path'] = json_path

            # Belgeyi kapat
            FreeCAD.closeDocument(self.doc.Name)
            self.debug("Analiz tamamlandı", "SUCCESS")

            return summary

        except Exception as e:
            self.debug(f"Dosya analiz hatası: {e}", "ERROR")
            if self.doc:
                FreeCAD.closeDocument(self.doc.Name)
            raise

    def _save_json(self, input_file: str, data: Dict) -> str:
        """JSON dosyasına kaydet"""
        if self.output_cfg.json_filename:
            json_path = os.path.join(self.output_cfg.output_dir, self.output_cfg.json_filename)
        else:
            base_name = Path(input_file).stem
            json_path = os.path.join(self.output_cfg.output_dir, f"{base_name}_analysis.json")

        os.makedirs(self.output_cfg.output_dir, exist_ok=True)

        with open(json_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)

        self.debug(f"JSON kaydedildi: {json_path}")
        return json_path


# ============================================================================
# YARDIMCI FONKSİYONLAR
# ============================================================================

def load_config_from_json(json_path: str) -> Dict:
    """JSON'dan konfigürasyon yükle"""
    with open(json_path, 'r', encoding='utf-8') as f:
        return json.load(f)


def save_config_template(output_path: str):
    """Örnek konfigürasyon şablonu oluştur"""
    config = {
        'input_file': 'model.stp',
        'material': asdict(MaterialConfig()),
        'analysis': asdict(AnalysisConfig()),
        'output': asdict(OutputConfig()),
        'units': asdict(UnitsConfig())
    }

    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=2, ensure_ascii=False)

    print(f"Konfigürasyon şablonu oluşturuldu: {output_path}")


def print_summary(summary: Dict):
    """Özet raporu yazdır"""
    print("\n" + "="*70)
    print("ANALİZ RAPORU")
    print("="*70)
    print(f"Dosya           : {summary['file_name']}")
    print(f"Toplam Nesne    : {summary['total_objects_analyzed']}/{summary['total_objects_found']}")
    
    if 'step_entities_parsed' in summary:
        print(f"STEP Entity     : {summary['step_entities_parsed']} isim parse edildi")
    
    print(f"Malzeme         : {summary['material']['name']} ({summary['material']['symbol']})")
    print(f"Yoğunluk        : {summary['material']['density_kg_dm3']} kg/dm³")
    print(f"Toplam Ağırlık  : {summary['total_weight']['value']} {summary['total_weight']['unit']}")
    print(f"                : {summary['total_weight']['kg']} kg")
    print("="*70)

    for i, obj in enumerate(summary['objects'], 1):
        print(f"\n[{i}] {obj['object_name']} ({obj['object_label']})")
        print(f"    Kaynak : {obj['object_name_source']}")
        print(f"    Tip    : {obj['object_type']}")

        bbox = obj['bounding_box_optimal']
        print(f"    BBox   : X={bbox['x_length']} Y={bbox['y_length']} Z={bbox['z_length']} {bbox['unit']}")
        print(f"    Hacim  : {obj['volume']['value']} {obj['volume']['unit']}")
        print(f"    Ağırlık: {obj['weight']['value']} {obj['weight']['unit']}")

    print("\n" + "="*70 + "\n")


# ============================================================================
# MALZEME ÖN TANIMLARI
# ============================================================================

MATERIALS = {
    'aluminium': MaterialConfig(name='Aluminium', density_kg_dm3=2.71, symbol='Al'),
    'steel': MaterialConfig(name='Steel', density_kg_dm3=7.85, symbol='Fe'),
    'copper': MaterialConfig(name='Copper', density_kg_dm3=8.96, symbol='Cu'),
    'titanium': MaterialConfig(name='Titanium', density_kg_dm3=4.51, symbol='Ti'),
    'plastic_pla': MaterialConfig(name='PLA Plastic', density_kg_dm3=1.25, symbol='PLA'),
    'plastic_abs': MaterialConfig(name='ABS Plastic', density_kg_dm3=1.05, symbol='ABS'),
    'wood': MaterialConfig(name='Wood (Oak)', density_kg_dm3=0.75, symbol='Wood'),
    'glass': MaterialConfig(name='Glass', density_kg_dm3=2.50, symbol='Glass'),
    'bronze': MaterialConfig(name='Bronze', density_kg_dm3=8.80, symbol='Bronze'),
    'brass': MaterialConfig(name='Brass', density_kg_dm3=8.50, symbol='Brass'),
}


# ============================================================================
# MAIN - CLI VE WEB API UYUMLU
# ============================================================================

def main():
    parser = argparse.ArgumentParser(
        description="FreeCAD Model Analyzer API - Parametrik Analiz Aracı (STEP Parser v2)",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Örnekler:
  # Basit kullanım (STEP parse ile)
  freecadcmd free.py model.stp

  # JSON config ile
  freecadcmd free.py -c analysis-config.json

  # Farklı malzeme
  freecadcmd free.py model.stp --material steel

  # Özel yoğunluk
  freecadcmd free.py model.stp --density 7.85

  # Çıktı dizini
  freecadcmd free.py model.stp -o ./results

  # STEP parse'ı devre dışı bırak
  freecadcmd free.py model.stp --no-step-parse

  # Config şablonu oluştur
  freecadcmd free.py --create-config analysis-config.json
        """
    )

    parser.add_argument('input_file', nargs='?', help='STEP dosya yolu (.stp/.step)')
    parser.add_argument('-c', '--config', help='JSON konfigürasyon dosyası')
    parser.add_argument('-o', '--output', help='Çıktı dizini')
    parser.add_argument('--material', choices=list(MATERIALS.keys()), help='Malzeme seçimi')
    parser.add_argument('--density', type=float, help='Özel malzeme yoğunluğu (kg/dm³)')
    parser.add_argument('--precision', type=int, help='Ondalık hassasiyet')
    parser.add_argument('--no-json', action='store_true', help='JSON kaydetme')
    parser.add_argument('--no-optimal-bbox', action='store_true', help='Sadece axis-aligned bbox kullan')
    parser.add_argument('--no-inertia', action='store_true', help='İnertia hesaplama')
    parser.add_argument('--no-step-parse', action='store_true', help='STEP dosyası parse etme')
    parser.add_argument('--include-candidates', action='store_true', help='İsim aday listesini dahil et')
    parser.add_argument('--create-config', help='Örnek config.json oluştur')
    parser.add_argument('--list-materials', action='store_true', help='Mevcut malzemeleri listele')
    parser.add_argument('--debug', action='store_true', help='Detaylı debug çıktısı')

    args = parser.parse_args()

    # Malzeme listesi
    if args.list_materials:
        print("\nMevcut Malzemeler:")
        print("="*60)
        for key, mat in MATERIALS.items():
            print(f"  {key:15s} : {mat.name:20s} ({mat.symbol}) - {mat.density_kg_dm3} kg/dm³")
        print("="*60 + "\n")
        return

    # Config şablonu oluştur
    if args.create_config:
        save_config_template(args.create_config)
        return

    # Konfigürasyon yükle
    if args.config:
        config_data = load_config_from_json(args.config)
        input_file = config_data.get('input_file', args.input_file)
        material_cfg = MaterialConfig(**config_data.get('material', {}))
        analysis_cfg = AnalysisConfig(**config_data.get('analysis', {}))
        output_cfg = OutputConfig(**config_data.get('output', {}))
        units_cfg = UnitsConfig(**config_data.get('units', {}))
    else:
        # Varsayılan değerler
        input_file = args.input_file
        material_cfg = MATERIALS['aluminium']
        analysis_cfg = AnalysisConfig()
        output_cfg = OutputConfig()
        units_cfg = UnitsConfig()

    # CLI override
    if args.material:
        material_cfg = MATERIALS[args.material]
    if args.density:
        material_cfg.density_kg_dm3 = args.density
        material_cfg.name = f"Custom ({args.density} kg/dm³)"
    if args.output:
        output_cfg.output_dir = args.output
    if args.precision is not None:
        analysis_cfg.decimal_precision = args.precision
    if args.no_json:
        output_cfg.save_json = False
    if args.no_optimal_bbox:
        analysis_cfg.use_optimal_bbox = False
    if args.no_inertia:
        analysis_cfg.calculate_inertia = False
    if args.no_step_parse:
        analysis_cfg.parse_step_file = False
    if args.include_candidates:
        output_cfg.include_name_candidates = True
    if args.debug:
        output_cfg.save_detailed_debug = True

    # Dosya kontrolü
    if not input_file:
        parser.print_help()
        print("\n[HATA] STEP dosyası belirtilmedi!")
        return

    if not os.path.exists(input_file):
        print(f"\n[HATA] Dosya bulunamadı: {input_file}\n")
        return

    # Analiz et
    try:
        analyzer = FreeCADAnalyzer(
            material_cfg,
            analysis_cfg,
            output_cfg,
            units_cfg
        )

        summary = analyzer.analyze_file(input_file)

        # Rapor yazdır
        print_summary(summary)

        # JSON yolu
        if output_cfg.save_json:
            print(f"JSON çıktısı: {summary.get('json_output_path', 'N/A')}\n")

    except Exception as e:
        print(f"\n[HATA] Analiz başarısız: {e}\n")
        import traceback
        if args.debug:
            traceback.print_exc()
        sys.exit(1)


if __name__ == "__main__":
    main()