<?php

namespace App\Http\Controllers\Agency;

use App\Http\Controllers\Controller;
use App\Models\Customer\Customer;
use App\Models\Agency\AgencySalesTarget;
use App\Models\Product\Product;
use App\Models\Product\ProductType;
use App\Models\Sale\Sale;
use App\Models\Sale\SaleActivity;
use App\Models\Sale\SaleInvoice;
use App\Models\Sale\SaleItem;
use App\Models\User;
use App\Services\Operation\DepartureInventoryService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class SalesController extends Controller
{
    private function agencyId(): int
    {
        return Auth::user()->currentAgencyId();
    }

    /**
     * Ayarlardaki kurlara göre para birimi → TL çarpanı haritası (TRY = 1).
     * Pasif bırakılmış kurlar da dahil edilir; eski satışlar o birimde olabilir.
     */
    private function currencyRates(): array
    {
        $agency = Auth::user()->agency;
        $agency->getActiveCurrencies(); // hiç kur tanımı yoksa varsayılanları oluşturur

        $rates = [];
        foreach ($agency->currencies()->get() as $c) {
            $rate = (float) $c->exchange_rate;
            $rates[strtoupper((string) $c->code)] = $rate > 0 ? $rate : 1.0;
        }
        $rates['TRY'] = 1.0;

        return $rates;
    }

    /**
     * Sorgudaki tutar kolonunu para birimine göre gruplayıp ayar kurlarıyla
     * TL karşılığına çevirerek toplar (74 USD → 74 × kur TL).
     */
    private function sumInTry($query, string $column, array $rates): float
    {
        $total = 0.0;
        $rows = (clone $query)->reorder()
            ->select('currency', DB::raw("SUM({$column}) as t"))
            ->groupBy('currency')
            ->get();
        foreach ($rows as $row) {
            $code = strtoupper((string) ($row->currency ?: 'TRY'));
            $total += (float) $row->t * ($rates[$code] ?? 1.0);
        }

        return $total;
    }

    // ─── CARİ / BORÇLANDIRMA ──────────────────────────────────────────────────

    private function credit(): \App\Services\Customer\CustomerCreditService
    {
        return app(\App\Services\Customer\CustomerCreditService::class);
    }

    /**
     * Satışın müşteriye yüklediği açık borç — TL karşılığı.
     *
     * Yalnızca borç doğuran durumlarda (bkz. CustomerCreditService::DEBT_STATUSES)
     * sayılır: potansiyel/teklif aşamasındaki bir kayıt henüz cari borç değildir.
     */
    private function saleDebtInTry(Sale $sale): float
    {
        if (!in_array($sale->status, \App\Services\Customer\CustomerCreditService::DEBT_STATUSES, true)) {
            return 0.0;
        }

        return round($this->credit()->convert(
            max(0.0, (float) $sale->remaining_amount),
            $sale->currency,
            'TRY',
            $sale->agency_id
        ), 2);
    }

    /**
     * Ödeme alınmadan borçlandırma kuralı — engellenmesi gerekiyorsa mesaj döner.
     *
     * Kural, borcun ARTTIĞI anda işler: başlık değiştirmek ya da tahsilat girip
     * borcu azaltmak limiti aşan bir satışta da serbesttir. Aksi halde limiti
     * dolmuş müşterinin kartında hiçbir düzenleme kaydedilemezdi.
     *
     * @param float $previousDebtTry Değişiklikten ÖNCEKİ borç (TL) — yeni kayıtta 0.
     */
    private function creditBlockMessage(Sale $sale, float $previousDebtTry = 0.0): ?string
    {
        $debtTry = $this->saleDebtInTry($sale);
        if ($debtTry <= 0.0 || $debtTry <= $previousDebtTry) {
            return null;
        }

        $customer = $sale->customer_id
            ? Customer::byAgency($sale->agency_id)->find($sale->customer_id)
            : null;
        if (!$customer) {
            return null;
        }

        $check = $this->credit()->evaluate(
            $customer,
            max(0.0, (float) $sale->remaining_amount),
            $sale->currency,
            $sale->id
        );

        return $check['ok'] ? null : $check['message'];
    }

    private function inventory(): DepartureInventoryService
    {
        return app(DepartureInventoryService::class);
    }

    private function operations(): \App\Services\Operation\OperationService
    {
        return app(\App\Services\Operation\OperationService::class);
    }

    private function privateDepartures(): \App\Services\Operation\PrivateDepartureService
    {
        return app(\App\Services\Operation\PrivateDepartureService::class);
    }

    /**
     * Özel kalkış, satışın mevcut katalog bağını EZMEMELİ: yalnızca satış
     * kalkışsızsa ya da zaten kendi özel kalkışına bağlıysa bağlanır.
     */
    private function saleCanBindPrivate(Sale $sale, $departure): bool
    {
        if (!$sale->tour_date_id) {
            return true;
        }

        return $sale->tour_date_type === get_class($departure)
            && (int) $sale->tour_date_id === (int) $departure->id;
    }

    /** İstemciden gelen tarih değerini normalize eder (boş/geçersiz → null). */
    private function cleanDate($value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }
        try {
            return \Carbon\Carbon::parse($value)->toDateString();
        } catch (\Throwable $e) {
            return null;
        }
    }

    /** İstemciden gelen saat değerini "HH:MM" olarak normalize eder. */
    private function cleanTime($value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }
        if (!preg_match('/^(\d{1,2}):(\d{2})/', (string) $value, $m)) {
            return null;
        }
        $h = (int) $m[1];
        $i = (int) $m[2];
        if ($h > 23 || $i > 59) {
            return null;
        }

        return sprintf('%02d:%02d', $h, $i);
    }

    /**
     * Bir satışa bağlı kalkışın kapasitesini yeniden sayar ve operasyon
     * durumunu (otomatik kesin kalkış vb.) değerlendirir.
     */
    private function refreshDeparture(Sale $sale): void
    {
        $this->inventory()->recountForSale($sale);
        $this->operations()->evaluateForSale($sale);
    }

    /**
     * Satışı seçilen tur tarihine (kalkışa) bağlar ve etkilenen tarih(ler)in
     * kapasitesini yeniden sayar. reservable_type (productable) üzerinden doğru
     * tarih modeli sınıfı belirlenir.
     */
    private function syncDeparture(Sale $sale, $tourDateId): void
    {
        $service = $this->inventory();

        $oldType = $sale->getOriginal('tour_date_type');
        $oldId = $sale->getOriginal('tour_date_id');

        $dateClass = $service->dateClassForProductable($sale->reservable_type);
        $tourDateId = $tourDateId ? (int) $tourDateId : null;

        if ($tourDateId && $dateClass) {
            $sale->tour_date_type = $dateClass;
            $sale->tour_date_id = $tourDateId;
        } else {
            $sale->tour_date_type = null;
            $sale->tour_date_id = null;
        }
        $sale->save();

        // Yeni bağlı kalkışı say + operasyon durumunu değerlendir
        $service->recountForSale($sale);
        $this->operations()->evaluateForSale($sale);

        // Bağ değiştiyse eski kalkışı da güncelle
        if ($oldType && $oldId && ($oldType !== $sale->tour_date_type || (int) $oldId !== (int) $sale->tour_date_id)) {
            if ($oldDate = $service->resolve($oldType, (int) $oldId)) {
                $service->recount($oldDate);
                $this->operations()->evaluate($oldDate);
            }
        }
    }

    // ─── INDEX ────────────────────────────────────────────────────────────────

    public function index(Request $request)
    {
        $agencyId = $this->agencyId();
        $view = $request->get('view', 'dashboard');

        $query = Sale::byAgency($agencyId)
            ->with(['customer', 'productType', 'creator', 'assignedAgent', 'reservable', 'tourDate'])
            ->orderBy('created_at', 'desc');

        // Filtreler
        if ($request->filled('status')) {
            $query->byStatus($request->status);
        }
        if ($request->filled('product_type_id')) {
            $query->byProductType($request->product_type_id);
        }
        if ($request->filled('product_id')) {
            $query->where('product_id', $request->product_id);
        }
        // Diğer Satışlar: ürüne bağlı olmayan (elle başlık/fiyat girilmiş ya da hiç ürün eklenmemiş) satışlar
        if ($request->boolean('other')) {
            $query->whereNull('product_id')->whereNull('reservable_id');
        }
        if ($request->filled('search')) {
            $s = $request->search;
            $query->where(function ($q) use ($s) {
                $q->where('sale_code', 'like', "%$s%")
                    ->orWhereHas('customer', fn($cq) => $cq->where('first_name', 'like', "%$s%")
                        ->orWhere('last_name', 'like', "%$s%")
                        ->orWhere('emails', 'like', "%$s%"));
            });
        }
        if ($request->filled('date_from')) {
            $query->where('start_date', '>=', $request->date_from);
        }
        if ($request->filled('date_to')) {
            $query->where('start_date', '<=', $request->date_to);
        }

        // Görünüm Moduna Göre Veri Hazırlama
        $sales = null;
        $kanbanGroups = [];

        if ($view === 'list') {
            // Default: devam eden satışlar (status filtresi ve "diğer" filtresi yoksa)
            if (!$request->filled('status') && !$request->boolean('other')) {
                $query->whereIn('status', ['potential', 'offer_sent', 'revised']);
            }
            $allowedSorts = ['start_date', 'total_amount', 'status', 'payment_status', 'sale_code', 'created_at'];
        $sortBy = in_array($request->get('sort'), $allowedSorts) ? $request->get('sort') : 'created_at';
        $sortDir = $request->get('dir') === 'asc' ? 'asc' : 'desc';
        $query->reorder($sortBy, $sortDir);

        $perPage = (int) $request->input('per_page', 20);
        $perPage = in_array($perPage, [20, 50, 100, 200], true) ? $perPage : 20;
        $sales = $query->paginate($perPage)->withQueryString();
        } elseif ($view === 'kanban') {
            // Kanban için aktif ve tamamlanan satışları alıyoruz (İptal ve İade hariç)
            $kanbanData = $query->whereNotIn('status', ['cancelled', 'refunded'])
                ->get();

            $kanbanGroups = $kanbanData->groupBy('status');
        }

        // İstatistikler (Filtrelere göre dinamikleşsin istiyorsak aşağıdaki gibi baz query üzerinden gidebiliriz)
        // Ancak genellikle "Genel Durum" istendiği için tüm ajans verisi kalsın, biz sadece baz istatistiği filtrelenmiş yapalım
        $baseStatsQuery = Sale::byAgency($agencyId);
        if ($request->filled('product_type_id'))
            $baseStatsQuery->byProductType($request->product_type_id);
        if ($request->filled('product_id'))
            $baseStatsQuery->where('product_id', $request->product_id);
        if ($request->boolean('other'))
            $baseStatsQuery->whereNull('product_id')->whereNull('reservable_id');
        if ($request->filled('date_from'))
            $baseStatsQuery->where('start_date', '>=', $request->date_from);
        if ($request->filled('date_to'))
            $baseStatsQuery->where('start_date', '<=', $request->date_to);

        // Tutar toplamları ayarlardaki kurlarla TL'ye çevrilir (74 USD ≠ 74 TL)
        $rates = $this->currencyRates();

        $stats = [
            'total' => $baseStatsQuery->count(),
            'potential_val' => $this->sumInTry((clone $baseStatsQuery)->whereIn('status', ['potential', 'offer_sent', 'revised', 'pending']), 'total_amount', $rates),
            'won_val' => $this->sumInTry((clone $baseStatsQuery)->whereIn('status', ['confirmed', 'in_progress', 'completed']), 'total_amount', $rates),
            'paid_val' => $this->sumInTry(clone $baseStatsQuery, 'paid_amount', $rates),
            'unpaid_val' => $this->sumInTry((clone $baseStatsQuery)->whereNotIn('status', ['cancelled', 'refunded']), 'remaining_amount', $rates),
            'monthly_count' => (clone $baseStatsQuery)->whereMonth('created_at', now()->month)->count(),
            'pending_count' => (clone $baseStatsQuery)->whereIn('status', ['potential', 'offer_sent', 'revised', 'pending'])->count(),
        ];

        $productTypes = $this->saleProductTypes();

        // ─── SATIŞ HEDEFLERİ & FİLTRELEME ──────────────────────────────────────
        $targetPeriod = $request->get('period', 'year');
        $currentYear = $request->get('year', now()->year);
        $currentMonth = $request->get('month', now()->month);

        $targetsQuery = \App\Models\Agency\AgencySalesTarget::where('agency_id', $agencyId)
            ->where('target_year', $currentYear)
            ->where('target_period', $targetPeriod);
        
        if ($targetPeriod === 'month') {
            $targetsQuery->where('target_month', $currentMonth);
        }
        
        $targets = $targetsQuery->get();

        // Varsayılan hedef tipi: revenue (ciro)
        $targetType = $request->get('target_type', 'revenue');
        
        $achievedQuery = Sale::byAgency($agencyId)
            ->whereIn('status', ['confirmed', 'in_progress', 'completed'])
            ->whereYear('created_at', $currentYear);

        if ($targetPeriod === 'month') {
            $achievedQuery->whereMonth('created_at', $currentMonth);
        }

        if ($targetType === 'revenue') {
            // Para birimi bazında grupla, ayar kurlarıyla TL'ye çevirip tur tipine topla
            $achievedData = [];
            $achievedRows = $achievedQuery->select('product_type_id', 'currency', DB::raw('SUM(total_amount) as total'))
                ->groupBy('product_type_id', 'currency')
                ->get();
            foreach ($achievedRows as $row) {
                $code = strtoupper((string) ($row->currency ?: 'TRY'));
                $achievedData[$row->product_type_id ?? 0] = ($achievedData[$row->product_type_id ?? 0] ?? 0.0)
                    + (float) $row->total * ($rates[$code] ?? 1.0);
            }
        } else {
            $achievedData = $achievedQuery->select('product_type_id', DB::raw('COUNT(*) as count'))
                ->groupBy('product_type_id')
                ->pluck('count', 'product_type_id');
        }

        $salesTargets = [];
        foreach ($productTypes as $pt) {
            $target = $targets->where('product_type_id', $pt->id)->where('target_type', $targetType)->first();
            $targetValue = $target ? (float)$target->target_value : 0;
            $achievedValue = isset($achievedData[$pt->id]) ? (float)$achievedData[$pt->id] : 0;
            
            $salesTargets[$pt->id] = [
                'name' => $pt->name,
                'target' => $targetValue,
                'achieved' => $achievedValue,
                'percentage' => $targetValue > 0 ? min(100, round(($achievedValue / $targetValue) * 100, 1)) : 0,
                'color' => $pt->color ?? '#2563eb'
            ];
        }

        // Genel Hedef Hesabı
        $totalTarget = collect($salesTargets)->sum('target');
        $totalAchieved = collect($salesTargets)->sum('achieved');
        $totalPercentage = $totalTarget > 0 ? min(100, round(($totalAchieved / $totalTarget) * 100, 1)) : 0;

        // İlerleme Çizgisi Hesabı (Seçilen döneme göre)
        if ($targetPeriod === 'month') {
            $yearProgress = round((now()->day / now()->daysInMonth) * 100, 1);
        } else {
            $yearProgress = round((now()->dayOfYear() / 365) * 100, 1);
        }

        $saleProducts = $this->saleProductOptions($agencyId);

        return view('agency.sales.index', compact(
            'sales',
            'kanbanGroups',
            'stats',
            'productTypes',
            'saleProducts',
            'view',
            'salesTargets',
            'totalTarget',
            'totalAchieved',
            'totalPercentage',
            'yearProgress',
            'currentYear',
            'currentMonth',
            'targetType',
            'targetPeriod'
        ));
    }

    /**
     * Satış kartındaki ürün kataloğunun kaynağı.
     *
     * `bookable()` ile filtrelenmez — pasif/taslak ürünler de döner. Sebebi:
     * "ürünü ekledim ama satışta göremiyorum" durumunda ürün sessizce
     * kayboluyor ve kullanıcı nedenini göremiyordu. Artık satılabilirlik
     * bilgisi listeyle taşınır; katalog bu kayıtları ayrı bir
     * "satışa kapalı" grubunda, sebebiyle birlikte gösterir.
     *
     * Ekstra hizmetler hariç tutulur: onların kendi sekmesi var
     * (bkz. extraServiceCatalog), iki yerde birden çıkmasınlar.
     */
    private function catalogProducts(int $agencyId)
    {
        $extraTypeId = \App\Models\Product\ExtraService::typeId();

        return Product::byAgency($agencyId)
            ->when($extraTypeId, fn ($q) => $q->where('product_type_id', '!=', $extraTypeId))
            ->with('productType')
            ->orderByDesc('is_active')
            ->orderByDesc('is_bookable')
            ->orderBy('name')
            ->get(['id', 'name', 'base_price', 'currency', 'product_type_id', 'productable_type', 'productable_id', 'is_active', 'is_bookable']);
    }

    /**
     * Satış modülünün her yerinde kullanılan ürün tipi listesi:
     * liste filtreleri, satış hedefleri ve yeni satış tipi seçicisi.
     *
     * `saleSubject()` ile ekstra hizmetler dışarıda kalır — onlar satışın
     * konusu değil, satışa eklenen bir kalemdir (katalogdaki kendi sekmesinden
     * seçilir, bkz. extraServiceCatalog).
     */
    private function saleProductTypes()
    {
        return ProductType::active()->saleSubject()->orderBy('sort_order')->get();
    }

    /**
     * "Kataloğa Ekle" akışında seçilebilecek ürün tipleri.
     *
     * Yalnızca acentenin yetkili (agency_product_types.is_active) olduğu tipler
     * döner — aksi halde acentede kapalı olan transfer/vize/konaklama gibi
     * tipler de listeleniyordu. Acentede hiç tanım yoksa liste boş kalmasın
     * diye sistemde aktif tiplere düşülür.
     *
     * Ekstra hizmetler burada da çıkmaz: bu akış satış kalemini `service_products`
     * olarak kataloğa alır, ekstra hizmetlerin kendi tablosu ve ekranı vardır.
     */
    private function agencyServiceTypes()
    {
        $agency = Auth::user()->agency;

        $types = $agency
            ? $agency->productTypes()
                ->where('agency_product_types.is_active', true)
                ->where('product_types.is_active', true)
                ->saleSubject()
                ->orderBy('product_types.sort_order')
                ->get()
            : collect();

        return $types->isNotEmpty() ? $types : $this->saleProductTypes();
    }

    /** Satışlarda kullanılmış ürünlerin filtre seçenekleri: [id => ad] */
    private function saleProductOptions(int $agencyId): array
    {
        $ids = Sale::byAgency($agencyId)->whereNotNull('product_id')->distinct()->pluck('product_id');

        return Product::whereIn('id', $ids)->orderBy('name')->pluck('name', 'id')->all();
    }

    /** Liste sayfalarının ortak ürün filtreleri (ürün tipi + ürün). */
    private function applyProductFilters($query, Request $request): void
    {
        if ($request->filled('product_type_id')) {
            $query->where('product_type_id', $request->product_type_id);
        }
        if ($request->filled('product_id')) {
            $query->where('product_id', $request->product_id);
        }
    }

    // ─── LEADS (Müşteri Adayları) ─────────────────────────────────────────────

    public function leads(Request $request)
    {
        $agencyId = $this->agencyId();

        $query = Sale::byAgency($agencyId)
            ->with(['customer', 'productType', 'creator', 'assignedAgent', 'reservable', 'tourDate'])
            ->whereIn('status', ['potential', 'offer_sent', 'revised'])
            ->orderBy('created_at', 'desc');

        if ($request->filled('search')) {
            $s = $request->search;
            $query->where(function ($q) use ($s) {
                $q->where('sale_code', 'like', "%$s%")
                    ->orWhereHas('customer', fn($cq) => $cq->where('first_name', 'like', "%$s%")
                        ->orWhere('last_name', 'like', "%$s%"));
            });
        }
        if ($request->filled('status')) {
            $query->where('status', $request->status);
        }
        $this->applyProductFilters($query, $request);
        if ($request->filled('date_from')) {
            $query->where('start_date', '>=', $request->date_from);
        }
        if ($request->filled('date_to')) {
            $query->where('start_date', '<=', $request->date_to);
        }

        $allowedSorts = ['start_date', 'total_amount', 'status', 'payment_status', 'sale_code', 'created_at'];
        $sortBy = in_array($request->get('sort'), $allowedSorts) ? $request->get('sort') : 'created_at';
        $sortDir = $request->get('dir') === 'asc' ? 'asc' : 'desc';
        $query->reorder($sortBy, $sortDir);

        $sales = $query->paginate(20)->withQueryString();
        $productTypes = $this->saleProductTypes();

        $saleProducts = $this->saleProductOptions($agencyId);

        return view('agency.sales.leads', compact('sales', 'productTypes', 'saleProducts'));
    }

    // ─── DEALS (Anlaşmalar) ───────────────────────────────────────────────────

    public function deals(Request $request)
    {
        $agencyId = $this->agencyId();

        $query = Sale::byAgency($agencyId)
            ->with(['customer', 'productType', 'creator', 'assignedAgent', 'reservable', 'tourDate'])
            ->whereIn('status', ['confirmed', 'in_progress', 'completed'])
            ->orderBy('created_at', 'desc');

        if ($request->filled('search')) {
            $s = $request->search;
            $query->where(function ($q) use ($s) {
                $q->where('sale_code', 'like', "%$s%")
                    ->orWhereHas('customer', fn($cq) => $cq->where('first_name', 'like', "%$s%")
                        ->orWhere('last_name', 'like', "%$s%"));
            });
        }
        if ($request->filled('status')) {
            $query->where('status', $request->status);
        }
        $this->applyProductFilters($query, $request);
        if ($request->filled('date_from')) {
            $query->where('start_date', '>=', $request->date_from);
        }
        if ($request->filled('date_to')) {
            $query->where('start_date', '<=', $request->date_to);
        }

        $allowedSorts = ['start_date', 'total_amount', 'status', 'payment_status', 'sale_code', 'created_at'];
        $sortBy = in_array($request->get('sort'), $allowedSorts) ? $request->get('sort') : 'created_at';
        $sortDir = $request->get('dir') === 'asc' ? 'asc' : 'desc';
        $query->reorder($sortBy, $sortDir);

        $sales = $query->paginate(20)->withQueryString();
        $productTypes = $this->saleProductTypes();

        $saleProducts = $this->saleProductOptions($agencyId);

        return view('agency.sales.deals', compact('sales', 'productTypes', 'saleProducts'));
    }

    // ─── CANCELLATIONS (İptal ve İadeler) ─────────────────────────────────────

    public function cancellations(Request $request)
    {
        $agencyId = $this->agencyId();

        $query = Sale::byAgency($agencyId)
            ->with(['customer', 'productType', 'creator', 'assignedAgent', 'reservable', 'tourDate'])
            ->whereIn('status', ['cancelled', 'refunded'])
            ->orderBy('created_at', 'desc');

        if ($request->filled('search')) {
            $s = $request->search;
            $query->where(function ($q) use ($s) {
                $q->where('sale_code', 'like', "%$s%")
                    ->orWhereHas('customer', fn($cq) => $cq->where('first_name', 'like', "%$s%")
                        ->orWhere('last_name', 'like', "%$s%"));
            });
        }
        if ($request->filled('status')) {
            $query->where('status', $request->status);
        }
        $this->applyProductFilters($query, $request);
        if ($request->filled('date_from')) {
            $query->where('start_date', '>=', $request->date_from);
        }
        if ($request->filled('date_to')) {
            $query->where('start_date', '<=', $request->date_to);
        }

        $allowedSorts = ['start_date', 'total_amount', 'status', 'payment_status', 'sale_code', 'created_at'];
        $sortBy = in_array($request->get('sort'), $allowedSorts) ? $request->get('sort') : 'created_at';
        $sortDir = $request->get('dir') === 'asc' ? 'asc' : 'desc';
        $query->reorder($sortBy, $sortDir);

        $sales = $query->paginate(20)->withQueryString();
        $productTypes = $this->saleProductTypes();

        $saleProducts = $this->saleProductOptions($agencyId);

        return view('agency.sales.cancellations', compact('sales', 'productTypes', 'saleProducts'));
    }

    // ─── CREATE / STORE ───────────────────────────────────────────────────────

    public function create(Request $request)
    {
        $agencyId = $this->agencyId();

        $productTypes = $this->saleProductTypes();

        $customers = Customer::byAgency($agencyId)
            ->where('status', '!=', 'blocked')
            ->orderBy('first_name')
            ->get(['id', 'first_name', 'last_name', 'emails', 'phone']);

        $agents = User::byAgency($agencyId)
            ->where('is_active', true)
            ->orderBy('first_name')
            ->get(['id', 'first_name', 'last_name']);

        $products = collect();
        $selectedProductType = null;
        $extraFields = [];

        if ($request->filled('product_type_id')) {
            $selectedProductType = ProductType::find($request->product_type_id);
            if ($selectedProductType) {
                $products = Product::byAgency($agencyId)
                    ->byProductType($selectedProductType->id)
                    ->bookable()
                    ->orderBy('name')
                    ->get(['id', 'name', 'base_price', 'currency', 'slug', 'productable_type', 'productable_id']);

                $extraFields = Sale::extraFieldsFor($selectedProductType->code);
            }
        }

        $preselectedCustomer = null;
        if ($request->filled('customer_id')) {
            $preselectedCustomer = Customer::byAgency($agencyId)->find($request->customer_id);

            // Eğer müşteri listede yoksa (örneğin engellenmişse veya filtreye takılmışsa) listeye ekliyoruz
            if ($preselectedCustomer && !$customers->contains('id', $preselectedCustomer->id)) {
                $customers->push($preselectedCustomer);
            }
        }

        return view('agency.sales.create', compact(
            'productTypes',
            'customers',
            'agents',
            'products',
            'selectedProductType',
            'extraFields',
            'preselectedCustomer'
        ));
    }

    public function quickCreate()
    {
        $agencyId = $this->agencyId();

        $productTypes = $this->saleProductTypes();

        $customers = Customer::byAgency($agencyId)
            ->where('status', '!=', 'blocked')
            ->orderBy('first_name')
            ->get(['id', 'first_name', 'last_name', 'emails', 'phone']);

        $agents = User::byAgency($agencyId)
            ->where('is_active', true)
            ->orderBy('first_name')
            ->get(['id', 'first_name', 'last_name']);

        $products = $this->catalogProducts($agencyId);

        $activeCurrencies = auth()->user()->agency->getActiveCurrencies();
        $taxRates = auth()->user()->agency->getActiveTaxRates(\App\Models\Agency\TaxType::CODE_KDV);
        // "Kataloğa ekle" akışı: yalnızca acentenin yetkili olduğu ürün tipleri.
        $serviceTypes = $this->agencyServiceTypes();
        $serviceFieldSchemas = \App\Models\Product\ServiceProduct::allFieldSchemas($serviceTypes);

        // Tedarikçi hizmet kataloğu (otel odası, transfer, menü, vize...) — satılabilir fiyat kalemleri.
        $supplierCatalog = $this->supplierCatalog($agencyId);

        // Acentenin kendi tanımladığı ekstra hizmetler (VQ alan vergisi, vize harcı…)
        $extraServices = $this->extraServiceCatalog($agencyId);

        return view('agency.sales.quick-create', compact('productTypes', 'customers', 'agents', 'products', 'activeCurrencies', 'taxRates', 'supplierCatalog', 'extraServices', 'serviceTypes', 'serviceFieldSchemas'));
    }

    /**
     * Satış kataloğunun "Ekstra Hizmetler" sekmesi.
     *
     * Ekstra hizmetler tur/ürün tiplerinden bağımsızdır; kategorisi olmayanlar
     * "Kategorisiz" başlığında toplanır (blade tarafında gruplanır).
     */
    private function extraServiceCatalog(int $agencyId)
    {
        return \App\Models\Product\ExtraService::byAgency($agencyId)
            ->active()
            ->with('category:id,name,color_code')
            ->orderBy('sort_order')
            ->orderBy('name')
            ->get();
    }

    /**
     * Satış kataloğu için acentenin aktif tedarikçi fiyat kalemlerini normalize eder.
     * Otel odası / transfer / menü vb. hepsi supplier_price_items'ta; tip farkı metadata'da.
     */
    private function supplierCatalog(int $agencyId)
    {
        $typeLabels = \App\Models\Supplier\Supplier::supplierTypes();
        $typeIcons = [
            'hotel' => 'fa-hotel',
            'transportation' => 'fa-bus',
            'restaurant' => 'fa-utensils',
            'insurance' => 'fa-shield-heart',
            'visa' => 'fa-passport',
            'airline' => 'fa-plane',
        ];
        $roomTypes = ['single' => 'Single', 'double' => 'Double', 'triple' => 'Triple', 'child_0_6' => 'Çocuk 0-6', 'child_0_12' => 'Çocuk 0-12'];

        $items = \App\Models\Supplier\SupplierPriceItem::whereHas('priceList', function ($q) use ($agencyId) {
                $q->where('agency_id', $agencyId)->where('is_active', true);
            })
            ->with('priceList.supplier:id,type,company_name,first_name,last_name,supplier_type')
            ->orderByDesc('id')
            ->get();

        return $items->map(function ($it) use ($typeLabels, $typeIcons, $roomTypes) {
            $meta = is_array($it->metadata) ? $it->metadata : [];
            $sup = $it->priceList?->supplier;
            $type = $sup?->supplier_type ?? 'other';

            // Tipe göre okunur alt başlık (metadata'dan)
            $subtitle = match ($type) {
                'hotel' => trim(implode(' · ', array_filter([
                    $roomTypes[$meta['room_type'] ?? ''] ?? ($meta['room_type'] ?? null),
                    $meta['board_type'] ?? null,
                    isset($meta['market']) ? ucfirst($meta['market']) : null,
                ]))),
                'transportation' => trim(($meta['from_location'] ?? '') . (!empty($meta['to_location']) ? ' → ' . $meta['to_location'] : '') . (!empty($meta['vehicle_type']) ? ' · ' . $meta['vehicle_type'] : '')),
                'restaurant' => $meta['menu_name'] ?? '',
                'insurance' => $meta['policy_name'] ?? '',
                'visa' => trim(($meta['visa_type'] ?? '') . (!empty($meta['country']) ? ' · ' . strtoupper($meta['country']) : '')),
                'airline' => trim(($meta['route'] ?? '') . (!empty($meta['flight_class']) ? ' · ' . $meta['flight_class'] : '')),
                default => '',
            };

            return [
                'id' => $it->id,
                'title' => $it->title,
                'subtitle' => $subtitle,
                'sale_price' => (float) $it->sale_price,
                'net_price' => (float) $it->net_price,
                'currency' => $it->currency ?: 'TRY',
                'tax_rate' => (float) $it->tax_rate,
                'supplier_id' => $sup?->id, // tedarikçi kartına çapraz link için
                'supplier_name' => $sup?->full_name ?: '—',
                'type' => $type,
                'type_label' => $typeLabels[$type] ?? ucfirst($type),
                'type_icon' => $typeIcons[$type] ?? 'fa-cube',
            ];
        })->values();
    }

    /**
     * "Yeni Satış"a basınca anında potansiyel (müşterisiz) satış oluşturur → quick-view URL döner.
     * Ürün modülündeki taslak deseninin (openDraftQuickView) satış muadili.
     */
    public function quickDraft(Request $request)
    {
        $agencyId = $this->agencyId();
        try {
            $sale = new Sale();
            $sale->agency_id = $agencyId;
            $sale->status = 'potential';
            $sale->payment_status = 'unpaid';
            $sale->start_date = today();
            $sale->adult_count = 1;
            $sale->child_count = 0;
            $sale->infant_count = 0;
            $sale->currency = 'TRY';
            $sale->assigned_to = Auth::id();
            $sale->source = 'manual';

            // Seçilen ürün tipini (satış kanalı modalı / ürün tipi ekranı) taslağa ön-tanımla.
            // Yalnızca acentenin yetkili olduğu aktif tipler kabul edilir.
            $ptId = (int) $request->input('product_type_id');
            $agencyModel = Auth::user()->agency;
            if ($ptId && $agencyModel && $agencyModel->productTypes()
                    ->where('agency_product_types.is_active', true)
                    ->where('product_types.is_active', true)
                    ->where('product_types.id', $ptId)
                    ->exists()) {
                $sale->product_type_id = $ptId;
            }

            // Müşteri kartından açıldıysa müşteriyi ön-tanımla
            $custId = (int) $request->input('customer_id');
            if ($custId && \App\Models\Customer\Customer::where('id', $custId)->where('agency_id', $agencyId)->exists()) {
                $sale->customer_id = $custId;
            }

            $sale->save(); // sale_code boot() ile üretilir

            return response()->json([
                'success' => true,
                'sale_id' => $sale->id,
                'quick_view_url' => route('agency.sales.quick-view', $sale),
            ]);
        } catch (\Throwable $e) {
            Log::error('Sale quick-draft error', ['message' => $e->getMessage(), 'agency' => $agencyId]);
            return response()->json(['success' => false, 'message' => 'Taslak oluşturulamadı: ' . $e->getMessage()], 500);
        }
    }

    /**
     * Quick-view alanları değiştikçe debounce'lu tam-durum otomatik kaydı (submit yerine).
     */
    public function quickUpdate(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);

        // Çoklu müşteri kuralı istemciden bağımsız olarak burada da uygulanır:
        // ya TEK şirket ya da bir/birden fazla kişi — karışık olamaz.
        if ($request->has('customer_ids')) {
            if ($msg = $this->saleCustomerRuleError((array) $request->input('customer_ids', []))) {
                return response()->json(['success' => false, 'message' => $msg], 422);
            }
        }

        // Cari kuralı borcun ARTTIĞI anda işler; bunun için değişiklikten önceki
        // borç ölçülür (otomatik kayıt her tuş vuruşunda çalıştığı için limiti
        // aşmayan düzenlemeler engellenmemeli).
        $previousDebtTry = $this->saleDebtInTry($sale);

        try {
            DB::beginTransaction();

            $fields = ['customer_id', 'title', 'start_date', 'end_date', 'product_type_id', 'assigned_to',
                'adult_count', 'child_count', 'infant_count', 'total_amount', 'paid_amount',
                'currency', 'payment_status', 'status', 'pickup_location', 'flight_number',
                'customer_note', 'internal_note', 'discount_rate', 'discount_amount'];
            foreach ($fields as $f) {
                if (!$request->has($f)) continue;
                $v = $request->input($f);
                if (in_array($f, ['customer_id', 'product_type_id', 'assigned_to', 'end_date'], true) && $v === '') $v = null;
                if (in_array($f, ['discount_rate', 'discount_amount'], true)) $v = ($v === '' || $v === null) ? 0 : (float) $v;
                $sale->{$f} = $v;
            }


            // Ürün → reservable bağı.
            // "0" = bilinçli kaldırma; boş string = DOKUNMA. Eskiden boş değer de
            // temizliyordu ve açılışta bağ alanı doldurulmayan kartların otomatik
            // kaydı satışın ürün/kalkış bağını sessizce siliyordu.
            if ($request->has('product_id')) {
                $pid = (string) $request->input('product_id', '');
                if ($pid === '0') {
                    $sale->product_id = null;
                    $sale->reservable_type = null;
                    $sale->reservable_id = null;
                } elseif ($pid !== '' && ($product = Product::find($pid)) && $product->productable_type) {
                    $sale->product_id = $product->id;
                    $sale->reservable_type = $product->productable_type;
                    $sale->reservable_id = $product->productable_id;
                }
            }

            // Çoklu müşteri: gönderildiyse birincil müşteri listenin ilkidir.
            // (Kural doğrulaması syncSaleCustomers içinde — hata mesajı kullanıcıya döner.)
            $customerIds = null;
            if ($request->has('customer_ids')) {
                $customerIds = $this->normalizeSaleCustomerIds((array) $request->input('customer_ids', []));
                $sale->customer_id = $customerIds[0] ?? null;
            }

            $sale->save();

            if ($customerIds !== null) {
                $this->syncSaleCustomers($sale, $customerIds);
            }

            // Odalama — satış kartındaki oda kurgusu sale_rooms'a yazılır.
            // Operasyondaki "Odalama" sekmesi AYNI kayıtları okur ve yolcuları
            // (sale_passengers.sale_room_id) bu odalara yerleştirir; bu yüzden
            // "hepsini sil + yeniden yaz" YAPILMAZ — kalemlerdeki gibi konum
            // eşlemeli senkron kullanılır ki oda id'leri (dolayısıyla yolcu
            // yerleşimi) otomatik kaydın her turunda kaybolmasın.
            if ($request->has('rooms')) {
                $this->syncSaleRooms($sale, (array) $request->input('rooms', []));
            }

            // Fiyatın çekildiği pazaryeri: satış yeniden hesaplandığında aynı
            // fiyatın gelmesi için satışta saklanır. Yeni kolon açılmadı —
            // sales.extra_data JSON'una "pricing" anahtarı altında yazılır.
            if ($request->has('pricing_marketplace_id')) {
                $mid = (int) $request->input('pricing_marketplace_id');
                $extra = (array) ($sale->extra_data ?? []);
                $pricing = (array) ($extra['pricing'] ?? []);
                if ($mid > 0) {
                    $pricing['marketplace_id'] = $mid;
                    $pricing['marketplace_name'] = (string) $request->input('pricing_marketplace_name', '') ?: null;
                } else {
                    unset($pricing['marketplace_id'], $pricing['marketplace_name']);
                }
                if ($pricing) {
                    $extra['pricing'] = $pricing;
                } else {
                    unset($extra['pricing']);
                }
                $sale->extra_data = $extra ?: null;
                $sale->save();
            }

            // Kalemler — konum eşlemeli senkronizasyon.
            // "Hepsini sil + yeniden yaz" yerine sırayla güncellenir: otomatik kayıt
            // her tuş vuruşunda çalıştığı için kalem id'leri sürekli değişmesin ve
            // metadata gibi istemcinin göndermediği kolonlar kaybolmasın.
            if ($request->has('items')) {
                $rows = [];
                foreach ((array) $request->input('items', []) as $it) {
                    if (empty($it['name'])) continue;
                    $taxRate = (float) ($it['tax_rate'] ?? 0);
                    $qty = (float) ($it['quantity'] ?? 1);
                    $price = (float) ($it['unit_price'] ?? 0);
                    $taxIncl = filter_var($it['tax_included'] ?? false, FILTER_VALIDATE_BOOLEAN);
                    $discType = in_array($it['discount_type'] ?? 'percent', ['percent', 'amount'], true) ? ($it['discount_type'] ?? 'percent') : 'percent';
                    $discValue = max(0, (float) ($it['discount_value'] ?? 0));
                    $break = SaleItem::breakdown($qty, $price, $taxRate, $taxIncl, $discType, $discValue);
                    $rows[] = [
                        'product_id' => !empty($it['product_id']) ? $it['product_id'] : null,
                        'itemable_type' => !empty($it['itemable_type']) ? $it['itemable_type'] : null,
                        'itemable_id' => !empty($it['itemable_id']) ? $it['itemable_id'] : null,
                        'name' => $it['name'],
                        'quantity' => $qty,
                        'unit_price' => $price,
                        'tax_rate' => $taxRate,
                        'tax_included' => $taxIncl,
                        'discount_type' => $discType,
                        'discount_value' => $discValue,
                        'discount_amount' => $break['discount'],
                        'tax_amount' => $break['tax'],
                        'total_price' => $break['total'],
                        'currency' => $it['currency'] ?? 'TRY',
                        // Kişiye özel satış: kaleme doğrudan tarih girilebilir
                        'start_date' => $this->cleanDate($it['start_date'] ?? null),
                        'end_date' => $this->cleanDate($it['end_date'] ?? null),
                        'start_time' => $this->cleanTime($it['start_time'] ?? null),
                        'end_time' => $this->cleanTime($it['end_time'] ?? null),
                    ];
                }

                $existing = $sale->items()->orderBy('id')->get();
                foreach ($rows as $i => $row) {
                    if (isset($existing[$i])) {
                        $existing[$i]->update($row);
                    } else {
                        SaleItem::create($row + ['sale_id' => $sale->id]);
                    }
                }
                if ($existing->count() > count($rows)) {
                    $removed = $existing->slice(count($rows));
                    // Önce bu kalemlerin ürettiği özel kalkışları temizle, sonra sil —
                    // aksi halde kalkış hiçbir kaleme bağlı olmadan ortada kalırdı.
                    $this->privateDepartures()->detachItems($removed);
                    SaleItem::whereIn('id', $removed->pluck('id'))->delete();
                }

                $sale->unsetRelation('items'); // calculateTotals() taze kalemleri okusun
                if ($sale->items()->exists()) {
                    $sale->calculateTotals(); // kalem varsa toplamı kalemlerden hesapla
                }
            }

            // Kalem tarihleri → kişiye özel kalkış (operasyon + acente takvimi).
            // Kataloğa kayıtlı bir kalkış seçilmişse o önceliklidir.
            $private = ['departure' => null, 'skipped' => 0];
            if ($request->has('items')) {
                $sale->unsetRelation('items');
                $private = $this->privateDepartures()->syncForSale($sale);
            }

            // Kalkış bağı: "0" = bilinçli kaldırma, boş = mevcut bağa dokunma.
            $tourDateId = (string) $request->input('tour_date_id', '');
            if ($tourDateId === '0') {
                if ($private['departure']) {
                    // Tarihli tur kalemi varken kalkış tamamen boş kalamaz
                    $this->privateDepartures()->bindSale($sale, $private['departure']);
                } else {
                    $this->syncDeparture($sale, null);
                }
            } elseif ($tourDateId !== '') {
                $this->syncDeparture($sale, $tourDateId);
            } elseif ($private['departure'] && $this->saleCanBindPrivate($sale, $private['departure'])) {
                $this->privateDepartures()->bindSale($sale, $private['departure']);
            }

            // ─── CARİ KONTROLÜ ────────────────────────────────────────────────
            // Toplam/ödenen/durum kesinleştikten sonra bakılır: bu kayıt müşteriyi
            // limitinin üstünde borçlandırıyorsa değişiklik yazılmaz.
            if ($msg = $this->creditBlockMessage($sale, $previousDebtTry)) {
                DB::rollBack();
                return response()->json([
                    'success' => false,
                    'message' => $msg,
                    'credit_blocked' => true,
                ], 422);
            }

            DB::commit();

            $departure = $private['departure'];

            return response()->json([
                'success' => true,
                'sale_id' => $sale->id,
                'sale_code' => $sale->sale_code,
                'total_amount' => (float) $sale->total_amount,
                'remaining_amount' => (float) $sale->remaining_amount,
                'items_count' => $sale->items()->count(),
                // Satış kartı "operasyona işlendi" rozetini bundan çizer
                'private_departure' => $departure ? [
                    'id' => $departure->id,
                    'url' => route('agency.operations.quick-view', [
                        $this->operations()->meta($departure)['key'],
                        $departure->id,
                    ]),
                ] : null,
                'private_skipped' => $private['skipped'],
            ]);
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Sale quick-update error', ['sale' => $sale->id, 'message' => $e->getMessage()]);

            // Eksik migration gibi şema hataları kullanıcıya "bir şey olmadı" gibi
            // görünüyordu; sebebi anlaşılır bir cümleyle döneriz.
            $message = $e->getMessage();
            if ($e instanceof \Illuminate\Database\QueryException) {
                $message = 'Veritabanı şeması güncel değil ya da kayıt reddedildi. '
                    . 'Sunucuda "php artisan migrate" çalıştırılmalı. (' . $e->getMessage() . ')';
            }

            return response()->json(['success' => false, 'message' => $message], 500);
        }
    }

    // ─── ODALAMA ─────────────────────────────────────────────────────────────

    /**
     * Satış kartından gelen oda kurgusunu `sale_rooms` ile eşitler.
     *
     * Konum eşlemeli: mevcut odalar sırayla güncellenir, fazlası silinir, eksiği
     * eklenir. Böylece operasyondaki yolcu yerleşimi (`sale_passengers.sale_room_id`)
     * otomatik kaydın her turunda kopmaz.
     *
     * Beklenen biçim (operasyondaki RoomingService ile birebir uyumlu):
     * `room_type, label, capacity, adult_count, child_count, infant_count,
     *  extra_bed, unit_price, notes, sort_order`
     */
    protected function syncSaleRooms(Sale $sale, array $rooms): void
    {
        $types = array_keys(\App\Models\Sale\SaleRoom::ROOM_TYPES);

        $wanted = [];
        foreach (array_values($rooms) as $i => $r) {
            $type = in_array($r['room_type'] ?? '', $types, true) ? $r['room_type'] : 'double';
            $capacity = (int) ($r['capacity'] ?? \App\Models\Sale\SaleRoom::defaultCapacityFor($type));
            $wanted[] = [
                'room_type'    => $type,
                'label'        => trim((string) ($r['label'] ?? '')) ?: ('Oda ' . ($i + 1)),
                'capacity'     => max(1, min(9, $capacity)),
                'adult_count'  => max(0, (int) ($r['adult_count'] ?? 0)),
                'child_count'  => max(0, (int) ($r['child_count'] ?? 0)),
                'infant_count' => max(0, (int) ($r['infant_count'] ?? 0)),
                'extra_bed'    => filter_var($r['extra_bed'] ?? false, FILTER_VALIDATE_BOOLEAN),
                'unit_price'   => isset($r['unit_price']) && $r['unit_price'] !== '' ? (float) $r['unit_price'] : null,
                'notes'        => trim((string) ($r['notes'] ?? '')) ?: null,
                'sort_order'   => $i,
            ];
        }

        $existing = \App\Models\Sale\SaleRoom::where('sale_id', $sale->id)
            ->orderBy('sort_order')->orderBy('id')->get();

        foreach ($wanted as $i => $row) {
            if (!isset($existing[$i])) {
                \App\Models\Sale\SaleRoom::create($row + ['sale_id' => $sale->id]);
                continue;
            }

            $room = $existing[$i];
            $oldLabel = (string) ($room->label ?: ('Oda ' . ($i + 1)));

            // Operasyonda elle verilmiş oda etiketi ("301" gibi) korunur: satış
            // kartı yalnızca varsayılan "Oda N" gönderiyorsa üzerine yazmayız.
            if (preg_match('/^Oda \d+$/u', $row['label']) && $room->label && $room->label !== $row['label']) {
                $row['label'] = $room->label;
            }

            $room->update($row);

            // Etiket gerçekten değiştiyse yolcuların serbest metin oda numarası da
            // tazelenir (manifesto ve sözleşme şablonları room_number'ı okuyor).
            if ((string) $row['label'] !== $oldLabel) {
                DB::table('sale_passengers')
                    ->where('sale_room_id', $room->id)
                    ->update(['room_number' => $row['label'], 'updated_at' => now()]);
            }
        }

        if ($existing->count() > count($wanted)) {
            // Fazla odalar silinir; yolcu bağı FK'da nullOnDelete olduğu için
            // yolcular kaybolmaz, yalnızca "odaya atanmamış" listesine döner.
            \App\Models\Sale\SaleRoom::whereIn('id', $existing->slice(count($wanted))->pluck('id'))->delete();
        }

        $sale->unsetRelation('rooms');
    }

    // ─── ÇOKLU MÜŞTERİ ───────────────────────────────────────────────────────

    /**
     * Gelen müşteri id listesini temizler: sayıya çevirir, boşları/tekrarları atar,
     * sırayı korur (ilk eleman birincil müşteridir).
     *
     * @return int[]
     */
    protected function normalizeSaleCustomerIds(array $ids): array
    {
        return collect($ids)
            ->map(fn ($id) => (int) $id)
            ->filter(fn ($id) => $id > 0)
            ->unique()
            ->values()
            ->all();
    }

    /**
     * Çoklu müşteri kuralı: bir satışta ya TEK şirket ya da bir/birden fazla kişi olur.
     * Kural ihlali varsa kullanıcıya gösterilecek Türkçe mesajı, yoksa null döner.
     */
    protected function saleCustomerRuleError(array $ids): ?string
    {
        $ids = $this->normalizeSaleCustomerIds($ids);
        if (count($ids) < 2) {
            return null; // 0 veya 1 müşteri her zaman geçerli
        }

        $types = Customer::where('agency_id', $this->agencyId())
            ->whereIn('id', $ids)
            ->pluck('customer_type', 'id');

        $corporate = $types->filter(fn ($t) => $t === 'corporate')->count();

        if ($corporate > 0) {
            return $corporate === count($types)
                ? 'Bir satışa yalnızca tek bir şirket müşteri eklenebilir. Birden fazla şirket seçilemez.'
                : 'Şirket müşteri ile kişi müşteriler aynı satışta birleştirilemez. Ya tek bir şirket ya da bir veya daha fazla kişi seçin.';
        }

        return null;
    }

    /**
     * Pivotu (sale_customers) verilen sıraya göre kurar; ilk müşteri birincildir.
     * Acente dışı müşteri id'leri sessizce elenir.
     */
    protected function syncSaleCustomers(Sale $sale, array $ids): void
    {
        $valid = Customer::where('agency_id', $this->agencyId())
            ->whereIn('id', $ids)
            ->pluck('id')
            ->all();

        // Gelen sıra korunur (birincil = ilk geçerli id)
        $ordered = array_values(array_filter($ids, fn ($id) => in_array($id, $valid, true)));

        $payload = [];
        foreach ($ordered as $i => $id) {
            $payload[$id] = ['is_primary' => $i === 0];
        }

        $sale->customers()->sync($payload);
    }

    public function store(Request $request)
    {
        $agencyId = $this->agencyId();

        $productType = null;
        $extraFields = [];

        if ($request->filled('product_type_id')) {
            $productType = ProductType::find($request->product_type_id);
            if ($productType) {
                $extraFields = Sale::extraFieldsFor($productType->code);
            }
        }

        $rules = [
            'customer_id' => 'required|exists:customers,id',
            'product_type_id' => 'nullable|exists:product_types,id',
            'product_id' => 'nullable|exists:products,id',
            'start_date' => 'required|date',
            'end_date' => 'nullable|date|after_or_equal:start_date',
            'pickup_time' => 'nullable|date',
            'adult_count' => 'required|integer|min:1',
            'child_count' => 'nullable|integer|min:0',
            'infant_count' => 'nullable|integer|min:0',
            'unit_price' => 'nullable|numeric|min:0',
            'child_price' => 'nullable|numeric|min:0',
            'infant_price' => 'nullable|numeric|min:0',
            'discount_rate' => 'nullable|numeric|min:0|max:100',
            'discount_amount' => 'nullable|numeric|min:0',
            'tax_amount' => 'nullable|numeric|min:0',
            'total_amount' => 'nullable|numeric|min:0',
            'currency' => 'nullable|string|max:3',
            'status' => 'required|in:potential,offer_sent,revised,pending,confirmed,in_progress,completed,cancelled,refunded',
            'payment_status' => 'required|in:unpaid,partial,paid,refunded',
            'paid_amount' => 'nullable|numeric|min:0',
            'pickup_location' => 'nullable|string|max:255',
            'dropoff_location' => 'nullable|string|max:255',
            'flight_number' => 'nullable|string|max:30',
            'source' => 'nullable|string|max:50',
            'assigned_to' => 'nullable|exists:users,id',
            'customer_note' => 'nullable|string',
            'internal_note' => 'nullable|string',
        ];

        foreach ($extraFields as $field) {
            $rules["extra_data.{$field['key']}"] = 'nullable|string|max:255';
        }

        try {
            $validated = $request->validate($rules);

            $customer = Customer::byAgency($agencyId)->findOrFail($validated['customer_id']);

            DB::beginTransaction();

            $sale = new Sale($validated);
            $sale->agency_id = $agencyId;
            $sale->remaining_amount = (string) max(0.0, (float) ($validated['total_amount'] ?? 0) - (float) ($validated['paid_amount'] ?? 0));

            if (!empty($validated['product_id'])) {
                $product = Product::find($validated['product_id']);
                if ($product?->productable_type && $product?->productable_id) {
                    $sale->reservable_type = $product->productable_type;
                    $sale->reservable_id = $product->productable_id;
                }
            }

            $sale->save();

            // ─── ÇOKLU MÜŞTERİ ───────────────────────────────────────────────
            // Birincil müşteri sales.customer_id'de kalır; pivot ek müşterileri taşır.
            $extraCustomers = $this->normalizeSaleCustomerIds((array) $request->input('customer_ids', []));
            $ids = array_values(array_unique(array_merge([(int) $sale->customer_id], $extraCustomers)));
            if ($msg = $this->saleCustomerRuleError($ids)) {
                DB::rollBack();
                return response()->json(['success' => false, 'message' => $msg], 422);
            }
            $this->syncSaleCustomers($sale, $ids);

            // ─── ÜRÜNLERİ KAYDET ─────────────────────────────────────────────
            if ($request->has('items') && is_array($request->items)) {
                foreach ($request->items as $itemData) {
                    if (empty($itemData['name'])) continue;
                    $taxRate = (float) ($itemData['tax_rate'] ?? 0);
                    $qty     = (float) ($itemData['quantity'] ?? 1);
                    $price   = (float) ($itemData['unit_price'] ?? 0);
                    $taxIncl = filter_var($itemData['tax_included'] ?? false, FILTER_VALIDATE_BOOLEAN);
                    $discType = in_array($itemData['discount_type'] ?? 'percent', ['percent', 'amount'], true) ? ($itemData['discount_type'] ?? 'percent') : 'percent';
                    $discValue = max(0, (float) ($itemData['discount_value'] ?? 0));
                    $break   = SaleItem::breakdown($qty, $price, $taxRate, $taxIncl, $discType, $discValue);
                    $taxAmt  = $break['tax'];
                    $total   = $break['total'];

                    // Tedarikçi hizmeti / tur kalemi için polimorfik itemable + metadata izlenebilirliği
                    $itemableType = !empty($itemData['itemable_type']) ? $itemData['itemable_type'] : null;
                    $itemableId   = !empty($itemData['itemable_id']) ? $itemData['itemable_id'] : null;
                    $metadata     = (isset($itemData['metadata']) && is_array($itemData['metadata'])) ? $itemData['metadata'] : null;

                    SaleItem::create([
                        'sale_id'       => $sale->id,
                        'product_id'    => !empty($itemData['product_id']) ? $itemData['product_id'] : null,
                        'itemable_type' => $itemableType,
                        'itemable_id'   => $itemableId,
                        'name'          => $itemData['name'],
                        'quantity'      => $qty,
                        'unit_price'    => $price,
                        'tax_rate'      => $taxRate,
                        'tax_included'  => $taxIncl,
                        'discount_type' => $discType,
                        'discount_value' => $discValue,
                        'discount_amount' => $break['discount'],
                        'tax_amount'    => $taxAmt,
                        'total_price'   => $total,
                        'currency'      => $itemData['currency'] ?? 'TRY',
                        'metadata'      => $metadata,
                        // Kişiye özel satış: kaleme doğrudan girilen tarih
                        'start_date'    => $this->cleanDate($itemData['start_date'] ?? null),
                        'end_date'      => $this->cleanDate($itemData['end_date'] ?? null),
                        'start_time'    => $this->cleanTime($itemData['start_time'] ?? null),
                        'end_time'      => $this->cleanTime($itemData['end_time'] ?? null),
                    ]);
                }
            }

            $sale->calculateTotals();

            // ─── CARİ KONTROLÜ ────────────────────────────────────────────────
            // Ödeme alınmadan kalan tutar müşterinin kredi limitine sığmıyorsa
            // satış kaydedilmez; kullanıcıya ne yapması gerektiği söylenir.
            if ($msg = $this->creditBlockMessage($sale)) {
                DB::rollBack();
                if ($request->expectsJson()) {
                    return response()->json(['success' => false, 'message' => $msg], 422);
                }
                return back()->withInput()->with('error', $msg);
            }

            // ─── YOLCULARI KAYDET ─────────────────────────────────────────────
            if ($request->has('passengers')) {
                foreach ($request->passengers as $pData) {
                    $passengerId = $pData['id'] ?? null;

                    // Eğer yolcu zaten varsa, bilgilerini güncelle
                    if ($passengerId) {
                        $p = \App\Models\CustomerPassenger::find($passengerId);
                        if ($p) {
                            $p->first_name = $pData['first_name'] ?? $p->first_name;
                            $p->last_name = $pData['last_name'] ?? $p->last_name;
                            $p->gender = $pData['gender'] ?? $p->gender;
                            $p->birth_date = !empty($pData['birth_date']) ? $pData['birth_date'] : $p->birth_date;
                            $p->national_id = $pData['national_id'] ?? $p->national_id;
                            $p->phone = $pData['phone'] ?? $p->phone;
                            $p->email = $pData['email'] ?? $p->email;
                            $p->relationship = $pData['relationship'] ?? $p->relationship;
                            $p->save();
                        }
                    }

                    // Eğer yeni yolcu ise CustomerPassenger olarak kaydet
                    if (!$passengerId && !empty($pData['first_name'])) {
                        $newP = new \App\Models\CustomerPassenger();
                        $newP->customer_id = $customer->id;
                        $newP->first_name = $pData['first_name'];
                        $newP->last_name = $pData['last_name'] ?? '';
                        $newP->gender = $pData['gender'] ?? null;
                        $newP->birth_date = $pData['birth_date'] ?? null;
                        $newP->national_id = $pData['national_id'] ?? null;
                        $newP->passport_number = $pData['passport_number'] ?? null;
                        $newP->passport_expiry = $pData['passport_expiry'] ?? null;
                        $newP->phone = $pData['phone'] ?? null;
                        $newP->email = $pData['email'] ?? null;
                        $newP->relationship = $pData['relationship'] ?? null;
                        $newP->created_by = Auth::id();
                        $newP->save();
                        $passengerId = $newP->id;
                    }

                    if ($passengerId) {
                        $sale->passengers()->attach($passengerId, [
                            'room_number' => $pData['room_number'] ?? null,
                            'seat_number' => $pData['seat_number'] ?? null,
                            'special_request' => $pData['special_request'] ?? null,
                        ]);
                    }
                }
            }

            // Kalem tarihleri → kişiye özel kalkış; sonra kalkış bağı + envanter.
            // Kataloğa kayıtlı kalkış seçilmişse o önceliklidir.
            $sale->unsetRelation('items');
            $private = $this->privateDepartures()->syncForSale($sale);

            if ($request->filled('tour_date_id')) {
                $this->syncDeparture($sale, $request->input('tour_date_id'));
            } elseif ($private['departure']) {
                $this->privateDepartures()->bindSale($sale, $private['departure']);
            } else {
                $this->syncDeparture($sale, null);
            }

            if ((float) $sale->total_amount > 0) {
                $customer->recordBooking((float) $sale->total_amount);
            } else {
                $customer->update(['last_booking_date' => today()]);
            }


            DB::commit();

            if ($request->expectsJson()) {
                return response()->json([
                    'success' => true,
                    'message' => 'Rezervasyon başarıyla oluşturuldu.',
                    'sale_id' => $sale->id,
                    'sale_code' => $sale->sale_code
                ]);
            }

            return redirect()
                ->route('agency.sales.show', $sale)
                ->with('success', 'Rezervasyon başarıyla oluşturuldu. Kod: ' . $sale->sale_code);

        } catch (\Illuminate\Validation\ValidationException $e) {
            Log::warning('Sale store validation error', ['errors' => $e->errors(), 'agency' => $agencyId]);
            if ($request->expectsJson()) {
                return response()->json(['success' => false, 'errors' => $e->errors()], 422);
            }
            throw $e;
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Sale store error', ['message' => $e->getMessage(), 'agency' => $agencyId]);
            if ($request->expectsJson()) {
                return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
            }
            return back()->withInput()->with('error', 'Rezervasyon oluşturulurken bir hata oluştu: ' . $e->getMessage());
        }
    }

    // ─── SHOW ─────────────────────────────────────────────────────────────────

    public function show(Sale $sale)
    {
        $this->authorizeAgency($sale);

        $sale->load([
            'customer',
            'productType',
            'product',
            'reservable',
            'creator',
            'assignedAgent',
            'confirmedBy',
            'cancelledBy',
        ]);

        $productType = $sale->productType;
        $extraFields = $productType ? Sale::extraFieldsFor($productType->code) : [];

        return view('agency.sales.show', compact('sale', 'extraFields', 'productType'));
    }

    // ─── EDIT / UPDATE ────────────────────────────────────────────────────────

    public function edit(Sale $sale)
    {
        $this->authorizeAgency($sale);
        $agencyId = $this->agencyId();

        $productTypes = $this->saleProductTypes();

        $customers = Customer::byAgency($agencyId)
            ->orderBy('first_name')
            ->get(['id', 'first_name', 'last_name', 'emails', 'phone']);

        $agents = User::byAgency($agencyId)
            ->where('is_active', true)
            ->orderBy('first_name')
            ->get(['id', 'first_name', 'last_name']);

        $products = collect();
        $extraFields = [];
        $selectedProductType = $sale->productType;

        if ($selectedProductType) {
            $products = Product::byAgency($agencyId)
                ->byProductType($selectedProductType->id)
                ->bookable()
                ->orderBy('name')
                ->get(['id', 'name', 'base_price', 'currency', 'productable_type', 'productable_id']);

            $extraFields = Sale::extraFieldsFor($selectedProductType->code);
        }

        return view('agency.sales.edit', compact(
            'sale',
            'productTypes',
            'customers',
            'agents',
            'products',
            'selectedProductType',
            'extraFields'
        ));
    }

    public function update(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);

        $productType = $sale->productType;
        $extraFields = $productType ? Sale::extraFieldsFor($productType->code) : [];

        $rules = [
            'customer_id' => 'required|exists:customers,id',
            'product_type_id' => 'nullable|exists:product_types,id',
            'product_id' => 'nullable|exists:products,id',
            'start_date' => 'required|date',
            'end_date' => 'nullable|date|after_or_equal:start_date',
            'pickup_time' => 'nullable|date',
            'adult_count' => 'required|integer|min:1',
            'child_count' => 'nullable|integer|min:0',
            'infant_count' => 'nullable|integer|min:0',
            'unit_price' => 'nullable|numeric|min:0',
            'child_price' => 'nullable|numeric|min:0',
            'infant_price' => 'nullable|numeric|min:0',
            'discount_rate' => 'nullable|numeric|min:0|max:100',
            'discount_amount' => 'nullable|numeric|min:0',
            'tax_amount' => 'nullable|numeric|min:0',
            'status' => 'required|in:pending,confirmed,in_progress,completed,cancelled,refunded',
            'payment_status' => 'required|in:unpaid,partial,paid,refunded',
            'paid_amount' => 'nullable|numeric|min:0',
            'pickup_location' => 'nullable|string|max:255',
            'dropoff_location' => 'nullable|string|max:255',
            'flight_number' => 'nullable|string|max:30',
            'assigned_to' => 'nullable|exists:users,id',
            'customer_note' => 'nullable|string',
            'internal_note' => 'nullable|string',
            'cancellation_reason' => 'nullable|string',
        ];

        foreach ($extraFields as $field) {
            $rules["extra_data.{$field['key']}"] = 'nullable';
        }

        // Cari kuralı borcun arttığı anda işler (bkz. creditBlockMessage)
        $previousDebtTry = $this->saleDebtInTry($sale);

        try {
            $validated = $request->validate($rules);

            DB::beginTransaction();

            // extra_data'nın "pricing" anahtarı (satış kartında seçilen pazaryeri)
            // bu eski tam-sayfa formunda gönderilmez; tamamen ezilmesin.
            if (array_key_exists('extra_data', $validated)) {
                $keep = (array) data_get($sale->extra_data, 'pricing', []);
                if ($keep) {
                    $validated['extra_data'] = ((array) $validated['extra_data']) + ['pricing' => $keep];
                }
            }

            $sale->update($validated);
            $sale->calculateTotals();

            if ($request->has('passengers')) {
                $passengerIds = [];
                foreach ($request->passengers as $pData) {
                    $passengerId = $pData['id'] ?? null;

                    // Eğer yolcu zaten varsa, bilgilerini güncelle
                    if ($passengerId) {
                        $p = \App\Models\CustomerPassenger::find($passengerId);
                        if ($p) {
                            $p->first_name = $pData['first_name'] ?? $p->first_name;
                            $p->last_name = $pData['last_name'] ?? $p->last_name;
                            $p->gender = $pData['gender'] ?? $p->gender;
                            $p->birth_date = !empty($pData['birth_date']) ? $pData['birth_date'] : $p->birth_date;
                            $p->national_id = $pData['national_id'] ?? $p->national_id;
                            $p->phone = $pData['phone'] ?? $p->phone;
                            $p->email = $pData['email'] ?? $p->email;
                            $p->relationship = $pData['relationship'] ?? $p->relationship;
                            $p->save();
                        }
                    }

                    // Eğer yeni yolcu ise CustomerPassenger olarak kaydet
                    if (!$passengerId && !empty($pData['first_name'])) {
                        $newP = new \App\Models\CustomerPassenger();
                        $newP->customer_id = $sale->customer_id;
                        $newP->first_name = $pData['first_name'];
                        $newP->last_name = $pData['last_name'] ?? '';
                        $newP->gender = $pData['gender'] ?? null;
                        $newP->birth_date = $pData['birth_date'] ?? null;
                        $newP->national_id = $pData['national_id'] ?? null;
                        $newP->passport_number = $pData['passport_number'] ?? null;
                        $newP->passport_expiry = $pData['passport_expiry'] ?? null;
                        $newP->phone = $pData['phone'] ?? null;
                        $newP->email = $pData['email'] ?? null;
                        $newP->relationship = $pData['relationship'] ?? null;
                        $newP->created_by = Auth::id();
                        $newP->save();
                        $passengerId = $newP->id;
                    }

                    if ($passengerId) {
                        $passengerIds[$passengerId] = [
                            'room_number' => $pData['room_number'] ?? null,
                            'seat_number' => $pData['seat_number'] ?? null,
                            'special_request' => $pData['special_request'] ?? null,
                        ];
                    }
                }
                $sale->passengers()->sync($passengerIds);
            }

            if (!empty($validated['product_id'])) {

                $product = \App\Models\Product\Product::find($validated['product_id']);
                if ($product?->productable_type && $product?->productable_id) {
                    $sale->reservable_type = $product->productable_type;
                    $sale->reservable_id = $product->productable_id;
                    $sale->save();
                }
            }

            if ($validated['status'] === 'cancelled' && !$sale->cancelled_at) {
                $sale->cancelled_at = now();
                $sale->cancelled_by = auth()->id();
                $sale->save();
            }

            // Kalkış bağını güncelle + envanteri yeniden say (durum/pax/tarih değişmiş olabilir)
            $this->syncDeparture($sale, $request->input('tour_date_id', $sale->tour_date_id));

            // Cari kontrolü: borç artıyorsa müşterinin limitine sığmalı
            if ($msg = $this->creditBlockMessage($sale, $previousDebtTry)) {
                DB::rollBack();
                return back()->withInput()->with('error', $msg);
            }

            DB::commit();

            return back()->with('success', 'Rezervasyon başarıyla güncellendi.');

        } catch (\Illuminate\Validation\ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Sale update error', ['message' => $e->getMessage(), 'sale_id' => $sale->id]);
            return back()->withInput()->with('error', 'Güncelleme sırasında hata: ' . $e->getMessage());
        }
    }

    // ─── DELETE ───────────────────────────────────────────────────────────────

    public function destroy(Sale $sale)
    {
        $this->authorizeAgency($sale);

        $code = $sale->sale_code;
        $sale->delete();
        $this->refreshDeparture($sale);

        return redirect()
            ->route('agency.sales.index')
            ->with('success', "$code numaralı rezervasyon silindi.");
    }

    public function bulkDelete(Request $request)
    {
        $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'integer',
        ]);

        $sales = Sale::byAgency($this->agencyId())->whereIn('id', $request->ids)->get();
        foreach ($sales as $sale) {
            $sale->delete();
            $this->refreshDeparture($sale);
        }

        \App\Models\Agency\ActivityLog::custom('delete', $sales->count() . ' satış kaydı toplu silindi', [
            'ids' => $sales->pluck('id')->all(),
            'titles' => $sales->pluck('title')->all(),
        ]);

        return response()->json([
            'success' => true,
            'message' => $sales->count() . ' satış kaydı silindi.',
        ]);
    }

    // ─── QUICK ACTIONS ────────────────────────────────────────────────────────

    public function confirm(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);
        $sale->confirm();
        $this->refreshDeparture($sale);
        return back()->with('success', 'Rezervasyon onaylandı.');
    }

    public function cancel(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);
        $request->validate(['reason' => 'nullable|string|max:500']);
        $sale->cancel($request->reason ?? '');
        $this->refreshDeparture($sale);
        return back()->with('success', 'Rezervasyon iptal edildi.');
    }

    /**
     * Hızlı tahsilat (operasyon kartı / satış ekranındaki kısa form).
     *
     * Tutarı doğrudan satışa yazmak yerine ödemeler defterine bir hareket
     * açar; bakiye oradan hesaplanır. Böylece hızlı giriş de belge, yöntem
     * ve iptal edilebilirlik açısından diğer ödemelerle aynı kayıt olur.
     */
    public function addPayment(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);
        $request->validate([
            'amount' => 'required|numeric|min:0.01',
            'method' => 'nullable|string',
        ]);

        $method = $request->input('method');
        $payment = \App\Models\Payment\Payment::create([
            'agency_id' => $sale->agency_id,
            'direction' => 'in',
            'status' => 'completed',
            'method' => array_key_exists((string) $method, \App\Models\Payment\Payment::selectableMethods()) ? $method : 'cash',
            'category' => 'sale',
            'amount' => (float) $request->amount,
            'currency' => $sale->currency,
            'paid_at' => now(),
            'sale_id' => $sale->id,
            'counterparty_type' => $sale->customer_id ? \App\Models\Customer\Customer::class : null,
            'counterparty_id' => $sale->customer_id,
            'description' => 'Hızlı tahsilat',
        ]);

        app(\App\Services\Payment\PaymentPostingService::class)->post($payment);
        $sale->refresh();

        // Otomatik görev: ödeme alındı (her ödeme ayrı görev üretebilsin diye dedupe_suffix)
        app(\App\Services\Task\AutoTaskService::class)->fireSafe('payment_received', $sale, $sale->autoTaskContext() + [
            'amount' => number_format((float) $request->amount, 2, ',', '.'),
            'currency' => $sale->currency,
            'paid_suffix' => $sale->payment_status === 'paid' ? ' — tamamı tahsil edildi' : '',
            'dedupe_suffix' => 'p' . $payment->id,
        ], $sale->autoTaskRelations());

        $msg = number_format($request->amount, 2) . ' ' . $sale->currency . ' ödeme kaydedildi.';

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'message' => $msg,
                'paid_amount' => (float) $sale->paid_amount,
                'remaining_amount' => (float) $sale->remaining_amount,
                'payment_status' => $sale->payment_status,
            ]);
        }

        return back()->with('success', $msg);
    }

    // ─── AJAX: Ürün tipi → ürünler + extra alanlar ───────────────────────────

    public function getProductsByType(Request $request)
    {
        $agencyId = $this->agencyId();
        $productType = ProductType::find($request->product_type_id);

        if (!$productType) {
            return response()->json(['products' => [], 'extra_fields' => [], 'type_code' => null]);
        }

        $products = Product::byAgency($agencyId)
            ->byProductType($productType->id)
            ->with(['productable.departureCity'])
            ->bookable()
            ->orderBy('name')
            ->get(['id', 'name', 'base_price', 'currency', 'slug', 'productable_type', 'productable_id', 'code'])
            ->map(function($p) {
                $item = [
                    'id' => $p->id,
                    'name' => $p->name,
                    'code' => $p->code,
                    'base_price' => (float) $p->base_price,
                    'currency' => $p->currency,
                    'has_dates' => !empty($p->productable_type),
                    'productable_type' => $p->productable_type,
                    'productable_id' => $p->productable_id,
                ];

                if ($p->productable) {
                    $item['duration'] = $p->productable->duration_hours ?? ($p->productable->duration_days ?? null);
                    $item['departure_city'] = $p->productable->departureCity?->name;
                    $item['visit_locations'] = is_array($p->productable->visit_locations) ? implode(' - ', array_slice($p->productable->visit_locations, 0, 3)) : null;
                    $item['meal_included'] = $p->productable->meal_included ?? false;
                }

                return $item;
            });

        $extraFields = Sale::extraFieldsFor($productType->code);

        return response()->json([
            'products' => $products,
            'extra_fields' => $extraFields,
            'type_code' => $productType->code,
        ]);
    }

    // ─── AJAX: Ürün → tarihler + fiyatlar ────────────────────────────────────

    public function getProductDates(Request $request)
    {
        $product = Product::find($request->product_id);

        if (!$product) {
            return response()->json(['dates' => [], 'has_dates' => false]);
        }

        $dates = [];
        $hasDates = false;

        // Polimorfik ilişkiyle bağlı modeli belirle
        $productableType = $product->productable_type ?? '';

        // Kültür/yurtdışı fiyatları tarih tablosunda değil pazaryeri matrisinde.
        // Fiyatın HANGİ pazaryerinden geldiği satış kartında görünür olmalı, bu yüzden
        // yalnızca satırı değil çözüm gerekçesini de döneriz.
        //
        // Öncelik: (1) kullanıcının bu satış için seçtiği pazar, (2) ürünün ana pazarı,
        // (3) hiçbiri yoksa fiyatı olan herhangi bir pazar ("belirsiz" olarak işaretlenir).
        $prefMarketId = (int) $request->input('marketplace_id') ?: null;
        $primaryMarketId = $product->primary_marketplace_id ? (int) $product->primary_marketplace_id : null;

        // Pazaryeri adları tek sorguda (her tarih için ayrı ilişki yüklemeyelim)
        $marketNames = \App\Models\Agency\Marketplace::query()->pluck('name', 'id');

        $pickRow = function ($d) use ($prefMarketId, $primaryMarketId, $marketNames) {
            $rows = $d->marketplacePrices;

            // Tek pazaryerine ait satırlardan gösterilecek olanı seç:
            // sınıfsız (taban) satır öncelikli, yoksa en düşük kişi başı fiyat.
            $best = fn ($pool) => $pool->firstWhere('tour_class_id', null)
                ?? $pool->sortBy(fn ($r) => (float) $r->adult_price)->first();

            // Pazaryeri bazında seçenek listesi (kullanıcı satışta değiştirebilsin)
            $options = $rows->groupBy('marketplace_id')->map(function ($pool, $mid) use ($best, $marketNames, $primaryMarketId) {
                $r = $best($pool);
                return [
                    'id' => (int) $mid,
                    'name' => $marketNames[(int) $mid] ?? ('Pazaryeri #' . $mid),
                    'is_primary' => $primaryMarketId !== null && (int) $mid === $primaryMarketId,
                    'currency' => $r?->currency ?: 'TRY',
                    'adult_price' => (float) ($r?->adult_price ?? 0),
                    'child_price' => (float) ($r?->child_price_1 ?? $r?->child_price ?? 0),
                    'child_price_2' => (float) ($r?->child_price_2 ?? 0),
                    'infant_price' => (float) ($r?->infant_price ?? 0),
                    'single_supplement' => (float) ($r?->single_supplement ?? 0),
                    'extra_bed_price' => (float) ($r?->extra_bed_price ?? 0),
                ];
            })->sortByDesc('is_primary')->values()->all();

            $chosenId = null;
            $source = 'none';
            if ($prefMarketId && $rows->where('marketplace_id', $prefMarketId)->isNotEmpty()) {
                $chosenId = $prefMarketId;
                $source = $primaryMarketId === $prefMarketId ? 'primary' : 'selected';
            } elseif ($primaryMarketId && $rows->where('marketplace_id', $primaryMarketId)->isNotEmpty()) {
                $chosenId = $primaryMarketId;
                $source = 'primary';
            } elseif ($rows->isNotEmpty()) {
                $chosenId = (int) $rows->first()->marketplace_id;
                $source = 'fallback';   // hangi pazarın fiyatı geldiği BELİRSİZ → kullanıcı uyarılır
            }

            $row = $chosenId ? $best($rows->where('marketplace_id', $chosenId)) : null;

            return [
                'row' => $row,
                'marketplace_id' => $chosenId,
                'marketplace_name' => $chosenId ? ($marketNames[$chosenId] ?? ('Pazaryeri #' . $chosenId)) : null,
                'marketplace_source' => $source,
                'marketplaces' => $options,
            ];
        };

        if (str_contains($productableType, 'InternationalTour')) {
            // Yurt Dışı Turu → InternationalTourDate (international_tour_dates)
            $tourDates = \App\Models\InternationalTour\InternationalTourDate::where('international_tour_id', $product->productable_id)
                ->where('is_bookable', true)
                ->where('is_active', true)
                ->where('start_date', '>=', now()->toDateString())
                ->orderBy('start_date')
                ->with('marketplacePrices')
                ->get(['id', 'start_date', 'end_date', 'currency', 'remaining_capacity', 'duration_days', 'season_type', 'special_discount', 'guarantee_departure',
                       'room_capacity', 'child_min_age', 'child_max_age', 'infant_min_age', 'infant_max_age', 'child_allowed', 'infant_allowed', 'prices_include_tax', 'tax_rate']);

            $dates = $tourDates->map(function($d) use ($pickRow) {
                $pick = $pickRow($d);
                $row = $pick['row'];
                $startDate = $d->start_date;
                $searchText = $startDate->format('d.m.Y') . ' ' .
                             $startDate->translatedFormat('d F Y') . ' ' .
                             $startDate->translatedFormat('d M Y') . ' ' .
                             $startDate->format('Y-m-d');

                return [
                    'id' => $d->id,
                    'date_type' => 'international_date',
                    'is_overnight' => true,
                    // Fiyat hangi pazaryerinden geldi? (satış kartı bunu gösterir/saklar)
                    'marketplace_id' => $pick['marketplace_id'],
                    'marketplace_name' => $pick['marketplace_name'],
                    'marketplace_source' => $pick['marketplace_source'],
                    'marketplaces' => $pick['marketplaces'],
                    'start_date' => $startDate->format('Y-m-d'),
                    'end_date' => $d->end_date?->format('Y-m-d'),
                    'start_date_display' => $startDate->translatedFormat('d M Y'),
                    'end_date_display' => $d->end_date?->translatedFormat('d M Y'),
                    'search_text' => strtolower($searchText),
                    'adult_price' => (float) ($row?->adult_price ?? 0),
                    'child_price' => (float) ($row?->child_price_1 ?? $row?->child_price ?? 0),
                    'child_price_2' => (float) ($row?->child_price_2 ?? 0),
                    'infant_price' => (float) ($row?->infant_price ?? 0),
                    'single_supplement' => (float) ($row?->single_supplement ?? 0),
                    'extra_bed_price' => (float) ($row?->extra_bed_price ?? 0),
                    'currency' => $row?->currency ?: 'TRY',
                    'remaining_capacity' => $d->remaining_capacity,
                    'duration_days' => $d->duration_days,
                    'season_type' => $d->season_type,
                    'special_discount' => (float) $d->special_discount,
                    'guarantee_departure' => $d->guarantee_departure,
                    // Odalama + yaş kuralları (satış kartındaki odalama bölümü okur)
                    'room_capacity' => $d->room_capacity !== null ? (int) $d->room_capacity : null,
                    'child_min_age' => $d->child_min_age !== null ? (int) $d->child_min_age : null,
                    'child_max_age' => $d->child_max_age !== null ? (int) $d->child_max_age : null,
                    'infant_min_age' => $d->infant_min_age !== null ? (int) $d->infant_min_age : null,
                    'infant_max_age' => $d->infant_max_age !== null ? (int) $d->infant_max_age : null,
                    'child_allowed' => (bool) $d->child_allowed,
                    'infant_allowed' => (bool) $d->infant_allowed,
                    'prices_include_tax' => (bool) $d->prices_include_tax,
                    'tax_rate' => (float) $d->tax_rate,
                ];
            })->toArray();
            $hasDates = count($dates) > 0;

        } elseif (str_contains($productableType, 'Tour') && !str_contains($productableType, 'Day')) {
            // Kültür Turu → TourDate (cultural_tour_dates)
            $tourDates = \App\Models\Tour\TourDate::where('cultural_tour_id', $product->productable_id)
                ->where('is_bookable', true)
                ->where('is_active', true)
                ->where('start_date', '>=', now()->toDateString())
                ->orderBy('start_date')
                ->with('marketplacePrices')
                ->get(['id', 'start_date', 'end_date', 'currency', 'remaining_capacity', 'duration_days', 'season_type', 'special_discount', 'guarantee_departure',
                       'room_capacity', 'child_min_age', 'child_max_age', 'infant_min_age', 'infant_max_age', 'child_allowed', 'infant_allowed', 'prices_include_tax', 'tax_rate']);

            $dates = $tourDates->map(function($d) use ($pickRow) {
                $pick = $pickRow($d);
                $row = $pick['row'];
                $startDate = $d->start_date;
                $searchText = $startDate->format('d.m.Y') . ' ' .
                             $startDate->translatedFormat('d F Y') . ' ' .
                             $startDate->translatedFormat('d M Y') . ' ' .
                             $startDate->format('Y-m-d');

                return [
                    'id' => $d->id,
                    'date_type' => 'tour_date',
                    'is_overnight' => true,
                    // Fiyat hangi pazaryerinden geldi? (satış kartı bunu gösterir/saklar)
                    'marketplace_id' => $pick['marketplace_id'],
                    'marketplace_name' => $pick['marketplace_name'],
                    'marketplace_source' => $pick['marketplace_source'],
                    'marketplaces' => $pick['marketplaces'],
                    'start_date' => $startDate->format('Y-m-d'),
                    'end_date' => $d->end_date?->format('Y-m-d'),
                    'start_date_display' => $startDate->translatedFormat('d M Y'),
                    'end_date_display' => $d->end_date?->translatedFormat('d M Y'),
                    'search_text' => strtolower($searchText),
                    'adult_price' => (float) ($row?->adult_price ?? 0),
                    'child_price' => (float) ($row?->child_price_1 ?? $row?->child_price ?? 0),
                    'child_price_2' => (float) ($row?->child_price_2 ?? 0),
                    'infant_price' => (float) ($row?->infant_price ?? 0),
                    'single_supplement' => (float) ($row?->single_supplement ?? 0),
                    'extra_bed_price' => (float) ($row?->extra_bed_price ?? 0),
                    'currency' => $row?->currency ?: 'TRY',
                    'remaining_capacity' => $d->remaining_capacity,
                    'duration_days' => $d->duration_days,
                    'season_type' => $d->season_type,
                    'special_discount' => (float) $d->special_discount,
                    'guarantee_departure' => $d->guarantee_departure,
                    // Odalama + yaş kuralları (satış kartındaki odalama bölümü okur)
                    'room_capacity' => $d->room_capacity !== null ? (int) $d->room_capacity : null,
                    'child_min_age' => $d->child_min_age !== null ? (int) $d->child_min_age : null,
                    'child_max_age' => $d->child_max_age !== null ? (int) $d->child_max_age : null,
                    'infant_min_age' => $d->infant_min_age !== null ? (int) $d->infant_min_age : null,
                    'infant_max_age' => $d->infant_max_age !== null ? (int) $d->infant_max_age : null,
                    'child_allowed' => (bool) $d->child_allowed,
                    'infant_allowed' => (bool) $d->infant_allowed,
                    'prices_include_tax' => (bool) $d->prices_include_tax,
                    'tax_rate' => (float) $d->tax_rate,
                ];
            })->toArray();
            $hasDates = count($dates) > 0;

        } elseif (str_contains($productableType, 'DayTour')) {
            // Günübirlik Tur → DayTourDate
            $dayDates = \App\Models\DayTour\DayTourDate::where('day_tour_id', $product->productable_id)
                ->where('is_active', true)
                ->where('tour_date', '>=', now()->toDateString())
                ->orderBy('tour_date')
                ->get();

            $dates = $dayDates->map(function($d) {
                $tourDate = \Carbon\Carbon::parse($d->tour_date);
                $searchText = $tourDate->format('d.m.Y') . ' ' . 
                             $tourDate->translatedFormat('d F Y') . ' ' . 
                             $tourDate->translatedFormat('d M Y') . ' ' .
                             $tourDate->format('Y-m-d');

                return [
                    'id' => $d->id,
                    'date_type' => 'day_tour_date',
                    'is_overnight' => false,      // konaklama yok → odalama paneli gizli
                    'marketplace_id' => null,
                    'marketplace_name' => null,
                    'marketplace_source' => 'none',
                    'marketplaces' => [],
                    'start_date' => $tourDate->format('Y-m-d'),
                    'end_date' => $tourDate->format('Y-m-d'),
                    'start_date_display' => $tourDate->translatedFormat('d M Y'),
                    'end_date_display' => null,
                    'search_text' => strtolower($searchText),
                    'adult_price' => (float) $d->adult_price,
                    'child_price' => (float) $d->child_price,
                    'child_price_2' => 0,           // günübirlikte 2. çocuk tarifesi yok
                    'infant_price' => (float) $d->infant_price,
                    'single_supplement' => 0,
                    'room_capacity' => null,        // günübirlik → konaklama yok, odalama gizli
                    'extra_bed_price' => 0,
                    'currency' => 'TRY',
                    'remaining_capacity' => $d->remaining_capacity,
                    'duration_days' => 1,
                    'departure_time' => $d->start_time ? \Carbon\Carbon::parse($d->start_time)->format('H:i') : null,
                    'return_time' => $d->end_time ? \Carbon\Carbon::parse($d->end_time)->format('H:i') : null,
                    'guarantee_departure' => $d->is_guaranteed,
                ];
            })->toArray();
            $hasDates = count($dates) > 0;

        } elseif (str_contains($productableType, 'DayTrip')) {
            // Diğer Günübirlik Tur Versiyonu → DayTripTourDate
            $dayDates = \App\Models\Product\DayTripTourDate::where('day_trip_tour_id', $product->productable_id)
                ->where('is_bookable', true)
                ->where('is_active', true)
                ->where('tour_date', '>=', now()->toDateString())
                ->orderBy('tour_date')
                ->get();

            $dates = $dayDates->map(function($d) {
                $tourDate = \Carbon\Carbon::parse($d->tour_date);
                $searchText = $tourDate->format('d.m.Y') . ' ' . 
                             $tourDate->translatedFormat('d F Y') . ' ' . 
                             $tourDate->translatedFormat('d M Y') . ' ' .
                             $tourDate->format('Y-m-d');

                return [
                    'id' => $d->id,
                    'date_type' => 'day_trip_date',
                    'is_overnight' => false,      // konaklama yok → odalama paneli gizli
                    'marketplace_id' => null,
                    'marketplace_name' => null,
                    'marketplace_source' => 'none',
                    'marketplaces' => [],
                    'start_date' => $tourDate->format('Y-m-d'),
                    'end_date' => $tourDate->format('Y-m-d'),
                    'start_date_display' => $tourDate->translatedFormat('d M Y'),
                    'end_date_display' => null,
                    'search_text' => strtolower($searchText),
                    'adult_price' => (float) $d->price_adult,
                    'child_price' => (float) $d->price_child,
                    'child_price_2' => 0,           // günübirlikte 2. çocuk tarifesi yok
                    'infant_price' => (float) $d->price_infant,
                    'single_supplement' => 0,
                    'room_capacity' => null,        // günübirlik → konaklama yok, odalama gizli
                    'extra_bed_price' => 0,
                    'currency' => 'TRY',
                    'remaining_capacity' => $d->remaining_capacity,
                    'duration_days' => 1,
                    'departure_time' => $d->departure_time ? \Carbon\Carbon::parse($d->departure_time)->format('H:i') : null,
                    'return_time' => $d->return_time ? \Carbon\Carbon::parse($d->return_time)->format('H:i') : null,
                    'guarantee_departure' => $d->guarantee_departure,
                ];
            })->toArray();
            $hasDates = count($dates) > 0;
        }

        return response()->json([
            'has_dates' => $hasDates,
            'dates' => $dates,
            'base_price' => (float) $product->base_price,
            'currency' => $product->currency,
            // Ürünün ana pazarı seçili değilse satış kartı kullanıcıyı uyarır ve
            // düzeltmenin ürün kartından yapılacağını söyler.
            'primary_marketplace_id' => $primaryMarketId,
            'primary_marketplace_name' => $primaryMarketId ? ($marketNames[$primaryMarketId] ?? null) : null,
            'product_edit_url' => $this->productEditUrlFor($product),
        ]);
    }

    /** Ürün kartının quick-view adresi (ana pazar seçimi orada düzeltilir). */
    protected function productEditUrlFor(Product $product): ?string
    {
        if (!$product->productable_id) {
            return null;
        }

        return match (class_basename((string) $product->productable_type)) {
            'Tour'              => route('agency.products.kultur-turlari.quick-view', $product->productable_id),
            'InternationalTour' => route('agency.products.yurtdisi-turlari.quick-view', $product->productable_id),
            'DayTour'           => route('agency.products.gunubirlik-turlar.quick-view', $product->productable_id),
            default             => null,
        };
    }

    // ─── AJAX: Ürün → tarihler + fiyatlar ────────────────────────────────────

    public function quickStoreCustomer(Request $request)
    {
        $agencyId = $this->agencyId();

        try {
            $validated = $request->validate([
                'first_name' => 'required|string|max:255',
                'last_name' => 'required|string|max:255',
                'email' => 'nullable|email|max:255',
                'phone' => 'nullable|string|max:20',
                'gender' => 'nullable|in:male,female,other',
                'national_id' => 'nullable|string|max:20',
                'birth_date' => 'nullable|date',
            ], [
                'first_name.required' => 'Ad alanı zorunludur.',
                'last_name.required' => 'Soyad alanı zorunludur.',
                'email.email' => 'Geçerli bir e-posta adresi girin (örn. ad@site.com).',
                'email.max' => 'E-posta 255 karakteri aşamaz.',
                'birth_date.date' => 'Doğum tarihi geçerli bir tarih olmalıdır.',
                'gender.in' => 'Cinsiyet değeri geçersiz.',
            ], [
                'first_name' => 'Ad',
                'last_name' => 'Soyad',
                'email' => 'E-posta',
                'phone' => 'Telefon',
                'national_id' => 'T.C. / Pasaport',
                'birth_date' => 'Doğum Tarihi',
            ]);

            $customer = new Customer();
            $customer->agency_id = $agencyId;
            $customer->first_name = $validated['first_name'];
            $customer->last_name = $validated['last_name'];
            $customer->phone = $validated['phone'] ?? null;
            $customer->gender = $validated['gender'] ?? null;
            $customer->national_id = $validated['national_id'] ?? null;
            $customer->birth_date = $validated['birth_date'] ?? null;

            if (!empty($validated['email'])) {
                $customer->emails = [$validated['email']];
            }

            $customer->status = 'active';
            $customer->created_by = Auth::id();
            $customer->save();

            return response()->json([
                'success' => true,
                'customer' => [
                    'id' => $customer->id,
                    'full_name' => $customer->full_name,
                    'first_name' => $customer->first_name,
                    'last_name' => $customer->last_name,
                    'phone' => $customer->phone,
                    'email' => $validated['email'] ?? '',
                    'initials' => $customer->initials,
                ]
            ]);

        } catch (\Illuminate\Validation\ValidationException $e) {
            return response()->json(['success' => false, 'errors' => $e->errors()], 422);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
        }
    }

    public function getCustomerPassengers(Request $request)
    {
        $agencyId = $this->agencyId();
        $customerId = $request->customer_id;

        $customer = Customer::byAgency($agencyId)->findOrFail($customerId);
        $passengers = $customer->passengers()->orderBy('first_name')->get();

        return response()->json([
            'success' => true,
            'passengers' => $passengers
        ]);
    }


    // ─── YARDIMCI ─────────────────────────────────────────────────────────────

    public function storeTargets(Request $request)
    {
        $agencyId = $this->agencyId();
        $validated = $request->validate([
            'year' => 'required|integer',
            'period' => 'required|in:year,month',
            'month' => 'nullable|integer|min:1|max:12',
            'type' => 'required|in:revenue,quantity',
            'targets' => 'required|array',
            'targets.*' => 'nullable|string', // String because of formatting dots
        ]);

        $targetMonth = ($validated['period'] === 'month') ? $validated['month'] : null;

        foreach ($validated['targets'] as $productTypeId => $value) {
            $numericValue = (float)str_replace('.', '', $value);

            \App\Models\Agency\AgencySalesTarget::updateOrCreate(
                [
                    'agency_id' => $agencyId,
                    'product_type_id' => $productTypeId,
                    'target_year' => $validated['year'],
                    'target_period' => $validated['period'],
                    'target_month' => $targetMonth,
                    'target_type' => $validated['type'],
                ],
                [
                    'target_value' => $numericValue,
                ]
            );
        }

        return back()->with('success', 'Satış hedefleri başarıyla güncellendi.');
    }

    public function getTargetsJson(Request $request)
    {
        $agencyId = $this->agencyId();
        $targets = \App\Models\Agency\AgencySalesTarget::where('agency_id', $agencyId)
            ->where('target_year', $request->year)
            ->where('target_period', $request->period)
            ->where('target_type', $request->type)
            ->when($request->period === 'month', function($q) use ($request) {
                return $q->where('target_month', $request->month);
            })
            ->pluck('target_value', 'product_type_id');

        return response()->json($targets);
    }

    public function quickView(Sale $sale)
    {
        $this->authorizeAgency($sale);
        $agencyId = $this->agencyId();

        // Yeni satış kartıyla BİRLEŞİK tasarım: quick-create blade'ini mevcut satışa bağlı render eder.
        $sale->load([
            'customer', 'productType', 'product', 'reservable', 'tourDate', 'items', 'passengers',
            'invoices.creator',
            // Ödemeler sekmesi: belge sayısı da tek sorguda gelsin
            'payments' => fn ($q) => $q->with(['invoice:id,invoice_number', 'counterparty'])->withCount('documents'),
        ]);

        // Çoklu müşteri + odalar satış kartında açılışta çizilir. Migration henüz
        // çalıştırılmadıysa kart yine açılsın (blade birincil müşteriye / boş oda
        // listesine düşer).
        try {
            $sale->load(['customers']);
        } catch (\Throwable $e) {
            Log::warning('Sale quick-view: sale_customers yüklenemedi (migrate gerekli olabilir)', ['message' => $e->getMessage()]);
        }
        try {
            $sale->load(['rooms']);
        } catch (\Throwable $e) {
            Log::warning('Sale quick-view: sale_rooms yüklenemedi (migrate gerekli olabilir)', ['message' => $e->getMessage()]);
        }

        $productTypes = $this->saleProductTypes();
        $agents = User::byAgency($agencyId)->where('is_active', true)->orderBy('first_name')->get(['id', 'first_name', 'last_name']);
        $products = $this->catalogProducts($agencyId);
        $activeCurrencies = auth()->user()->agency->getActiveCurrencies();
        $taxRates = auth()->user()->agency->getActiveTaxRates(\App\Models\Agency\TaxType::CODE_KDV);
        // "Kataloğa ekle" akışı: yalnızca acentenin yetkili olduğu ürün tipleri.
        $serviceTypes = $this->agencyServiceTypes();
        $serviceFieldSchemas = \App\Models\Product\ServiceProduct::allFieldSchemas($serviceTypes);
        $supplierCatalog = $this->supplierCatalog($agencyId);
        $extraServices = $this->extraServiceCatalog($agencyId);

        return view('agency.sales.quick-create', compact('sale', 'productTypes', 'agents', 'products', 'activeCurrencies', 'taxRates', 'supplierCatalog', 'extraServices', 'serviceTypes', 'serviceFieldSchemas'));
    }

    public function updateField(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);

        $request->validate([
            'field' => 'required|string',
            'value' => 'nullable'
        ]);

        $field = $request->field;
        $value = $request->value;

        if (str_starts_with($field, 'custom_field_')) {
            $defId = (int) str_replace('custom_field_', '', $field);
            $definition = \App\Models\Sale\SaleFieldDefinition::where('id', $defId)
                ->where('agency_id', $sale->agency_id)
                ->first();
            if (!$definition) {
                return response()->json(['success' => false, 'message' => 'Geçersiz özel alan.'], 422);
            }
            
            try {
                DB::beginTransaction();
                
                $oldValueRecord = \App\Models\Sale\SaleFieldValue::where('sale_id', $sale->id)
                    ->where('field_definition_id', $defId)
                    ->first();
                $oldValue = $oldValueRecord?->value ?? '—';
                
                \App\Models\Sale\SaleFieldValue::updateOrCreate(
                    ['sale_id' => $sale->id, 'field_definition_id' => $defId],
                    ['value' => $value]
                );
                
                SaleActivity::create([
                    'agency_id' => $sale->agency_id,
                    'sale_id' => $sale->id,
                    'performed_by' => auth()->id(),
                    'activity_type' => 'update',
                    'subject' => 'Özel Alan Güncellendi: ' . $definition->label,
                    'description' => "<b>{$definition->label}</b> özel alanı '{$oldValue}' değerinden '{$value}' değerine güncellendi.",
                    'metadata' => ['field' => $field, 'old' => $oldValue, 'new' => $value]
                ]);
                
                DB::commit();
                
                return response()->json([
                    'success' => true,
                    'message' => 'Özel alan güncellendi.',
                    'display_value' => $value
                ]);
            } catch (\Exception $e) {
                DB::rollBack();
                return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
            }
        }

        // İzin verilen alanlar
        $allowedFields = [
            'title', 'status', 'start_date', 'end_date', 'assigned_to', 
            'customer_note', 'internal_note', 'pickup_location', 'dropoff_location',
            'currency'
        ];

        if (!in_array($field, $allowedFields)) {
            return response()->json(['success' => false, 'message' => 'Bu alan güncellenemez.'], 422);
        }

        try {
            DB::beginTransaction();

            $oldValue = $sale->$field;
            $sale->$field = $value;
            $sale->save();

            if ($field === 'currency') {
                $sale->calculateTotals();
            }

            // Daha açıklayıcı aktivite mesajı
            $subject = 'Alan Güncellendi: ' . $field;
            $desc = "'$field' alanı '$oldValue' değerinden '$value' değerine güncellendi.";

            if ($field === 'status') {
                $oldLabel = $sale->getStatusLabel($oldValue);
                $newLabel = $sale->status_label;
                $subject = 'Satış Durumu Değişti';
                $desc = "Satış durumu <b>$oldLabel</b> aşamasından <b>$newLabel</b> aşamasına çekildi.";
            }

            if ($field === 'assigned_to') {
                $oldUser = User::find($oldValue)?->full_name ?? 'Atanmamış';
                $newUser = User::find($value)?->full_name ?? 'Atanmamış';
                $subject = 'Sorumlu Değiştirildi';
                $desc = "Satış sorumlusu <b>$oldUser</b> yerine <b>$newUser</b> olarak güncellendi.";
            }

            // Aktivite kaydı
            SaleActivity::create([
                'agency_id' => $sale->agency_id,
                'sale_id' => $sale->id,
                'performed_by' => auth()->id(),
                'activity_type' => 'update',
                'subject' => $subject,
                'description' => $desc,
                'metadata' => ['field' => $field, 'old' => $oldValue, 'new' => $value]
            ]);

            DB::commit();

            $displayValue = $value;
            if ($field === 'status') $displayValue = $sale->status_label;
            if ($field === 'assigned_to') $displayValue = $sale->assignedAgent?->full_name ?? '—';

            return response()->json([
                'success' => true,
                'message' => 'Güncelleme başarılı.',
                'display_value' => $displayValue
            ]);

        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
        }
    }

    public function addItem(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);

        $validated = $request->validate([
            'product_id' => 'nullable|exists:products,id',
            'product_type_id' => 'nullable|exists:product_types,id',
            'name' => 'required|string',
            'quantity' => 'required|numeric|min:0.01',
            'unit_price' => 'required|numeric',
            'tax_rate' => 'nullable|numeric|min:0|max:100',
            'tax_included' => 'nullable|boolean',
            'currency' => 'required|string|max:3',
        ]);

        try {
            DB::beginTransaction();

            // Oran gelmediyse Ayarlar > Vergi Oranları'ndaki varsayılana düş.
            // Varsayılan işaretli tanım yoksa en yüksek aktif orana düşülür — sessizce %0 vergi yazmamak için.
            if (isset($validated['tax_rate'])) {
                $taxRate = (float) $validated['tax_rate'];
            } else {
                $agencyTaxRates = auth()->user()->agency->getActiveTaxRates(\App\Models\Agency\TaxType::CODE_KDV);
                $taxRate = (float) (($agencyTaxRates->firstWhere('is_default', true) ?? $agencyTaxRates->first())?->rate ?? 0);
            }
            $taxIncluded = filter_var($validated['tax_included'] ?? false, FILTER_VALIDATE_BOOLEAN);
            $break = SaleItem::breakdown(
                (float) $validated['quantity'],
                (float) $validated['unit_price'],
                $taxRate,
                $taxIncluded
            );
            $taxAmount = $break['tax'];
            $totalPrice = $break['total'];

            $item = $sale->items()->create([
                'product_id' => $validated['product_id'] ?? null,
                'product_type_id' => $validated['product_type_id'] ?? ($validated['product_id'] ? \App\Models\Product\Product::find($validated['product_id'])->product_type_id : null),
                'name' => $validated['name'],
                'quantity' => $validated['quantity'],
                'unit_price' => $validated['unit_price'],
                'tax_rate' => $taxRate,
                'tax_included' => $taxIncluded,
                'tax_amount' => $taxAmount,
                'total_price' => $totalPrice,
                'currency' => $validated['currency'],
            ]);

            // Rezervasyon toplamını güncelle
            $sale->calculateTotals(); // Bu metod subtotal, tax_amount ve total_amount'u günceller

            // Aktivite
            SaleActivity::create([
                'agency_id' => $sale->agency_id,
                'sale_id' => $sale->id,
                'performed_by' => auth()->id(),
                'activity_type' => 'item_added',
                'subject' => 'Ürün Eklendi: ' . $item->name,
                'description' => "Satışa {$item->quantity} adet {$item->name} eklendi.",
                'metadata' => ['item_id' => $item->id, 'total' => $item->total_price]
            ]);

            DB::commit();

            return response()->json(['success' => true, 'message' => 'Ürün eklendi.']);

        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
        }
    }

    /**
     * Satışta elle girilen bir kalemi kalıcı hizmete (ürün kataloğuna) dönüştürür.
     *
     * Kullanıcı ürün tipini seçer; tarih/saat ve konum alanları o tipe göre
     * etiketlenir (bkz. ServiceProduct::fieldSchema) ve custom olarak girilir.
     * Kayıt bir `service_products` satırı + ona bağlı bir `products` satırı
     * üretir; böylece hizmet bundan sonra katalogda çıkar ve tekrar satılabilir.
     */
    public function storeCatalogService(Request $request)
    {
        $agencyId = $this->agencyId();

        $validated = $request->validate([
            'product_type_id' => 'required|exists:product_types,id',
            'name' => 'required|string|max:255',
            'description' => 'nullable|string|max:2000',
            'unit_price' => 'nullable|numeric|min:0',
            'currency' => 'required|string|max:3',
            'tax_rate' => 'nullable|numeric|min:0|max:100',
            'tax_included' => 'nullable|boolean',
            'start_date' => 'nullable|date',
            'end_date' => 'nullable|date|after_or_equal:start_date',
            'start_time' => 'nullable|date_format:H:i',
            'end_time' => 'nullable|date_format:H:i',
            'duration_days' => 'nullable|integer|min:0|max:9999',
            'start_location' => 'nullable|string|max:255',
            'end_location' => 'nullable|string|max:255',
            'capacity' => 'nullable|integer|min:0',
            'notes' => 'nullable|string|max:2000',
            'sale_id' => 'nullable|integer',
        ], [
            'end_date.after_or_equal' => 'Bitiş tarihi başlangıç tarihinden önce olamaz.',
        ]);

        // Tip listesi istemciden geliyor; acentenin yetkisi olmayan bir tip
        // el ile gönderilemesin diye sunucuda da doğrulanır.
        $productType = $this->agencyServiceTypes()->firstWhere('id', (int) $validated['product_type_id']);
        if (!$productType) {
            return response()->json([
                'success' => false,
                'message' => 'Bu ürün tipi acentenizde tanımlı değil. Ürün Yönetimi → Ürün Tipleri ekranından etkinleştirebilirsiniz.',
            ], 422);
        }

        try {
            DB::beginTransaction();

            $service = \App\Models\Product\ServiceProduct::create([
                'agency_id' => $agencyId,
                'product_type_id' => $productType->id,
                'name' => $validated['name'],
                'description' => $validated['description'] ?? null,
                'unit_price' => $validated['unit_price'] ?? 0,
                'currency' => $validated['currency'],
                'tax_rate' => $validated['tax_rate'] ?? 0,
                'tax_included' => filter_var($validated['tax_included'] ?? false, FILTER_VALIDATE_BOOLEAN),
                'start_date' => $validated['start_date'] ?? null,
                'end_date' => $validated['end_date'] ?? null,
                'start_time' => $validated['start_time'] ?? null,
                'end_time' => $validated['end_time'] ?? null,
                'duration_days' => $validated['duration_days'] ?? null,
                'start_location' => $validated['start_location'] ?? null,
                'end_location' => $validated['end_location'] ?? null,
                'capacity' => $validated['capacity'] ?? null,
                'notes' => $validated['notes'] ?? null,
                'is_active' => true,
                'created_by' => auth()->id(),
            ]);

            $code = Product::generateUniqueCode($agencyId, (int) $productType->id);

            $product = Product::create([
                'agency_id' => $agencyId,
                'product_type_id' => $productType->id,
                'productable_type' => \App\Models\Product\ServiceProduct::class,
                'productable_id' => $service->id,
                'code' => $code,
                'name' => $service->name,
                'slug' => Product::generateUniqueSlug($service->name . '-' . $code),
                'description' => $service->description,
                'base_price' => $service->unit_price,
                'currency' => $service->currency,
                // Satıştan eklenen hizmet doğrudan satılabilir olmalı
                'is_active' => true,
                'is_bookable' => true,
                'published_at' => now(),
                'created_by' => auth()->id(),
            ]);

            $service->update(['product_id' => $product->id]);

            // Satış bağlamında yapıldıysa geçmişe düş
            if (!empty($validated['sale_id']) && ($sale = Sale::byAgency($agencyId)->find($validated['sale_id']))) {
                SaleActivity::create([
                    'agency_id' => $sale->agency_id,
                    'sale_id' => $sale->id,
                    'performed_by' => auth()->id(),
                    'activity_type' => 'item_added',
                    'subject' => 'Ürün kataloğuna eklendi: ' . $service->name,
                    'description' => "Satışta elle girilen \"{$service->name}\" kalemi "
                        . ($productType->name ?? 'hizmet') . ' olarak ürün kataloğuna kaydedildi.',
                    'metadata' => ['product_id' => $product->id, 'service_product_id' => $service->id],
                ]);
            }

            DB::commit();

            return response()->json([
                'success' => true,
                'message' => '"' . $service->name . '" ürün kataloğuna eklendi.',
                'product' => [
                    'id' => $product->id,
                    'name' => $product->name,
                    'code' => $product->code,
                    'base_price' => (float) $product->base_price,
                    'currency' => $product->currency,
                    'tax_rate' => (float) $service->tax_rate,
                    'tax_included' => (bool) $service->tax_included,
                    'product_type_id' => $productType->id,
                    'product_type_name' => $productType->name,
                    'product_type_icon' => $productType->icon ?? 'fa-box',
                    'date_summary' => $service->date_summary,
                ],
            ]);
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error('Sale catalog service create error', ['message' => $e->getMessage()]);

            return response()->json([
                'success' => false,
                'message' => 'Kataloğa eklenemedi: ' . $e->getMessage(),
            ], 500);
        }
    }

    public function removeItem(SaleItem $item)
    {
        $sale = $item->sale;
        $this->authorizeAgency($sale);

        try {
            DB::beginTransaction();

            $itemName = $item->name;
            $item->delete();

            // Rezervasyon toplamını güncelle
            $sale->calculateTotals();

            // Aktivite
            SaleActivity::create([
                'agency_id' => $sale->agency_id,
                'sale_id' => $sale->id,
                'performed_by' => auth()->id(),
                'activity_type' => 'item_removed',
                'subject' => 'Ürün Çıkarıldı: ' . $itemName,
                'description' => "Satıştan {$itemName} çıkarıldı.",
            ]);

            DB::commit();

            return response()->json(['success' => true, 'message' => 'Ürün çıkarıldı.']);

        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
        }
    }

    public function getActivities(Sale $sale)
    {
        $this->authorizeAgency($sale);
        
        $sale->load([
            'activities' => function ($q) {
                $q->orderBy('is_pinned', 'desc')->orderBy('created_at', 'desc');
            },
            'activities.performer'
        ]);

        $html = view('agency.sales.partials.activities-feed', compact('sale'))->render();

        return response()->json(['success' => true, 'html' => $html]);
    }

    public function storeActivity(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);

        $validated = $request->validate([
            'activity_type' => 'required|string|in:' . implode(',', \App\Enums\ActivityType::values()),
            'subject' => 'nullable|string|max:255',
            'description' => 'nullable|string',
            'due_date' => 'nullable|date',
            'is_pinned' => 'boolean',
            'is_important' => 'boolean',
            'attachment' => 'nullable|file|max:10240',
        ]);

        $validated['agency_id'] = $this->agencyId();
        $validated['sale_id'] = $sale->id;
        $validated['performed_by'] = auth()->id();
        $validated['status'] = $validated['activity_type'] === 'task' ? 'pending' : 'completed';
        $validated['activity_date'] = now();

        if ($request->hasFile('attachment')) {
            $file = $request->file('attachment');
            $path = $file->store('sales/activities/' . $sale->id, 'public');
            
            $validated['attachments'] = [
                [
                    'name' => $file->getClientOriginalName(),
                    'path' => $path,
                    'size' => $file->getSize(),
                    'mime' => $file->getMimeType(),
                    'uploaded_at' => now()->toDateTimeString(),
                ]
            ];
        }

        $activity = SaleActivity::create($validated);

        $sale->load([
            'activities' => function ($q) {
                $q->orderBy('is_pinned', 'desc')->orderBy('created_at', 'desc');
            },
            'activities.performer'
        ]);

        $html = view('agency.sales.partials.activities-feed', compact('sale'))->render();

        return response()->json([
            'success' => true,
            'message' => 'Aktivite başarıyla kaydedildi.',
            'html' => $html
        ]);
    }

    public function togglePinActivity(Sale $sale, SaleActivity $activity)
    {
        $this->authorizeAgency($sale);
        
        if ($activity->sale_id !== $sale->id) {
            return response()->json(['success' => false, 'message' => 'Geçersiz aktivite.'], 404);
        }

        $activity->is_pinned = !$activity->is_pinned;
        $activity->save();

        $sale->load([
            'activities' => function ($q) {
                $q->orderBy('is_pinned', 'desc')->orderBy('created_at', 'desc');
            },
            'activities.performer'
        ]);

        $html = view('agency.sales.partials.activities-feed', compact('sale'))->render();

        return response()->json([
            'success' => true,
            'message' => $activity->is_pinned ? 'Aktivite sabitlendi.' : 'Sabitleme kaldırıldı.',
            'html' => $html
        ]);
    }

    public function destroyActivity(Sale $sale, SaleActivity $activity)
    {
        $this->authorizeAgency($sale);

        if ($activity->sale_id !== $sale->id) {
            return response()->json(['success' => false, 'message' => 'Geçersiz aktivite.'], 404);
        }

        $activity->delete();

        $sale->load([
            'activities' => function ($q) {
                $q->orderBy('is_pinned', 'desc')->orderBy('created_at', 'desc');
            },
            'activities.performer'
        ]);

        $html = view('agency.sales.partials.activities-feed', compact('sale'))->render();

        return response()->json([
            'success' => true,
            'message' => 'Aktivite silindi.',
            'html' => $html
        ]);
    }

    public function storeFieldDefinition(Request $request)
    {
        $agencyId = $this->agencyId();
        
        $validated = $request->validate([
            'label' => 'required|string|max:120',
            'field_type' => 'required|string|in:text,select,textarea,number,date',
            'options' => 'nullable|string',
            'is_required' => 'nullable|boolean',
        ]);
        
        $optionsArray = null;
        if ($validated['field_type'] === 'select' && !empty($validated['options'])) {
            $optionsArray = array_filter(array_map('trim', explode(',', $validated['options'])));
        }
        
        $fieldKey = \Str::slug($validated['label'], '_');
        $origKey = $fieldKey;
        $counter = 1;
        while (\App\Models\Sale\SaleFieldDefinition::where('agency_id', $agencyId)->where('field_key', $fieldKey)->exists()) {
            $fieldKey = $origKey . '_' . $counter;
            $counter++;
        }
        
        $definition = \App\Models\Sale\SaleFieldDefinition::create([
            'agency_id' => $agencyId,
            'field_key' => $fieldKey,
            'label' => $validated['label'],
            'field_type' => $validated['field_type'],
            'options' => $optionsArray,
            'is_required' => (bool)($request->is_required ?? false),
            'is_active' => true,
            'sort_order' => 0,
        ]);
        
        return response()->json([
            'success' => true,
            'message' => 'Özel alan tanımlandı.',
            'definition' => $definition
        ]);
    }

    public function updateFieldDefinition(Request $request, \App\Models\Sale\SaleFieldDefinition $definition)
    {
        if ($definition->agency_id !== $this->agencyId()) {
            return response()->json(['success' => false, 'message' => 'Yetkisiz erişim.'], 403);
        }
        
        $validated = $request->validate([
            'label' => 'required|string|max:120',
            'field_type' => 'required|string|in:text,select,textarea,number,date',
            'options' => 'nullable|string',
            'is_required' => 'nullable|boolean',
        ]);
        
        $optionsArray = null;
        if ($validated['field_type'] === 'select' && !empty($validated['options'])) {
            $optionsArray = array_filter(array_map('trim', explode(',', $validated['options'])));
        }
        
        $definition->update([
            'label' => $validated['label'],
            'field_type' => $validated['field_type'],
            'options' => $optionsArray,
            'is_required' => (bool)($request->is_required ?? false),
        ]);
        
        return response()->json([
            'success' => true,
            'message' => 'Özel alan tanımı güncellendi.',
            'definition' => $definition
        ]);
    }

    public function storeDocument(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);

        $request->validate([
            'file' => 'required|file|max:10240', // 10MB limit
            'notes' => 'nullable|string|max:1000',
        ]);

        $file = $request->file('file');
        $originalName = $file->getClientOriginalName();
        $mimeType = $file->getMimeType();
        $fileSize = $file->getSize();

        $path = $file->store("agency/{$sale->agency_id}/sales/{$sale->id}/documents", 'public');

        $document = \App\Models\Sale\SaleDocument::create([
            'agency_id' => $sale->agency_id,
            'sale_id' => $sale->id,
            'file_name' => $originalName,
            'file_path' => $path,
            'file_size' => $fileSize,
            'mime_type' => $mimeType,
            'notes' => $request->notes,
            'created_by' => auth()->id(),
        ]);

        SaleActivity::create([
            'agency_id' => $sale->agency_id,
            'sale_id' => $sale->id,
            'performed_by' => auth()->id(),
            'activity_type' => 'document',
            'subject' => 'Yeni Belge Yüklendi',
            'description' => "Satışa <b>{$originalName}</b> adlı yeni bir belge yüklendi.",
            'metadata' => ['document_id' => $document->id, 'file_name' => $originalName]
        ]);

        return response()->json([
            'success' => true,
            'message' => 'Belge başarıyla yüklendi.',
            'document' => [
                'id' => $document->id,
                'file_name' => $document->file_name,
                'size' => $document->formatted_size,
                'created_at' => $document->created_at->format('d.m.Y H:i'),
            ]
        ]);
    }

    public function destroyDocument(Sale $sale, \App\Models\Sale\SaleDocument $document)
    {
        $this->authorizeAgency($sale);

        if ($document->sale_id !== $sale->id) {
            return response()->json(['success' => false, 'message' => 'Geçersiz belge.'], 404);
        }

        \Storage::disk('public')->delete($document->file_path);
        
        $document->delete();

        SaleActivity::create([
            'agency_id' => $sale->agency_id,
            'sale_id' => $sale->id,
            'performed_by' => auth()->id(),
            'activity_type' => 'document',
            'subject' => 'Belge Silindi',
            'description' => "<b>{$document->file_name}</b> adlı belge silindi.",
            'metadata' => ['document_id' => $document->id, 'file_name' => $document->file_name]
        ]);

        return response()->json([
            'success' => true,
            'message' => 'Belge başarıyla silindi.'
        ]);
    }

    // ─── FATURALAR (satış quick-view "Faturalar" sekmesi) ─────────────────────

    /** Fatura listesinin güncel HTML'i + sayacı (AJAX yanıtlarında kullanılır). */
    private function invoicesPayload(Sale $sale): array
    {
        $sale->load('invoices.creator');

        return [
            'html' => view('agency.sales.partials.invoices-list', compact('sale'))->render(),
            'count' => $sale->invoices->count(),
        ];
    }

    public function storeInvoice(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);

        $validated = $request->validate([
            'invoice_type' => 'required|in:sale,refund',
            'billing_type' => 'nullable|in:individual,company',
            'from_sale' => 'nullable|boolean',
            'issue_date' => 'required|date',
            'amount' => 'required|numeric|min:0',
            'currency' => 'required|string|max:3',
            'status' => 'required|in:draft,issued,paid,cancelled',
            'notes' => 'nullable|string|max:1000',
            'file' => 'nullable|file|max:10240|mimes:pdf,jpg,jpeg,png,webp,xml',
        ]);

        $fileData = [];
        if ($request->hasFile('file')) {
            $file = $request->file('file');
            $fileData = [
                'file_path' => $file->store("agency/{$sale->agency_id}/sales/{$sale->id}/invoices", 'public'),
                'file_name' => $file->getClientOriginalName(),
                'file_size' => $file->getSize(),
            ];
        }

        // Fatura muhatabı: şirket seçildi ama müşterinin fatura bilgisi (unvan +
        // vergi no) eksikse otomatik şahsa düşülür — TC kimlik numarasına kesilir.
        $customer = $sale->customer;
        $billingType = $validated['billing_type'] ?? null;
        $billing = [];
        if ($customer) {
            $billingType = $billingType ?: \App\Http\Controllers\Agency\InvoiceController::suggestedBillingType($customer);
            if ($billingType === 'company') {
                $billing = \App\Http\Controllers\Agency\InvoiceController::companyBillingFor($customer);
                if ($billing === null) {
                    $billingType = 'individual';
                    $billing = \App\Http\Controllers\Agency\InvoiceController::individualBillingFor($customer);
                }
            } else {
                $billing = \App\Http\Controllers\Agency\InvoiceController::individualBillingFor($customer);
            }
        } else {
            $billingType = $billingType ?: 'individual';
        }

        // Kalemler: ürünlerdeki her satır fatura kalemi olarak taşınır (varsayılan).
        $items = [];
        if ($request->boolean('from_sale', true)) {
            $items = $sale->items->map(fn ($i) => [
                'name' => $i->name,
                'quantity' => (float) $i->quantity,
                'unit_price' => (float) $i->unit_price,
                'tax_rate' => (float) $i->tax_rate,
                'discount_type' => $i->discount_type ?? 'percent',
                'discount_value' => (float) ($i->discount_value ?? 0),
            ])->values()->all();
        }

        // Fatura numarası acentenin numara kuralına göre otomatik üretilir
        // (Ayarlar → Fatura Şablonu → Fatura Numarası Kuralı).
        $numberService = app(\App\Services\Sale\InvoiceNumberService::class);

        $invoice = new SaleInvoice(array_merge(
            collect($validated)->except(['billing_type', 'from_sale'])->all(),
            $fileData,
            [
                'agency_id' => $sale->agency_id,
                'sale_id' => $sale->id,
                'customer_id' => $sale->customer_id,
                'invoice_number' => $numberService->next($sale->agency),
                'billing_type' => $billingType,
                'billing_snapshot' => $billing ?: [],
                'items' => $items,
                'created_by' => auth()->id(),
            ]
        ));

        if (!empty($items)) {
            $invoice->recalcFromItems(); // tutar kalemlerden hesaplanır
        }

        $invoice->save();

        SaleActivity::create([
            'agency_id' => $sale->agency_id,
            'sale_id' => $sale->id,
            'performed_by' => auth()->id(),
            'activity_type' => 'note',
            'subject' => 'Fatura Eklendi',
            'description' => "<b>{$invoice->invoice_number}</b> numaralı {$invoice->type_label} eklendi ("
                . number_format((float) $invoice->amount, 2, ',', '.') . " {$invoice->currency}).",
            'metadata' => ['invoice_id' => $invoice->id, 'invoice_number' => $invoice->invoice_number],
        ]);

        app(\App\Services\Task\AutoTaskService::class)->fireSafe('invoice_created', $invoice);

        return response()->json(array_merge(
            [
                'success' => true,
                'message' => 'Fatura eklendi: ' . $invoice->invoice_number,
                'next_number' => $numberService->next($sale->agency),
            ],
            $this->invoicesPayload($sale)
        ));
    }

    public function updateInvoiceStatus(Request $request, Sale $sale, SaleInvoice $invoice)
    {
        $this->authorizeAgency($sale);
        if ($invoice->sale_id !== $sale->id) {
            return response()->json(['success' => false, 'message' => 'Geçersiz fatura.'], 404);
        }

        $validated = $request->validate(['status' => 'required|in:draft,issued,paid,cancelled']);
        $wasCancelled = $invoice->status === 'cancelled';
        $invoice->update(['status' => $validated['status']]);

        if (! $wasCancelled && $invoice->status === 'cancelled') {
            app(\App\Services\Task\AutoTaskService::class)->fireSafe('invoice_cancelled', $invoice);
        }

        return response()->json(array_merge(
            ['success' => true, 'message' => 'Fatura durumu güncellendi.'],
            $this->invoicesPayload($sale)
        ));
    }

    public function destroyInvoice(Sale $sale, SaleInvoice $invoice)
    {
        $this->authorizeAgency($sale);
        if ($invoice->sale_id !== $sale->id) {
            return response()->json(['success' => false, 'message' => 'Geçersiz fatura.'], 404);
        }

        if ($invoice->file_path) {
            \Storage::disk('public')->delete($invoice->file_path);
        }
        $number = $invoice->invoice_number;
        $invoice->delete();

        SaleActivity::create([
            'agency_id' => $sale->agency_id,
            'sale_id' => $sale->id,
            'performed_by' => auth()->id(),
            'activity_type' => 'note',
            'subject' => 'Fatura Silindi',
            'description' => "<b>{$number}</b> numaralı fatura silindi.",
        ]);

        return response()->json(array_merge(
            ['success' => true, 'message' => 'Fatura silindi.'],
            $this->invoicesPayload($sale)
        ));
    }

    public function updateItems(Request $request, Sale $sale)
    {
        $this->authorizeAgency($sale);
        
        $validated = $request->validate([
            'items' => 'present|array',
            'items.*.id' => 'nullable|integer',
            'items.*.product_id' => 'nullable|integer',
            'items.*.name' => 'required|string|max:255',
            'items.*.quantity' => 'required|numeric|min:0',
            'items.*.unit_price' => 'required|numeric|min:0',
            'items.*.tax_rate' => 'required|numeric|min:0|max:100',
            'items.*.tax_included' => 'nullable|boolean',
            'items.*.discount_type' => 'nullable|in:percent,amount',
            'items.*.discount_value' => 'nullable|numeric|min:0',
            'items.*.currency' => 'required|string|max:3',
            // Genel indirim (özet bloğundan gelir; gönderilmezse dokunulmaz)
            'general_discount_type' => 'nullable|in:percent,amount',
            'general_discount_value' => 'nullable|numeric|min:0',
        ]);
        
        try {
            DB::beginTransaction();
            
            $sentIds = collect($validated['items'])->pluck('id')->filter()->toArray();
            
            $sale->items()->whereNotIn('id', $sentIds)->delete();
            
            foreach ($validated['items'] as $itemData) {
                $taxIncluded = filter_var($itemData['tax_included'] ?? false, FILTER_VALIDATE_BOOLEAN);
                $discountType = $itemData['discount_type'] ?? 'percent';
                $discountValue = (float) ($itemData['discount_value'] ?? 0);

                $break = SaleItem::breakdown(
                    (float) $itemData['quantity'],
                    (float) $itemData['unit_price'],
                    (float) $itemData['tax_rate'],
                    $taxIncluded,
                    $discountType,
                    $discountValue
                );

                $data = [
                    'product_id' => $itemData['product_id'] ?? null,
                    'product_type_id' => $itemData['product_id'] ? \App\Models\Product\Product::find($itemData['product_id'])->product_type_id : null,
                    'name' => $itemData['name'],
                    'quantity' => $itemData['quantity'],
                    'unit_price' => $itemData['unit_price'],
                    'tax_rate' => $itemData['tax_rate'],
                    'tax_included' => $taxIncluded,
                    'discount_type' => $discountType,
                    'discount_value' => $discountValue,
                    'discount_amount' => $break['discount'],
                    'tax_amount' => $break['tax'],
                    'total_price' => $break['total'],
                    'currency' => $itemData['currency'],
                ];

                if (!empty($itemData['id'])) {
                    $sale->items()->where('id', $itemData['id'])->update($data);
                } else {
                    $sale->items()->create($data);
                }
            }

            // Genel indirim: yüzde ise discount_rate, tutar ise discount_amount
            // olarak saklanır (calculateTotals aynı öncelikle hesaplar).
            if ($request->filled('general_discount_type')) {
                $gdValue = (float) ($validated['general_discount_value'] ?? 0);
                $sale->forceFill($validated['general_discount_type'] === 'percent'
                    ? ['discount_rate' => min($gdValue, 100), 'discount_amount' => 0]
                    : ['discount_rate' => 0, 'discount_amount' => $gdValue]
                )->save();
            }

            $sale->calculateTotals();
            
            SaleActivity::create([
                'agency_id' => $sale->agency_id,
                'sale_id' => $sale->id,
                'performed_by' => auth()->id(),
                'activity_type' => 'update',
                'subject' => 'Ürünler Güncellendi',
                'description' => 'Satış ürün listesi ve fiyat hesaplamaları güncellendi.',
            ]);
            
            DB::commit();

            \App\Models\Agency\ActivityLog::custom('update', 'Satış kalemleri toplu güncellendi (' . count($validated['items']) . ' kalem) — Satış #' . $sale->id, [
                'sale_id' => $sale->id,
                'item_count' => count($validated['items']),
            ]);

            return response()->json([
                'success' => true,
                'message' => 'Ürünler başarıyla güncellendi.',
            ]);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json([
                'success' => false,
                'message' => $e->getMessage()
            ], 500);
        }
    }

    protected function authorizeAgency(Sale $sale): void
    {
        if ($sale->agency_id !== $this->agencyId()) {
            abort(403, 'Bu rezervasyona erişim yetkiniz yok.');
        }
    }
}

