# GoogleMap Component - Final Architecture

## 📐 COMPONENT MİMARİSİ

### State Management

```typescript
// Map instance refs
const mapRef = useRef<HTMLDivElement>(null);
const mapInstanceRef = useRef<google.maps.Map | null>(null);

// Marker management
const markersRef = useRef<Map<string, google.maps.Marker>>(new Map());
const polylineRef = useRef<google.maps.Polyline | null>(null);
const infoWindowRef = useRef<google.maps.InfoWindow | null>(null);

// Center control
const hasCenteredRef = useRef(false); // ✅ Center sadece 1 kez

// Loading state
const [isScriptLoaded, setIsScriptLoaded] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
```

---

## 🔄 EFFECT LIFECYCLE

### Effect 1: Google Maps Script Loading

**Dependency:** `[]` (mount only)

**Sorumluluklar:**
- Google Maps script'ini yükle
- `isScriptLoaded` state'ini güncelle
- Error handling

```typescript
useEffect(() => {
  loadGoogleMapsScript()
    .then(() => setIsScriptLoaded(true))
    .catch((error) => {
      console.error('Google Maps yükleme hatası:', error);
      setLoadError('Harita yüklenemedi. Lütfen sayfayı yenileyin.');
    });
}, []);
```

---

### Effect 2: Map Initialization

**Dependency:** `[isScriptLoaded, places]`

**Sorumluluklar:**
- Map instance'ı oluştur (sadece 1 kez)
- Initial center hesapla (places varsa ilk place, yoksa default)
- InfoWindow oluştur
- hasCenteredRef'i set et

```typescript
useEffect(() => {
  if (!mapRef.current || !isScriptLoaded || !window.google) return;
  if (mapInstanceRef.current) return; // ✅ Zaten oluşturulmuş

  try {
    const initialCenter = places.length > 0 
      ? { lat: places[0].lat, lng: places[0].lng }
      : { lat: 38.9637, lng: 35.2433 };

    const mapInstance = new google.maps.Map(mapRef.current, {
      center: initialCenter,
      zoom: 12,
      // ... map options
    });

    mapInstanceRef.current = mapInstance;
    infoWindowRef.current = new google.maps.InfoWindow();
    hasCenteredRef.current = true;
  } catch (error) {
    console.error('Harita başlatma hatası:', error);
    setLoadError('Harita oluşturulamadı.');
  }

  return () => {
    // Cleanup: Sadece unmount'ta
    markersRef.current.forEach(marker => marker.setMap(null));
    markersRef.current.clear();
    if (polylineRef.current) {
      polylineRef.current.setMap(null);
    }
  };
}, [isScriptLoaded, places]);
```

---

### Effect 3: Marker Creation/Deletion

**Dependency:** `[places]` ⚠️ SADECE places

**Sorumluluklar:**
- Artık olmayan marker'ları sil
- Yeni marker'ları oluştur (zaten varsa ATLA)
- Event listener'ları ekle (click, hover)
- Auto-fit bounds (sadece ilk kez - hasCenteredRef)

**ÖNEMLİ:**
- ❌ onMarkerClick/onMarkerHover dependency DEĞİL
- ❌ hoveredPlaceId/selectedPlaceId/activeDayId dependency DEĞİL
- ✅ Marker recreation SADECE places değiştiğinde

```typescript
useEffect(() => {
  if (!mapInstanceRef.current || !window.google) return;

  const map = mapInstanceRef.current;
  const currentPlaceIds = new Set(places.map(p => p.id));

  // 1. Artık olmayan marker'ları sil
  markersRef.current.forEach((marker, id) => {
    if (!currentPlaceIds.has(id)) {
      marker.setMap(null);
      markersRef.current.delete(id);
    }
  });

  // 2. Yeni marker'ları oluştur (zaten varsa ATLA)
  places.forEach((place) => {
    if (markersRef.current.has(place.id)) return; // ⚠️ ATLA

    const label = `${(place.orderIndex || 0) + 1}`;

    const marker = new google.maps.Marker({
      position: { lat: place.lat, lng: place.lng },
      map: map,
      title: place.title,
      label: { text: label, color: 'white', fontSize: '14px', fontWeight: 'bold' },
      icon: createMarkerIcon(place.dayIndex || 0, 'default'),
      zIndex: place.orderIndex || 0,
    });

    // Event listeners (closure içinde callback'leri yakala)
    marker.addListener('click', () => {
      if (onMarkerClick) onMarkerClick(place.id);
      // ... info window, pan
    });

    marker.addListener('mouseover', () => {
      if (onMarkerHover) onMarkerHover(place.id);
    });

    marker.addListener('mouseout', () => {
      if (onMarkerHover) onMarkerHover(null);
    });

    markersRef.current.set(place.id, marker);
  });

  // 3. Auto-fit bounds (sadece ilk kez)
  if (places.length > 0 && !hasCenteredRef.current) {
    const bounds = new google.maps.LatLngBounds();
    places.forEach(place => bounds.extend({ lat: place.lat, lng: place.lng }));
    map.fitBounds(bounds);
    
    const listener = google.maps.event.addListenerOnce(map, 'idle', () => {
      const currentZoom = map.getZoom();
      if (currentZoom && currentZoom > 15) {
        map.setZoom(15);
      }
    });
    
    hasCenteredRef.current = true;
  }

  return () => {
    // Cleanup: Sadece unmount'ta
    markersRef.current.forEach(marker => marker.setMap(null));
    markersRef.current.clear();
  };
}, [places]); // ⚠️ SADECE places
```

---

### Effect 4: Visual Updates (Icon, Visibility, Animation)

**Dependency:** `[hoveredPlaceId, selectedPlaceId, activeDayId, places]`

**Sorumluluklar:**
- Marker visibility güncelle (activeDayId)
- Marker icon güncelle (hover/select state)
- Marker zIndex güncelle
- Marker animation güncelle
- Marker label güncelle

**ÖNEMLİ:**
- ❌ Bu effect marker OLUŞTURMAZ
- ✅ Sadece mevcut marker'ları GÜNCELLER
- ✅ setIcon, setVisible, setZIndex, setAnimation, setLabel

```typescript
useEffect(() => {
  if (!mapInstanceRef.current || !window.google) return;

  markersRef.current.forEach((marker, id) => {
    const place = places.find(p => p.id === id);
    if (!place) return;

    // 1. Visibility kontrolü
    const isVisible = !activeDayId || place.dayId === activeDayId;
    marker.setVisible(isVisible);

    // 2. State belirleme
    let state: 'default' | 'hover' | 'selected' = 'default';
    
    if (id === selectedPlaceId) {
      state = 'selected';
      marker.setZIndex(1000);
      marker.setAnimation(google.maps.Animation.BOUNCE);
      setTimeout(() => marker.setAnimation(null), 2000);
    } else if (id === hoveredPlaceId) {
      state = 'hover';
      marker.setZIndex(999);
      marker.setAnimation(null);
    } else {
      marker.setZIndex(place.orderIndex || 0);
      marker.setAnimation(null);
    }

    // 3. Icon güncelleme (sadece style - size/anchor sabit)
    marker.setIcon(createMarkerIcon(place.dayIndex || 0, state));
    
    // 4. Label güncelleme
    const label = `${(place.orderIndex || 0) + 1}`;
    marker.setLabel({
      text: label,
      color: 'white',
      fontSize: state === 'default' ? '14px' : '16px',
      fontWeight: 'bold'
    });
  });
}, [hoveredPlaceId, selectedPlaceId, activeDayId, places]);
```

---

### Effect 5: Polyline Update

**Dependency:** `[places, activeDayId, showPolyline]`

**Sorumluluklar:**
- Polyline oluştur/güncelle
- activeDayId'ye göre filtrele
- Gün renklerini uygula

```typescript
useEffect(() => {
  if (!mapInstanceRef.current || !showPolyline) return;

  const map = mapInstanceRef.current;

  // Eski polyline'ı sil
  if (polylineRef.current) {
    polylineRef.current.setMap(null);
  }

  // activeDayId varsa sadece o günün place'lerini al
  let filteredPlaces = places;
  if (activeDayId) {
    filteredPlaces = places.filter(p => p.dayId === activeDayId);
  }

  if (filteredPlaces.length < 2) return;

  // Polyline path oluştur
  const path = filteredPlaces.map(p => ({ lat: p.lat, lng: p.lng }));

  // Polyline rengi (ilk place'in günü)
  const firstPlace = filteredPlaces[0];
  const dayColor = getDayColor(firstPlace.dayIndex || 0);

  const polyline = new google.maps.Polyline({
    path: path,
    geodesic: true,
    strokeColor: dayColor.stroke,
    strokeOpacity: 0.8,
    strokeWeight: 3,
    map: map,
  });

  polylineRef.current = polyline;
}, [places, activeDayId, showPolyline]);
```

---

## 🎨 HELPER FUNCTIONS

### getDayColor

**Sorumluluk:** Gün index'ine göre renk döndür

```typescript
const getDayColor = (dayIndex: number): { fill: string; stroke: string } => {
  const colors = [
    { fill: '#f97316', stroke: '#ea580c' }, // Turuncu (Gün 1)
    { fill: '#3b82f6', stroke: '#2563eb' }, // Mavi (Gün 2)
    { fill: '#10b981', stroke: '#059669' }, // Yeşil (Gün 3)
    { fill: '#8b5cf6', stroke: '#7c3aed' }, // Mor (Gün 4)
    { fill: '#ec4899', stroke: '#db2777' }, // Pembe (Gün 5)
    { fill: '#f59e0b', stroke: '#d97706' }, // Sarı (Gün 6)
    { fill: '#06b6d4', stroke: '#0891b2' }, // Cyan (Gün 7)
  ];
  return colors[dayIndex % colors.length];
};
```

---

### createMarkerIcon

**Sorumluluk:** Marker icon oluştur (size/anchor SABİT)

```typescript
const createMarkerIcon = (
  dayIndex: number,
  state: 'default' | 'hover' | 'selected'
) => {
  const scale = 20; // ⚠️ SABİT
  const color = getDayColor(dayIndex);
  const fillColor = state === 'default' ? color.fill : color.stroke;
  
  return {
    path: google.maps.SymbolPath.CIRCLE,
    scale: scale, // ⚠️ SABİT
    fillColor: fillColor,
    fillOpacity: 1,
    strokeColor: 'white',
    strokeWeight: state === 'selected' ? 4 : 3,
    anchor: new google.maps.Point(0, 0), // ⚠️ SABİT anchor
    labelOrigin: new google.maps.Point(0, 0),
  };
};
```

---

## 📊 DATA FLOW

### TripPlanner → GoogleMap

```typescript
// TripPlanner.tsx
const allPlaces = trip?.days?.flatMap((day: any, dayIndex: number) => {
  return day.places?.map((place: any, orderIndex: number) => ({
    id: place.id,
    lat: place.position.lat,
    lng: place.position.lng,
    dayId: day.id,
    dayIndex: dayIndex,
    orderIndex: orderIndex,
    title: place.name,
    // ✅ Color YOK - GoogleMap içinde hesaplanacak
  })) || [];
}) || [];

<GoogleMap 
  places={allPlaces}
  hoveredPlaceId={hoveredPlaceId}
  selectedPlaceId={selectedPlaceId}
  activeDayId={activeDayId}
  onMarkerClick={handleMarkerClick}
  onMarkerHover={handleMarkerHover}
  showPolyline={true}
/>
```

---

### GoogleMap Props

```typescript
interface PlaceData {
  id: string;
  lat: number;
  lng: number;
  dayId?: string;
  dayIndex?: number;
  orderIndex?: number;
  title: string;
  // ❌ color YOK
}

interface GoogleMapProps {
  places?: PlaceData[];
  // ❌ center YOK
  // ❌ zoom YOK
  className?: string;
  hoveredPlaceId?: string | null;
  selectedPlaceId?: string | null;
  activeDayId?: string | null;
  onMarkerClick?: (placeId: string) => void;
  onMarkerHover?: (placeId: string | null) => void; // ❌ dayId YOK
  showPolyline?: boolean;
}
```

---

## 🔄 INTERACTION FLOW

### 1. User Hovers Timeline Place

```
Timeline Place Hover
  ↓
TripPlanner: setHoveredPlaceId(placeId)
  ↓
GoogleMap: hoveredPlaceId prop değişir
  ↓
Visual Update Effect tetiklenir
  ↓
Marker: setIcon (hover state)
Marker: setZIndex (999)
Marker: setLabel (16px)
```

**Önemli:**
- ❌ activeDayId DEĞİŞMEZ
- ❌ Marker yeniden oluşturulmaz
- ✅ Sadece görsel güncelleme

---

### 2. User Clicks Timeline Place

```
Timeline Place Click
  ↓
TripPlanner: setSelectedPlaceId(placeId)
  ↓
GoogleMap: selectedPlaceId prop değişir
  ↓
Visual Update Effect tetiklenir
  ↓
Marker: setIcon (selected state)
Marker: setZIndex (1000)
Marker: setAnimation (BOUNCE)
Marker: setLabel (16px)
```

**Önemli:**
- ❌ Marker yeniden oluşturulmaz
- ✅ Sadece görsel güncelleme
- ✅ 2 saniye sonra animation durur

---

### 3. User Opens Day Accordion

```
Timeline Day Accordion Open
  ↓
TripPlanner: setActiveDayId(dayId)
  ↓
GoogleMap: activeDayId prop değişir
  ↓
Visual Update Effect tetiklenir
  ↓
Marker: setVisible (dayId === activeDayId)
```

**Önemli:**
- ❌ Marker silinmez
- ✅ Sadece gizlenir/gösterilir
- ✅ Smooth visibility toggle

---

### 4. User Hovers Map Marker

```
Map Marker Hover
  ↓
Marker: mouseover event
  ↓
onMarkerHover(placeId) callback
  ↓
TripPlanner: setHoveredPlaceId(placeId)
  ↓
GoogleMap: hoveredPlaceId prop değişir
  ↓
Visual Update Effect tetiklenir
  ↓
Marker: setIcon (hover state)
```

**Önemli:**
- ❌ activeDayId DEĞİŞMEZ
- ✅ Sadece hoveredPlaceId değişir
- ✅ Timeline'da place highlight olur

---

### 5. User Clicks Map Marker

```
Map Marker Click
  ↓
Marker: click event
  ↓
onMarkerClick(placeId) callback
  ↓
TripPlanner: setSelectedPlaceId(placeId)
  ↓
GoogleMap: selectedPlaceId prop değişir
  ↓
Visual Update Effect tetiklenir
  ↓
Marker: setIcon (selected state)
Marker: setAnimation (BOUNCE)
  ↓
InfoWindow: open
Map: panTo marker
```

---

## ✅ BEST PRACTICES

### 1. Marker Lifecycle

✅ **DO:**
- Marker'ları SADECE places değiştiğinde oluştur
- Mevcut marker'ları useRef içinde sakla
- Görsel güncellemeler için ayrı effect kullan
- Event listener'ları marker creation sırasında ekle

❌ **DON'T:**
- Marker'ları hover/select'te yeniden oluşturma
- onMarkerClick/onMarkerHover'ı dependency'ye ekleme
- Her effect'te marker loop yapma

---

### 2. Center Management

✅ **DO:**
- hasCenteredRef kullan
- Center'ı sadece 1 kez ayarla
- Kullanıcı zoom/pan'i koru

❌ **DON'T:**
- Her places değişiminde fitBounds çağırma
- Kullanıcı zoom/pan'i resetleme

---

### 3. Visual Updates

✅ **DO:**
- setIcon, setVisible, setZIndex, setAnimation kullan
- Marker size/anchor'ı sabit tut
- State'e göre sadece color değiştir

❌ **DON'T:**
- Marker'ı yeniden oluşturma
- Size/anchor değiştirme (jitter)
- Gereksiz DOM manipulation

---

### 4. Performance

✅ **DO:**
- Effect dependency'lerini minimize et
- Marker recreation'ı önle
- Memory leak'leri temizle

❌ **DON'T:**
- Gereksiz effect tetikleme
- Marker'ları silmeden bırakma
- Çok fazla dependency ekleme

---

## 🎯 SONUÇ

GoogleMap component'i optimal marker lifecycle yönetimi ile:

✅ **Performans:**
- Marker recreation: Minimal (sadece places değişiminde)
- Effect execution: Optimize edilmiş
- Memory kullanımı: Stabil

✅ **Kullanıcı Deneyimi:**
- Smooth hover/select transitions
- Marker jitter YOK
- Map zoom/pan korunuyor
- Responsive interactions

✅ **Kod Kalitesi:**
- Clean separation of concerns
- Predictable lifecycle
- Easy to maintain
- Well documented

**GoogleMap component production-ready!** 🎉
