#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FreeCAD API Server - Flask REST API Wrapper for free.py
HTTP endpoint to analyze STEP files using FreeCAD

OPTİMİZE EDİLMİŞ VERSİYON:
- Dosya cache sistemi ile analiz sonrası dosya tekrar yüklenmez
- /analyze endpoint: Dosyayı yükle, analiz et, cache'le, file_id döndür
- /render-cached endpoint: file_id ile cache'den dosya al, render et
"""

from flask import Flask, request, jsonify, send_file
from werkzeug.utils import secure_filename
import os
import sys
import json
import tempfile
import traceback
import subprocess
from pathlib import Path
import uuid
import time
from threading import Lock
import base64
import redis

# free.py modülünü import et
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import free

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024  # 500MB max file size

# Upload klasörü
UPLOAD_FOLDER = tempfile.gettempdir()
ALLOWED_EXTENSIONS = {'stp', 'step'}

# ============== REDIS CACHE SİSTEMİ ==============
# Redis bağlantısı (worker'lar arası paylaşımlı cache)
try:
    redis_client = redis.Redis(
        host=os.getenv('REDIS_HOST', 'localhost'),
        port=int(os.getenv('REDIS_PORT', '6379')),
        db=0,
        decode_responses=False,  # Binary data için False
        socket_connect_timeout=5,
        socket_timeout=5
    )
    # Bağlantı testi
    redis_client.ping()
    print("[REDIS] ✅ Bağlantı başarılı!", flush=True)
    USE_REDIS = True
except Exception as e:
    print(f"[REDIS] ⚠️ Bağlantı hatası, in-memory cache kullanılacak: {e}", flush=True)
    redis_client = None
    USE_REDIS = False

# Fallback: In-memory cache (Redis yoksa)
FILE_CACHE = {}  # {file_id: {"path": str, "filename": str, "timestamp": float, "analysis": dict}}
CACHE_LOCK = Lock()
CACHE_EXPIRY_SECONDS = 3600  # 1 saat TTL


def allowed_file(filename):
    """Dosya uzantısı kontrolü"""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def cache_file_data(file_id, data):
    """Cache'e dosya bilgisi kaydet (Redis veya in-memory)"""
    if USE_REDIS:
        try:
            # Redis'e JSON olarak kaydet + TTL
            redis_client.setex(
                f"freecad:file:{file_id}",
                CACHE_EXPIRY_SECONDS,
                json.dumps(data)
            )
            print(f"[REDIS CACHE] ✅ Kaydedildi: {file_id}", flush=True)
        except Exception as e:
            print(f"[REDIS CACHE] ❌ Kayıt hatası: {e}", flush=True)
    else:
        # Fallback: In-memory
        with CACHE_LOCK:
            FILE_CACHE[file_id] = data


def get_cached_file_data(file_id):
    """Cache'den dosya bilgisi al (Redis veya in-memory)"""
    if USE_REDIS:
        try:
            cached_json = redis_client.get(f"freecad:file:{file_id}")
            if cached_json:
                print(f"[REDIS CACHE] ✅ Bulundu: {file_id}", flush=True)
                return json.loads(cached_json)
            else:
                print(f"[REDIS CACHE] ⚠️ Bulunamadı: {file_id}", flush=True)
                return None
        except Exception as e:
            print(f"[REDIS CACHE] ❌ Okuma hatası: {e}", flush=True)
            return None
    else:
        # Fallback: In-memory
        with CACHE_LOCK:
            return FILE_CACHE.get(file_id)


def cleanup_expired_cache():
    """Süresi dolmuş cache dosyalarını temizle (sadece in-memory için, Redis otomatik TTL)"""
    if USE_REDIS:
        # Redis zaten TTL ile otomatik temizliyor
        return

    with CACHE_LOCK:
        current_time = time.time()
        expired_ids = []

        for file_id, data in FILE_CACHE.items():
            if current_time - data['timestamp'] > CACHE_EXPIRY_SECONDS:
                expired_ids.append(file_id)
                # Dosyayı sil
                try:
                    if os.path.exists(data['path']):
                        os.unlink(data['path'])
                        print(f"[CACHE] Temizlendi: {file_id} ({data['filename']})", flush=True)
                except:
                    pass

        # Cache'den kaldır
        for file_id in expired_ids:
            del FILE_CACHE[file_id]

        if expired_ids:
            print(f"[CACHE] {len(expired_ids)} eski dosya temizlendi", flush=True)


def _get_render_params_from_request():
    """Request'ten tüm render parametrelerini al (ortak fonksiyon)"""
    params = {}

    # Görüntü boyutları
    params['width'] = int(request.form.get('width', 800))
    params['height'] = int(request.form.get('height', 600))

    # Kamera ayarları
    params['zoom'] = float(request.form.get('zoom', 1.0))
    params['view_type'] = request.form.get('view_type', 'axonometric')
    params['background_color'] = request.form.get('background_color', 'White')

    # Animasyon ayarları
    params['rotation_speed'] = int(request.form.get('rotation_speed', 5))
    params['num_frames'] = int(request.form.get('num_frames', 72))
    params['fps'] = int(request.form.get('fps', 24))

    # Debug: Animasyon parametreleri
    print(f"[PARAMS] rotation_speed={params['rotation_speed']}, num_frames={params['num_frames']}, fps={params['fps']}", flush=True)

    # Rotasyon eksenleri
    params['yaw_enabled'] = request.form.get('yaw_enabled', 'true').lower() == 'true'
    params['pitch_enabled'] = request.form.get('pitch_enabled', 'true').lower() == 'true'
    params['roll_enabled'] = request.form.get('roll_enabled', 'false').lower() == 'true'

    params['yaw_range'] = json.loads(request.form.get('yaw_range', '[0, 360]'))
    params['pitch_range'] = json.loads(request.form.get('pitch_range', '[0, 360]'))
    params['roll_range'] = json.loads(request.form.get('roll_range', '[0, 360]'))

    # GIF ayarları
    params['gif_optimize'] = request.form.get('gif_optimize', 'true').lower() == 'true'
    params['gif_quality'] = int(request.form.get('gif_quality', 85))
    params['pause_duration'] = float(request.form.get('pause_duration', 0))
    params['loop'] = int(request.form.get('loop', 0))

    # ✅ DÜZELTME: object_name parametresini düzgün oku
    object_name_raw = request.form.get('object_name', '').strip()
    
    # Boş string, 'none', 'null' gibi değerleri None'a çevir
    if object_name_raw and object_name_raw.lower() not in ['none', 'null', '']:
        params['object_name'] = object_name_raw
        print(f"[DEBUG] ✅ object_name parametresi alındı: '{object_name_raw}'", flush=True)
    else:
        params['object_name'] = None
        print(f"[DEBUG] ⚠️ object_name parametresi boş: '{object_name_raw}'", flush=True)


    # Profesyonel render parametreleri
    params['render_quality'] = request.form.get('render_quality', 'high')
    params['anti_aliasing'] = request.form.get('anti_aliasing', 'true').lower() == 'true'
    params['edge_detection'] = request.form.get('edge_detection', 'false').lower() == 'true'
    params['sharpen'] = request.form.get('sharpen', 'true').lower() == 'true'
    params['sharpen_intensity'] = float(request.form.get('sharpen_intensity', 1.5))
    params['smooth'] = request.form.get('smooth', 'false').lower() == 'true'
    params['smooth_radius'] = int(request.form.get('smooth_radius', 2))
    params['contrast'] = float(request.form.get('contrast', 1.2))
    params['brightness'] = float(request.form.get('brightness', 1.0))
    params['saturation'] = float(request.form.get('saturation', 1.1))

    params['ambient_occlusion'] = request.form.get('ambient_occlusion', 'false').lower() == 'true'
    params['ao_intensity'] = float(request.form.get('ao_intensity', 0.3))
    params['shadow'] = request.form.get('shadow', 'false').lower() == 'true'
    params['shadow_offset_x'] = int(request.form.get('shadow_offset_x', 10))
    params['shadow_offset_y'] = int(request.form.get('shadow_offset_y', 10))
    params['shadow_blur'] = int(request.form.get('shadow_blur', 5))
    params['shadow_opacity'] = float(request.form.get('shadow_opacity', 0.5))
    params['vignette'] = request.form.get('vignette', 'false').lower() == 'true'
    params['vignette_intensity'] = float(request.form.get('vignette_intensity', 0.5))
    params['wireframe'] = request.form.get('wireframe', 'false').lower() == 'true'
    params['wireframe_density'] = int(request.form.get('wireframe_density', 10))

    params['lighting_enabled'] = request.form.get('lighting_enabled', 'true').lower() == 'true'
    params['light_intensity'] = float(request.form.get('light_intensity', 1.0))
    params['light_color_r'] = int(request.form.get('light_color_r', 255))
    params['light_color_g'] = int(request.form.get('light_color_g', 255))
    params['light_color_b'] = int(request.form.get('light_color_b', 200))

    params['material_metallic'] = float(request.form.get('material_metallic', 0.5))
    params['material_roughness'] = float(request.form.get('material_roughness', 0.5))

    params['gradient_background'] = request.form.get('gradient_background', 'false').lower() == 'true'
    params['gradient_color_1'] = request.form.get('gradient_color_1', '#F0F0FF')
    params['gradient_color_2'] = request.form.get('gradient_color_2', '#C8C8DC')

    params['depth_of_field'] = request.form.get('depth_of_field', 'false').lower() == 'true'
    params['dof_focus_distance'] = float(request.form.get('dof_focus_distance', 50))
    params['dof_aperture'] = float(request.form.get('dof_aperture', 2.8))

    params['bloom'] = request.form.get('bloom', 'false').lower() == 'true'
    params['bloom_intensity'] = float(request.form.get('bloom_intensity', 0.5))
    params['bloom_threshold'] = float(request.form.get('bloom_threshold', 0.8))

    params['motion_blur'] = request.form.get('motion_blur', 'false').lower() == 'true'
    params['motion_blur_intensity'] = float(request.form.get('motion_blur_intensity', 0.5))

    params['grid_overlay'] = request.form.get('grid_overlay', 'false').lower() == 'true'
    params['grid_size'] = int(request.form.get('grid_size', 10))
    params['ruler_enabled'] = request.form.get('ruler_enabled', 'false').lower() == 'true'

    params['reflection_intensity'] = float(request.form.get('reflection_intensity', 0.3))
    params['normal_map_intensity'] = float(request.form.get('normal_map_intensity', 1.0))

    params['fog'] = request.form.get('fog', 'false').lower() == 'true'
    params['fog_density'] = float(request.form.get('fog_density', 0.02))
    params['fog_color'] = request.form.get('fog_color', '#CCCCCC')

    return params


def _convert_step_to_gltf(temp_path, filename):
    """
    STEP dosyasını glTF formatına dönüştür (FreeCAD ile)

    Args:
        temp_path: STEP dosyasının yolu
        filename: Dosya adı

    Returns:
        dict: {"gltf_path": str, "bin_path": str or None}
    """
    output_dir = tempfile.mkdtemp()
    base_name = os.path.splitext(filename)[0]
    gltf_path = os.path.join(output_dir, f"{base_name}.gltf")

    print(f"[GLTF CONVERT] {filename} dönüştürülüyor...", flush=True)

    # FreeCAD'i GUI modda çalıştır (glTF export için GUI gerekli)
    script_content = f"""
import FreeCAD
import Part
import Import
import os
import sys

# Debug log fonksiyonu - stdout'a yaz ama JSON response sonrasinda
# Bu sayede subprocess stdout'u goreceğiz
def log(msg):
    print(f"[DEBUG-LOG] {{msg}}", flush=True)

# GUI modüllerini yükle
try:
    import FreeCADGui
    FreeCADGui.showMainWindow()
    import ImportGui
    log("GUI modulleri yuklendi")
except Exception as e:
    log(f"GUI yuklenemedi: {{e}}")
    raise

# STEP dosyasını yükle
doc = FreeCAD.newDocument("TempDoc")
FreeCADGui.ActiveDocument = FreeCADGui.getDocument("TempDoc")

try:
    # KRITIK: STEP import parametrelerini ayarla - RENKLER icin!
    param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Import/hSTEP")
    param.SetInt("ReadShapeCompoundMode", 0)  # 0 = Compound with colors
    param.SetBool("UseLinkGroup", False)
    param.SetBool("ImportHiddenObjects", True)
    param.SetBool("ReduceObjects", False)
    param.SetBool("ExpandCompound", True)
    param.SetBool("ShowProgress", False)
    param.SetInt("ImportMode", 0)  # 0 = XCAF with colors

    log("STEP import parametreleri ayarlandi (XCAF color mode)")
    log("STEP dosyasi ImportGui.insert() ile yukleniyor: {temp_path}")

    # ImportGui.insert kullan - parametrelerle beraber renkler yuklenecek
    ImportGui.insert("{temp_path}", "TempDoc")

    log("ImportGui.insert basarili")

    # Tüm Part nesnelerini al
    parts = [obj for obj in doc.Objects if hasattr(obj, 'Shape')]

    if not parts:
        raise Exception("STEP dosyasinda hic geometri bulunamadi")

    log(f"{{len(parts)}} adet Part nesnesi bulundu")

    # Document'i refresh et (ViewObject update)
    doc.recompute()
    log("Document recomputed")

    # STEP dosyasindaki FACE-level renkleri oku
    # Her face farkli renkte olabilir - bunlari topla
    face_colors = {{}}  # face index -> RGB color

    # FreeCAD'de Shape.Faces ile tum face'leri al
    all_faces = []
    for obj in parts:
        if hasattr(obj, 'Shape') and obj.Shape:
            shape_faces = obj.Shape.Faces
            log(f"Part {{obj.Label}}: {{len(shape_faces)}} face")
            all_faces.extend(shape_faces)

    log(f"Toplam {{len(all_faces)}} face bulundu")

    # ViewObject DiffuseColor face renkleri - eger varsa
    for obj_idx, obj in enumerate(parts):
        if hasattr(obj, 'ViewObject') and hasattr(obj.ViewObject, 'DiffuseColor'):
            diff_colors = obj.ViewObject.DiffuseColor
            if len(diff_colors) > 0:
                log(f"Part[{{obj_idx}}] {{obj.Label}}: {{len(diff_colors)}} face rengi mevcut")
                # Her face icin renk kaydet
                for face_idx, color in enumerate(diff_colors):
                    global_face_idx = sum(len(parts[i].Shape.Faces) for i in range(obj_idx)) + face_idx
                    face_colors[global_face_idx] = (color[0], color[1], color[2])
                    if face_idx < 3:  # Ilk 3 face'i logla
                        log(f"  Face[{{face_idx}}]: RGB=({{color[0]:.3f}}, {{color[1]:.3f}}, {{color[2]:.3f}})")

    log(f"Toplam {{len(face_colors)}} face rengi okundu")

    # Part renkleri: Her part icin dominant/ortalama renk hesapla
    part_colors = {{}}  # Part index -> RGB color mapping

    face_offset = 0
    for i, obj in enumerate(parts):
        log(f"Part {{i}} - Label: {{obj.Label}}")

        # Bu part'in face'lerini kontrol et
        if hasattr(obj, 'ViewObject') and hasattr(obj.ViewObject, 'DiffuseColor'):
            diff_colors = obj.ViewObject.DiffuseColor
            if len(diff_colors) > 0:
                # Ilk face rengini kullan (veya ortalama alabilirsiniz)
                c = diff_colors[0]
                part_colors[i] = (c[0], c[1], c[2])
                log(f"  DiffuseColor[0]: RGB=({{c[0]:.3f}}, {{c[1]:.3f}}, {{c[2]:.3f}})")
                face_offset += len(diff_colors)
                continue

        # ShapeColor kontrolü (fallback)
        if hasattr(obj, 'ViewObject') and hasattr(obj.ViewObject, 'ShapeColor'):
            sc = obj.ViewObject.ShapeColor
            part_colors[i] = (sc[0], sc[1], sc[2])
            log(f"  ShapeColor: RGB=({{sc[0]:.3f}}, {{sc[1]:.3f}}, {{sc[2]:.3f}})")
            continue

        log(f"  No color - using default")

    log(f"=== {{len(part_colors)}} Part rengi hazir ===")

    # glTF export et - TUM objeleri sec ve export et (GUI gibi)
    log("glTF export ediliyor: {gltf_path}")

    # KRITIK: GUI Selection kullanarak export et (renkleri korur!)
    FreeCADGui.Selection.clearSelection()
    for obj in parts:
        FreeCADGui.Selection.addSelection(obj)

    log(f"{{len(parts)}} obje secildi, export ediliyor...")

    try:
        # GUI export komutu kullan
        ImportGui.export(FreeCADGui.Selection.getSelection(), "{gltf_path}")
        log("glTF export basarili (ImportGui with Selection)")
    except Exception as e1:
        log(f"ImportGui.export hatasi: {{e1}}")
        # Fallback: Direkt parts listesi
        try:
            ImportGui.export(parts, "{gltf_path}")
            log("glTF export basarili (ImportGui direct)")
        except Exception as e2:
            log(f"Import.export hatasi: {{e2}}")
            raise Exception(f"glTF export basarisiz: {{e1}}, {{e2}}")

    # FreeCAD'in kendi glTF export'u zaten renkleri iceriyor!
    # Ekstra modifikasyon yapma
    log("glTF export tamamlandi - FreeCAD XCAF renkleri otomatik ekledi!")

except Exception as e:
    log(f"HATA: {{e}}")
    import traceback
    traceback.print_exc(file=sys.stderr)
    raise
finally:
    try:
        FreeCAD.closeDocument("TempDoc")
        log("TempDoc kapatildi")
    except:
        pass
"""

    script_path = os.path.join(output_dir, "convert_gltf.py")
    with open(script_path, 'w', encoding='utf-8') as f:
        f.write(script_content)

    # FreeCAD'i Python ile GUI modda çalıştır (render gibi)
    cmd = [
        'xvfb-run',
        '-a',
        '-s', '-screen 0 1920x1080x24',
        'python3',
        script_path
    ]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=120
    )

    # Debug: stderr'i hem dosyaya yaz hem konsola bas
    if result.stderr:
        debug_file = '/tmp/freecad_gltf_debug.log'
        with open(debug_file, 'w') as f:
            f.write(result.stderr)
        print(f"[GLTF-DEBUG] FreeCAD stderr ({len(result.stderr)} bytes):", flush=True)
        # Sadece renk ile ilgili satirlari bas
        for line in result.stderr.split('\n'):
            if any(keyword in line for keyword in ['DiffuseColor', 'ShapeColor', 'Part [', 'renk', 'color', 'RGB', 'UYARI', 'bulundu']):
                print(f"  [FREECAD] {line}", flush=True)

    if result.stdout:
        print(f"[GLTF STDOUT]\n{result.stdout}", flush=True)

    if result.returncode != 0:
        raise Exception(f"glTF dönüştürme hatası:\nStderr: {result.stderr}\nStdout: {result.stdout}")

    if not os.path.exists(gltf_path):
        raise Exception(f"glTF dosyası oluşturulamadı: {gltf_path}")

    # .bin dosyası var mı kontrol et
    bin_path = os.path.join(output_dir, f"{base_name}.bin")
    if not os.path.exists(bin_path):
        bin_path = None

    print(f"[GLTF CONVERT] ✅ Başarılı: {gltf_path}", flush=True)
    if bin_path:
        print(f"[GLTF CONVERT] ✅ Binary: {bin_path}", flush=True)

    return {
        "gltf_path": gltf_path,
        "bin_path": bin_path
    }


def _convert_step_to_wrl97(temp_path, filename):
    """
    STEP dosyasını WRL97 (VRML 2.0) formatına dönüştür (FreeCAD ile)

    Args:
        temp_path: STEP dosyasının yolu
        filename: Dosya adı

    Returns:
        dict: {"wrl_path": str}
    """
    output_dir = tempfile.mkdtemp()
    base_name = os.path.splitext(filename)[0]
    wrl_path = os.path.join(output_dir, f"{base_name}.wrl")

    print(f"[WRL97 CONVERT] {filename} dönüştürülüyor...", flush=True)

    # FreeCAD'i GUI modda çalıştır (VRML export için GUI gerekli)
    script_content = f"""
import FreeCAD
import Part
import Import
import os
import sys

def log(msg):
    print(f"[DEBUG-LOG] {{msg}}", flush=True)

try:
    log("FreeCAD modulleri yukleniyor...")

    # GUI modulleri
    try:
        import FreeCADGui
        FreeCADGui.showMainWindow()
        import ImportGui
        log("GUI modulleri yuklendi")
    except Exception as e:
        log(f"GUI modulleri yuklenemedi: {{e}}")
        raise

    # Yeni document olustur
    doc = FreeCAD.newDocument("TempDoc")
    log("Document olusturuldu: TempDoc")

    # STEP import parametrelerini ayarla (XCAF color mode)
    step_param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Import/hSTEP")
    step_param.SetInt("ReadShapeCompoundMode", 1)  # 1 = XCAF color mode
    step_param.SetBool("UseLinkGroup", False)
    log("STEP import parametreleri ayarlandi (XCAF color mode)")

    log("STEP dosyasi ImportGui.insert() ile yukleniyor: {temp_path}")

    # ImportGui.insert kullan - renklerle beraber yukler
    ImportGui.insert("{temp_path}", "TempDoc")
    log("ImportGui.insert basarili")

    # Tum Part nesnelerini al - SADECE Part::Feature (Product/Assembly degil)
    all_objects = [obj for obj in doc.Objects if hasattr(obj, 'Shape') and obj.Shape and not obj.Shape.isNull()]

    parts = []
    for obj in all_objects:
        obj_type = obj.TypeId if hasattr(obj, 'TypeId') else 'Unknown'
        if 'Part::Feature' in obj_type or obj_type == 'Part::Feature':
            parts.append(obj)
            log(f"  Part eklendi: {{obj.Label}} (Type: {{obj_type}})")
        else:
            log(f"  Atlandı: {{obj.Label}} (Type: {{obj_type}})")

    log(f"Toplam {{len(parts)}} part bulundu")

    if len(parts) == 0:
        raise Exception("STEP dosyasinda part bulunamadi!")

    # Document'i recompute et
    doc.recompute()
    log("Document recomputed")

    # CRITICAL FIX: STEP'i metre -> mm donusumu
    log("STEP geometrisini 1000x scale ediliyor (metre -> mm donusumu)...")

    scale_matrix = FreeCAD.Matrix()
    scale_matrix.scale(1000.0, 1000.0, 1000.0)

    # Renk bilgilerini sakla
    color_info = []
    for i, obj in enumerate(parts):
        colors = {{}}
        if hasattr(obj, 'ViewObject'):
            if hasattr(obj.ViewObject, 'DiffuseColor') and len(obj.ViewObject.DiffuseColor) > 0:
                colors['DiffuseColor'] = list(obj.ViewObject.DiffuseColor)
                log(f"Part[{{i}}] {{obj.Label}}: {{len(colors['DiffuseColor'])}} DiffuseColor")
            if hasattr(obj.ViewObject, 'ShapeColor'):
                colors['ShapeColor'] = obj.ViewObject.ShapeColor
        color_info.append(colors)

    # Scale uygula
    scaled_parts = []
    for i, obj in enumerate(parts):
        if hasattr(obj, 'Shape') and obj.Shape:
            scaled_shape = obj.Shape.transformGeometry(scale_matrix)
            new_part = doc.addObject("Part::Feature", f"Scaled_{{obj.Label}}")
            new_part.Shape = scaled_shape
            scaled_parts.append(new_part)
            log(f"Part[{{i}}] scaled 1000x")

    # Recompute
    doc.recompute()
    FreeCADGui.updateGui()
    log("Document recomputed, ViewObjects hazır")

    # Renkleri yeni part'lara uygula
    for i, new_part in enumerate(scaled_parts):
        if i < len(color_info) and color_info[i]:
            colors = color_info[i]
            if 'DiffuseColor' in colors:
                new_part.ViewObject.DiffuseColor = colors['DiffuseColor']
                log(f"  {{new_part.Label}}: {{len(colors['DiffuseColor'])}} DiffuseColor uygulandı")
            if 'ShapeColor' in colors:
                new_part.ViewObject.ShapeColor = colors['ShapeColor']

    # Eski objeleri sil
    for obj in parts:
        doc.removeObject(obj.Name)

    parts = scaled_parts
    log("Eski objeler silindi, parts listesi güncellendi")

    # WRL97 export et
    log("WRL97 export ediliyor: {wrl_path}")

    # CRITICAL: FreeCAD'in ImportGui.export WRL/VRML desteği çalışmıyor
    # Bunun yerine Mesh export kullan
    try:
        import Mesh
        import MeshPart

        log(f"{{len(parts)}} part Mesh'e dönüştürülüyor...")

        # Tüm partları tek bir mesh'e dönüştür
        meshes = []
        for obj in parts:
            if hasattr(obj, 'Shape') and obj.Shape:
                mesh = MeshPart.meshFromShape(obj.Shape, LinearDeflection=0.1, AngularDeflection=0.523599, Relative=False)
                meshes.append(mesh)
                log(f"  {{obj.Label}}: {{len(mesh.Facets)}} facet")

        if not meshes:
            raise Exception("Mesh oluşturulamadı")

        # Mesh'leri birleştir
        combined_mesh = Mesh.Mesh()
        for mesh in meshes:
            combined_mesh.addMesh(mesh)

        log(f"Combined mesh: {{len(combined_mesh.Facets)}} toplam facet")

        # VRML/WRL formatında kaydet
        combined_mesh.write("{wrl_path}", "VRML")
        log("WRL97 export başarılı (Mesh.write VRML)")

    except Exception as e:
        log(f"Mesh export hatası: {{e}}")
        import traceback
        traceback.print_exc(file=sys.stderr)
        raise Exception(f"WRL97 export başarısız: {{e}}")

    log("WRL97 export tamamlandı!")

except Exception as e:
    log(f"HATA: {{e}}")
    import traceback
    traceback.print_exc(file=sys.stderr)
    raise
finally:
    try:
        FreeCAD.closeDocument("TempDoc")
        log("TempDoc kapatildi")
    except:
        pass
"""

    script_path = os.path.join(output_dir, "convert_wrl97.py")
    with open(script_path, 'w', encoding='utf-8') as f:
        f.write(script_content)

    # FreeCAD'i Python ile GUI modda çalıştır
    cmd = [
        'xvfb-run',
        '-a',
        '-s', '-screen 0 1920x1080x24',
        'python3',
        script_path
    ]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=120
    )

    print(f"[WRL97 CONVERT] FreeCAD exit code: {result.returncode}", flush=True)
    print(f"[WRL97 CONVERT] Stdout:\n{result.stdout}", flush=True)

    if result.stderr:
        print(f"[WRL97 CONVERT] Stderr:\n{result.stderr}", flush=True)

    if result.returncode != 0:
        raise Exception(f"WRL97 dönüştürme hatası:\nStderr: {result.stderr}\nStdout: {result.stdout}")

    if not os.path.exists(wrl_path):
        raise Exception(f"WRL97 dosyası oluşturulamadı: {wrl_path}")

    print(f"[WRL97 CONVERT] Başarılı: {wrl_path}", flush=True)
    print(f"[WRL97 CONVERT] Dosya boyutu: {os.path.getsize(wrl_path)} bytes", flush=True)

    return {
        "wrl_path": wrl_path
    }


def _execute_render(temp_path, filename, params):
    """
    STEP dosyasını render et (ortak render fonksiyonu)

    Args:
        temp_path: STEP dosyasının yolu
        filename: Dosya adı
        params: Render parametreleri (_get_render_params_from_request()'ten)

    Returns:
        GIF dosyasının yolu
    """
    output_dir = tempfile.mkdtemp()
    gif_path = os.path.join(output_dir, 'render.gif')

    # Duration hesapla
    base_duration = 0.08
    duration = base_duration / (params['rotation_speed'] / 5.0)

    # Debug: rotation_speed ve duration
    print(f"[RENDER] Rotation Speed: {params['rotation_speed']}, Duration: {duration}s ({int(duration*1000)}ms)", flush=True)

    # Config oluştur
    script_dir = os.path.dirname(os.path.abspath(__file__))
    script_path = os.path.join(script_dir, 'freesim-yatay-dikey.py')
    config_path = os.path.join(output_dir, 'render_config.json')

    config = {
        "input_file": temp_path,
        "render": {
            "width": params['width'],
            "height": params['height'],
            "background": params['background_color'],
            "view_type": params['view_type'],
            "render_quality": params['render_quality'],
            "anti_aliasing": params['anti_aliasing'],
            "object_name": params.get('object_name', None)  # ✅ Burada ekle
        },
        "animation": {
            "enabled": True,
            "num_frames": params['num_frames'],
            "angle_step": 360 / params['num_frames'],
            "duration": duration,
            "pause_duration": params['pause_duration'],
            "loop": params['loop'],
            "fps": params['fps'],
            "yaw_enabled": params['yaw_enabled'],
            "pitch_enabled": params['pitch_enabled'],
            "roll_enabled": params['roll_enabled'],
            "yaw_range": params['yaw_range'],
            "pitch_range": params['pitch_range'],
            "roll_range": params['roll_range']
        },
        "output": {
            "output_dir": output_dir,
            "base_name": "render",
            "save_png": False,
            "save_gif": True,
            "save_frames": False,
            "gif_optimize": params['gif_optimize'],
            "gif_quality": params['gif_quality']
        },
        "camera": {
            "zoom_level": params['zoom'] * 1.5,
            "camera_position": None,
            "look_at": None
        },
        "render_params": {
            "rotation_speed": params['rotation_speed']
        },
        "post_processing": {
            "edge_detection": params['edge_detection'],
            "sharpen": params['sharpen'],
            "sharpen_intensity": params['sharpen_intensity'],
            "smooth": params['smooth'],
            "smooth_radius": params['smooth_radius'],
            "contrast": params['contrast'],
            "brightness": params['brightness'],
            "saturation": params['saturation'],
            "ambient_occlusion": params['ambient_occlusion'],
            "ao_intensity": params['ao_intensity'],
            "shadow": params['shadow'],
            "shadow_offset_x": params['shadow_offset_x'],
            "shadow_offset_y": params['shadow_offset_y'],
            "shadow_blur": params['shadow_blur'],
            "shadow_opacity": params['shadow_opacity'],
            "vignette": params['vignette'],
            "vignette_intensity": params['vignette_intensity'],
            "wireframe": params['wireframe'],
            "wireframe_density": params['wireframe_density'],
            "depth_of_field": params['depth_of_field'],
            "dof_focus_distance": params['dof_focus_distance'],
            "dof_aperture": params['dof_aperture'],
            "bloom": params['bloom'],
            "bloom_intensity": params['bloom_intensity'],
            "bloom_threshold": params['bloom_threshold'],
            "motion_blur": params['motion_blur'],
            "motion_blur_intensity": params['motion_blur_intensity'],
            "grid_overlay": params['grid_overlay'],
            "grid_size": params['grid_size'],
            "ruler_enabled": params['ruler_enabled'],
            "fog": params['fog'],
            "fog_density": params['fog_density'],
            "fog_color": params['fog_color']
        },
        "lighting": {
            "enabled": params['lighting_enabled'],
            "intensity": params['light_intensity'],
            "color": [params['light_color_r'], params['light_color_g'], params['light_color_b']]
        },
        "material": {
            "metallic": params['material_metallic'],
            "roughness": params['material_roughness'],
            "reflection_intensity": params['reflection_intensity'],
            "normal_map_intensity": params['normal_map_intensity']
        },
        "background": {
            "gradient": params['gradient_background'],
            "gradient_color_1": params['gradient_color_1'],
            "gradient_color_2": params['gradient_color_2']
        }
    }

    # Debug log
    if params.get('object_name'):
        print(f"[RENDER CONFIG] ✅ object_name config'e eklendi: '{params['object_name']}'", flush=True)
    else:
        print(f"[RENDER CONFIG] ⚠️ object_name None, tüm nesneler render edilecek", flush=True)


    with open(config_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=2)

    print(f"[RENDER] {filename}: {params['width']}x{params['height']}, {params['num_frames']} frames @ {params['fps']}fps", flush=True)

    # xvfb-run ile render
    cmd = [
        'xvfb-run',
        '-a',
        '-s', '-screen 0 1920x1080x24',
        'python3',
        script_path,
        '-c', config_path
    ]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=300  # 5 dakika timeout
    )

    # Debug: stdout ve stderr'i loga yaz
    if result.stdout:
        print(f"[RENDER STDOUT]\n{result.stdout}", flush=True)
    if result.stderr:
        print(f"[RENDER STDERR]\n{result.stderr}", flush=True)

    if result.returncode != 0:
        raise Exception(f"Render hatası:\nStderr: {result.stderr}\nStdout: {result.stdout}")

    if not os.path.exists(gif_path):
        raise Exception(f"GIF oluşturulamadı. Output: {result.stdout}\nError: {result.stderr}")

    gif_size = os.path.getsize(gif_path)
    print(f"[RENDER] GIF oluşturuldu: {gif_size} bytes", flush=True)

    return gif_path


# ============== ENDPOINTS ==============

@app.route('/health', methods=['GET'])
def health_check():
    """Health check endpoint"""
    cache_count = len(FILE_CACHE)
    return jsonify({
        'status': 'ok',
        'service': 'FreeCAD API Server (Optimized)',
        'version': '2.0.0',
        'cache_count': cache_count,
        'cache_expiry_seconds': CACHE_EXPIRY_SECONDS
    }), 200


@app.route('/analyze', methods=['POST'])
def analyze_step_file():
    """
    STEP dosyasını analiz et ve cache'le (OPTİMİZE EDİLMİŞ)

    Request:
        - Multipart form-data
        - file: STEP dosyası (.stp/.step)
        - material (optional): Malzeme tipi
        - density (optional): Özel yoğunluk (kg/dm³)
        - precision (optional): Ondalık hassasiyet

    Response:
        {
            "success": true,
            "file_id": "uuid",          // Render için kullan
            "data": { ... },            // Analiz sonuçları
            "cache_expires_in": 3600
        }
    """
    cleanup_expired_cache()

    try:
        # Dosya kontrolü
        if 'file' not in request.files:
            return jsonify({'success': False, 'error': 'No file in request'}), 400

        file = request.files['file']
        if not file.filename:
            return jsonify({'success': False, 'error': 'No file selected'}), 400

        if not allowed_file(file.filename):
            return jsonify({'success': False, 'error': 'Invalid file type (only .stp/.step)'}), 400

        filename = secure_filename(file.filename)
        file_id = str(uuid.uuid4())
        file_id = str(file.filename).replace('.stp', '').replace('.step', '')

        # Geçici dosya oluştur
        with tempfile.NamedTemporaryFile(delete=False, suffix='.stp', prefix=f'{file_id}_') as temp_file:
            temp_path = temp_file.name
            file.save(temp_path)

        # Parametreler
        material_name = request.form.get('material', 'aluminium')
        custom_density = request.form.get('density', None)
        precision = request.form.get('precision', None)

        # Material config
        material_cfg = free.MATERIALS.get(material_name, free.MATERIALS['aluminium'])
        if custom_density:
            try:
                density_value = float(custom_density)
                material_cfg = free.MaterialConfig(
                    name=f"Custom ({density_value} kg/dm³)",
                    density_kg_dm3=density_value,
                    symbol="Custom"
                )
            except ValueError:
                pass

        # Analysis config
        analysis_cfg = free.AnalysisConfig()
        if precision:
            try:
                analysis_cfg.decimal_precision = int(precision)
            except ValueError:
                pass

        output_cfg = free.OutputConfig(
            output_dir=tempfile.gettempdir(),
            save_json=False,
            save_detailed_debug=False
        )
        units_cfg = free.UnitsConfig()

        # Analiz et
        try:
            analyzer = free.FreeCADAnalyzer(material_cfg, analysis_cfg, output_cfg, units_cfg)
            summary = analyzer.analyze_file(temp_path)

            # CACHE'LE (dosya silinmedi!) - Redis veya in-memory
            cache_data = {
                "path": temp_path,
                "filename": filename,
                "timestamp": time.time(),
                "analysis": summary, 
                "material": material_name,
                "density": custom_density
            }
            cache_file_data(file_id, cache_data)

            print(f"[CACHE] ✅ KAYDEDILDI: file_id={file_id}, filename={filename}", flush=True)
            print(f"[CACHE] Dosya yolu: {temp_path}", flush=True)

            return jsonify({
                'success': True,
                'file_id': file_id,  # RENDER İÇİN BU ID'Yİ KULLAN!
                'data': summary,
                'cache_expires_in': CACHE_EXPIRY_SECONDS,
                'message': 'Analiz başarıyla tamamlandı'
            }), 200

        except Exception as e:
            # Analiz hatası - dosyayı sil
            try:
                os.unlink(temp_path)
            except:
                pass
            return jsonify({
                'success': False,
                'error': str(e),
                'message': 'FreeCAD analiz hatası'
            }), 500

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/render-cached', methods=['POST'])
def render_cached_file():
    """
    Cache'lenmiş dosyayı render et (DOSYA TEKRAR YÜKLENMİYOR!)

    Request:
        - Form data
        - file_id (ZORUNLU): /analyze'den dönen UUID
        - [Tüm render parametreleri...]

    Response:
        - GIF binary stream
    """
    cleanup_expired_cache()

    try:
        request.form._cached = None  # Cache'i temizle
        file_id = request.form.get('file_id', '', type=str)
        object_name = request.form.get('object_name', '', type=str)
        
        print(f"[DEBUG] file_id: {file_id}")
        print(f"[DEBUG] object_name: {object_name}")

        if not file_id:
            return jsonify({'success': False, 'error': 'file_id required'}), 400

        # Cache'den al (Redis veya in-memory)
        cached_file = get_cached_file_data(file_id)

        if not cached_file:
            return jsonify({
                'success': False,
                'error': 'File not found in cache (expired?)',
                'message': f'file_id={file_id} bulunamadı'
            }), 404

        temp_path = cached_file['path']
        filename = cached_file['filename']

        # Dosya hala var mı?
        if not os.path.exists(temp_path):
            return jsonify({'success': False, 'error': 'Cached file was deleted'}), 404

        print(f"[RENDER-CACHED] ✓ {filename} (file_id: {file_id})", flush=True)

        # Timestamp güncelle (cache'de kalsın)
        cached_file['timestamp'] = time.time()
        cache_file_data(file_id, cached_file)

        # Render parametrelerini al
        params = _get_render_params_from_request()

        if object_name:
            print(f"[RENDER-CACHED] Sadece '{object_name}' parçası render edilecek", flush=True)
            params['object_name'] = object_name

        # Render et
        try:
            gif_path = _execute_render(temp_path, filename, params)

            # GIF'i gönder
            return send_file(
                gif_path,
                mimetype='image/gif',
                as_attachment=True,
                download_name=f"{os.path.splitext(filename)[0]}.gif"
            )

        except Exception as e:
            error_msg = str(e)
            print(f"[RENDER-CACHED] HATA: {error_msg}", flush=True)
            return jsonify({
                'success': False,
                'error': error_msg,
                'message': 'Render hatası'
            }), 500

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/render', methods=['POST'])
def render_step_file():
    """
    STEP dosyasından direkt GIF render (ESKİ YOL - cache kullanmaz)

    Request:
        - Multipart form-data
        - file: STEP dosyası
        - [Render parametreleri...]

    Response:
        - GIF binary stream
    """
    try:
        if 'file' not in request.files:
            return jsonify({'success': False, 'error': 'No file in request'}), 400

        file = request.files['file']
        if not file.filename:
            return jsonify({'success': False, 'error': 'No file selected'}), 400

        if not allowed_file(file.filename):
            return jsonify({'success': False, 'error': 'Invalid file type'}), 400

        filename = secure_filename(file.filename)

        # Geçici dosya
        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.stp')
        temp_path = temp_file.name
        file.save(temp_path)
        temp_file.close()

        try:
            # Render parametreleri
            params = _get_render_params_from_request()

            # Render et
            gif_path = _execute_render(temp_path, filename, params)

            # Geçici dosyayı sil
            try:
                os.unlink(temp_path)
            except:
                pass

            # GIF'i gönder
            return send_file(
                gif_path,
                mimetype='image/gif',
                as_attachment=True,
                download_name=f"{os.path.splitext(filename)[0]}.gif"
            )

        except Exception as e:
            # Temizlik
            try:
                os.unlink(temp_path)
            except:
                pass

            error_msg = str(e)
            print(f"[RENDER] HATA: {error_msg}", flush=True)
            return jsonify({'success': False, 'error': error_msg}), 500

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/convert-to-gltf', methods=['POST'])
def convert_to_gltf():
    """
    STEP dosyasını glTF formatına dönüştür ve base64 encoded olarak döndür

    Request:
        - Multipart form-data
        - file: STEP dosyası (.stp/.step)
        - response_type (optional): "base64" (default) veya "urls"

    Response (base64 mode):
        {
            "success": true,
            "gltf": "base64_encoded_gltf_content",
            "bin": "base64_encoded_bin_content" or null,
            "filename": "model.gltf"
        }

    Response (urls mode):
        {
            "success": true,
            "file_id": "uuid",
            "gltf_url": "/download-gltf/{file_id}/gltf",
            "bin_url": "/download-gltf/{file_id}/bin" or null
        }
    """
    try:
        if 'file' not in request.files:
            return jsonify({'success': False, 'error': 'No file in request'}), 400

        file = request.files['file']
        if not file.filename:
            return jsonify({'success': False, 'error': 'No file selected'}), 400

        if not allowed_file(file.filename):
            return jsonify({'success': False, 'error': 'Invalid file type (only .stp/.step)'}), 400

        filename = secure_filename(file.filename)
        response_type = request.form.get('response_type', 'base64')

        # Geçici dosya oluştur
        with tempfile.NamedTemporaryFile(delete=False, suffix='.stp') as temp_file:
            temp_path = temp_file.name
            file.save(temp_path)

        try:
            # glTF'e dönüştür
            result = _convert_step_to_gltf(temp_path, filename)
            gltf_path = result['gltf_path']
            bin_path = result['bin_path']

            # Analysis data da hesapla (hacim/ağırlık için)
            analysis_result = None
            try:
                print(f"[CONVERT-TO-GLTF] Analysis hesaplanıyor: {filename}", flush=True)
                analysis_result = analyze_step_file(temp_path)
                print(f"[CONVERT-TO-GLTF] Analysis tamamlandı: {len(analysis_result.get('objects', []))} obje", flush=True)
            except Exception as e:
                print(f"[CONVERT-TO-GLTF] Analysis hatası (devam ediliyor): {e}", flush=True)

            if response_type == 'base64':
                # Base64 encoded response
                with open(gltf_path, 'r', encoding='utf-8') as f:
                    gltf_content = f.read()
                    gltf_base64 = base64.b64encode(gltf_content.encode('utf-8')).decode('utf-8')

                bin_base64 = None
                if bin_path and os.path.exists(bin_path):
                    with open(bin_path, 'rb') as f:
                        bin_content = f.read()
                        bin_base64 = base64.b64encode(bin_content).decode('utf-8')

                # Cleanup
                try:
                    os.unlink(temp_path)
                    os.unlink(gltf_path)
                    if bin_path:
                        os.unlink(bin_path)
                except:
                    pass

                response_data = {
                    'success': True,
                    'gltf': gltf_base64,
                    'bin': bin_base64,
                    'filename': os.path.splitext(filename)[0] + '.gltf',
                    'has_bin': bin_base64 is not None
                }

                # Analysis data ekle (varsa)
                if analysis_result:
                    response_data['analysis'] = analysis_result

                return jsonify(response_data), 200

            else:  # urls mode - cache'e kaydet
                file_id = str(uuid.uuid4())
                cache_data = {
                    "path": temp_path,
                    "filename": filename,
                    "timestamp": time.time(),
                    "gltf_path": gltf_path,
                    "bin_path": bin_path
                }
                cache_file_data(file_id, cache_data)

                response_data = {
                    'success': True,
                    'file_id': file_id,
                    'gltf_url': f'/download-gltf/{file_id}/gltf',
                    'cache_expires_in': CACHE_EXPIRY_SECONDS
                }

                if bin_path:
                    response_data['bin_url'] = f'/download-gltf/{file_id}/bin'

                return jsonify(response_data), 200

        except Exception as e:
            # Cleanup
            try:
                os.unlink(temp_path)
            except:
                pass

            error_msg = str(e)
            print(f"[GLTF CONVERT] HATA: {error_msg}", flush=True)
            return jsonify({
                'success': False,
                'error': error_msg,
                'message': 'glTF dönüştürme hatası'
            }), 500

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/convert-to-wrl97', methods=['POST'])
def convert_to_wrl97():
    """
    STEP dosyasını WRL97 (VRML 2.0) formatına dönüştür ve base64 encoded olarak döndür

    Request:
        - Multipart form-data
        - file: STEP dosyası (.stp/.step)
        - response_type (optional): "base64" (default) veya "urls"

    Response (base64 mode):
        {
            "success": true,
            "wrl": "base64_encoded_wrl_content",
            "filename": "model.wrl"
        }

    Response (urls mode):
        {
            "success": true,
            "file_id": "uuid",
            "wrl_url": "/download-wrl/{file_id}"
        }
    """
    try:
        if 'file' not in request.files:
            return jsonify({'success': False, 'error': 'No file in request'}), 400

        file = request.files['file']
        if not file.filename:
            return jsonify({'success': False, 'error': 'No file selected'}), 400

        if not allowed_file(file.filename):
            return jsonify({'success': False, 'error': 'Invalid file type (only .stp/.step)'}), 400

        filename = secure_filename(file.filename)
        response_type = request.form.get('response_type', 'base64')

        # Geçici dosya oluştur
        with tempfile.NamedTemporaryFile(delete=False, suffix='.stp') as temp_file:
            temp_path = temp_file.name
            file.save(temp_path)

        try:
            # WRL97'e dönüştür
            result = _convert_step_to_wrl97(temp_path, filename)
            wrl_path = result['wrl_path']

            if response_type == 'base64':
                # Base64 encoded response
                with open(wrl_path, 'r', encoding='utf-8') as f:
                    wrl_content = f.read()
                    wrl_base64 = base64.b64encode(wrl_content.encode('utf-8')).decode('utf-8')

                # Cleanup
                try:
                    os.unlink(temp_path)
                    os.unlink(wrl_path)
                except:
                    pass

                return jsonify({
                    'success': True,
                    'wrl': wrl_base64,
                    'filename': os.path.basename(wrl_path)
                })

            elif response_type == 'urls':
                # File URL response - cache'e kaydet
                file_id = str(uuid.uuid4())

                # WRL dosyasını cache'e kaydet
                with open(wrl_path, 'rb') as f:
                    wrl_data = f.read()
                    cache_file_data(f"{file_id}_wrl", wrl_data)

                # Cleanup
                try:
                    os.unlink(temp_path)
                    os.unlink(wrl_path)
                except:
                    pass

                return jsonify({
                    'success': True,
                    'file_id': file_id,
                    'wrl_url': f'/download-wrl/{file_id}'
                })

            else:
                return jsonify({'success': False, 'error': 'Invalid response_type (use base64 or urls)'}), 400

        except Exception as e:
            # Cleanup on error
            try:
                os.unlink(temp_path)
            except:
                pass

            error_msg = str(e)
            print(f"[WRL97 CONVERT ERROR] {error_msg}", flush=True)
            traceback.print_exc()

            return jsonify({
                'success': False,
                'error': error_msg,
                'message': 'WRL97 dönüştürme hatası'
            }), 500

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/convert-cached-to-wrl97', methods=['POST'])
def convert_cached_to_wrl97():
    """
    Cache'lenmiş STEP dosyasını WRL97'e dönüştür

    Request:
        - Form data
        - file_id (ZORUNLU): /analyze'den dönen UUID
        - response_type (optional): "base64" (default) veya "download"

    Response (base64 mode):
        {
            "success": true,
            "wrl": "base64_encoded_wrl_content"
        }

    Response (download mode):
        - WRL binary stream
    """
    try:
        file_id = request.form.get('file_id')
        if not file_id:
            return jsonify({'success': False, 'error': 'file_id is required'}), 400

        response_type = request.form.get('response_type', 'base64')

        # Cache'den dosyayı al
        cached_file = get_cached_file_data(file_id)
        if not cached_file:
            return jsonify({'success': False, 'error': 'File not found in cache'}), 404

        filename = cached_file.get('filename', 'model.stp')
        file_data = cached_file.get('data')

        # Geçici dosya oluştur
        with tempfile.NamedTemporaryFile(delete=False, suffix='.stp') as temp_file:
            temp_path = temp_file.name
            temp_file.write(file_data)

        # Cache'e tekrar kaydet (TTL yenileme)
        cache_file_data(file_id, cached_file)

        # WRL97'e dönüştür
        result = _convert_step_to_wrl97(temp_path, filename)
        wrl_path = result['wrl_path']

        if response_type == 'base64':
            # Base64 response
            with open(wrl_path, 'r', encoding='utf-8') as f:
                wrl_content = f.read()
                wrl_base64 = base64.b64encode(wrl_content.encode('utf-8')).decode('utf-8')

            # Cleanup
            try:
                os.unlink(temp_path)
                os.unlink(wrl_path)
            except:
                pass

            return jsonify({
                'success': True,
                'wrl': wrl_base64
            })

        elif response_type == 'download':
            # Direct download
            try:
                os.unlink(temp_path)
            except:
                pass

            return send_file(
                wrl_path,
                mimetype='model/vrml',
                as_attachment=True,
                download_name=filename.replace('.stp', '.wrl').replace('.step', '.wrl')
            )

        else:
            return jsonify({'success': False, 'error': 'Invalid response_type (use base64 or download)'}), 400

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/download-wrl/<file_id>', methods=['GET'])
def download_wrl_file(file_id):
    """
    Cache'lenmiş WRL dosyasını indir

    Path Parameters:
        - file_id: UUID (convert-to-wrl97'den urls mode ile dönen)

    Response:
        - WRL binary stream
    """
    try:
        # Cache'den WRL dosyasını al
        wrl_data = get_cached_file_data(f"{file_id}_wrl")
        if not wrl_data:
            return jsonify({'success': False, 'error': 'WRL file not found in cache'}), 404

        # Geçici dosya oluştur ve gönder
        with tempfile.NamedTemporaryFile(delete=False, suffix='.wrl') as temp_file:
            temp_file.write(wrl_data['data'])
            temp_path = temp_file.name

        return send_file(
            temp_path,
            mimetype='model/vrml',
            as_attachment=True,
            download_name='model.wrl'
        )

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/convert-cached-to-gltf', methods=['POST'])
def convert_cached_to_gltf():
    """
    Cache'lenmiş STEP dosyasını glTF'e dönüştür

    Request:
        - Form data
        - file_id (ZORUNLU): /analyze'den dönen UUID
        - response_type (optional): "base64" (default) veya "download"

    Response (base64 mode):
        {
            "success": true,
            "gltf": "base64_encoded_gltf_content",
            "bin": "base64_encoded_bin_content" or null
        }

    Response (download mode):
        - glTF binary stream
    """
    try:
        file_id = request.form.get('file_id', '')
        response_type = request.form.get('response_type', 'base64')

        if not file_id:
            return jsonify({'success': False, 'error': 'file_id required'}), 400

        # Cache'den al
        cached_file = get_cached_file_data(file_id)
        if not cached_file:
            return jsonify({
                'success': False,
                'error': 'File not found in cache'
            }), 404

        temp_path = cached_file['path']
        filename = cached_file['filename']

        if not os.path.exists(temp_path):
            return jsonify({'success': False, 'error': 'Cached file was deleted'}), 404

        print(f"[GLTF-CACHED] ✓ {filename} (file_id: {file_id})", flush=True)

        # Timestamp güncelle
        cached_file['timestamp'] = time.time()
        cache_file_data(file_id, cached_file)

        # glTF'e dönüştür
        result = _convert_step_to_gltf(temp_path, filename)
        gltf_path = result['gltf_path']
        bin_path = result['bin_path']

        if response_type == 'base64':
            # Base64 encoded response
            with open(gltf_path, 'r', encoding='utf-8') as f:
                gltf_content = f.read()
                gltf_base64 = base64.b64encode(gltf_content.encode('utf-8')).decode('utf-8')

            bin_base64 = None
            if bin_path and os.path.exists(bin_path):
                with open(bin_path, 'rb') as f:
                    bin_content = f.read()
                    bin_base64 = base64.b64encode(bin_content).decode('utf-8')

            # Cleanup temp files
            try:
                os.unlink(gltf_path)
                if bin_path:
                    os.unlink(bin_path)
            except:
                pass

            return jsonify({
                'success': True,
                'gltf': gltf_base64,
                'bin': bin_base64,
                'filename': os.path.splitext(filename)[0] + '.gltf',
                'has_bin': bin_base64 is not None
            }), 200

        else:  # download mode
            return send_file(
                gltf_path,
                mimetype='model/gltf+json',
                as_attachment=True,
                download_name=f"{os.path.splitext(filename)[0]}.gltf"
            )

    except Exception as e:
        error_msg = str(e)
        print(f"[GLTF-CACHED] HATA: {error_msg}", flush=True)
        return jsonify({'success': False, 'error': error_msg}), 500


@app.route('/download-gltf/<file_id>/<file_type>', methods=['GET'])
def download_gltf_file(file_id, file_type):
    """
    Cache'lenmiş glTF dosyasını indir

    Path Parameters:
        - file_id: Cache'teki dosya ID'si
        - file_type: "gltf" veya "bin"
    """
    try:
        if file_type not in ['gltf', 'bin']:
            return jsonify({'success': False, 'error': 'Invalid file_type (use gltf or bin)'}), 400

        cached_file = get_cached_file_data(file_id)
        if not cached_file:
            return jsonify({'success': False, 'error': 'File not found in cache'}), 404

        if file_type == 'gltf':
            file_path = cached_file.get('gltf_path')
            mimetype = 'model/gltf+json'
            extension = '.gltf'
        else:  # bin
            file_path = cached_file.get('bin_path')
            mimetype = 'application/octet-stream'
            extension = '.bin'

        if not file_path or not os.path.exists(file_path):
            return jsonify({'success': False, 'error': f'{file_type.upper()} file not found'}), 404

        filename = cached_file['filename']
        download_name = f"{os.path.splitext(filename)[0]}{extension}"

        return send_file(
            file_path,
            mimetype=mimetype,
            as_attachment=True,
            download_name=download_name
        )

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/materials', methods=['GET'])
def list_materials():
    """Mevcut malzeme listesi"""
    materials_list = []
    for key, mat in free.MATERIALS.items():
        materials_list.append({
            'key': key,
            'name': mat.name,
            'symbol': mat.symbol,
            'density_kg_dm3': mat.density_kg_dm3
        })

    return jsonify({'success': True, 'materials': materials_list}), 200


@app.route('/cache/clear', methods=['POST'])
def clear_cache():
    """Tüm cache'i temizle (manual)"""
    with CACHE_LOCK:
        count = len(FILE_CACHE)
        for file_id, data in FILE_CACHE.items():
            try:
                if os.path.exists(data['path']):
                    os.unlink(data['path'])
            except:
                pass
        FILE_CACHE.clear()

    return jsonify({
        'success': True,
        'message': f'{count} dosya cache\'den temizlendi'
    }), 200


if __name__ == '__main__':
    port = int(os.getenv('FREECAD_API_PORT', '8090'))
    workers = int(os.getenv('FREECAD_API_WORKERS', '4'))  # Redis ile 4 worker kullanılabilir!
    debug = os.getenv('FLASK_DEBUG', 'False').lower() == 'true'
    use_gunicorn = os.getenv('USE_GUNICORN', 'false').lower() == 'true'

    print(f"""
╔════════════════════════════════════════════════════════════════════╗
║      FreeCAD API Server v2.1 - OPTİMİZE EDİLMİŞ + glTF             ║
║────────────────────────────────────────────────────────────────────║
║  Port            : {port}                                               ║
║  Workers         : {workers if use_gunicorn else 'threaded'}                                               ║
║  Cache TTL       : {CACHE_EXPIRY_SECONDS}s (1 saat)                                   ║
║  Max File        : 500MB                                           ║
║  Redis Cache     : {'✅ Aktif' if USE_REDIS else '⚠️ In-memory fallback'}                                      ║
║────────────────────────────────────────────────────────────────────║
║  Endpoints:                                                        ║
║    GET  /health                   - Health check + cache info      ║
║    POST /analyze                  - Analiz et + cache'le (file_id)║
║    POST /render-cached            - Cache'den GIF render           ║
║    POST /render                   - Direkt GIF render (eski)       ║
║    POST /convert-to-gltf          - STP → glTF (base64/urls) 🆕   ║
║    POST /convert-cached-to-gltf   - Cache'den glTF (base64/dl) 🆕 ║
║    GET  /download-gltf/<id>/<type>- glTF/bin dosya indirme 🆕      ║
║    GET  /materials                - Malzeme listesi                ║
║    POST /cache/clear              - Cache temizle                  ║
╚════════════════════════════════════════════════════════════════════╝

✨ OPTİMİZASYON: Dosya 1 kez yüklenir, analiz + render + glTF için kullanılır!
🎯 glTF EXPORT: STP dosyalarını Three.js için glTF formatına dönüştürün!
    """)

    if use_gunicorn:
        try:
            cmd = [
                'gunicorn',
                '--bind', f'0.0.0.0:{port}',
                '--workers', str(workers),
                '--timeout', '600',
                '--worker-class', 'sync',
                '--access-logfile', '-',
                '--error-logfile', '-',
                'freecad_api_server:app'
            ]
            subprocess.run(cmd)
        except FileNotFoundError:
            print("⚠️  Gunicorn bulunamadı, Flask dev server kullanılıyor")
            app.run(host='0.0.0.0', port=port, debug=debug, threaded=True)
    else:
        app.run(host='0.0.0.0', port=port, debug=debug, threaded=True)
