#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FreeCAD 3D Model Renderer & GIF Generator API
Parametrik 3D model render ve animasyon oluşturma aracı
"""

import sys, os, time, json, argparse
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass, asdict
from pathlib import Path

# 🔹 FreeCAD modülleri yükleniyor
if os.name == 'nt':  # Windows
    FREECAD_PATH = r"D:\__sistem__\ProgramFiles\FreeCAD\bin"
else:  # Linux (Docker)
    FREECAD_PATH = "/usr/lib/freecad-python3/lib"

if FREECAD_PATH not in sys.path:
    sys.path.append(FREECAD_PATH)

import FreeCAD
from FreeCAD import Base

# 🔹 GUI yükle
try:
    import FreeCADGui
    FreeCADGui.showMainWindow()
    import ImportGui
    print("[INFO] FreeCADGui loaded successfully", flush=True)
except Exception as e:
    print(f"[ERROR] FreeCADGui yüklenemedi: {e}", flush=True)
    sys.exit(1)

# Image processing libraries
try:
    from PIL import Image, ImageEnhance, ImageFilter, ImageDraw, ImageFont
    import numpy as np
    USE_PIL = True
except ImportError:
    import imageio.v2 as imageio
    USE_PIL = False


@dataclass
class RenderConfig:
    """Render ayarları"""
    width: int = 1920
    height: int = 1080
    background: str = "White"  # White, Black, Transparent
    view_type: str = "axonometric"  # axonometric, perspective, orthographic
    # Yeni profesyonel parametreler
    render_quality: str = "high"  # low, medium, high, ultra
    anti_aliasing: bool = True
    object_name: str = None


@dataclass
class AnimationConfig:
    """Animasyon ayarları"""
    enabled: bool = True
    num_frames: int = 36
    angle_step: int = 10
    duration: float = 0.08  # frame başına süre (saniye)
    pause_duration: float = 1.5  # dönüşler arası duraklama
    loop: int = 0  # 0=sonsuz, 1+=tekrar sayısı
    fps: int = 12

    # Dönüş ayarları
    yaw_enabled: bool = True  # Y ekseni (sağ-sol)
    pitch_enabled: bool = True  # X ekseni (yukarı-aşağı)
    roll_enabled: bool = False  # Z ekseni

    # Dönüş açıları
    yaw_range: Tuple[int, int] = (0, 360)
    pitch_range: Tuple[int, int] = (0, 360)
    roll_range: Tuple[int, int] = (0, 360)


@dataclass
class OutputConfig:
    """Çıktı ayarları"""
    output_dir: str = "."
    base_name: str = "render"
    save_png: bool = True
    save_gif: bool = True
    save_frames: bool = False  # Ara frameleri sakla
    gif_optimize: bool = True
    gif_quality: int = 85


@dataclass
class CameraConfig:
    """Kamera ayarları"""
    zoom_level: float = 1.0
    camera_position: Optional[Tuple[float, float, float]] = None
    look_at: Optional[Tuple[float, float, float]] = None


@dataclass
class PostProcessingConfig:
    """Post-processing ayarları"""
    # Filtreler
    edge_detection: bool = False
    sharpen: bool = True
    sharpen_intensity: float = 1.5
    smooth: bool = False
    smooth_radius: int = 2
    # Renk ayarları
    contrast: float = 1.2
    brightness: float = 1.0
    saturation: float = 1.1
    # Efektler
    ambient_occlusion: bool = False
    ao_intensity: float = 0.3
    shadow: bool = False
    shadow_offset_x: int = 10
    shadow_offset_y: int = 10
    shadow_blur: int = 5
    shadow_opacity: float = 0.5
    vignette: bool = False
    vignette_intensity: float = 0.5
    wireframe: bool = False
    wireframe_density: int = 10
    # Gelişmiş efektler
    depth_of_field: bool = False
    dof_focus_distance: float = 50
    dof_aperture: float = 2.8
    bloom: bool = False
    bloom_intensity: float = 0.5
    bloom_threshold: float = 0.8
    motion_blur: bool = False
    motion_blur_intensity: float = 0.5
    # Grid/Cetvel
    grid_overlay: bool = False
    grid_size: int = 10
    ruler_enabled: bool = False
    # Ortam
    fog: bool = False
    fog_density: float = 0.02
    fog_color: str = "#CCCCCC"


@dataclass
class LightingConfig:
    """Işıklandırma ayarları"""
    enabled: bool = True
    intensity: float = 1.0
    color: Tuple[int, int, int] = (255, 255, 200)


@dataclass
class MaterialConfig:
    """Malzeme ayarları"""
    metallic: float = 0.5
    roughness: float = 0.5
    reflection_intensity: float = 0.3
    normal_map_intensity: float = 1.0


@dataclass
class BackgroundConfig:
    """Arka plan ayarları"""
    gradient: bool = False
    gradient_color_1: str = "#F0F0FF"
    gradient_color_2: str = "#C8C8DC"


class FreeCADRenderer:
    """FreeCAD render motoru"""

    def __init__(self, input_file: str, render_cfg: RenderConfig,
                 anim_cfg: AnimationConfig, output_cfg: OutputConfig,
                 camera_cfg: CameraConfig,
                 post_cfg: Optional[PostProcessingConfig] = None,
                 lighting_cfg: Optional[LightingConfig] = None,
                 material_cfg: Optional[MaterialConfig] = None,
                 background_cfg: Optional[BackgroundConfig] = None):
        self.input_file = input_file
        self.render_cfg = render_cfg
        self.anim_cfg = anim_cfg
        self.output_cfg = output_cfg
        self.camera_cfg = camera_cfg
        self.post_cfg = post_cfg or PostProcessingConfig()
        self.lighting_cfg = lighting_cfg or LightingConfig()
        self.material_cfg = material_cfg or MaterialConfig()
        self.background_cfg = background_cfg or BackgroundConfig()

        self.doc = None
        self.view = None
        self.frames: List[str] = []
        self.initial_camera_height = None  # İlk kamera yüksekliğini sakla

    def debug(self, msg: str, level: str = "INFO"):
        """Debug mesajı"""
        print(f"[{level}] {msg}", flush=True)

    def setup_document(self):
        """Doküman ve model kurulumu"""
        if not os.path.exists(self.input_file):
            raise FileNotFoundError(f"Girdi dosyası bulunamadı: {self.input_file}")

        # Doküman oluştur
        self.doc = FreeCAD.newDocument("AutoRender")
        self.debug("Yeni belge oluşturuldu")

        # STEP dosyası içe aktar
        ImportGui.insert(self.input_file, self.doc.Name)
        self.debug(f"Model içe aktarıldı: {self.input_file}")

    def setup_view(self):
        """Görünüm ayarları"""
        FreeCADGui.showMainWindow()
        FreeCADGui.ActiveDocument = FreeCADGui.getDocument(self.doc.Name)
        self.view = FreeCADGui.ActiveDocument.ActiveView

        # View type ayarla
        if self.render_cfg.view_type == "axonometric":
            self.view.viewAxonometric()
            self.debug("View type: Axonometric")
        elif self.render_cfg.view_type == "perspective":
            self.view.viewPerspective()
            self.debug("View type: Perspective")
        elif self.render_cfg.view_type == "orthographic":
            self.view.viewOrthographic()
            self.debug("View type: Orthographic")

        # View type'ı doğrula
        cam = self.view.getCameraNode()
        if cam:
            cam_type = cam.getTypeId().getName()
            self.debug(f"Kamera tipi: {cam_type}")

        # Kamera pozisyonu
        if self.camera_cfg.camera_position:
            self.view.setCameraOrientation(
                FreeCAD.Rotation(*self.camera_cfg.camera_position)
            )

        # ÖNEMLİ: İlk önce TÜM nesnelerle fitAll yap, kamera yüksekliğini kaydet
        # Sonra filtreleme yap - böylece tek parça gösterilse bile kamera tüm montaja göre ayarlı olur
        for obj in self.doc.Objects:
            # Düzlemleri ve yardımcı nesneleri hariç tut
            if obj.TypeId in ['App::Plane', 'App::Origin', 'PartDesign::Plane', 'PartDesign::Line', 'PartDesign::Point']:
                obj.ViewObject.Visibility = False
            else:
                obj.ViewObject.Visibility = True

        self.view.fitAll()

        # Zoom ayarı - fitAll sonrası zoom faktörünü uygula
        if self.camera_cfg.zoom_level != 1.0:
            # Zoom faktörünü doğrudan uygula
            cam = self.view.getCameraNode()
            if cam:
                try:
                    # Perspective/Axonometric kamera için heightAngle
                    if hasattr(cam, 'heightAngle'):
                        current_height = cam.heightAngle.getValue()
                        new_height = current_height / self.camera_cfg.zoom_level
                        cam.heightAngle.setValue(new_height)
                    # Orthographic kamera için height
                    elif hasattr(cam, 'height'):
                        current_height = cam.height.getValue()
                        new_height = current_height / self.camera_cfg.zoom_level
                        cam.height.setValue(new_height)
                    else:
                        raise AttributeError("Kamera tipi desteklenmiyor")
                except Exception as e:
                    self.debug(f"Zoom uygulanamadı: {e}, fallback kullanılıyor", "WARNING")
                    # Fallback: iterative zoom
                    zoom_diff = self.camera_cfg.zoom_level - 1.0
                    zoom_steps = int(abs(zoom_diff) * 10)
                    for _ in range(zoom_steps):
                        if zoom_diff > 0:
                            self.view.zoomIn()
                        else:
                            self.view.zoomOut()
            else:
                # Fallback: iterative zoom
                zoom_diff = self.camera_cfg.zoom_level - 1.0
                zoom_steps = int(abs(zoom_diff) * 10)
                for _ in range(zoom_steps):
                    if zoom_diff > 0:
                        self.view.zoomIn()
                    else:
                        self.view.zoomOut()

        FreeCADGui.updateGui()

        # İlk kamera yüksekliğini kaydet (frame'lerde kullanmak için)
        cam = self.view.getCameraNode()
        if cam:
            try:
                if hasattr(cam, 'heightAngle'):
                    self.initial_camera_height = cam.heightAngle.getValue()
                elif hasattr(cam, 'height'):
                    self.initial_camera_height = cam.height.getValue()
                self.debug(f"İlk kamera yüksekliği kaydedildi (tüm montaj için): {self.initial_camera_height}")
            except:
                pass

        # ŞIMDI filtreleme yap - kamera ayarları tüm montaja göre ayarlandı
        if self.render_cfg.object_name and str(self.render_cfg.object_name).strip():
            self.debug(f"Object filtreleme başlıyor: '{self.render_cfg.object_name}'")

            # ÖNEMLİ: Visibility = False yerine transparan yap (render problemi çözümü)
            # Label etiketine göre nesneleri filtrele
            found_count = 0
            try:
                for obj in self.doc.Objects:
                    # Düzlemleri atla
                    if obj.TypeId in ['App::Plane', 'App::Origin', 'PartDesign::Plane', 'PartDesign::Line', 'PartDesign::Point']:
                        continue

                    # Label etiketini kontrol et (büyük/küçük harf duyarsız)
                    if hasattr(obj, 'Label') and self.render_cfg.object_name.lower() in obj.Label.lower():
                        # İstenen nesne - tamamen görünür ve renkli
                        obj.ViewObject.Visibility = True
                        if hasattr(obj.ViewObject, 'DisplayMode'):
                            obj.ViewObject.DisplayMode = "Shaded"

                        # Material config'den renk al (varsa), yoksa varsayılan
                        if hasattr(obj.ViewObject, 'ShapeColor'):
                            # Metalik görünüm için gri-mavi ton
                            metallic_factor = self.material_cfg.metallic if hasattr(self, 'material_cfg') else 0.5
                            base_color = 0.6 + (metallic_factor * 0.2)  # 0.6-0.8 arası
                            obj.ViewObject.ShapeColor = (base_color, base_color, base_color + 0.1)

                        if hasattr(obj.ViewObject, 'Transparency'):
                            obj.ViewObject.Transparency = 0

                        # Lighting intensity'ye göre parlaklık (destekleniyorsa)
                        if hasattr(obj.ViewObject, 'ShapeMaterial') and self.lighting_cfg.enabled:
                            try:
                                # FreeCAD material özellikleri (tüm sürümler desteklemeyebilir)
                                pass  # Material ayarları gelişmiş özellik, şimdilik atlıyoruz
                            except:
                                pass

                        found_count += 1
                        self.debug(f"✓ Görünür: {obj.Label} (Metallic: {getattr(self.material_cfg, 'metallic', 0.5)})")
                    else:
                        # Diğer nesneler - tamamen transparan (gizli gibi ama render edilir)
                        obj.ViewObject.Visibility = True
                        if hasattr(obj.ViewObject, 'Transparency'):
                            obj.ViewObject.Transparency = 95  # %95 transparan

                if found_count > 0:
                    self.debug(f"Toplam {found_count} nesne filtrelendi: '{self.render_cfg.object_name}'")

                    # Debug: Görünür nesneleri kontrol et
                    visible_count = sum(1 for o in self.doc.Objects if hasattr(o, 'ViewObject') and o.ViewObject.Visibility)
                    self.debug(f"Görünür nesne sayısı: {visible_count}")
                else:
                    # Hiçbir nesne bulunamazsa, tüm nesneleri tekrar göster (düzlemler hariç)
                    self.debug(f"UYARI: Hiçbir nesne bulunamadı, tüm parçalar gösteriliyor", "WARNING")
                    for obj in self.doc.Objects:
                        if obj.TypeId not in ['App::Plane', 'App::Origin', 'PartDesign::Plane', 'PartDesign::Line', 'PartDesign::Point']:
                            obj.ViewObject.Visibility = True
            except Exception as e:
                self.debug(f"Filtreleme hatası: {e}, tüm parçalar gösteriliyor", "WARNING")
                for obj in self.doc.Objects:
                    if obj.TypeId not in ['App::Plane', 'App::Origin', 'PartDesign::Plane', 'PartDesign::Line', 'PartDesign::Point']:
                        obj.ViewObject.Visibility = True

            FreeCADGui.updateGui()
            time.sleep(0.3)  # GUI güncellemesi için bekle

            # ÖNEMLİ: FitAll YAPMA! İlk kaydedilen kamera ayarlarını koru
            # Aksi halde zoom ve diğer ayarlar sıfırlanır
            self.debug(f"İlk kamera ayarları korunuyor (tüm montaj referanslı): {self.initial_camera_height}")
        else:
            self.debug("Object filtreleme YOK - tüm montaj render edilecek")

        # Render ayarları özeti
        self.debug("="*50)
        self.debug("RENDER AYARLARI ÖZETİ:")
        self.debug(f"  • Çözünürlük: {self.render_cfg.width}x{self.render_cfg.height}")
        self.debug(f"  • Arka Plan: {self.render_cfg.background}")
        self.debug(f"  • View Type: {self.render_cfg.view_type}")
        self.debug(f"  • Zoom: {self.camera_cfg.zoom_level}x")
        self.debug(f"  • Animasyon: {'Açık' if self.anim_cfg.enabled else 'Kapalı'}")
        if self.anim_cfg.enabled:
            self.debug(f"  • Frame Sayısı: {self.anim_cfg.num_frames}")
            self.debug(f"  • Frame Duration: {self.anim_cfg.duration}s ({int(self.anim_cfg.duration*1000)}ms)")
            self.debug(f"  • FPS: {self.anim_cfg.fps}")
            self.debug(f"  • Angle Step: {self.anim_cfg.angle_step}°")
            self.debug(f"  • Pause Duration: {self.anim_cfg.pause_duration}s")
            self.debug(f"  • Yaw/Pitch/Roll: {self.anim_cfg.yaw_enabled}/{self.anim_cfg.pitch_enabled}/{self.anim_cfg.roll_enabled}")
        self.debug(f"  • Object Filter: {self.render_cfg.object_name or 'YOK'}")
        self.debug(f"  • Kamera Yüksekliği: {self.initial_camera_height}")
        self.debug("="*50)

    def render_static_image(self) -> str:
        """Statik görüntü render et"""
        if not self.output_cfg.save_png:
            return None

        output_file = os.path.join(
            self.output_cfg.output_dir,
            f"{self.output_cfg.base_name}.png"
        )

        # Debug: Render öncesi görünür nesne sayısı
        visible = [o for o in self.doc.Objects if hasattr(o, 'ViewObject') and o.ViewObject.Visibility]
        self.debug(f"Render öncesi görünür nesne sayısı: {len(visible)}")
        for obj in visible:
            self.debug(f"  - {obj.Label} (Visible: {obj.ViewObject.Visibility})")

        FreeCADGui.updateGui()
        time.sleep(0.2)

        self.view.saveImage(
            output_file,
            self.render_cfg.width,
            self.render_cfg.height,
            self.render_cfg.background
        )

        file_size = os.path.getsize(output_file) if os.path.exists(output_file) else 0
        self.debug(f"Statik görüntü kaydedildi: {output_file} ({file_size} bytes)")
        return output_file

    def create_rotation_frames(self, axis: str, angle_range: Tuple[int, int]) -> List[str]:
        """Belirli eksen için dönüş frameleri oluştur"""
        frames = []
        start_angle, end_angle = angle_range

        num_frames = int((end_angle - start_angle) / self.anim_cfg.angle_step)

        self.debug(f"{axis.upper()} ekseni dönüş frameleri oluşturuluyor ({num_frames} frame)...")

        for i in range(num_frames):
            angle = start_angle + (i * self.anim_cfg.angle_step)

            # Eksen vektörü belirle
            if axis == "yaw":
                rotation = FreeCAD.Rotation(Base.Vector(0, 1, 0), angle)
            elif axis == "pitch":
                rotation = FreeCAD.Rotation(Base.Vector(1, 0, 0), angle)
            elif axis == "roll":
                rotation = FreeCAD.Rotation(Base.Vector(0, 0, 1), angle)
            else:
                continue

            self.view.setCameraOrientation(rotation)

            # İlk kamera yüksekliğini uygula (fitAll yerine)
            if self.initial_camera_height is not None:
                cam = self.view.getCameraNode()
                if cam:
                    try:
                        if hasattr(cam, 'heightAngle'):
                            cam.heightAngle.setValue(self.initial_camera_height)
                        elif hasattr(cam, 'height'):
                            cam.height.setValue(self.initial_camera_height)
                    except:
                        pass  # Sessizce devam et
            else:
                # Fallback: fitAll kullan
                self.view.fitAll()

                # Zoom'u her frame'de yeniden uygula
                if self.camera_cfg.zoom_level != 1.0:
                    cam = self.view.getCameraNode()
                    if cam:
                        try:
                            if hasattr(cam, 'heightAngle'):
                                current_height = cam.heightAngle.getValue()
                                new_height = current_height / self.camera_cfg.zoom_level
                                cam.heightAngle.setValue(new_height)
                            elif hasattr(cam, 'height'):
                                current_height = cam.height.getValue()
                                new_height = current_height / self.camera_cfg.zoom_level
                                cam.height.setValue(new_height)
                        except:
                            pass  # Sessizce devam et

            FreeCADGui.updateGui()
            time.sleep(0.1)  # GUI render için bekle

            frame_file = os.path.join(
                self.output_cfg.output_dir,
                f"frame_{axis}_{i:04d}.png"
            )

            # İlk frame için debug
            if i == 0:
                visible = [o for o in self.doc.Objects if hasattr(o, 'ViewObject') and o.ViewObject.Visibility]
                self.debug(f"İlk frame - Görünür nesne: {len(visible)}")

            self.view.saveImage(
                frame_file,
                self.render_cfg.width,
                self.render_cfg.height,
                self.render_cfg.background
            )

            frames.append(frame_file)

            # İlk frame boyutunu kontrol et
            if i == 0 and os.path.exists(frame_file):
                fsize = os.path.getsize(frame_file)
                self.debug(f"İlk frame boyutu: {fsize} bytes")

            if (i + 1) % 10 == 0:
                self.debug(f"  Frame {i+1}/{num_frames} ({axis.upper()})")

        return frames

    def create_pause_frames(self, count: int) -> List[str]:
        """Duraklama frameleri oluştur"""
        frames = []
        for i in range(count):
            frame_file = os.path.join(
                self.output_cfg.output_dir,
                f"frame_pause_{i:04d}.png"
            )
            self.view.saveImage(
                frame_file,
                self.render_cfg.width,
                self.render_cfg.height,
                self.render_cfg.background
            )
            frames.append(frame_file)
        return frames

    def create_animation(self) -> Optional[str]:
        """GIF animasyonu oluştur"""
        if not self.anim_cfg.enabled or not self.output_cfg.save_gif:
            return None

        self.debug("Animasyon frameleri oluşturuluyor...")
        self.frames = []

        # YAW (Y ekseni - sağ-sol)
        if self.anim_cfg.yaw_enabled:
            yaw_frames = self.create_rotation_frames("yaw", self.anim_cfg.yaw_range)
            self.frames.extend(yaw_frames)

            # Duraklama
            pause_count = int(self.anim_cfg.pause_duration * self.anim_cfg.fps)
            pause_frames = self.create_pause_frames(pause_count)
            self.frames.extend(pause_frames)

        # PITCH (X ekseni - yukarı-aşağı)
        if self.anim_cfg.pitch_enabled:
            pitch_frames = self.create_rotation_frames("pitch", self.anim_cfg.pitch_range)
            self.frames.extend(pitch_frames)

            # Duraklama
            if self.anim_cfg.roll_enabled:
                pause_count = int(self.anim_cfg.pause_duration * self.anim_cfg.fps)
                pause_frames = self.create_pause_frames(pause_count)
                self.frames.extend(pause_frames)

        # ROLL (Z ekseni)
        if self.anim_cfg.roll_enabled:
            roll_frames = self.create_rotation_frames("roll", self.anim_cfg.roll_range)
            self.frames.extend(roll_frames)

        # GIF oluştur
        gif_path = os.path.join(
            self.output_cfg.output_dir,
            f"{self.output_cfg.base_name}.gif"
        )

        self.save_gif(gif_path)

        # Ara frameleri temizle
        if not self.output_cfg.save_frames:
            self.cleanup_frames()

        return gif_path

    def save_gif(self, output_path: str):
        """GIF'i kaydet (PIL veya imageio kullanarak)"""
        self.debug(f"GIF oluşturuluyor ({len(self.frames)} frame)...")

        if USE_PIL:
            self._save_gif_pil(output_path)
        else:
            self._save_gif_imageio(output_path)

    def _save_gif_pil(self, output_path: str):
        """PIL kullanarak GIF kaydet"""
        images = []

        self.debug("Frame'ler yükleniyor ve post-processing uygulanıyor...")

        for i, f in enumerate(self.frames):
            if not os.path.exists(f):
                continue

            img = Image.open(f)

            # Post-processing uygula
            img = self.apply_post_processing(img)

            images.append(img)

            if (i + 1) % 10 == 0:
                self.debug(f"  Post-processing: {i+1}/{len(self.frames)} frame işlendi")

        if not images:
            raise ValueError("Hiç frame oluşturulamadı!")

        self.debug(f"Toplam {len(images)} frame işlendi, GIF oluşturuluyor...")

        # GIF kaydet
        gif_duration_ms = int(self.anim_cfg.duration * 1000)
        images[0].save(
            output_path,
            save_all=True,
            append_images=images[1:],
            duration=gif_duration_ms,  # milisaniye
            loop=self.anim_cfg.loop,
            optimize=self.output_cfg.gif_optimize,
            quality=self.output_cfg.gif_quality
        )

        total_duration = (len(images) * gif_duration_ms) / 1000.0
        self.debug(f"GIF oluşturuldu (PIL): {output_path}")
        self.debug(f"GIF duration: {gif_duration_ms}ms/frame, Toplam süre: {total_duration:.2f}s ({len(images)} frame)")

    def _save_gif_imageio(self, output_path: str):
        """imageio kullanarak GIF kaydet"""
        images = [imageio.imread(f) for f in self.frames if os.path.exists(f)]

        if not images:
            raise ValueError("Hiç frame oluşturulamadı!")

        imageio.mimsave(
            output_path,
            images,
            duration=self.anim_cfg.duration,
            loop=self.anim_cfg.loop,
            subrectangles=True
        )

        self.debug(f"GIF oluşturuldu (imageio): {output_path}")

    def apply_post_processing(self, img: Image.Image) -> Image.Image:
        """Post-processing efektlerini uygula"""
        if not USE_PIL:
            return img

        cfg = self.post_cfg

        # 1. Sharpen (Keskinleştirme)
        if cfg.sharpen:
            enhancer = ImageEnhance.Sharpness(img)
            img = enhancer.enhance(cfg.sharpen_intensity)

        # 2. Smooth (Yumuşatma)
        if cfg.smooth:
            img = img.filter(ImageFilter.GaussianBlur(radius=cfg.smooth_radius))

        # 3. Contrast (Kontrast)
        if cfg.contrast != 1.0:
            enhancer = ImageEnhance.Contrast(img)
            img = enhancer.enhance(cfg.contrast)

        # 4. Brightness (Parlaklık)
        if cfg.brightness != 1.0:
            enhancer = ImageEnhance.Brightness(img)
            img = enhancer.enhance(cfg.brightness)

        # 5. Saturation (Doygunluk)
        if cfg.saturation != 1.0:
            enhancer = ImageEnhance.Color(img)
            img = enhancer.enhance(cfg.saturation)

        # 6. Edge Detection (Kenar Algılama)
        if cfg.edge_detection:
            edges = img.filter(ImageFilter.FIND_EDGES)
            img = Image.blend(img, edges, 0.5)

        # 7. Vignette (Köşe Karartma)
        if cfg.vignette:
            img = self._apply_vignette(img, cfg.vignette_intensity)

        # 8. Shadow (Gölge Efekti)
        if cfg.shadow:
            img = self._apply_shadow(img, cfg.shadow_offset_x, cfg.shadow_offset_y,
                                     cfg.shadow_blur, cfg.shadow_opacity)

        # 9. Grid Overlay (Izgara)
        if cfg.grid_overlay:
            img = self._apply_grid(img, cfg.grid_size, cfg.ruler_enabled)

        # 10. Bloom (Parıltı)
        if cfg.bloom:
            img = self._apply_bloom(img, cfg.bloom_intensity, cfg.bloom_threshold)

        # 11. Fog (Sis)
        if cfg.fog:
            img = self._apply_fog(img, cfg.fog_density, cfg.fog_color)

        return img

    def _apply_vignette(self, img: Image.Image, intensity: float) -> Image.Image:
        """Vignette efekti uygula"""
        width, height = img.size

        # Radial gradient oluştur
        vignette = Image.new('L', (width, height), 0)
        draw = ImageDraw.Draw(vignette)

        # Merkez ve radius
        center_x, center_y = width // 2, height // 2
        max_radius = min(width, height) // 2

        # Gradient çiz
        for i in range(max_radius, 0, -1):
            alpha = int(255 * (1 - (i / max_radius) * intensity))
            bbox = [center_x - i, center_y - i, center_x + i, center_y + i]
            draw.ellipse(bbox, fill=alpha)

        # Uygula
        img = img.convert('RGBA')
        vignette = vignette.convert('RGBA')
        return Image.composite(img, Image.new('RGBA', img.size, (0, 0, 0, 255)), vignette)

    def _apply_shadow(self, img: Image.Image, offset_x: int, offset_y: int,
                      blur: int, opacity: float) -> Image.Image:
        """Gölge efekti ekle"""
        # Gölge layer oluştur
        shadow = Image.new('RGBA', img.size, (0, 0, 0, 0))

        # Gölgeyi çiz (basit versiyonu - nesneyi offset ile tekrar çiz)
        shadow.paste(img, (offset_x, offset_y))
        shadow = shadow.filter(ImageFilter.GaussianBlur(blur))

        # Opacity ayarla
        alpha = shadow.split()[3]
        alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
        shadow.putalpha(alpha)

        # Birleştir
        result = Image.alpha_composite(shadow, img.convert('RGBA'))
        return result

    def _apply_grid(self, img: Image.Image, grid_size: int, ruler: bool) -> Image.Image:
        """Grid overlay ekle"""
        img = img.convert('RGBA')
        overlay = Image.new('RGBA', img.size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(overlay)

        width, height = img.size

        # Grid çizgileri (açık gri)
        grid_color = (200, 200, 200, 128)

        # Dikey çizgiler
        for x in range(0, width, grid_size):
            draw.line([(x, 0), (x, height)], fill=grid_color, width=1)

        # Yatay çizgiler
        for y in range(0, height, grid_size):
            draw.line([(0, y), (width, y)], fill=grid_color, width=1)

        # Ruler (cetvel) - ana eksenleri kalın çiz
        if ruler:
            ruler_color = (255, 100, 100, 180)
            center_x, center_y = width // 2, height // 2

            # X ekseni (kırmızı)
            draw.line([(0, center_y), (width, center_y)], fill=ruler_color, width=2)

            # Y ekseni (kırmızı)
            draw.line([(center_x, 0), (center_x, height)], fill=ruler_color, width=2)

        return Image.alpha_composite(img, overlay)

    def _apply_bloom(self, img: Image.Image, intensity: float, threshold: float) -> Image.Image:
        """Bloom efekti (parıltı)"""
        # Parlak alanları bul
        enhancer = ImageEnhance.Brightness(img)
        bright = enhancer.enhance(2.0)

        # Threshold uygula
        bright_np = np.array(bright)
        threshold_val = int(threshold * 255)
        mask = np.all(bright_np > threshold_val, axis=-1) if bright_np.ndim == 3 else bright_np > threshold_val

        # Blur uygula
        bloom = bright.filter(ImageFilter.GaussianBlur(radius=15))

        # Orijinal ile blend et
        result = Image.blend(img, bloom, intensity * 0.5)
        return result

    def _apply_fog(self, img: Image.Image, density: float, color_hex: str) -> Image.Image:
        """Sis efekti"""
        # Hex rengi RGB'ye çevir
        color_hex = color_hex.lstrip('#')
        fog_color = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))

        # Sis layer oluştur
        fog_layer = Image.new('RGB', img.size, fog_color)

        # Blend et
        result = Image.blend(img.convert('RGB'), fog_layer, density)
        return result

    def cleanup_frames(self):
        """Ara frameleri temizle"""
        self.debug("Ara frameler temizleniyor...")
        for frame in self.frames:
            if os.path.exists(frame):
                try:
                    os.remove(frame)
                except Exception as e:
                    self.debug(f"Frame silinemedi {frame}: {e}", "WARNING")

    def render(self) -> Dict[str, str]:
        """Tüm render işlemini gerçekleştir"""
        results = {}

        try:
            self.setup_document()
            self.setup_view()

            # Statik görüntü
            png_path = self.render_static_image()
            if png_path:
                results['png'] = png_path

            # Animasyon
            gif_path = self.create_animation()
            if gif_path:
                results['gif'] = gif_path

            self.debug("İşlem başarıyla tamamlandı!", "SUCCESS")

        except Exception as e:
            self.debug(f"HATA: {e}", "ERROR")
            raise

        return results


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",
        "render": asdict(RenderConfig()),
        "animation": asdict(AnimationConfig()),
        "output": asdict(OutputConfig()),
        "camera": asdict(CameraConfig())
    }

    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 main():
    parser = argparse.ArgumentParser(
        description="FreeCAD 3D Model Renderer & GIF Generator API"
    )

    parser.add_argument('input_file', nargs='?', help='STEP dosya yolu (.stp)')
    parser.add_argument('-c', '--config', help='JSON konfigürasyon dosyası')
    parser.add_argument('-o', '--output', help='Çıktı dizini')
    parser.add_argument('--width', type=int, help='Görüntü genişliği')
    parser.add_argument('--height', type=int, help='Görüntü yüksekliği')
    parser.add_argument('--frames', type=int, help='Animasyon frame sayısı')
    parser.add_argument('--fps', type=int, help='Animasyon FPS')
    parser.add_argument('--no-gif', action='store_true', help='GIF oluşturma')
    parser.add_argument('--no-png', action='store_true', help='PNG kaydetme')
    parser.add_argument('--save-frames', action='store_true', help='Ara frameleri sakla')
    parser.add_argument('--create-config', help='Örnek config.json oluştur')

    args = parser.parse_args()

    # Config şablonu oluştur
    if args.create_config:
        save_config_template(args.create_config)
        return

    # Varsayılan değerler
    if args.config:
        # JSON'dan yükle
        config_data = load_config_from_json(args.config)
        input_file = config_data.get('input_file', args.input_file)
        render_cfg = RenderConfig(**config_data.get('render', {}))
        anim_cfg = AnimationConfig(**config_data.get('animation', {}))
        output_cfg = OutputConfig(**config_data.get('output', {}))
        camera_cfg = CameraConfig(**config_data.get('camera', {}))
        post_cfg = PostProcessingConfig(**config_data.get('post_processing', {}))
        lighting_cfg = LightingConfig(**config_data.get('lighting', {}))
        material_cfg = MaterialConfig(**config_data.get('material', {}))
        background_cfg = BackgroundConfig(**config_data.get('background', {}))
    else:
        # Varsayılan + CLI argümanları
        input_file = args.input_file or r"D:\__sistem__\ProgramFiles\FreeCAD\bin\freecad.stp"
        render_cfg = RenderConfig()
        anim_cfg = AnimationConfig()
        output_cfg = OutputConfig()
        camera_cfg = CameraConfig()
        post_cfg = PostProcessingConfig()
        lighting_cfg = LightingConfig()
        material_cfg = MaterialConfig()
        background_cfg = BackgroundConfig()

    # CLI override
    if args.output:
        output_cfg.output_dir = args.output
    if args.width:
        render_cfg.width = args.width
    if args.height:
        render_cfg.height = args.height
    if args.frames:
        anim_cfg.num_frames = args.frames
    if args.fps:
        anim_cfg.fps = args.fps
    if args.no_gif:
        output_cfg.save_gif = False
    if args.no_png:
        output_cfg.save_png = False
    if args.save_frames:
        output_cfg.save_frames = True

    # Çıktı dizini oluştur
    os.makedirs(output_cfg.output_dir, exist_ok=True)

    # Render et
    renderer = FreeCADRenderer(
        input_file,
        render_cfg,
        anim_cfg,
        output_cfg,
        camera_cfg,
        post_cfg,
        lighting_cfg,
        material_cfg,
        background_cfg
    )

    results = renderer.render()

    # Sonuçları yazdır
    print("\n" + "="*60)
    print("RENDER TAMAMLANDI")
    print("="*60)
    for key, path in results.items():
        print(f"{key.upper()}: {path}")
    print("="*60)


if __name__ == "__main__":
    main()
