#!/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ı 
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


@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


# ============================================================================
# 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] = []

    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 find_object_name(self, obj) -> Tuple[str, str, Dict]:
        """
        Nesnenin gerçek ismini bul
        Returns: (real_name, source, candidates_dict)
        """
        print(f"\n[DEBUG] ===== Nesne Özellikleri =====")
        print(f"[DEBUG] Label: {obj.Label if hasattr(obj, 'Label') else 'Yok'}")
        print(f"[DEBUG] Label2: {obj.Label2 if hasattr(obj, 'Label2') else 'Yok'}")
        print(f"[DEBUG] Name: {obj.Name if hasattr(obj, 'Name') else 'Yok'}")
        print(f"[DEBUG] Tüm metodlar: {obj.__dir__()}")
        
        name_candidates = {}

        # 1. Label özellikleri
        if hasattr(obj, 'Label') and obj.Label:
            name_candidates['Label'] = obj.Label
        if hasattr(obj, 'Label2') and obj.Label2:
            name_candidates['Label2'] = obj.Label2
        if hasattr(obj, 'Name') and obj.Name:
            name_candidates['Name'] = obj.Name
        if hasattr(obj, 'OldLabel') and obj.OldLabel:
            name_candidates['OldLabel'] = obj.OldLabel
        if hasattr(obj, 'FullName') and obj.FullName:
            name_candidates['FullName'] = obj.FullName

        # 2. STEP dosyasından gerçek isimleri oku
        if hasattr(self, 'current_file_path') and self.current_file_path:
            try:
                with open(self.current_file_path, 'r', encoding='utf-8', errors='ignore') as f:
                    content = f.read()
                
                import re
                # PRODUCT('Part_Name',...) formatını ara
                products = re.findall(r"PRODUCT\('([^']+)'", content)
                # SHAPE_REPRESENTATION('Part_Name',...) formatını ara
                shapes = re.findall(r"SHAPE_REPRESENTATION\('([^']+)'", content)
                # PRODUCT_DEFINITION('Part_Name',...) formatını ara
                prod_defs = re.findall(r"PRODUCT_DEFINITION\('([^']+)'", content)
                
                step_names = products + shapes + prod_defs
                
                if step_names:
                    for idx, step_name in enumerate(step_names):
                        name_candidates[f'STEP.Name[{idx}]'] = step_name
                        
                print(f"[DEBUG] STEP'ten bulunan isimler: {step_names}")
            except Exception as e:
                print(f"[DEBUG] STEP parse hatası: {e}")

        # 3. LinkedObject
        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

        # 4. Parent zinciri
        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

        # 5. ParentGroup
        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

        # 6. 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

        # 7. Shape tag
        if hasattr(obj, 'Shape') and hasattr(obj.Shape, 'Tag'):
            try:
                name_candidates['Shape.Tag'] = str(obj.Shape.Tag)
            except:
                pass

        # Öncelik sıralaması
        real_name = None
        source = None

        priority_list = [
            'Label', 'Label2', 'OldLabel', 'LinkedObject.Label'
        ]

        for key in priority_list:
            if key in name_candidates and name_candidates[key]:
                real_name = name_candidates[key]
                source = key
                break

        # STEP isimlerini kontrol et
        if not real_name:
            for key in sorted([k for k in name_candidates.keys() if k.startswith('STEP.Name')]):
                if name_candidates[key]:
                    real_name = name_candidates[key]
                    source = key
                    break

        # Parent seviyelerini kontrol et (en üstten)
        if not real_name:
            for level in range(max_depth, 0, -1):
                key = f'Parent.Level{level}.Label'
                if key in name_candidates and name_candidates[key]:
                    real_name = name_candidates[key]
                    source = key
                    break

        # ParentGroup, Parents, FullName, Name
        if not real_name:
            fallback_keys = [
                'ParentGroup.Label',
                'Parents[0].Label',
                'FullName',
                'Name'
            ]
            for key in fallback_keys:
                if key in name_candidates and name_candidates[key]:
                    real_name = name_candidates[key]
                    source = key
                    break

        if not real_name:
            real_name = "Unknown"
            source = "None"

        print(f"[DEBUG] Bulunan adaylar: {name_candidates}")
        print(f"[DEBUG] Seçilen: {real_name} (kaynak: {source})")

        return real_name, source, name_candidates
        
        def find_object_name111(self, obj) -> Tuple[str, str, Dict]:
            """
            Nesnenin gerçek ismini bul
            Returns: (real_name, source, candidates_dict)
            """
            print(f"\n[DEBUG] ===== Nesne: {obj.__dir__()} =====")
            print(f"\n[DEBUG] ===== Nesne: {obj.Label } =====")
            print(f"\n[DEBUG] ===== Nesne: {obj.Label2 } =====")
            return obj.Label if hasattr(obj, 'Label') else "Yok", obj.Label if hasattr(obj, 'Label') else "Yok", {"Label": obj.Label if hasattr(obj, 'Label') else "Yok", "liste":obj.__dir__()}

            name_candidates = {}

            # 1. Label özellikleri
            if hasattr(obj, 'Label') and obj.Label:
                name_candidates['Label'] = obj.Label
            if hasattr(obj, 'Label2') and hasattr(obj, 'Label2') and obj.Label2:
                name_candidates['Label2'] = obj.Label2
            if hasattr(obj, 'Name') and obj.Name:
                name_candidates['Name'] = obj.Name
            if hasattr(obj, 'OldLabel') and hasattr(obj, 'OldLabel') and obj.OldLabel:
                name_candidates['OldLabel'] = obj.OldLabel
            if hasattr(obj, 'FullName') and hasattr(obj, 'FullName') and obj.FullName:
                name_candidates['FullName'] = obj.FullName

            # 2. LinkedObject
            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

            # 3. Parent zinciri
            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 hasattr(parent, 'Label2') and parent.Label2:
                            name_candidates[f'Parent.Level{parent_level}.Label2'] = parent.Label2
                        current_obj = parent
                    else:
                        break
                except:
                    break

            # 4. ParentGroup
            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

            # 5. 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

            # 6. Shape tag
            if hasattr(obj, 'Shape') and hasattr(obj.Shape, 'Tag'):
                try:
                    name_candidates['Shape.Tag'] = str(obj.Shape.Tag)
                except:
                    pass

            # Öncelik sıralaması
            real_name = None
            source = None

            priority_list = [
                'Label','Label2',  'OldLabel', 'LinkedObject.Label'
            ]

            for key in priority_list:
                if key in name_candidates and name_candidates[key]:
                    real_name = name_candidates[key]
                    source = key
                    break

            # Parent seviyelerini kontrol et (en üstten)
            if not real_name:
                for level in range(max_depth, 0, -1):
                    key = f'Parent.Level{level}.Label'
                    if key in name_candidates and name_candidates[key]:
                        real_name = name_candidates[key]
                        source = key
                        break

            # ParentGroup, Parents, FullName, Name
            if not real_name:
                fallback_keys = [
                    'ParentGroup.Label',
                    'Parents[0].Label',
                    'FullName',
                    'Name'
                ]
                for key in fallback_keys:
                    if key in name_candidates and name_candidates[key]:
                        real_name = name_candidates[key]
                        source = key
                        break

            if not real_name:
                real_name = "Unknown"
                source = "None"

            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
            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"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"Dosya bulunamadı: {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),
                '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
                },
                '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
            }

            # 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']}")
    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"    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ı",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Örnekler:
  # Basit kullanım
  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

  # 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('--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.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()
