#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FreeCAD Model Analyzer API - STEP Label Parser v3.1
GELİŞTİRİLMİŞ:
- Gerçek STEP etiketlerini yakalama
- Entity ilişki haritalama
- Detaylı Ürün Ağacı (Tree View JSON)
- Her node'da analiz verileri (hacim, ağırlık, boyut)
- Hiyerarşik grup yapısı
- STEP entity bilgileri her node'da
"""

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
    symbol: str = "Al"


@dataclass
class AnalysisConfig:
    """Analiz ayarları"""
    decimal_precision: int = 4
    use_optimal_bbox: bool = True
    calculate_inertia: bool = True
    name_search_depth: int = 10
    parse_step_file: bool = True


@dataclass
class OutputConfig:
    """Çıktı ayarları"""
    output_dir: str = "."
    save_json: bool = True
    save_detailed_debug: bool = True
    json_filename: Optional[str] = None
    include_name_candidates: bool = True


@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


# ============================================================================
# GELİŞTİRİLMİŞ STEP PARSER v3.0
# ============================================================================

class STEPParser:
    """
    STEP dosyasından metadata parse eden geliştirilmiş parser
    
    YENİ ÖZELLİKLER:
    - Entity ilişki haritası (references tracking)
    - PRODUCT_DEFINITION_SHAPE parse
    - SHAPE_DEFINITION_REPRESENTATION parse
    - Tag-to-Entity mapping
    - Çoklu isim stratejisi
    """
    
    def __init__(self, file_path: str):
        self.file_path = file_path
        self.content = ""
        self.entities = {}  # {entity_id: {type, name, refs}}
        self.entity_relationships = {}  # {entity_id: [referenced_ids]}
        self.tag_to_entity = {}  # {tag: entity_id}
        
    def parse(self) -> Dict[str, any]:
        """STEP dosyasını kapsamlı parse et"""
        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")
            
            # Tüm entity tiplerini parse et
            self._parse_products()
            self._parse_product_definitions()
            self._parse_product_definition_shapes()  # YENİ!
            self._parse_shape_representations()
            self._parse_shape_definition_representations()  # YENİ!
            self._parse_advanced_brep_shapes()
            self._parse_assemblies()
            self._parse_manifold_solid_breps()  # YENİ!
            
            # İlişki haritasını oluştur
            self._build_relationship_map()
            
            # Tag-to-entity mapping
            self._build_tag_mapping()
            
            # Tüm isimleri topla
            all_names = {}
            for entity_id, entity_data in self.entities.items():
                if entity_data.get('name'):
                    key = f"{entity_data['type']}_{entity_id}"
                    all_names[key] = entity_data['name']
            
            print(f"[STEP_PARSER] ✓ Toplam {len(all_names)} isimli entity bulundu")
            print(f"[STEP_PARSER] ✓ {len(self.entity_relationships)} entity ilişkisi mapping yapıldı")
            
            return {
                'all_names': all_names,
                'entities': self.entities,
                'relationships': self.entity_relationships,
                'tag_mapping': self.tag_to_entity,
                'total_entities': len(all_names)
            }
            
        except Exception as e:
            print(f"[STEP_PARSER ERROR] {e}")
            import traceback
            traceback.print_exc()
            return {
                'all_names': {},
                'entities': {},
                'relationships': {},
                'tag_mapping': {}
            }
    
    def _parse_products(self):
        """PRODUCT entity'leri"""
        import re
        pattern = r"#(\d+)=PRODUCT\('([^']+)','([^']*)',"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name1, name2 in matches:
            best_name = name1 or name2
            if best_name and best_name.strip() and best_name not in ['', 'Default', 'UNKNOWN']:
                self.entities[entity_id] = {
                    'type': 'PRODUCT',
                    'name': best_name.strip(),
                    'refs': []
                }
                print(f"[STEP_PARSER] ✓ PRODUCT #{entity_id}: '{best_name}'")
    
    def _parse_product_definitions(self):
        """PRODUCT_DEFINITION entity'leri"""
        import re
        pattern = r"#(\d+)=PRODUCT_DEFINITION\('([^']*)',\s*'([^']*)',\s*#(\d+)"
        matches = re.findall(pattern, self.content)
        
        for entity_id, prod_id, description, ref_id in matches:
            if description and description.strip():
                self.entities[entity_id] = {
                    'type': 'PRODUCT_DEFINITION',
                    'name': description.strip(),
                    'refs': [ref_id]
                }
                print(f"[STEP_PARSER] ✓ PRODUCT_DEFINITION #{entity_id}: '{description}' -> #{ref_id}")
    
    def _parse_product_definition_shapes(self):
        """PRODUCT_DEFINITION_SHAPE entity'leri (ÖNEMLI!)"""
        import re
        pattern = r"#(\d+)=PRODUCT_DEFINITION_SHAPE\('([^']*)',\s*'([^']*)',\s*#(\d+)"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name, description, ref_id in matches:
            best_name = description or name
            if best_name and best_name.strip():
                self.entities[entity_id] = {
                    'type': 'PRODUCT_DEFINITION_SHAPE',
                    'name': best_name.strip(),
                    'refs': [ref_id]
                }
                print(f"[STEP_PARSER] ✓ PRODUCT_DEFINITION_SHAPE #{entity_id}: '{best_name}' -> #{ref_id}")
    
    def _parse_shape_representations(self):
        """SHAPE_REPRESENTATION entity'leri"""
        import re
        pattern = r"#(\d+)=SHAPE_REPRESENTATION\('([^']+)',\(([^)]+)\)"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name, refs_str in matches:
            if name and name.strip():
                # Referansları çıkar
                refs = re.findall(r'#(\d+)', refs_str)
                self.entities[entity_id] = {
                    'type': 'SHAPE_REPRESENTATION',
                    'name': name.strip(),
                    'refs': refs
                }
                print(f"[STEP_PARSER] ✓ SHAPE_REPRESENTATION #{entity_id}: '{name}' ({len(refs)} refs)")
    
    def _parse_shape_definition_representations(self):
        """SHAPE_DEFINITION_REPRESENTATION entity'leri"""
        import re
        pattern = r"#(\d+)=SHAPE_DEFINITION_REPRESENTATION\(#(\d+),#(\d+)\)"
        matches = re.findall(pattern, self.content)
        
        for entity_id, ref1, ref2 in matches:
            self.entities[entity_id] = {
                'type': 'SHAPE_DEFINITION_REPRESENTATION',
                'name': None,
                'refs': [ref1, ref2]
            }
    
    def _parse_advanced_brep_shapes(self):
        """ADVANCED_BREP_SHAPE_REPRESENTATION entity'leri"""
        import re
        pattern = r"#(\d+)=ADVANCED_BREP_SHAPE_REPRESENTATION\('([^']+)'"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name in matches:
            if name and name.strip():
                self.entities[entity_id] = {
                    'type': 'ADVANCED_BREP',
                    'name': name.strip(),
                    'refs': []
                }
                print(f"[STEP_PARSER] ✓ ADVANCED_BREP #{entity_id}: '{name}'")
    
    def _parse_assemblies(self):
        """NEXT_ASSEMBLY_USAGE_OCCURRENCE entity'leri"""
        import re
        pattern = r"#(\d+)=NEXT_ASSEMBLY_USAGE_OCCURRENCE\('([^']+)'"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name in matches:
            if name and name.strip():
                self.entities[entity_id] = {
                    'type': 'ASSEMBLY',
                    'name': name.strip(),
                    'refs': []
                }
                print(f"[STEP_PARSER] ✓ ASSEMBLY #{entity_id}: '{name}'")
    
    def _parse_manifold_solid_breps(self):
        """MANIFOLD_SOLID_BREP entity'leri"""
        import re
        pattern = r"#(\d+)=MANIFOLD_SOLID_BREP\('([^']+)'"
        matches = re.findall(pattern, self.content)
        
        for entity_id, name in matches:
            if name and name.strip():
                self.entities[entity_id] = {
                    'type': 'MANIFOLD_SOLID_BREP',
                    'name': name.strip(),
                    'refs': []
                }
                print(f"[STEP_PARSER] ✓ MANIFOLD_SOLID_BREP #{entity_id}: '{name}'")
    
    def _build_relationship_map(self):
        """Entity'ler arası referans haritası oluştur"""
        import re
        
        # Tüm #ID referanslarını bul
        lines = self.content.split('\n')
        for line in lines:
            if line.startswith('#'):
                # Satırın başındaki entity ID
                match = re.match(r'#(\d+)=', line)
                if match:
                    entity_id = match.group(1)
                    
                    # Bu satırdaki tüm #ID referanslarını bul
                    refs = re.findall(r'#(\d+)', line[len(match.group(0)):])
                    
                    if refs:
                        self.entity_relationships[entity_id] = refs
        
        print(f"[STEP_PARSER] ✓ {len(self.entity_relationships)} entity ilişkisi map'lendi")
    
    def _build_tag_mapping(self):
        """Tag-to-Entity mapping (FreeCAD Shape.Tag için)"""
        # FreeCAD'deki Shape.Tag genellikle entity ID'sine karşılık gelir
        for entity_id in self.entities.keys():
            self.tag_to_entity[entity_id] = entity_id
        
        print(f"[STEP_PARSER] ✓ {len(self.tag_to_entity)} tag mapping oluşturuldu")
    
    def find_name_by_tag(self, tag: str) -> Optional[str]:
        """
        Bir Tag değeri için en iyi ismi bul
        
        Strateji:
        1. Direkt Tag'e karşılık gelen entity'nin ismi
        2. Tag'in referans ettiği entity'lerin isimleri
        3. Tag'i referans eden entity'lerin isimleri
        """
        tag_str = str(tag)
        
        # 1. Direkt eşleşme
        if tag_str in self.entities and self.entities[tag_str].get('name'):
            name = self.entities[tag_str]['name']
            print(f"[TAG_RESOLVER] ✓ Direkt: Tag={tag} -> #{tag_str} = '{name}'")
            return name
        
        # 2. Bu entity'nin referans ettiği entity'lerin isimleri
        if tag_str in self.entity_relationships:
            for ref_id in self.entity_relationships[tag_str]:
                if ref_id in self.entities and self.entities[ref_id].get('name'):
                    name = self.entities[ref_id]['name']
                    print(f"[TAG_RESOLVER] ✓ Forward ref: Tag={tag} -> #{tag_str} -> #{ref_id} = '{name}'")
                    return name
        
        # 3. Bu entity'yi referans eden entity'lerin isimleri (reverse lookup)
        for entity_id, refs in self.entity_relationships.items():
            if tag_str in refs:
                if entity_id in self.entities and self.entities[entity_id].get('name'):
                    name = self.entities[entity_id]['name']
                    print(f"[TAG_RESOLVER] ✓ Backward ref: Tag={tag} <- #{entity_id} = '{name}'")
                    return name
        
        print(f"[TAG_RESOLVER] ✗ Tag={tag} için isim bulunamadı")
        return None
    
    def find_name_by_label(self, label: str) -> Optional[str]:
        """FreeCAD Label'ı ile STEP entity eşleştir"""
        label_lower = label.lower()
        
        # Tam eşleşme
        for entity_id, entity_data in self.entities.items():
            name = entity_data.get('name', '')
            if name and name.lower() == label_lower:
                print(f"[LABEL_RESOLVER] ✓ Tam eşleşme: Label='{label}' = STEP '{name}'")
                return name
        
        # Kısmi eşleşme (label içinde veya entity name içinde)
        for entity_id, entity_data in self.entities.items():
            name = entity_data.get('name', '')
            if name:
                if label_lower in name.lower() or name.lower() in label_lower:
                    print(f"[LABEL_RESOLVER] ✓ Kısmi eşleşme: Label='{label}' ≈ STEP '{name}'")
                    return name
        
        return None


# ============================================================================
# GELİŞTİRİLMİŞ FREECAD ANALİZ MOTORU
# ============================================================================

class FreeCADAnalyzer:
    """FreeCAD model analiz motoru - STEP Label v3.0"""

    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] = []
        self.step_parser: Optional[STEPParser] = None

    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"""
        if not self.analysis_cfg.parse_step_file:
            return
        
        try:
            print(f"\n{'='*70}")
            print("STEP DOSYA PARSE")
            print('='*70)
            
            self.step_parser = STEPParser(file_path)
            parser_data = self.step_parser.parse()
            
            print(f"\n[STEP_PARSE] ✓ {parser_data['total_entities']} entity parse edildi")
            print('='*70)
            
        except Exception as e:
            self.debug(f"STEP parse hatası: {e}", "WARNING")
            import traceback
            traceback.print_exc()

    def find_object_name(self, obj) -> Tuple[str, str, Dict]:
        """
        Nesnenin gerçek ismini bul - GELİŞTİRİLMİŞ v3.0
        
        STRATEJİ SIRASI:
        1. Shape.Tag ile STEP entity'den direkt isim çözümle
        2. FreeCAD Label ile STEP entity eşleştir
        3. Entity ilişki haritasından isim bul
        4. FreeCAD metadata (Label, Label2, vb.)
        5. Parent zinciri
        """
        print(f"\n{'='*70}")
        print(f"[NAME_FINDER] Obje: {obj.Name}")
        print('='*70)
        
        name_candidates = {}
        real_name = None
        source = None
        
        # FreeCAD özellikleri
        obj_name = obj.Name if hasattr(obj, 'Name') else ''
        obj_label = obj.Label if hasattr(obj, 'Label') else ''
        obj_label2 = obj.Label2 if hasattr(obj, 'Label2') else ''
        
        print(f"[NAME_FINDER] FreeCAD Properties:")
        print(f"  - Name     : {obj_name}")
        print(f"  - Label    : {obj_label}")
        print(f"  - Label2   : {obj_label2}")
        print(f"  - TypeId   : {obj.TypeId if hasattr(obj, 'TypeId') else 'N/A'}")
        
        # === STRATEJİ 1: Shape.Tag ile STEP Entity ===
        if self.step_parser and hasattr(obj, 'Shape') and hasattr(obj.Shape, 'Tag'):
            try:
                tag = str(obj.Shape.Tag)
                print(f"\n[NAME_FINDER] Shape.Tag: {tag}")
                
                # Tag'den isim çözümle
                step_name = self.step_parser.find_name_by_tag(tag)
                if step_name:
                    real_name = step_name
                    source = f'STEP.TAG_RESOLVED.{tag}'
                    name_candidates[source] = step_name
                    print(f"[NAME_FINDER] ✓✓✓ STEP TAG RESOLVED: '{real_name}'")
                
            except Exception as e:
                print(f"[NAME_FINDER] Shape.Tag hatası: {e}")
        
        # === STRATEJİ 2: FreeCAD Label ile STEP Entity ===
        if not real_name and self.step_parser and obj_label:
            step_name = self.step_parser.find_name_by_label(obj_label)
            if step_name:
                real_name = step_name
                source = f'STEP.LABEL_MATCH'
                name_candidates[source] = step_name
                print(f"[NAME_FINDER] ✓✓ STEP LABEL MATCH: '{real_name}'")
        
        # === STRATEJİ 3: FreeCAD Metadata ===
        if not real_name:
            if obj_label and obj_label not in ['', 'None', 'Unknown']:
                name_candidates['FreeCAD.Label'] = obj_label
            if obj_label2 and obj_label2 not in ['', 'None', 'Unknown']:
                name_candidates['FreeCAD.Label2'] = obj_label2
            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
        
        # === STRATEJİ 4: Linked Object ===
        if not real_name:
            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
            except:
                pass
        
        # === STRATEJİ 5: Parent Zinciri ===
        if not real_name:
            current_obj = obj
            for level in range(1, self.analysis_cfg.name_search_depth + 1):
                try:
                    parent = current_obj.getParent()
                    if parent and parent != current_obj:
                        if hasattr(parent, 'Label') and parent.Label:
                            name_candidates[f'Parent.Level{level}.Label'] = parent.Label
                        current_obj = parent
                    else:
                        break
                except:
                    break
        
        # === ÖNCELIK SIRALAMASI ===
        if not real_name:
            priority_keys = [
                'FreeCAD.Label',
                'FreeCAD.Label2',
                'LinkedObject.Label',
                'FreeCAD.OldLabel',
                'Parent.Level1.Label',
                'FreeCAD.FullName'
            ]
            
            for key in priority_keys:
                if key in name_candidates:
                    value = name_candidates[key]
                    if value and str(value).strip():
                        real_name = str(value).strip()
                        source = key
                        print(f"[NAME_FINDER] ◯ FreeCAD Fallback: '{real_name}' ({key})")
                        break
        
        # === SON ÇARE ===
        if not real_name:
            real_name = obj_name or "Unknown"
            source = "Fallback"
            print(f"[NAME_FINDER] ✗ Fallback: '{real_name}'")
        
        print(f"\n[NAME_FINDER] ━━━ SONUÇ ━━━")
        print(f"[NAME_FINDER] İsim  : {real_name}")
        print(f"[NAME_FINDER] Kaynak: {source}")
        print(f"[NAME_FINDER] Adaylar: {len(name_candidates)}")
        print('='*70)
        
        return real_name, source, name_candidates

    def calculate_bounding_box(self, obj) -> Tuple[Dict, Dict]:
        """Bounding box hesapla"""
        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:
                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),
                    '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:
            # 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İŞ v3.0)
            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
            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 = {
                        'A11': matrix.A11, 'A12': matrix.A12, 'A13': matrix.A13,
                        'A21': matrix.A21, 'A22': matrix.A22, 'A23': matrix.A23,
                        'A31': matrix.A31, 'A32': matrix.A32, 'A33': matrix.A33,
                    }
                except:
                    inertia = {'error': 'Could not calculate'}

            # 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
                },
            }

            if hasattr(obj, 'Parent'):
                result['parent'] = {
                    'name': obj.Parent.Name if hasattr(obj.Parent, 'Name') else 'Unknown',
                    'label': obj.Parent.Label if hasattr(obj.Parent, 'Label') else 'Unknown',
                    'type': obj.Parent.TypeId if hasattr(obj.Parent, 'TypeId') else 'Unknown'
                }
                
            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 get_product_tree(self, analysis_results: List[Dict]) -> List[Dict]:
        """
        Ürün ağacını hiyerarşik olarak döndürür - GELİŞTİRİLMİŞ

        ÖZELLİKLER:
        - Group özelliğini kullanarak hiyerarşi
        - Her node için analiz verilerini ekler
        - STEP entity bilgilerini içerir
        - Hacim, ağırlık, boyut bilgileri eklenir
        """
        if not self.doc:
            return []

        # Analiz sonuçlarını obje adına göre map'le
        analysis_map = {}
        for result in analysis_results:
            obj_name = result.get('object_name_internal', '')
            if obj_name:
                analysis_map[obj_name] = result

        # Ziyaret edilen nesneleri takip etmek için set
        visited = set()
        tree = []

        # Düğüm oluşturma fonksiyonu
        def build_node(obj, level=0):
            if obj in visited:
                return None
            visited.add(obj)

            # Temel node bilgileri
            node = {
                'name': obj.Name,
                'label': obj.Label if hasattr(obj, 'Label') else obj.Name,
                'type': obj.TypeId if hasattr(obj, 'TypeId') else 'Unknown',
                'level': level,
                'children': []
            }

            # Analiz verilerini ekle
            if obj.Name in analysis_map:
                analysis_data = analysis_map[obj.Name]

                # Gerçek nesne adı (STEP'ten gelen)
                node['real_name'] = analysis_data.get('object_name', obj.Name)
                node['name_source'] = analysis_data.get('object_name_source', 'Unknown')

                # Geometrik bilgiler
                if 'bounding_box_optimal' in analysis_data:
                    bbox = analysis_data['bounding_box_optimal']
                    node['dimensions'] = {
                        'x': bbox.get('x_length'),
                        'y': bbox.get('y_length'),
                        'z': bbox.get('z_length'),
                        'unit': bbox.get('unit', 'mm')
                    }

                # Hacim bilgisi
                if 'volume' in analysis_data:
                    node['volume'] = {
                        'value': analysis_data['volume'].get('value'),
                        'unit': analysis_data['volume'].get('unit'),
                        'cm3': analysis_data['volume'].get('cm3')
                    }

                # Yüzey alanı
                if 'surface' in analysis_data:
                    node['surface'] = {
                        'value': analysis_data['surface'].get('value'),
                        'unit': analysis_data['surface'].get('unit')
                    }

                # Ağırlık bilgisi
                if 'weight' in analysis_data:
                    node['weight'] = {
                        'value': analysis_data['weight'].get('value'),
                        'unit': analysis_data['weight'].get('unit'),
                        'kg': analysis_data['weight'].get('kg'),
                        'material': analysis_data['weight'].get('material')
                    }

                # STEP entity bilgileri
                if hasattr(obj, 'Shape') and hasattr(obj.Shape, 'Tag'):
                    node['step_tag'] = str(obj.Shape.Tag)

                    # STEP parser'dan entity bilgilerini al
                    if self.step_parser:
                        tag_str = str(obj.Shape.Tag)
                        if tag_str in self.step_parser.entities:
                            entity = self.step_parser.entities[tag_str]
                            node['step_entity'] = {
                                'type': entity.get('type'),
                                'entity_id': tag_str,
                                'references': entity.get('refs', [])
                            }
            else:
                # Analiz verisi yoksa temel bilgiler
                node['real_name'] = obj.Label if hasattr(obj, 'Label') else obj.Name
                node['name_source'] = 'FreeCAD.Label'

            # Alt nesneleri ekle (Group özelliği varsa)
            if hasattr(obj, 'Group') and obj.Group:
                for child in obj.Group:
                    child_node = build_node(child, level + 1)
                    if child_node:
                        node['children'].append(child_node)

            # Alt grup sayısını ekle
            node['child_count'] = len(node['children'])

            # Toplam ağırlığı hesapla (kendisi + çocukları)
            total_weight = 0.0
            if 'weight' in node and node['weight'].get('value'):
                total_weight = node['weight']['value']

            for child in node['children']:
                if 'total_weight' in child:
                    total_weight += child['total_weight']

            if total_weight > 0:
                node['total_weight'] = total_weight
                node['total_weight_kg'] = self.around(total_weight / 1000.0)

            return node

        # Kök nesneleri bul (Parent'ı olmayan veya Parent belge olan)
        root_objects = []
        for obj in self.doc.Objects:
            is_root = True

            # Parent kontrolü
            try:
                if hasattr(obj, 'getParent'):
                    parent = obj.getParent()
                    if parent and parent in self.doc.Objects:
                        is_root = False
            except:
                pass

            # Group içinde mi kontrolü
            for other_obj in self.doc.Objects:
                if hasattr(other_obj, 'Group') and obj in other_obj.Group:
                    is_root = False
                    break

            if is_root:
                root_objects.append(obj)

        # Eğer kök nesne bulunamazsa, tüm nesneleri kök olarak kabul et
        if not root_objects:
            root_objects = [obj for obj in self.doc.Objects if obj not in visited]

        # Kök nesnelerden ağacı oluştur
        for obj in root_objects:
            if obj not in visited:
                node = build_node(obj, 0)
                if node:
                    tree.append(node)

        return tree

    def _get_tree_depth(self, node: Dict) -> int:
        """Ağaç node'unun maksimum derinliğini hesapla (recursive)"""
        if not node.get('children'):
            return 1

        max_child_depth = max((self._get_tree_depth(child) for child in node['children']), default=0)
        return 1 + max_child_depth

    def analyze_file(self, file_path: str) -> Dict:
        """STEP dosyasını analiz et - v3.1"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"Dosya bulunamadı: {file_path}")

        # STEP parse (ÖNCE!)
        self.parse_step_file(file_path)

        self.debug(f"\nDosya açılıyor: {file_path}")

        try:
            # FreeCAD import - Import.insert() kullan (daha iyi metadata)
            import Import
            self.doc = FreeCAD.newDocument("AnalysisDoc")
            Import.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ı")

            # Import sonrası debug
            if self.output_cfg.save_detailed_debug:
                print(f"\n{'='*70}")
                print("FREECAD IMPORT SONUÇLARI")
                print('='*70)
                for i, obj in enumerate(objects):
                    print(f"[{i+1}] {obj.Name}")
                    print(f"    Label : {obj.Label if hasattr(obj, 'Label') else 'N/A'}")
                    print(f"    Label2: {obj.Label2 if hasattr(obj, 'Label2') else 'N/A'}")
                    if hasattr(obj, 'Shape') and hasattr(obj.Shape, 'Tag'):
                        print(f"    Tag   : {obj.Shape.Tag}")
                print('='*70)

            # Her nesneyi analiz et
            results = []
            total_weight = 0.0

            for i, obj in enumerate(objects):
                self.debug(f"\n[{i+1}/{len(objects)}] {obj.Name}")
                result = self.analyze_object(obj)
                if result:
                    results.append(result)
                    total_weight += result['weight']['value']

            # Ürün ağacını al (analiz sonuçlarıyla birlikte)
            product_tree = self.get_product_tree(results)
            
            # Ürün ağacı istatistikleri
            def count_tree_nodes(tree):
                """Ağaçtaki toplam node sayısını hesapla"""
                count = 0
                for node in tree:
                    count += 1
                    if 'children' in node:
                        count += count_tree_nodes(node['children'])
                return count

            tree_node_count = count_tree_nodes(product_tree)
            tree_root_count = len(product_tree)

            # 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_parser.entities) if self.step_parser else 0,
                '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
                },
                'product_tree_stats': {
                    'total_nodes': tree_node_count,
                    'root_nodes': tree_root_count,
                    'max_depth': max((self._get_tree_depth(node) for node in product_tree), default=0)
                },
                'product_tree': product_tree,  # Ürün ağacı eklendi (v3.1 - Detaylı)
                'objects': results
            }
            
            # STEP parser sonuçlarını ekle
            if self.step_parser:
                summary['step_parser_summary'] = {
                    'total_entities': len(self.step_parser.entities),
                    'total_relationships': len(self.step_parser.entity_relationships),
                    'entity_types': {}
                }
                
                # Entity tiplerini say
                for entity_data in self.step_parser.entities.values():
                    entity_type = entity_data['type']
                    if entity_type not in summary['step_parser_summary']['entity_types']:
                        summary['step_parser_summary']['entity_types'][entity_type] = 0
                    summary['step_parser_summary']['entity_types'][entity_type] += 1

            # 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("\n✓ 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 v3.1 - Detaylı Ürün Ağacı")
    print("="*70)
    print(f"Dosya           : {summary['file_name']}")
    print(f"Toplam Nesne    : {summary['total_objects_analyzed']}/{summary['total_objects_found']}")

    if 'product_tree_stats' in summary:
        stats = summary['product_tree_stats']
        print(f"\nÜrün Ağacı İstatistikleri:")
        print(f"  - Kök Nesne   : {stats['root_nodes']}")
        print(f"  - Toplam Node : {stats['total_nodes']}")
        print(f"  - Maks Derinlik: {stats['max_depth']}")
    
    if 'step_parser_summary' in summary:
        print(f"\nSTEP Parser:")
        print(f"  - Toplam Entity: {summary['step_parser_summary']['total_entities']}")
        print(f"  - İlişkiler    : {summary['step_parser_summary']['total_relationships']}")
        print(f"  - Tipler       :")
        for entity_type, count in summary['step_parser_summary'].get('entity_types', {}).items():
            print(f"      • {entity_type}: {count}")
    
    print(f"\nMalzeme         : {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)

    # Ürün ağacını detaylı göster
    if 'product_tree' in summary and summary['product_tree']:
        print("\n" + "="*70)
        print("ÜRÜN AĞACI (Hiyerarşik Yapı)")
        print("="*70)

        def print_tree_node(node, indent="", is_last=True):
            """Ağaç node'unu yazdır (recursive)"""
            # Dal karakterleri
            connector = "└─ " if is_last else "├─ "
            child_indent = indent + ("   " if is_last else "│  ")

            # Node bilgisi
            real_name = node.get('real_name', node.get('label', 'Unknown'))
            node_type = node.get('type', 'Unknown')

            # Temel bilgi satırı
            info_parts = [f"{real_name} ({node_type})"]

            # Ağırlık bilgisi varsa ekle
            if 'weight' in node:
                weight = node['weight']
                info_parts.append(f"{weight.get('value', 0)}g")

            # Toplam ağırlık varsa ekle (alt gruplar dahil)
            if 'total_weight' in node and node.get('child_count', 0) > 0:
                info_parts.append(f"[Toplam: {node.get('total_weight_kg', 0)}kg]")

            # Boyut bilgisi varsa ekle
            if 'dimensions' in node:
                dims = node['dimensions']
                info_parts.append(f"[{dims.get('x')}×{dims.get('y')}×{dims.get('z')}{dims.get('unit', '')}]")

            # Yazdır
            print(f"{indent}{connector}{' '.join(info_parts)}")

            # STEP entity bilgisi varsa göster
            if node.get('step_tag'):
                step_info = f"STEP Tag: {node['step_tag']}"
                if 'step_entity' in node:
                    step_info += f" | Type: {node['step_entity'].get('type', 'N/A')}"
                print(f"{child_indent}  └─ {step_info}")

            # Alt nesneleri yazdır
            children = node.get('children', [])
            if children:
                for i, child in enumerate(children):
                    is_last_child = (i == len(children) - 1)
                    print_tree_node(child, child_indent, is_last_child)

        # Her kök node için ağacı yazdır
        for i, root in enumerate(summary['product_tree']):
            is_last_root = (i == len(summary['product_tree']) - 1)
            print_tree_node(root, "", is_last_root)

        print("="*70)

    for i, obj in enumerate(summary['objects'], 1):
        print(f"\n[{i}] {obj['object_name']}")
        print(f"    Kaynak  : {obj['object_name_source']}")
        print(f"    Internal: {obj['object_name_internal']}")
        print(f"    Label   : {obj['object_label']}")
        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']}")
        
        if 'name_search_results' in obj and obj['name_search_results']:
            print(f"    İsim Adayları ({len(obj['name_search_results'])}):")
            for key, value in list(obj['name_search_results'].items())[:3]:
                print(f"      • {key}: {value}")

    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
# ============================================================================

def main():
    parser = argparse.ArgumentParser(
        description="FreeCAD Model Analyzer API v3.1 - Geliştirilmiş STEP Label Parser + Detaylı Ürün Ağacı",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Örnekler:
  # Basit kullanım (geliştirilmiş STEP parse + detaylı ürün ağacı JSON)
  freecadcmd free.py model.stp

  # Debug modda detaylı çıktı
  freecadcmd free.py model.stp --debug

  # Farklı malzeme
  freecadcmd free.py model.stp --material steel

  # İsim aday listesini göster
  freecadcmd free.py model.stp --include-candidates

  # JSON config ile
  freecadcmd free.py -c analysis-config.json

  # Config şablonu oluştur
  freecadcmd free.py --create-config config.json

YENİ ÖZELLİKLER (v3.1):
  - Ürün ağacı JSON her node için analiz verilerini içerir
  - Hiyerarşik grup yapısı (parent-child ilişkileri)
  - Her node'da hacim, ağırlık, boyut bilgileri
  - STEP entity bilgileri (tag, type, references)
  - Toplam ağırlık hesaplama (alt gruplar dahil)
        """
    )

    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')
    parser.add_argument('--no-inertia', action='store_true', help='İnertia hesaplama')
    parser.add_argument('--no-step-parse', action='store_true', help='STEP parse devre dışı')
    parser.add_argument('--include-candidates', action='store_true', help='İsim aday listesi')
    parser.add_argument('--create-config', help='Örnek config.json oluştur')
    parser.add_argument('--list-materials', action='store_true', help='Malzeme listesi')
    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:
        print(f"\n{'='*70}")
        print("FreeCAD Model Analyzer v3.1")
        print("Geliştirilmiş STEP Label Parser + Detaylı Ürün Ağacı JSON")
        print('='*70)
        
        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()