#include "dosya_islemleri.hpp"

#include "kinematik_io.hpp"  // mekanizmaCikar / mekanizmaUygula (v1.17 KNMT)

#include "malzeme_io.hpp"
#include "dogrudan_ayar.hpp"  // importAyriBody (STEP → ayrı body opsiyonu)
#include "ayar_kayitlari.hpp"  // aptIthalSimulasyon (APT ithali → sim penceresi)
#include "cam_simulasyon_penceresi.hpp"  // camSimPencereAc (APT ithal simülasyonu)
#include "oto_kayit.hpp"  // aç/kaydet → kurtarma metası
#include "lisans_yukle.hpp"  // lisansSaglikKontrol (dağınık kurcalama kontrol noktası)

#include <commdlg.h>

#include <atomic>
#include <cctype>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <memory>
#include <string_view>
#include <thread>
#include <vector>

#include "uygulama_baglam.hpp"

#include "fiko/cam/apt.hpp"
#include "fiko/cekirdek/olay.hpp"  // AptYazildi (async finalize yayını)
#include "fiko/cam/cam_sistemi.hpp"
#include "fiko/render/orbit_kamera.hpp"  // APT ithalinde kamerayı yola sığdır (sigdir)
#include "fiko/io/fiko_format.hpp"
#include "fiko/io/web_io.hpp"           // FikoOp/WRL okuyucu + ithalSonucuKur
#include "fiko/cekirdek/cekirdek.hpp"
#include "fiko/cekirdek/gecici.hpp"       // merkezi geçici/önbellek dizini (kaynak yanına yazma yok)
#include "fiko/cekirdek/gorev.hpp"        // GorevSistemi/GorevDurum (async dosya işi)
#include "fiko/core/gunluk.hpp"
#include "fiko/platform/pencere.hpp"
#include "fiko/render/vertex.hpp"
#include "fiko/render/vulkan_baglam.hpp"   // toplu yükleme oturumu (import perf)
#include "fiko/sahne/sahne.hpp"
#include "fiko/ui/agac_simge_kayit.hpp"    // öğe-bazlı simge override kalıcılığı (v1.5 ASMG)

namespace fiko {

static void govdeleriAsenkronAktar(UygulamaBaglam& b, const std::string& s,
                                   std::string basariMesaj, std::string hataMesaj);

// Yol → uzantısız dosya adı ("C:\\a\\parca.step" → "parca"). Süreç kaydı belge etiketi.
static std::string dosyaTabanAdi(const std::string& yol) {
  std::string ad = yol;
  if (const size_t s = ad.find_last_of("/\\"); s != std::string::npos) ad = ad.substr(s + 1);
  if (const size_t d = ad.find_last_of('.'); d != std::string::npos) ad = ad.substr(0, d);
  return ad;
}

std::string dosyaSec(HWND pencere, bool kaydet) {
  wchar_t yol[MAX_PATH] = {};
  OPENFILENAMEW ofn{};
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = pencere;
  ofn.lpstrFilter = L"STEP Dosyaları (*.step;*.stp)\0*.step;*.stp\0Tümü (*.*)\0*.*\0";
  ofn.lpstrFile = yol;
  ofn.nMaxFile = MAX_PATH;
  ofn.lpstrDefExt = L"step";
  ofn.Flags = kaydet ? (OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST)
                     : (OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST);
  const BOOL tamam = kaydet ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn);
  if (!tamam) return {};
  const int bayt = WideCharToMultiByte(CP_UTF8, 0, yol, -1, nullptr, 0, nullptr, nullptr);
  std::string s(bayt - 1, 0);
  WideCharToMultiByte(CP_UTF8, 0, yol, -1, s.data(), bayt, nullptr, nullptr);
  return s;
}

// Görüntü/simge dosyası seç (aç). Ağaç tip-simgeleri ve öğe override için.
std::string resimDosyaSec(HWND pencere) {
  wchar_t yol[MAX_PATH] = {};
  OPENFILENAMEW ofn{};
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = pencere;
  ofn.lpstrFilter =
      L"Simge (*.svg;*.png;*.jpg;*.jpeg;*.bmp;*.gif)\0*.svg;*.png;*.jpg;*.jpeg;*.bmp;*.gif\0"
      L"Tümü (*.*)\0*.*\0";
  ofn.lpstrFile = yol;
  ofn.nMaxFile = MAX_PATH;
  ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
  if (!GetOpenFileNameW(&ofn)) return {};
  const int bayt = WideCharToMultiByte(CP_UTF8, 0, yol, -1, nullptr, 0, nullptr, nullptr);
  std::string s(bayt - 1, 0);
  WideCharToMultiByte(CP_UTF8, 0, yol, -1, s.data(), bayt, nullptr, nullptr);
  return s;
}

// APT/CL (.FikoApt/.apt) dosya seç (aç veya kaydet). dosyaSec kalıbı; ayrı
// filtre + varsayılan uzantı (.FikoApt birincil, .apt ikincil).
std::string aptDosyaSec(HWND pencere, bool kaydet) {
  wchar_t yol[MAX_PATH] = {};
  OPENFILENAMEW ofn{};
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = pencere;
  ofn.lpstrFilter = L"APT / CL (*.FikoApt;*.apt)\0*.FikoApt;*.apt\0Tümü (*.*)\0*.*\0";
  ofn.lpstrFile = yol;
  ofn.nMaxFile = MAX_PATH;
  ofn.lpstrDefExt = kaydet ? L"apt" : nullptr;
  ofn.Flags = kaydet ? (OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST)
                     : (OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST);
  const BOOL tamam = kaydet ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn);
  if (!tamam) return {};
  const int bayt = WideCharToMultiByte(CP_UTF8, 0, yol, -1, nullptr, 0, nullptr, nullptr);
  std::string s(bayt - 1, 0);
  WideCharToMultiByte(CP_UTF8, 0, yol, -1, s.data(), bayt, nullptr, nullptr);
  return s;
}

// Web dosyası seç (aç): .FikoOp (Fikoperasyon: mesh + takım yolu) + .wrl (VRML97
// mesh). dosyaSec kalıbı; birleşik filtre, uzantıya göre yolAc dağıtır.
std::string webDosyaSec(HWND pencere) {
  wchar_t yol[MAX_PATH] = {};
  OPENFILENAMEW ofn{};
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = pencere;
  ofn.lpstrFilter = L"Web CAD (*.FikoOp;*.wrl)\0*.FikoOp;*.wrl\0"
                    L"Fikoperasyon (*.FikoOp)\0*.FikoOp\0VRML (*.wrl)\0*.wrl\0"
                    L"Tümü (*.*)\0*.*\0";
  ofn.lpstrFile = yol;
  ofn.nMaxFile = MAX_PATH;
  ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
  if (!GetOpenFileNameW(&ofn)) return {};
  const int bayt = WideCharToMultiByte(CP_UTF8, 0, yol, -1, nullptr, 0, nullptr, nullptr);
  std::string s(bayt - 1, 0);
  WideCharToMultiByte(CP_UTF8, 0, yol, -1, s.data(), bayt, nullptr, nullptr);
  return s;
}

// STEP içe aktarma ağır (OCCT parse + ağ üretimi) → GorevSistemi'nde worker
// thread'de çalışır; parse+mesh saf CPU, sonuç main-thread'de GPU'ya yüklenir.
// stepYol: verilen yolu içe aktarır (dialog yok). stepAc dialog gösterip buna delege.
static void stepYol(UygulamaBaglam& b, const std::string& yol) {
  Cekirdek& cekirdek = b.cekirdek;
  if (yol.empty()) return;
  if (b.sonDosyaEkle) b.sonDosyaEkle(yol);  // MRU kaydı (başarılı seçim → kalıcı)
  // Dosya adı (uzantısız) → kök grup etiketi ("stp <ad>" hissi).
  std::string dosyaAdi = yol;
  if (const size_t s = dosyaAdi.find_last_of("/\\"); s != std::string::npos)
    dosyaAdi = dosyaAdi.substr(s + 1);
  if (const size_t d = dosyaAdi.find_last_of('.'); d != std::string::npos)
    dosyaAdi = dosyaAdi.substr(0, d);

  // Worker'da mesh'lenmiş, montaj hiyerarşisini koruyan hazır düğüm ağacı.
  struct HazirDugum {
    std::string ad;
    bool govdeVar = false;
    geometri::IthalGovde ig;  // yaprak geometri (dünya koordinatında)
    MeshVeri yuzey;
    KenarVeri kenar;
    NoktaVeri nokta;
    std::vector<HazirDugum> altlar;
  };
  auto sonuc = std::make_shared<std::vector<HazirDugum>>();
  cekirdek.gorevler().kuyrukla(
      "STEP İçe Aktar",
      [yol, sonuc](GorevBaglam& b) {  // WORKER: saf CPU (GPU/sahne/olay yasak)
        // ---- STEP MESH CACHE (.fkcache): ikinci açılış ANINDA. Anahtar =
        // dosya boyutu + değişim zamanı; format sürümlü. BREP BinTools ile,
        // mesh'ler ham POD dump ile saklanır → parse+tessellation tamamen atlanır.
        namespace fs = std::filesystem;
        // Önbellek dosyası kaynağın YANINA değil, uygulama geçici ağacına yazılır
        // (gecici.hpp): kullanıcı klasörü kirlenmez, salt-okunur/ağ dizininde de
        // çalışır, tek yerden budanır. Eski sürümün yan-dosyası varsa taşınır.
        gecici::eskiYanDosyaGoc(yol, "fkcache");
        const std::string cacheYol = gecici::onbellekDosyasi(yol, "fkcache").string();
        constexpr u32 CACHE_SURUM = 5;  // 5: brep per-yüz renkleri cache'e eklendi (govdeBaytYaz
                                        // yalnız shape taşır — renksiz brep ilk it/çek'te tüm renk
                                        // sigortalarını devre dışı bırakıp gövdeyi griye düşürüyordu)
        u64 dosyaBoyut = 0, dosyaZaman = 0;
        {
          std::error_code ec;
          dosyaBoyut = (u64)fs::file_size(yol, ec);
          const auto z = fs::last_write_time(yol, ec);
          dosyaZaman = (u64)z.time_since_epoch().count();
        }
        const auto yaz = [](std::ostream& o, const void* p, size_t n) {
          o.write((const char*)p, (std::streamsize)n);
        };
        const auto oku = [](std::istream& i, void* p, size_t n) {
          i.read((char*)p, (std::streamsize)n);
          return (bool)i;
        };
        // Düğüm (de)serileştirme — POD dump + uzunluk önekleri.
        std::function<void(std::ostream&, const HazirDugum&)> dugumYaz =
            [&](std::ostream& o, const HazirDugum& h) {
              const u32 adN = (u32)h.ad.size();
              yaz(o, &adN, 4); yaz(o, h.ad.data(), adN);
              const u8 gv = h.govdeVar ? 1 : 0; yaz(o, &gv, 1);
              if (h.govdeVar) {
                const u8 rv = h.ig.renkVar ? 1 : 0; yaz(o, &rv, 1);
                yaz(o, &h.ig.renk, sizeof(Vec3f));
                const u32 vN = (u32)h.yuzey.vertexler.size(), iN = (u32)h.yuzey.indeksler.size();
                yaz(o, &vN, 4); yaz(o, h.yuzey.vertexler.data(), vN * sizeof(Vertex));
                yaz(o, &iN, 4); yaz(o, h.yuzey.indeksler.data(), iN * 4);
                yaz(o, &h.yuzey.sinir, sizeof(Aabbf));
                yaz(o, &h.yuzey.yuzSayi, 4);
                const u32 kN = (u32)h.kenar.vertexler.size();
                yaz(o, &kN, 4); yaz(o, h.kenar.vertexler.data(), kN * sizeof(KenarVertex));
                yaz(o, &h.kenar.kenarSayi, 4);
                const u32 nN = (u32)h.nokta.vertexler.size();
                yaz(o, &nN, 4); yaz(o, h.nokta.vertexler.data(), nN * sizeof(KenarVertex));
                yaz(o, &h.nokta.noktaSayi, 4);
                const std::string brep = geometri::govdeBaytYaz(h.ig.govde);
                const u32 bN = (u32)brep.size();
                yaz(o, &bN, 4); yaz(o, brep.data(), bN);
                // Per-yüz brep renkleri (govdeBaytYaz TAŞIMAZ — BinTools yalnız shape yazar).
                // Bunlar olmadan cache'ten yüklenen brep renksiz kalır → ilk DM işleminde
                // renk propagasyonu/sigortaları çalışamaz, gövde tek renge düşer.
                const auto& yr = h.ig.govde.yuzRenkleri();
                const u32 yrN = h.ig.govde.yuzRenkliMi() ? (u32)yr.size() : 0;
                yaz(o, &yrN, 4);
                if (yrN) yaz(o, yr.data(), yrN * sizeof(Vec3f));
              }
              const u32 aN = (u32)h.altlar.size(); yaz(o, &aN, 4);
              for (const auto& c : h.altlar) dugumYaz(o, c);
            };
        std::function<bool(std::istream&, HazirDugum&)> dugumOku =
            [&](std::istream& i, HazirDugum& h) -> bool {
          u32 adN = 0;
          if (!oku(i, &adN, 4) || adN > 4096) return false;
          h.ad.resize(adN);
          if (adN && !oku(i, h.ad.data(), adN)) return false;
          u8 gv = 0;
          if (!oku(i, &gv, 1)) return false;
          h.govdeVar = gv != 0;
          if (h.govdeVar) {
            u8 rv = 0;
            if (!oku(i, &rv, 1) || !oku(i, &h.ig.renk, sizeof(Vec3f))) return false;
            h.ig.renkVar = rv != 0;
            u32 vN = 0, iN = 0;
            if (!oku(i, &vN, 4)) return false;
            h.yuzey.vertexler.resize(vN);
            if (vN && !oku(i, h.yuzey.vertexler.data(), vN * sizeof(Vertex))) return false;
            if (!oku(i, &iN, 4)) return false;
            h.yuzey.indeksler.resize(iN);
            if (iN && !oku(i, h.yuzey.indeksler.data(), iN * 4)) return false;
            if (!oku(i, &h.yuzey.sinir, sizeof(Aabbf)) || !oku(i, &h.yuzey.yuzSayi, 4))
              return false;
            u32 kN = 0;
            if (!oku(i, &kN, 4)) return false;
            h.kenar.vertexler.resize(kN);
            if (kN && !oku(i, h.kenar.vertexler.data(), kN * sizeof(KenarVertex))) return false;
            if (!oku(i, &h.kenar.kenarSayi, 4)) return false;
            u32 nN = 0;
            if (!oku(i, &nN, 4)) return false;
            h.nokta.vertexler.resize(nN);
            if (nN && !oku(i, h.nokta.vertexler.data(), nN * sizeof(KenarVertex))) return false;
            if (!oku(i, &h.nokta.noktaSayi, 4)) return false;
            u32 bN = 0;
            if (!oku(i, &bN, 4)) return false;
            std::string brep(bN, 0);
            if (bN && !oku(i, brep.data(), bN)) return false;
            h.ig.govde = geometri::govdeBaytOku(brep);
            if (!h.ig.govde.gecerliMi()) return false;
            u32 yrN = 0;
            if (!oku(i, &yrN, 4) || yrN > 10000000) return false;
            if (yrN) {
              std::vector<Vec3f> yr(yrN);
              if (!oku(i, yr.data(), yrN * sizeof(Vec3f))) return false;
              h.ig.govde.yuzRenkleriAta(std::move(yr));
            }
          }
          u32 aN = 0;
          if (!oku(i, &aN, 4) || aN > 100000) return false;
          h.altlar.resize(aN);
          for (auto& c : h.altlar)
            if (!dugumOku(i, c)) return false;
          return true;
        };
        // Cache dene: başlık eşleşirse parse+mesh tamamen atlanır.
        {
          std::ifstream cf(cacheYol, std::ios::binary);
          if (cf) {
            char magic[4] = {};
            u32 surum = 0; u64 cb = 0, cz = 0, kokN = 0;
            if (oku(cf, magic, 4) && std::memcmp(magic, "FKC1", 4) == 0 &&
                oku(cf, &surum, 4) && surum == CACHE_SURUM && oku(cf, &cb, 8) &&
                cb == dosyaBoyut && oku(cf, &cz, 8) && cz == dosyaZaman &&
                oku(cf, &kokN, 8) && kokN < 100000) {
              b.ilerlet(0.2f, "Önbellekten yükleniyor");
              std::vector<HazirDugum> ck((size_t)kokN);
              bool tamam = true;
              for (auto& k : ck)
                if (!dugumOku(cf, k)) { tamam = false; break; }
              if (tamam) {
                *sonuc = std::move(ck);
                gunluk::bilgi("STEP önbellekten yüklendi (anında): {}", yol);
                b.ilerlet(1.0f, "Önbellekten yüklendi");
                return;
              }
              sonuc->clear();
              gunluk::uyari("STEP önbellek bozuk, yeniden üretilecek: {}", cacheYol);
            }
          }
        }
        const auto parseBasla = std::chrono::steady_clock::now();
        auto kokler = geometri::stepYukleHiyerarsi(
            yol, [&](f32 o, std::string_view m) { b.ilerlet(o, m); });
        const f64 parseMs = std::chrono::duration<f64, std::milli>(
                                std::chrono::steady_clock::now() - parseBasla).count();
        gunluk::bilgi("STEP parse: {:.0f} ms", parseMs);
        // Yaprak (mesh'lenecek gövde) sayısını say → ilerleme oranı.
        std::function<int(const geometri::IthalDugum&)> yaprakSay =
            [&](const geometri::IthalDugum& d) {
              int s = d.govdeVar ? 1 : 0;
              for (const auto& c : d.altlar) s += yaprakSay(c);
              return s;
            };
        int toplam = 0;
        for (const auto& d : kokler) toplam += yaprakSay(d);
        // FAZ 1 — iskelet: ağacı mesh'lemeden kur, gövdeyi taşı, yaprakları topla.
        std::vector<HazirDugum*> yapraklar;
        yapraklar.reserve((size_t)toplam);
        std::function<HazirDugum(geometri::IthalDugum&)> iskelet =
            [&](geometri::IthalDugum& d) -> HazirDugum {
          HazirDugum h;
          h.ad = std::move(d.ad);
          h.govdeVar = d.govdeVar;
          if (d.govdeVar && d.govde.gecerliMi()) {
            h.ig.govde = std::move(d.govde);
            h.ig.renkVar = d.renkVar;
            h.ig.renk = d.renk;
          } else {
            h.govdeVar = false;
          }
          h.altlar.reserve(d.altlar.size());
          for (auto& c : d.altlar) h.altlar.push_back(iskelet(c));
          return h;
        };
        sonuc->reserve(kokler.size());
        for (auto& d : kokler) sonuc->push_back(iskelet(d));
        std::function<void(HazirDugum&)> yaprakTopla = [&](HazirDugum& h) {
          if (h.govdeVar) yapraklar.push_back(&h);
          for (auto& c : h.altlar) yaprakTopla(c);
        };
        for (auto& h : *sonuc) yaprakTopla(h);

        // FAZ 2 — gövdeler-arası PARALEL tessellation.
        // GÜVENLİK: STEP'ten bölünen kardeş gövdeler altta AYNI Geom yüzeylerini
        // paylaşır; paylaşımlı geometriyi iki thread'de mesh'lemek data-race/çökme
        // yapar. Bu yüzden önce SERİ derin kopya (paylaşım kopar, ucuz), sonra
        // her thread kendi ÖZEL geometrisini mesh'ler (tamamen güvenli).
        const auto meshBasla = std::chrono::steady_clock::now();
        std::atomic<size_t> kuyruk{0}, biten{0};
        const unsigned isciSayi = std::min<unsigned>(
            std::max(1u, std::thread::hardware_concurrency() - 1u),
            (unsigned)std::max<size_t>(yapraklar.size(), 1));
        if (isciSayi > 1 && yapraklar.size() > 1) {
          size_t k = 0;
          for (HazirDugum* h : yapraklar) {
            if (b.iptalIstendi()) return;
            geometri::BrepGovde kopya = geometri::govdeDerinKopya(h->ig.govde);
            if (kopya.gecerliMi()) h->ig.govde = std::move(kopya);
            // Kopya başarısızsa orijinal kalır → o gövde aşağıda TEK thread'de
            // (kuyruğun sonunda seri) işlense de paylaşım riski kalıyor olabilir;
            // pratikte Copy başarısızlığı ≈ geçersiz gövde, mesh de atlanır.
            if (toplam > 0)
              b.ilerlet(0.44f + 0.06f * (f32)(++k) / (f32)toplam, "Geometri ayrıştırılıyor");
          }
        }
        // Paralel BRepMesh YALNIZ tek gövdede (dış paralellik yok → OSD_ThreadPool
        // nesting/deadlock oluşmaz). Çok gövdede zaten gövde-başına thread var, iç
        // paralel nested-pool deadlock'u tetikler → seri. Ölçüldü: tek gövde 5.3× hız.
        const bool meshParalel = importParalelMesh() && yapraklar.size() == 1;
        const auto isci = [&] {
          // Bu lambda'yı çalıştıran her std::thread OCCT kullanır (ucgenle/kenarMesh/
          // noktaMesh) ama GorevSistemi'nin worker-başlatma kancasından (main.cpp:
          // isciBaslatmaKancasiAyarla) GEÇMEZ → OSD::SetSignal kurulmamış olur. Bazı
          // STEP'lerde (bozuk/uç-durum geometri) mesh/kenar üretimi sırasında OCCT bir
          // access violation atarsa bu SEH try/catch'e YAKALANMAZ ve süreç sessizce
          // düşer ("bazı STEP'ler açılmıyor" kök nedeni). Her thread'de BİR KEZ kur.
          geometri::occtSinyalKur();
          for (;;) {
            const size_t i = kuyruk.fetch_add(1);
            if (i >= yapraklar.size() || b.iptalIstendi()) return;
            HazirDugum& h = *yapraklar[i];
            const Vec3f renk = h.ig.renkVar ? h.ig.renk : Vec3f{0.62f, 0.66f, 0.72f};
            h.yuzey = h.ig.govde.ucgenle(renk, -1.0, meshParalel);
            h.kenar = h.ig.govde.kenarMesh();
            h.nokta = h.ig.govde.noktaMesh();
            h.ig.govde.yuzBilgiOnbellekKur();  // akıllı seçim cache'i WORKER'da (seçimde tıkanma yok)
            const size_t n = biten.fetch_add(1) + 1;
            if (toplam > 0)
              b.ilerlet(0.5f + 0.5f * (f32)n / (f32)toplam, "Ağ üretiliyor (paralel)");
          }
        };
        std::vector<std::thread> isciler;
        isciler.reserve(isciSayi);
        for (unsigned t = 0; t < isciSayi; ++t) isciler.emplace_back(isci);
        for (auto& t : isciler) t.join();
        const f64 meshMs = std::chrono::duration<f64, std::milli>(
                               std::chrono::steady_clock::now() - meshBasla).count();
        gunluk::bilgi("STEP ağ üretimi: {} gövde, {} işçi, {:.0f} ms",
                      yapraklar.size(), isciSayi, meshMs);

        // Cache yaz (geçici dosya + rename → yarım dosya asla geçerli olmaz).
        if (!b.iptalIstendi() && !sonuc->empty()) {
          const std::string gecici = cacheYol + ".tmp";
          std::ofstream cf(gecici, std::ios::binary | std::ios::trunc);
          if (cf) {
            yaz(cf, "FKC1", 4);
            yaz(cf, &CACHE_SURUM, 4);
            yaz(cf, &dosyaBoyut, 8);
            yaz(cf, &dosyaZaman, 8);
            const u64 kokN = (u64)sonuc->size();
            yaz(cf, &kokN, 8);
            for (const auto& k : *sonuc) dugumYaz(cf, k);
            cf.close();
            std::error_code ec;
            fs::rename(gecici, cacheYol, ec);
            if (ec) fs::remove(gecici, ec);
            else gunluk::bilgi("STEP önbelleği yazıldı: {}", cacheYol);
          }
        }
      },
      [&b, sonuc, dosyaAdi]() {  // FINALIZE: main-thread (GPU yükleme + sahne + olay)
        if (sonuc->empty()) { b.bilgiVer("STEP okunamadı / boş"); return; }
        // Toplu GPU yükleme oturumu: tüm gövde tamponları TEK submit + TEK
        // fence-wait (per-tampon stall'lar silinir — büyük montajda 5-20×).
        b.sahne.vulkan().bekle();               // oturum başı bir kez (per-düğüm değil)
        b.sahne.vulkan().topluYuklemeBasla();
        // Montaj ağacı Part altında bir grup olur (CATIA: import Part içine girer, kök değil).
        int govdeSay = 0;
        std::function<void(HazirDugum&, Varlik)> kur = [&](HazirDugum& h, Varlik ust) {
          if (!h.altlar.empty()) {
            const Varlik g = b.sahne.grupEkle(h.ad.empty() ? "Grup" : h.ad, ust);
            if (h.govdeVar) {
              Dugum& d = b.sahne.ithalEkleHazir(std::move(h.ig), std::move(h.yuzey),
                                                std::move(h.kenar), std::move(h.nokta), g);
              if (!h.ad.empty()) d.ad = h.ad;
              ++govdeSay;
            }
            for (auto& c : h.altlar) kur(c, g);
          } else if (h.govdeVar) {
            Dugum& d = b.sahne.ithalEkleHazir(std::move(h.ig), std::move(h.yuzey),
                                              std::move(h.kenar), std::move(h.nokta), ust);
            if (!h.ad.empty()) d.ad = h.ad;
            ++govdeSay;
          }
        };
        // CATIA: içe aktarılan STEP body-scope olur (In Work Object). importAyriBody + çok top-
        // level katı → HER biri AYRI PartBody (assembly); aksi halde hepsi tek import-body'sinde.
        if (dogrudanAyar().importAyriBody && sonuc->size() > 1) {
          Varlik sonBody = VARLIK_YOK;
          int idx = 0;
          for (auto& h : *sonuc) {
            const std::string ad = h.ad.empty() ? (dosyaAdi + "." + std::to_string(++idx)) : h.ad;
            const Varlik bg = b.sahne.grupEkle(ad, b.sahne.partKokAl());
            b.sahne.dunya().ekle<SimgeBilesen>(bg, SimgeBilesen{"📦"});
            b.sahne.bodyKaydet(bg);
            kur(h, bg);
            sonBody = bg;
          }
          if (sonBody != VARLIK_YOK) b.sahne.aktifBodyAyarla(sonBody);
        } else {
          const Varlik kokGrup = b.sahne.grupEkle(dosyaAdi, b.sahne.partKokAl());
          b.sahne.dunya().ekle<SimgeBilesen>(kokGrup, SimgeBilesen{"📦"});
          b.sahne.bodyKaydet(kokGrup);
          b.sahne.aktifBodyAyarla(kokGrup);
          for (auto& h : *sonuc) kur(h, kokGrup);
        }
        b.sahne.vulkan().topluYuklemeBitir();   // TEK submit + fence (GPU'ya boşalt)
        b.tumunuSigdir();
        if (b.belgeAdiAyarla) b.belgeAdiAyarla(dosyaAdi);  // süreç yöneticisi kaydına belge adı
        b.bilgiVer(std::to_string(govdeSay) + " gövde içe aktarıldı");
      });
}

void stepAc(UygulamaBaglam& b) {
  stepYol(b, dosyaSec((HWND)b.pencere.dogalTutamac(), false));
}

void stepKaydetKomut(UygulamaBaglam& b) {
  lisansSaglikKontrol();  // kontrol noktası #2 (dışa aktarma yolu)
  const std::string yol = dosyaSec((HWND)b.pencere.dogalTutamac(), true);
  if (yol.empty()) return;
  govdeleriAsenkronAktar(b, yol, "STEP kaydedildi", "STEP kaydedilemedi");
}

// ---- Yerel .fikocad belgesi (tam durum + kamera) ---------------------------
// .fikocad dosya seç (aç/kaydet) — özel filtre + varsayılan uzantı.
static std::string fikoDosyaSec(HWND pencere, bool kaydet) {
  wchar_t yol[MAX_PATH] = {};
  OPENFILENAMEW ofn{};
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = pencere;
  ofn.lpstrFilter = L"FikoCAD Belgesi (*.fikocad)\0*.fikocad\0Tümü (*.*)\0*.*\0";
  ofn.lpstrFile = yol;
  ofn.nMaxFile = MAX_PATH;
  ofn.lpstrDefExt = L"fikocad";
  ofn.Flags = kaydet ? (OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST)
                     : (OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST);
  const BOOL tamam = kaydet ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn);
  if (!tamam) return {};
  const int bayt = WideCharToMultiByte(CP_UTF8, 0, yol, -1, nullptr, 0, nullptr, nullptr);
  std::string s(bayt - 1, 0);
  WideCharToMultiByte(CP_UTF8, 0, yol, -1, s.data(), bayt, nullptr, nullptr);
  return s;
}

// Aynı başlıklı dosya işi zaten kuyruktaysa (Bekliyor/Çalışıyor) → çift-iş guard.
// main.cpp'ye dokunmadan GorevSistemi::anlik() başlıklarına bakar (MDI-güvenli).
static bool dosyaIsiAktif(Cekirdek& cekirdek, const char* baslik) {
  for (const GorevGorunum& g : cekirdek.gorevler().anlik())
    if (g.baslik == baslik &&
        (g.durum == GorevDurum::Bekliyor || g.durum == GorevDurum::Calisiyor))
      return true;
  return false;
}

// Verilen .fikocad yolunu ASENKRON yükle (dialog yok) — sürükle-bırak / palet / yolAc.
// STEP import kalıbı: worker read+inflate+parse+TESSELLATE (donma buradaydı), finalize
// GPU yükleme + sahne kurulum + kamera. "İşlemler" paneli ilerlemeyi otomatik gösterir.
static void fikoYol(UygulamaBaglam& b, const std::string& yol) {
  if (yol.empty()) return;
  Cekirdek& cekirdek = b.cekirdek;
  if (dosyaIsiAktif(cekirdek, "Belge Açılıyor")) {
    b.bilgiVer("Belge zaten açılıyor, lütfen bekleyin…");
    return;
  }
  auto sonuc = std::make_shared<io::HazirYukleme>();
  auto hata = std::make_shared<std::string>();
  auto tamam = std::make_shared<bool>(false);
  auto simgeler = std::make_shared<std::vector<std::pair<u64, std::string>>>();  // öğe simge override (v1.5)
  cekirdek.gorevler().kuyrukla(
      "Belge Açılıyor",
      [yol, sonuc, hata, tamam, simgeler](GorevBaglam& b) {  // WORKER: saf CPU (GPU/sahne yasak)
        b.ilerlet(0.05f, "Dosya okunuyor");
        std::string veri;
        {
          std::ifstream f(yol, std::ios::binary | std::ios::ate);
          if (!f) { *hata = "Dosya açılamadı: " + yol; return; }
          const std::streamsize n = f.tellg();
          if (n <= 0) { *hata = "Boş dosya: " + yol; return; }
          veri.resize((size_t)n);
          f.seekg(0);
          f.read(veri.data(), n);
        }
        if (b.iptalIstendi()) return;
        b.ilerlet(0.15f, "Çözülüyor");
        io::FikoBelge belge;
        if (!io::fikoOku(veri, belge, *hata)) return;  // *tamam=false → finalize hata gösterir
        *simgeler = std::move(belge.agacSimgeleri);    // öğe simge override → finalize'da uygula
        // Ağır tessellate 0.15→0.95 (iptal düğüm bazlı kontrol edilir).
        if (!io::belgeCoz(belge, *sonuc,
                          [&](f32 o, std::string_view m) { b.ilerlet(o, m); },
                          [&] { return b.iptalIstendi(); }))
          return;  // iptal → finalize çağrılmaz (durum Iptal)
        *tamam = true;
      },
      [&b, sonuc, hata, tamam, yol, simgeler]() {  // FINALIZE: main-thread (GPU + sahne + kamera)
        if (!*tamam) {
          b.bilgiVer(hata->empty() ? "Belge açılamadı" : ("Belge açılamadı — " + *hata));
          return;
        }
        std::unordered_map<u32, u32> dugumHarita;  // eski→yeni (malzeme atama remap'i)
        io::belgeUygula(b.sahne, b.kamera, *sonuc, &b.partD, &b.olcum, &b.pmiSis,
                        &dugumHarita);  // feature + ölçüm + PMI
        malzemeleriBelgedenAl(*sonuc, dugumHarita, b.malzemeKut, b.malzemeAta);  // v1.16 MLZM
        // Kinematik mekanizma: uzuv→düğüm bağları YÜKLEMEDE değişen Varlik'lerle remap edilir.
        mekanizmaUygula(b.mekanizma, sonuc->uzuvlar, sonuc->mafsallar, sonuc->yasalar,
                        dugumHarita);  // v1.17 KNMT
        // CAE kurulumları: `dugum` yüklemede DEĞİŞEN Varlik'tir → mekanizma uzuvlarıyla
        // AYNI haritayla remap; eşleşmeyen kayıt DÜŞÜRÜLÜR (yanlış gövdeye analiz bağlamaktansa).
        b.caeKurulumlar.clear();
        for (io::FikoCaeKurulum c : sonuc->caeKurulumlar) {
          const auto it = dugumHarita.find(c.dugum);
          if (it == dugumHarita.end()) continue;
          c.dugum = it->second;
          b.caeKurulumlar.push_back(c);
        }
        ui::AgacSimgeKayit::global().ogeleriTemizle();  // öğe-bazlı simge override'larını geri yükle
        for (const auto& [id, deger] : *simgeler) {
          ui::AgacSimgeKayit::global().ogeAyarla(id, deger);  // render önbelleği
          if (auto* gvo = b.cekirdek.gorunum((Varlik)id))
            gvo->agacSimgeOzelAyarla(deger);  // objenin kalıtımsal UI özelliği (sahip)
        }
        // Kurtarma anlık görüntüsü geçici ağaçtan yüklenir: MRU'ya YAZMA (her an
        // budanabilir bir yol "son dosyalar"da durmamalı).
        if (b.sonDosyaEkle && !gecici::kokIcindeMi(yol)) b.sonDosyaEkle(yol);
        if (b.belgeAdiAyarla) b.belgeAdiAyarla(dosyaTabanAdi(yol));  // süreç yöneticisi belge adı
        otoKayit().belgeYolu(yol);  // kurtarma metası + kirli sıfırla
        b.bilgiVer("Belge açıldı: " + yol);
      });
}

void fikoAc(UygulamaBaglam& b) {
  fikoYol(b, fikoDosyaSec((HWND)b.pencere.dogalTutamac(), false));
}

// .fikocad ASENKRON kaydet: snapshot (belgeCikar) ana-thread'te — govdeBaytYaz OCCT
// serialize dahil, tutarlı/atomik görüntü — sonra worker serialize+sıkıştır+atomik yaz.
void fikoKaydetKomut(UygulamaBaglam& b) {
  const std::string yol = fikoDosyaSec((HWND)b.pencere.dogalTutamac(), true);
  if (yol.empty()) return;
  fikoKaydetYola(b, yol);
}

// Diyalogsuz kaydetme ÇEKİRDEĞİ: yol dışarıdan gelir (makro/MCP otomasyonu, betik,
// ileride "Kaydet" kısayolu). Gövde `fikoKaydetKomut`tan BİREBİR taşındı — davranış
// aynı (aynı snapshot + aynı worker + aynı finalize/MRU/oto-kayıt bağı).
void fikoKaydetYola(UygulamaBaglam& b, const std::string& yol) {
  if (yol.empty()) return;
  Cekirdek& cekirdek = b.cekirdek;
  if (dosyaIsiAktif(cekirdek, "Belge Kaydediliyor")) {
    b.bilgiVer("Kayıt sürüyor, lütfen bekleyin…");
    return;
  }
  // SNAPSHOT ana-thread (kuyruklamadan ÖNCE): UCUZ görüntü — brepErtele=true ile ağır
  // BinTools serialize worker'a ertelenir (yalnız shared_ptr handle alınır → UI donmaz).
  // Handle değişmez (op'lar yeni handle üretir) → sahne eşzamanlı düzenlense de tutarlı.
  auto belge = std::make_shared<io::FikoBelge>(
      io::belgeCikar(b.sahne, b.kamera, &b.partD, /*brepErtele=*/true, &b.olcum, &b.pmiSis));
  belge->agacSimgeleri = ui::AgacSimgeKayit::global().ogeListesi();  // öğe simge override (v1.5 ASMG)
  malzemeleriBelgeyeYaz(b.malzemeKut, b.malzemeAta, *belge);  // malzeme + atamalar (v1.16 MLZM)
  mekanizmaCikar(b.mekanizma, *belge);  // kinematik uzuv/mafsal/yasa (v1.17 KNMT)
  belge->caeKurulumlar = b.caeKurulumlar;  // yapısal analiz kurulumları (v1.19 CAEK)
  auto hata = std::make_shared<std::string>();
  auto tamam = std::make_shared<bool>(false);
  cekirdek.gorevler().kuyrukla(
      "Belge Kaydediliyor",
      [belge, yol, hata, tamam](GorevBaglam& b) {  // WORKER: saf CPU (serialize+POD+deflate+disk)
        // Ertelenmiş gövdeleri PARALEL serileştir (ana-thread donmaz), sonra POD+sıkıştır.
        if (!io::belgeBrepSerilestir(*belge, [&](f32 o, std::string_view m) { b.ilerlet(o, m); },
                                     [&] { return b.iptalIstendi(); }))
          return;
        std::string veri;
        if (!io::fikoYaz(*belge, veri, *hata,
                         [&](f32 o, std::string_view m) { b.ilerlet(o, m); }))
          return;
        if (b.iptalIstendi()) return;  // iptal → hedef dosya dokunulmaz (rename yok)
        b.ilerlet(0.9f, "Yazılıyor");
        if (!io::dosyayaYaz(yol, veri, *hata)) return;
        b.ilerlet(1.0f, "Yazıldı");
        *tamam = true;
      },
      [&b, yol, hata, tamam]() {  // FINALIZE: main-thread (MRU + bilgi)
        if (!*tamam) {
          b.bilgiVer(hata->empty() ? "Belge kaydedilemedi"
                                   : ("Kaydedilemedi — " + *hata));
          return;
        }
        if (b.sonDosyaEkle) b.sonDosyaEkle(yol);  // başarılı kayıt → MRU
        if (b.belgeAdiAyarla) b.belgeAdiAyarla(dosyaTabanAdi(yol));  // süreç yöneticisi belge adı
        otoKayit().belgeYolu(yol);  // kurtarma metası + kirli sıfırla
        b.bilgiVer("Belge kaydedildi: " + yol);
      });
}

// Gövdeleri ASENKRON dışa aktar (STEP/STL/OBJ/IGES — uzantıya göre worker'da dispatch).
// Ana-thread yalnız UCUZ snapshot (brep shared_ptr + dönüşüm; op'lar yeni handle üretir →
// kopya tutarlı); donusumUygula (OCCT kopya+transform) + yazım worker'da. disaAktarKomut
// ve stepKaydetKomut ORTAK kullanır (aynı donma her iki yolda da vardı).
static void govdeleriAsenkronAktar(UygulamaBaglam& b, const std::string& s,
                                   std::string basariMesaj, std::string hataMesaj) {
  struct AktarGirdi {
    std::shared_ptr<const geometri::BrepGovde> brep;
    Donusumd donusum;
  };
  auto girdiler = std::make_shared<std::vector<AktarGirdi>>();
  const bool yalnizSecili = !b.sahne.secililer().empty();
  for (const auto& d : b.sahne.dugumler()) {
    if (yalnizSecili && !b.sahne.govdeSeciliMi(d.kimlik)) continue;
    if (!d.brep || !d.brep->gecerliMi()) continue;
    // Sağ tık ile verilen per-yüz renk d.yuzOz'da (Dugum override — görüntü/undo katmanı)
    // yaşar, brep.yuzRenkleri()'ne YAZILMAZ. Dışa aktarım brep'i doğrudan okuduğu için
    // buradaki override'ı export ÖNCESİ brep kopyasına "pişirmezsek" STEP renksiz çıkar
    // (bkz. brep_io.cpp stepKaydet — yalnız brep.yuzRenkleri() yazar).
    if (!d.yuzOz.empty()) {
      auto pisirilmis = std::make_shared<geometri::BrepGovde>(geometri::govdeDerinKopya(*d.brep));
      const u32 n = pisirilmis->yuzSayisi();
      std::vector<Vec3f> renkler = pisirilmis->yuzRenkliMi()
                                        ? pisirilmis->yuzRenkleri()
                                        : std::vector<Vec3f>(n, d.yuzVars.renk);
      renkler.resize(n, d.yuzVars.renk);
      for (const auto& oz_kv : d.yuzOz) {
        const u32 ord = oz_kv.first;
        if (ord >= 1 && ord <= n) renkler[ord - 1] = oz_kv.second.renk;
      }
      pisirilmis->yuzRenkleriAta(std::move(renkler));
      girdiler->push_back({pisirilmis, d.donusum});
    } else {
      girdiler->push_back({d.brep, d.donusum});
    }
  }
  if (girdiler->empty()) {
    b.bilgiVer(std::move(hataMesaj) + " — gövde yok");
    return;
  }
  auto tamam = std::make_shared<std::atomic<bool>>(false);
  b.cekirdek.gorevler().kuyrukla(
      "Dışa Aktarılıyor",
      [girdiler, s, tamam](GorevBaglam& gb) {  // WORKER: saf OCCT (sahneye dokunmaz)
        std::vector<geometri::BrepGovde> dunya;
        dunya.reserve(girdiler->size());
        size_t i = 0;
        for (const AktarGirdi& g : *girdiler) {
          if (gb.iptalIstendi()) return;
          dunya.push_back(geometri::donusumUygula(*g.brep, g.donusum));
          gb.ilerlet(0.6f * (f32)(++i) / (f32)girdiler->size(), "Geometri hazırlanıyor");
        }
        std::vector<const geometri::BrepGovde*> liste;
        for (const auto& g : dunya) liste.push_back(&g);
        gb.ilerlet(0.7f, "Yazılıyor");
        std::string uz;
        if (const size_t n = s.find_last_of('.'); n != std::string::npos) {
          uz = s.substr(n + 1);
          for (char& c : uz) c = (char)std::tolower((unsigned char)c);
        }
        if (uz == "stl") *tamam = geometri::stlKaydet(liste, s);
        else if (uz == "obj") *tamam = geometri::objKaydet(liste, s);
        else if (uz == "iges" || uz == "igs") *tamam = geometri::igesKaydet(liste, s);
        else *tamam = geometri::stepKaydet(liste, s);
      },
      [&b, s, tamam, basariMesaj = std::move(basariMesaj),
       hataMesaj = std::move(hataMesaj)]() {  // FINALIZE: MRU + bilgi
        if (*tamam && b.sonDosyaEkle) b.sonDosyaEkle(s);
        b.bilgiVer(*tamam ? basariMesaj : hataMesaj);
      });
}

// Çok-formatlı dışa aktarım: kaydet-dialog (STEP/STL/OBJ/IGES filtresi) → sahne.disaAktar
// uzantıya göre doğru dışa aktarıcıyı çağırır. Uzantı yazılmazsa seçili filtreye göre eklenir.
void disaAktarKomut(UygulamaBaglam& b) {
  lisansSaglikKontrol();  // kontrol noktası #3
  wchar_t yol[MAX_PATH] = {};
  OPENFILENAMEW ofn{};
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = (HWND)b.pencere.dogalTutamac();
  ofn.lpstrFilter = L"STEP (*.step)\0*.step\0STL (*.stl)\0*.stl\0"
                    L"Wavefront OBJ (*.obj)\0*.obj\0IGES (*.iges)\0*.iges\0";
  ofn.lpstrFile = yol;
  ofn.nMaxFile = MAX_PATH;
  ofn.nFilterIndex = 1;
  ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
  if (!GetSaveFileNameW(&ofn)) return;
  std::wstring w(yol);
  if (w.find(L'.') == std::wstring::npos) {  // uzantı yoksa filtreye göre ekle
    const wchar_t* ekler[] = {L".step", L".stl", L".obj", L".iges"};
    const int i = (ofn.nFilterIndex >= 1 && ofn.nFilterIndex <= 4) ? (int)ofn.nFilterIndex - 1 : 0;
    w += ekler[i];
  }
  const int bayt = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr);
  std::string s(bayt - 1, 0);
  WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, s.data(), bayt, nullptr, nullptr);
  govdeleriAsenkronAktar(b, s, "Dışa aktarıldı: " + s, "Dışa aktarılamadı");
}

// ---- APT/CL (.FikoApt) içe aktarma: parse worker'da, yükleme main-thread'de.
// STEP import kalıbı (kuyrukla + finalize) — CAM operasyonu + sahne yol düğümü.
static void aptYol(UygulamaBaglam& b, const std::string& yol) {
  Cekirdek& cekirdek = b.cekirdek;
  if (yol.empty()) return;
  if (b.sonDosyaEkle) b.sonDosyaEkle(yol);  // MRU kaydı (başarılı seçim → kalıcı)
  auto sonuc = std::make_shared<cam::AptSonuc>();
  cam::AptOkuSecenek aptSec;
  aptSec.yayToleransMm = aptYayTolerans();  // ana thread'de oku (worker'a POD kopya)
  cekirdek.gorevler().kuyrukla(
      "APT İçe Aktar",
      [yol, sonuc, aptSec](GorevBaglam& b) {  // WORKER: saf CPU (getline parse)
        b.ilerlet(0.1f, "APT okunuyor");
        *sonuc = cam::aptOku(yol, aptSec);
        b.ilerlet(1.0f, "Bitti");
      },
      [&b, sonuc]() {  // FINALIZE: main-thread (CAM op + sahne düğümü + olay)
        if (!sonuc->basarili) {
          b.bilgiVer(sonuc->hata.empty() ? "APT okunamadı" : ("APT: " + sonuc->hata));
          return;
        }
        const bool sim = aptIthalSimulasyon();
        const u32 op = b.camSis.aptIceAktar(*sonuc, /*sahneyeCiz=*/!sim,
                                            aptAktiviteAyir());
        if (!op) { b.bilgiVer("APT yolu boş / yüklenemedi"); return; }
        b.camSis.acik = true;
        // Kac operasyon (aktivite) acildi → kullanici dosyanin bolundugunu gorsun.
        const std::string opBilgi =
            sonuc->aktiviteler.size() > 1
                ? (", " + std::to_string(sonuc->aktiviteler.size()) + " operasyon")
                : std::string();
        if (sim) {
          // Sahneye çizmedik: veri tutuldu, yolu simülasyon penceresinde oynat.
          // Çok operasyonlu APT → 0 (tüm program): her aktivite timeline'da AYRI
          // segment olur. Tek op kimliğiyle açılsaydı yalnız ilki görünürdü.
          camSimPencereAc(sonuc->aktiviteler.size() > 1 ? 0u : op);
          // Kamerayı yola oturt: sim kipinde sahne düğümleri SONRAKİ karede kurulur,
          // yani tumunuSigdir burada yolu göremez. AABB'yi doğrudan hamlelerden al.
          // (Yoksa kamera eski yerinde kalıp yolun arkasında/içinde kalıyordu — APT
          // koordinatları makine orijininde, ör. X≈1120.)
          Aabbf ys;
          for (const cam::Hamle& h : sonuc->yol.hamleler)
            ys.genislet({(f32)h.nokta.x, (f32)h.nokta.y, (f32)h.nokta.z});
          if (ys.gecerliMi()) {
            const Vec3f bo = ys.boyut();
            const f32 yari = std::max(
                0.5f * std::sqrt(bo.x * bo.x + bo.y * bo.y + bo.z * bo.z), 1.0f);
            b.kamera.sigdir(ys.merkez(), yari * 1.05f);
          }
          b.bilgiVer("APT içe aktarıldı → simülasyon (" +
                     std::to_string(sonuc->gotoSayisi) + " GOTO" + opBilgi + ")");
        } else {
          b.tumunuSigdir();
          b.bilgiVer("APT içe aktarıldı (" + std::to_string(sonuc->gotoSayisi) +
                     " GOTO" + opBilgi + ")");
        }
      });
}

void aptAc(UygulamaBaglam& b) {
  aptYol(b, aptDosyaSec((HWND)b.pencere.dogalTutamac(), false));
}

// ---- .FikoOp (Fikoperasyon) içe aktarma: gzip+JSON parse + mesh + takım yolu
// worker'da (fiko::io::fikoOpOku), sahneye bağlama main-thread'de (ithalSonucuKur).
static void fikoOpYol(UygulamaBaglam& b, const std::string& yol) {
  Cekirdek& cekirdek = b.cekirdek;
  if (yol.empty()) return;
  if (b.sonDosyaEkle) b.sonDosyaEkle(yol);
  auto sonuc = std::make_shared<io::FikoOpSonuc>();
  cekirdek.gorevler().kuyrukla(
      "FikoOp İçe Aktar",
      [yol, sonuc](GorevBaglam& g) {  // WORKER: gzip inflate + simdjson + mesh/yol
        *sonuc = io::fikoOpOku(yol, [&g](f32 o, std::string_view m) { g.ilerlet(o, m); });
      },
      [&b, sonuc]() {  // FINALIZE: main-thread (grup + mesh düğüm + yol düğümü)
        if (!sonuc->basarili) {
          b.bilgiVer(sonuc->hata.empty() ? "FikoOp okunamadı" : ("FikoOp: " + sonuc->hata));
          return;
        }
        const u32 grup = io::ithalSonucuKur(b.sahne, *sonuc, &b.camSis);
        if (!grup) { b.bilgiVer("FikoOp boş (mesh/yol yok)"); return; }
        // Program varsa CAM görünümü aç (web'deki gibi sim'de oynanabilir).
        if (!sonuc->programlar.empty()) b.camSis.acik = true;
        b.tumunuSigdir();
        std::string msj = "FikoOp içe aktarıldı — " +
                          std::to_string(sonuc->cizimler.size()) + " çizim, " +
                          std::to_string(sonuc->programlar.size()) + " program (" +
                          std::to_string(sonuc->toplamNokta) + " nokta)";
        if (sonuc->atlananCizim)
          msj += ", " + std::to_string(sonuc->atlananCizim) + " sıkıştırılmış çizim atlandı";
        b.bilgiVer(msj);
      });
}

// ---- .wrl (VRML97) içe aktarma: STEP gibi BREP (per-yüz/kenar seçim + hover).
// AĞIR iş (vrmlOku parse + BrepGovde + ucgenle BRepMesh + altKimlik remap) WORKER'da;
// yalnız GPU yükleme (ithalEkleHazir) main-thread'de → dev WRL'de pencere DONMAZ.
// stepYol kalıbı (Sahne::vrmlIceAktar'ın bö+ async hali; monolitik metot donduruyordu).
static void wrlYol(UygulamaBaglam& b, const std::string& yol) {
  Cekirdek& cekirdek = b.cekirdek;
  if (yol.empty()) return;
  if (b.sonDosyaEkle) b.sonDosyaEkle(yol);

  struct WrlHazir {
    geometri::IthalGovde ig;  // BREP gövde (yüz→Shape seçim + per-yüz renk)
    MeshVeri mesh;            // ucgenle çıktısı (altKimlik = Shape ordinali)
    KenarVeri kenar;          // feature kenarları (IndexedLineSet)
    bool basarili = false;
  };
  auto sonuc = std::make_shared<WrlHazir>();
  cekirdek.gorevler().kuyrukla(
      "VRML İçe Aktar",
      [yol, sonuc](GorevBaglam& g) {  // WORKER: saf CPU (parse + BREP + mesh)
        g.ilerlet(0.1f, "VRML okunuyor");
        KenarVeri kenarlar;
        std::vector<Vec3f> yuzRenkleri;
        std::vector<u32> yuzShape;  // her yüz → Shape (CATIA yüzü / seçim grubu)
        geometri::BrepGovde govde = geometri::vrmlOku(yol, &kenarlar, &yuzRenkleri, &yuzShape);
        if (!govde.gecerliMi()) return;
        if (!yuzRenkleri.empty()) govde.yuzRenkleriAta(std::move(yuzRenkleri));
        g.ilerlet(0.5f, "Üçgenleniyor");
        MeshVeri mesh = govde.ucgenle(Vec3f{0.62f, 0.66f, 0.72f});
        // Seçim ordinali: üçgen-yüz → Shape (bir yüze tık = tüm CATIA yüzü, web `grup`).
        if (!yuzShape.empty()) {
          u32 maks = 0;
          for (auto& v : mesh.vertexler)
            if (v.altKimlik >= 1 && v.altKimlik <= yuzShape.size()) {
              v.altKimlik = yuzShape[v.altKimlik - 1];
              if (v.altKimlik > maks) maks = v.altKimlik;
            }
          mesh.yuzSayi = maks;
        }
        // Akıllı seçim (benzerYuzler/paralelYuzler) yüz sınıflandırma cache'ini WORKER'da
        // pre-warm et → her seçimde 10ms+ main-thread taraması yerine <1ms cache okuma.
        govde.yuzBilgiOnbellekKur();
        sonuc->ig.govde = std::move(govde);
        sonuc->mesh = std::move(mesh);
        sonuc->kenar = std::move(kenarlar);
        sonuc->basarili = true;
        g.ilerlet(1.0f, "Bitti");
      },
      [&b, sonuc]() {  // FINALIZE: main-thread (GPU yükleme + sahne düğümü)
        if (!sonuc->basarili) { b.bilgiVer("VRML okunamadı / boş (geçerli gövde yok)"); return; }
        b.sahne.ithalEkleHazir(std::move(sonuc->ig), std::move(sonuc->mesh),
                               std::move(sonuc->kenar), {}, VARLIK_YOK);
        b.tumunuSigdir();
        b.bilgiVer("VRML içe aktarıldı (BREP — yüz/kenar seçilebilir, STEP gibi)");
      });
}

void webAc(UygulamaBaglam& b) {
  yolAc(b, webDosyaSec((HWND)b.pencere.dogalTutamac()));
}

// Yola göre içe aktar: uzantıya göre STEP veya APT içe aktarıcıyı çağırır.
// Universal palet "Dosya" kaynağı buradan hızlı yeniden açar (dialog yok).
void yolAc(UygulamaBaglam& b, const std::string& yol) {
  if (yol.empty()) return;
  std::string uz;
  if (const size_t d = yol.find_last_of('.'); d != std::string::npos)
    for (char c : yol.substr(d)) uz += (char)std::tolower((unsigned char)c);
  if (uz == ".fikocad") {
    fikoYol(b, yol);  // yerel belge (tam durum + kamera)
  } else if (uz == ".apt" || uz == ".fikoapt") {
    aptYol(b, yol);
  } else if (uz == ".step" || uz == ".stp") {
    stepYol(b, yol);  // STEP içe aktarıcı (STEPCAFControl_Reader)
  } else if (uz == ".fikoop") {
    fikoOpYol(b, yol);  // Fikoperasyon (mesh + takım yolu, web export)
  } else if (uz == ".wrl") {
    wrlYol(b, yol);  // VRML97 mesh (CATIA export)
  } else {
    // Desteklenmeyen uzantı (ör. sürükle-bırak ile gelen rastgele dosya): çökme YOK.
    if (b.bilgiVer)
      b.bilgiVer("Desteklenmeyen dosya türü — .fikocad, STEP (.step/.stp), "
                 ".FikoApt, .FikoOp ve .wrl açılabilir");
  }
}

// ---- APT/CL dışa aktarma: seçili operasyon → .apt (ncKaydetSec kalıbı).
void aptAktar(UygulamaBaglam& b) {
  const int kimlik = b.camSis.seciliOp ? b.camSis.seciliOp : b.camSis.seciliKimlik;
  if (kimlik <= 0) { b.bilgiVer("Önce üretim ağacından bir operasyon seçin"); return; }
  const std::string yol = aptDosyaSec((HWND)b.pencere.dogalTutamac(), true);
  if (yol.empty()) return;
  // Metin ANA-THREAD'de üretilir (CAM verisi yalnız main'de okunur — worker'dan
  // okumak kullanıcı op silerken race olurdu), DISK yazımı worker'da (ağ sürücüsünde
  // donma kalkar). AptYazildi olayı yazım başarılı olunca finalize'da (davranış birebir).
  auto metin = std::make_shared<std::string>();
  u32 hamleSayi = 0;
  if (!b.camSis.aptMetinUret((u32)kimlik, *metin, hamleSayi)) {
    b.bilgiVer("APT aktarılamadı (yol hesaplı mı?)");
    return;
  }
  auto tamam = std::make_shared<std::atomic<bool>>(false);
  b.cekirdek.gorevler().kuyrukla(
      "APT Dışa Aktar",
      [metin, yol, tamam](GorevBaglam&) {  // WORKER: yalnız disk
        std::ofstream f(yol, std::ios::binary);
        if (f) {
          f.write(metin->data(), (std::streamsize)metin->size());
          *tamam = (bool)f;
        }
      },
      [&b, tamam, kimlik, hamleSayi]() {
        if (*tamam)
          b.cekirdek.olaylar().yayinla(AptYazildi{(u32)kimlik, hamleSayi}, "APT");
        b.bilgiVer(*tamam ? "APT dışa aktarıldı (.apt)" : "APT aktarılamadı (disk yazılamadı)");
      });
}

// ---- G-Kod (NC) dışa aktarma: tüm program → .nc/.tap (eski Üretim Programı
// panelindeki ⭳ G-Kod düğmesinin yerini alır; artık CAM menüsünden).
void gkodAktar(UygulamaBaglam& b) {
  lisansSaglikKontrol();  // kontrol noktası #4 (CAM çıktısı — en değerli üretim adımı)
  wchar_t yolW[MAX_PATH] = {};
  OPENFILENAMEW ofn{};
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = (HWND)b.pencere.dogalTutamac();
  ofn.lpstrFilter = L"NC Programı (*.nc;*.tap)\0*.nc;*.tap\0Tümü (*.*)\0*.*\0";
  ofn.lpstrFile = yolW;
  ofn.nMaxFile = MAX_PATH;
  ofn.lpstrDefExt = L"nc";
  ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
  if (!GetSaveFileNameW(&ofn)) return;
  const int bayt = WideCharToMultiByte(CP_UTF8, 0, yolW, -1, nullptr, 0, nullptr, nullptr);
  std::string yol(bayt - 1, 0);
  WideCharToMultiByte(CP_UTF8, 0, yolW, -1, yol.data(), bayt, nullptr, nullptr);
  // Metin main'de (CAM verisi), disk worker'da (disaAktar/apt kalıbı).
  auto metin = std::make_shared<std::string>(b.camSis.gkod());
  auto tamam = std::make_shared<std::atomic<bool>>(false);
  b.cekirdek.gorevler().kuyrukla(
      "G-Kod Yazılıyor",
      [metin, yol, tamam](GorevBaglam&) {
        std::ofstream f(yol, std::ios::binary);
        if (f) {
          f.write(metin->data(), (std::streamsize)metin->size());
          *tamam = (bool)f;
        }
      },
      [&b, tamam]() {
        b.bilgiVer(*tamam ? "G-Kod yazıldı (.nc)" : "G-Kod yazılamadı");
      });
}

}  // namespace fiko
