@charzing/vehicle-utils 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ import { Firestore } from 'firebase/firestore';
2
+
1
3
  /**
2
4
  * 브랜드 관련 타입 정의
3
5
  */
@@ -79,7 +81,7 @@ type ModelSlug = string;
79
81
  /**
80
82
  * 모델 매핑 결과
81
83
  */
82
- interface ModelMapping {
84
+ interface ModelMapping$1 {
83
85
  /** Firestore ID (소문자, 하이픈) */
84
86
  firestoreId: ModelId;
85
87
  /** 한글 모델명 */
@@ -450,12 +452,63 @@ declare function getStorageBrandName(brandId: BrandId): StorageBrandName;
450
452
  * // }
451
453
  * ```
452
454
  */
453
- declare function getBrandMapping(brandInput: string): BrandMapping;
455
+ declare function getBrandMapping$1(brandInput: string): BrandMapping;
454
456
  /**
455
457
  * 지원되는 모든 브랜드 ID 목록
456
458
  */
457
459
  declare const SUPPORTED_BRANDS: BrandId[];
458
460
 
461
+ /**
462
+ * 동적 브랜드 매핑 시스템
463
+ *
464
+ * Firestore에서 실시간으로 브랜드 정보를 조회하여 캐싱
465
+ * 새로운 브랜드 추가 시 코드 수정 불필요
466
+ */
467
+
468
+ /**
469
+ * Firestore에서 브랜드 정보를 조회하여 캐시 초기화
470
+ *
471
+ * @param firestore - Firestore 인스턴스
472
+ *
473
+ * @example
474
+ * ```typescript
475
+ * import { initializeBrandMappings } from '@charzing/vehicle-utils';
476
+ * import { db } from './firebase';
477
+ *
478
+ * // 앱 시작 시 한 번만 호출
479
+ * await initializeBrandMappings(db);
480
+ * ```
481
+ */
482
+ declare function initializeBrandMappings(firestore: Firestore): Promise<void>;
483
+ /**
484
+ * 브랜드 매핑 정보 조회
485
+ *
486
+ * @param brandInput - 브랜드명 (한글/영문/ID 모두 가능)
487
+ * @returns 브랜드 매핑 정보 또는 null
488
+ *
489
+ * @throws {Error} 초기화되지 않은 경우
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * getBrandMapping('현대'); // { firestoreId: 'hyundai', ... }
494
+ * getBrandMapping('HYUNDAI'); // { firestoreId: 'hyundai', ... }
495
+ * getBrandMapping('hyundai'); // { firestoreId: 'hyundai', ... }
496
+ * ```
497
+ */
498
+ declare function getBrandMapping(brandInput: string): BrandMapping | null;
499
+ /**
500
+ * 초기화 상태 확인
501
+ */
502
+ declare function isInitialized$1(): boolean;
503
+ /**
504
+ * 캐시 초기화 (테스트용)
505
+ */
506
+ declare function resetCache$1(): void;
507
+ /**
508
+ * 캐시된 브랜드 수
509
+ */
510
+ declare function getCachedBrandCount(): number;
511
+
459
512
  /**
460
513
  * 모델 매핑 유틸리티
461
514
  *
@@ -559,156 +612,103 @@ declare function getModelSlug(modelInput: string): ModelSlug;
559
612
  * // }
560
613
  * ```
561
614
  */
562
- declare function getModelMapping(modelInput: string, englishName?: string, koreanName?: string): ModelMapping;
615
+ declare function getModelMapping$1(modelInput: string, englishName?: string, koreanName?: string): ModelMapping$1;
563
616
 
564
617
  /**
565
- * 이미지 파일명 생성 유틸리티
618
+ * 동적 모델 매핑 시스템
566
619
  *
567
- * 표준 파일명 규칙: {brandSlug}__{modelSlug}__{year}__{trimSlug}.png
620
+ * Firestore에서 실시간으로 모델 정보를 조회하여 캐싱
621
+ * 새로운 모델 추가 시 코드 수정 불필요
568
622
  */
569
623
 
570
624
  /**
571
- * 차량 이미지 파일명 생성
572
- *
573
- * @param params - 차량 이미지 파라미터
574
- * @param extension - 파일 확장자 (기본: 'png')
575
- * @returns 이미지 파일명
576
- *
577
- * @example
578
- * ```typescript
579
- * generateImageFilename({
580
- * brand: 'hyundai',
581
- * model: 'ioniq-5',
582
- * year: 2024
583
- * });
584
- * // 'hyundai__ioniq-5__2024.png'
585
- *
586
- * generateImageFilename({
587
- * brand: 'mini',
588
- * model: 'cooper',
589
- * year: 2025,
590
- * trim: 'jcw'
591
- * });
592
- * // 'mini__cooper__2025__jcw.png'
593
- *
594
- * generateImageFilename({
595
- * brand: 'Tesla',
596
- * model: 'Model 3',
597
- * year: 2024,
598
- * trim: 'Highland'
599
- * }, 'webp');
600
- * // 'tesla__model-3__2024__highland.webp'
601
- * ```
625
+ * 모델 매핑 정보
602
626
  */
603
- declare function generateImageFilename(params: VehicleImageParams, extension?: 'png' | 'jpg' | 'webp'): ImageFilename;
627
+ interface ModelMapping {
628
+ firestoreId: string;
629
+ koreanName: string;
630
+ englishName: string;
631
+ storageName: string;
632
+ brandId: string;
633
+ }
604
634
  /**
605
- * 파일명에서 차량 정보 추출
635
+ * Firestore에서 모델 정보를 조회하여 캐시 초기화
606
636
  *
607
- * @param filename - 이미지 파일명
608
- * @returns 차량 정보 또는 null
637
+ * @param firestore - Firestore 인스턴스
638
+ * @param brandIds - 초기화할 브랜드 ID 목록 (생략 시 모든 브랜드)
609
639
  *
610
640
  * @example
611
641
  * ```typescript
612
- * parseImageFilename('hyundai__ioniq-5__2024.png');
613
- * // { brand: 'hyundai', model: 'ioniq-5', year: 2024 }
642
+ * // 모든 브랜드의 모델 초기화
643
+ * await initializeModelMappings(db);
614
644
  *
615
- * parseImageFilename('mini__cooper__2025__jcw.png');
616
- * // { brand: 'mini', model: 'cooper', year: 2025, trim: 'jcw' }
645
+ * // 특정 브랜드만 초기화
646
+ * await initializeModelMappings(db, ['hyundai', 'kia']);
617
647
  * ```
618
648
  */
619
- declare function parseImageFilename(filename: string): VehicleImageParams | null;
649
+ declare function initializeModelMappings(firestore: Firestore, brandIds?: string[]): Promise<void>;
620
650
  /**
621
- * 파일명 검증
651
+ * 모델 매핑 정보 조회
652
+ *
653
+ * @param brandId - 브랜드 ID
654
+ * @param modelInput - 모델명 (한글/영문/ID 모두 가능)
655
+ * @returns 모델 매핑 정보 또는 null
622
656
  *
623
- * @param filename - 검증할 파일명
624
- * @returns 유효 여부
657
+ * @throws {Error} 초기화되지 않은 경우
625
658
  *
626
659
  * @example
627
660
  * ```typescript
628
- * isValidImageFilename('hyundai__ioniq-5__2024.png'); // true
629
- * isValidImageFilename('invalid-filename.png'); // false
661
+ * getModelMapping('hyundai', '아이오닉 5'); // { firestoreId: 'ioniq-5', ... }
662
+ * getModelMapping('hyundai', 'IONIQ-5'); // { firestoreId: 'ioniq-5', ... }
663
+ * getModelMapping('hyundai', 'ioniq-5'); // { firestoreId: 'ioniq-5', ... }
630
664
  * ```
631
665
  */
632
- declare function isValidImageFilename(filename: string): boolean;
633
-
666
+ declare function getModelMapping(brandId: string, modelInput: string): ModelMapping | null;
634
667
  /**
635
- * 이미지 URL 생성 유틸리티
668
+ * 브랜드의 모든 모델 조회
636
669
  *
637
- * Firebase Storage URL 생성 및 Fallback 처리
670
+ * @param brandId - 브랜드 ID
671
+ * @returns 모델 매핑 배열
672
+ */
673
+ declare function getBrandModels(brandId: string): ModelMapping[];
674
+ /**
675
+ * 초기화 상태 확인
676
+ */
677
+ declare function isInitialized(): boolean;
678
+ /**
679
+ * 캐시 초기화 (테스트용)
680
+ */
681
+ declare function resetCache(): void;
682
+ /**
683
+ * 캐시된 모델 수
638
684
  */
685
+ declare function getCachedModelCount(): number;
639
686
 
640
687
  /**
641
- * 차량 이미지 URL 생성
688
+ * Firebase Storage 차량 이미지 URL/경로 생성 유틸리티
642
689
  *
643
- * @param params - 차량 이미지 파라미터
644
- * @param options - URL 생성 옵션
645
- * @returns 이미지 다운로드 URL
646
- *
647
- * @example
648
- * ```typescript
649
- * generateVehicleImageUrl({
650
- * brand: 'hyundai',
651
- * model: 'ioniq-5',
652
- * year: 2024
653
- * });
654
- * // 'https://firebasestorage.googleapis.com/v0/b/charzing-d1600.firebasestorage.app/o/
655
- * // vehicle-images%2FHYUNDAI%2FIONIQ-5%2F2024%2Fhyundai__ioniq-5__2024.png?alt=media'
656
- *
657
- * generateVehicleImageUrl({
658
- * brand: 'mini',
659
- * model: 'cooper',
660
- * year: 2025,
661
- * trim: 'jcw'
662
- * });
663
- * // 'https://firebasestorage.googleapis.com/v0/b/charzing-d1600.firebasestorage.app/o/
664
- * // vehicle-images%2FMINI%2FCOOPER%2F2025%2Fmini__cooper__2025__jcw.png?alt=media'
665
- * ```
690
+ * ⚠️ 주의: 파일은 순수 함수만 포함합니다.
691
+ * Firebase SDK나 업로드 로직은 포함하지 않습니다.
666
692
  */
667
- declare function generateVehicleImageUrl(params: VehicleImageParams, options?: ImageUrlOptions): string;
693
+ interface ImagePathParams {
694
+ brandId: string;
695
+ modelId: string;
696
+ year: number;
697
+ trim?: string;
698
+ }
668
699
  /**
669
- * 차량 이미지 경로 정보 가져오기
670
- *
671
- * @param params - 차량 이미지 파라미터
672
- * @param options - URL 생성 옵션
673
- * @returns 이미지 경로 정보
674
- *
675
- * @example
676
- * ```typescript
677
- * getVehicleImagePath({
678
- * brand: 'hyundai',
679
- * model: 'ioniq-5',
680
- * year: 2024
681
- * });
682
- * // {
683
- * // fullPath: 'vehicle-images/HYUNDAI/IONIQ-5/2024/hyundai__ioniq-5__2024.png',
684
- * // directory: 'vehicle-images/HYUNDAI/IONIQ-5/2024',
685
- * // filename: 'hyundai__ioniq-5__2024.png',
686
- * // downloadUrl: 'https://...'
687
- * // }
688
- * ```
700
+ * 차량 이미지 파일명 생성
701
+ * 예: kia__niro-ev__2022__standard.png
689
702
  */
690
- declare function getVehicleImagePath(params: VehicleImageParams, options?: ImageUrlOptions): ImagePath;
703
+ declare function generateImageFilename(params: ImagePathParams): string;
691
704
  /**
692
- * Fallback 이미지 URL 목록 생성
693
- *
694
- * @param params - 차량 이미지 파라미터
695
- * @param options - URL 생성 옵션
696
- * @returns Fallback URL 목록 (우선순위 순)
697
- *
698
- * @example
699
- * ```typescript
700
- * generateFallbackUrls({
701
- * brand: 'mini',
702
- * model: 'cooper',
703
- * year: 2025,
704
- * trim: 'jcw'
705
- * });
706
- * // [
707
- * // 'https://.../mini__cooper__2025__jcw.png', // 트림 이미지
708
- * // 'https://.../mini__cooper__2025.png', // 기본 모델 이미지
709
- * // ]
710
- * ```
705
+ * Storage 경로 생성 (업로드용)
706
+ * 예: vehicle-images/KIA/NIRO-EV/2022/kia__niro-ev__2022__standard.png
707
+ */
708
+ declare function generateStoragePath(params: ImagePathParams): string;
709
+ /**
710
+ * 완전한 Firebase Storage URL 생성 (읽기용)
711
711
  */
712
- declare function generateFallbackUrls(params: VehicleImageParams, options?: ImageUrlOptions): string[];
712
+ declare function generateVehicleImageUrl(params: ImagePathParams): string;
713
713
 
714
- export { type BatteryInfo, type Brand, type BrandId, type BrandMapping, type CompletedVehicle, type DriveType, type FirebaseStorageConfig, type ImageFallbackOptions, type ImageFallbackStrategy, type ImageFilename, type ImagePath, type ImageUrlOptions, type Model, type ModelId, type ModelMapping, type ModelSlug, SUPPORTED_BRANDS, type StorageBrandName, type TrimId, type TrimMapping, type TrimSlug, type UserVehicle, type VehicleDetails, type VehicleImageParams, type VehicleModel, type VehicleTrim, type VehicleVariant, convertKoreanToEnglish, findBestModelMatch, generateFallbackUrls, generateImageFilename, generateVehicleImageUrl, getBrandMapping, getBrandNameEnglish, getBrandNameKorean, getModelMapping, getModelSlug, getStorageBrandName, getVehicleImagePath, isValidImageFilename, normalizeBrandId, normalizeModelId, parseImageFilename, toSlug };
714
+ export { type BatteryInfo, type Brand, type BrandId, type BrandMapping, type CompletedVehicle, type DriveType, type FirebaseStorageConfig, type ImageFallbackOptions, type ImageFallbackStrategy, type ImageFilename, type ImagePath, type ImagePathParams, type ImageUrlOptions, type Model, type ModelId, type ModelMapping$1 as ModelMapping, type ModelSlug, SUPPORTED_BRANDS, type StorageBrandName, type TrimId, type TrimMapping, type TrimSlug, type UserVehicle, type VehicleDetails, type VehicleImageParams, type VehicleModel, type VehicleTrim, type VehicleVariant, convertKoreanToEnglish, findBestModelMatch, generateImageFilename, generateStoragePath, generateVehicleImageUrl, getBrandMapping$1 as getBrandMapping, getBrandModels, getBrandNameEnglish, getBrandNameKorean, getCachedBrandCount, getCachedModelCount, getBrandMapping as getDynamicBrandMapping, getModelMapping as getDynamicModelMapping, getModelMapping$1 as getModelMapping, getModelSlug, getStorageBrandName, initializeBrandMappings, initializeModelMappings, isInitialized$1 as isBrandMappingInitialized, isInitialized as isModelMappingInitialized, normalizeBrandId, normalizeModelId, resetCache$1 as resetBrandCache, resetCache as resetModelCache, toSlug };
@@ -115,6 +115,76 @@ var SUPPORTED_BRANDS = [
115
115
  "porsche"
116
116
  ];
117
117
 
118
+ // src/brand/dynamic-mapping.ts
119
+ import { collection, getDocs } from "firebase/firestore";
120
+ var brandCache = /* @__PURE__ */ new Map();
121
+ var initialized = false;
122
+ async function initializeBrandMappings(firestore) {
123
+ if (initialized) {
124
+ console.log("[Brand Mapping] Already initialized");
125
+ return;
126
+ }
127
+ console.log("[Brand Mapping] Initializing from Firestore...");
128
+ try {
129
+ const vehiclesRef = collection(firestore, "vehicles");
130
+ const snapshot = await getDocs(vehiclesRef);
131
+ snapshot.docs.forEach((doc) => {
132
+ const data = doc.data();
133
+ let storageName = data.englishName?.toUpperCase() || doc.id.toUpperCase();
134
+ if (doc.id === "mercedes-benz" || doc.id === "mercedes-maybach") {
135
+ storageName = "BENZ";
136
+ }
137
+ const mapping = {
138
+ firestoreId: doc.id,
139
+ koreanName: data.name || doc.id,
140
+ englishName: data.englishName || doc.id.toUpperCase(),
141
+ storageName
142
+ };
143
+ brandCache.set(doc.id, mapping);
144
+ brandCache.set(doc.id.toUpperCase(), mapping);
145
+ brandCache.set(doc.id.toLowerCase(), mapping);
146
+ brandCache.set(data.name, mapping);
147
+ brandCache.set(data.englishName, mapping);
148
+ brandCache.set(data.englishName?.toUpperCase(), mapping);
149
+ brandCache.set(data.englishName?.toLowerCase(), mapping);
150
+ });
151
+ initialized = true;
152
+ console.log(`[Brand Mapping] \u2705 Initialized with ${snapshot.docs.length} brands`);
153
+ } catch (error) {
154
+ console.error("[Brand Mapping] \u274C Initialization failed:", error);
155
+ throw error;
156
+ }
157
+ }
158
+ function getBrandMapping2(brandInput) {
159
+ if (!initialized) {
160
+ throw new Error(
161
+ "[Brand Mapping] Not initialized. Call initializeBrandMappings() first."
162
+ );
163
+ }
164
+ let mapping = brandCache.get(brandInput);
165
+ if (mapping) return mapping;
166
+ mapping = brandCache.get(brandInput.toLowerCase());
167
+ if (mapping) return mapping;
168
+ mapping = brandCache.get(brandInput.toUpperCase());
169
+ if (mapping) return mapping;
170
+ const trimmed = brandInput.trim();
171
+ mapping = brandCache.get(trimmed);
172
+ if (mapping) return mapping;
173
+ console.warn(`[Brand Mapping] Unknown brand: "${brandInput}"`);
174
+ return null;
175
+ }
176
+ function isInitialized() {
177
+ return initialized;
178
+ }
179
+ function resetCache() {
180
+ brandCache.clear();
181
+ initialized = false;
182
+ console.log("[Brand Mapping] Cache reset");
183
+ }
184
+ function getCachedBrandCount() {
185
+ return brandCache.size;
186
+ }
187
+
118
188
  // src/model/mapping.ts
119
189
  var KOREAN_TO_ENGLISH_MODEL = {
120
190
  // MINI
@@ -200,114 +270,188 @@ function getModelMapping(modelInput, englishName, koreanName) {
200
270
  };
201
271
  }
202
272
 
203
- // src/image/filename-generator.ts
204
- function generateImageFilename(params, extension = "png") {
205
- const brandSlug = toSlug(params.brand);
206
- const modelSlug = toSlug(params.model);
207
- const year = params.year;
208
- if (params.trim) {
209
- const trimSlug = toSlug(params.trim);
210
- return `${brandSlug}__${modelSlug}__${year}__${trimSlug}.${extension}`;
211
- } else {
212
- return `${brandSlug}__${modelSlug}__${year}.${extension}`;
273
+ // src/model/dynamic-mapping.ts
274
+ import { collection as collection2, getDocs as getDocs2 } from "firebase/firestore";
275
+ var modelCache = /* @__PURE__ */ new Map();
276
+ var brandModelsCache = /* @__PURE__ */ new Map();
277
+ var initialized2 = false;
278
+ async function initializeModelMappings(firestore, brandIds) {
279
+ console.log("[Model Mapping] Initializing from Firestore...");
280
+ try {
281
+ const vehiclesRef = collection2(firestore, "vehicles");
282
+ const brandsSnapshot = await getDocs2(vehiclesRef);
283
+ let processedBrands = 0;
284
+ let processedModels = 0;
285
+ for (const brandDoc of brandsSnapshot.docs) {
286
+ const brandId = brandDoc.id;
287
+ if (brandIds && !brandIds.includes(brandId)) {
288
+ continue;
289
+ }
290
+ const modelsRef = collection2(firestore, "vehicles", brandId, "models");
291
+ const modelsSnapshot = await getDocs2(modelsRef);
292
+ const brandModels = [];
293
+ modelsSnapshot.docs.forEach((modelDoc) => {
294
+ const data = modelDoc.data();
295
+ const mapping = {
296
+ firestoreId: modelDoc.id,
297
+ koreanName: data.name || modelDoc.id,
298
+ englishName: data.englishName || modelDoc.id.toUpperCase(),
299
+ storageName: data.englishName?.toUpperCase() || modelDoc.id.toUpperCase(),
300
+ brandId
301
+ };
302
+ brandModels.push(mapping);
303
+ processedModels++;
304
+ const keys = [
305
+ `${brandId}:${modelDoc.id}`,
306
+ // "hyundai:ioniq-5"
307
+ `${brandId}:${data.name}`,
308
+ // "hyundai:아이오닉 5"
309
+ `${brandId}:${data.englishName}`,
310
+ // "hyundai:IONIQ-5"
311
+ `${brandId}:${data.englishName?.toLowerCase()}`,
312
+ // "hyundai:ioniq-5"
313
+ `${brandId}:${modelDoc.id.toUpperCase()}`
314
+ // "hyundai:IONIQ-5"
315
+ ];
316
+ keys.forEach((key) => {
317
+ if (key && !key.includes("undefined")) {
318
+ modelCache.set(key, mapping);
319
+ }
320
+ });
321
+ });
322
+ brandModelsCache.set(brandId, brandModels);
323
+ processedBrands++;
324
+ }
325
+ initialized2 = true;
326
+ console.log(
327
+ `[Model Mapping] \u2705 Initialized: ${processedBrands} brands, ${processedModels} models`
328
+ );
329
+ } catch (error) {
330
+ console.error("[Model Mapping] \u274C Initialization failed:", error);
331
+ throw error;
213
332
  }
214
333
  }
215
- function parseImageFilename(filename) {
216
- const nameWithoutExt = filename.replace(/\.(png|jpg|webp)$/, "");
217
- const parts = nameWithoutExt.split("__");
218
- if (parts.length < 3) {
219
- return null;
334
+ function getModelMapping2(brandId, modelInput) {
335
+ if (!initialized2) {
336
+ throw new Error(
337
+ "[Model Mapping] Not initialized. Call initializeModelMappings() first."
338
+ );
220
339
  }
221
- const brand = parts[0];
222
- const model = parts[1];
223
- const year = parseInt(parts[2], 10);
224
- if (isNaN(year)) {
225
- return null;
340
+ const keys = [
341
+ `${brandId}:${modelInput}`,
342
+ `${brandId}:${modelInput.toLowerCase()}`,
343
+ `${brandId}:${modelInput.toUpperCase()}`,
344
+ `${brandId}:${modelInput.trim()}`
345
+ ];
346
+ for (const key of keys) {
347
+ const mapping = modelCache.get(key);
348
+ if (mapping) return mapping;
226
349
  }
227
- const trim = parts[3] || void 0;
228
- return {
229
- brand,
230
- model,
231
- year,
232
- trim
233
- };
350
+ const brandModels = brandModelsCache.get(brandId);
351
+ if (brandModels) {
352
+ const normalizedInput = modelInput.toLowerCase().replace(/[\s\-]/g, "");
353
+ for (const model of brandModels) {
354
+ const normalizedModel = model.englishName.toLowerCase().replace(/[\s\-]/g, "");
355
+ if (normalizedModel === normalizedInput) {
356
+ return model;
357
+ }
358
+ }
359
+ }
360
+ console.warn(`[Model Mapping] Unknown model: ${brandId}:${modelInput}`);
361
+ return null;
234
362
  }
235
- function isValidImageFilename(filename) {
236
- const pattern = /^[a-z0-9\-]+__[a-z0-9\-]+__\d{4}(__[a-z0-9\-]+)?\.(png|jpg|webp)$/;
237
- return pattern.test(filename);
363
+ function getBrandModels(brandId) {
364
+ if (!initialized2) {
365
+ throw new Error(
366
+ "[Model Mapping] Not initialized. Call initializeModelMappings() first."
367
+ );
368
+ }
369
+ return brandModelsCache.get(brandId) || [];
238
370
  }
239
-
240
- // src/image/url-generator.ts
241
- var DEFAULT_BUCKET = "charzing-d1600.firebasestorage.app";
242
- var DEFAULT_BASE_PATH = "vehicle-images";
243
- function encodeStoragePath(path) {
244
- return path.split("/").map((segment) => encodeURIComponent(segment)).join("%2F");
371
+ function isInitialized2() {
372
+ return initialized2;
245
373
  }
246
- function createDownloadUrl(bucket, fullPath) {
247
- const encodedPath = encodeStoragePath(fullPath);
248
- return `https://firebasestorage.googleapis.com/v0/b/${bucket}/o/${encodedPath}?alt=media`;
374
+ function resetCache2() {
375
+ modelCache.clear();
376
+ brandModelsCache.clear();
377
+ initialized2 = false;
378
+ console.log("[Model Mapping] Cache reset");
249
379
  }
250
- function generateVehicleImageUrl(params, options = {}) {
251
- const {
252
- bucket = DEFAULT_BUCKET,
253
- extension = "png"
254
- } = options;
255
- const storageBrand = getStorageBrandName(params.brand);
256
- const storageModel = toSlug(params.model).toUpperCase();
257
- const year = params.year;
258
- const filename = generateImageFilename(params, extension);
259
- const fullPath = `${DEFAULT_BASE_PATH}/${storageBrand}/${storageModel}/${year}/${filename}`;
260
- return createDownloadUrl(bucket, fullPath);
380
+ function getCachedModelCount() {
381
+ return modelCache.size;
261
382
  }
262
- function getVehicleImagePath(params, options = {}) {
263
- const {
264
- bucket = DEFAULT_BUCKET,
265
- extension = "png"
266
- } = options;
267
- const storageBrand = getStorageBrandName(params.brand);
268
- const storageModel = toSlug(params.model).toUpperCase();
269
- const year = params.year;
270
- const directory = `${DEFAULT_BASE_PATH}/${storageBrand}/${storageModel}/${year}`;
271
- const filename = generateImageFilename(params, extension);
272
- const fullPath = `${directory}/${filename}`;
273
- const downloadUrl = createDownloadUrl(bucket, fullPath);
274
- return {
275
- fullPath,
276
- directory,
277
- filename,
278
- downloadUrl
383
+
384
+ // src/image/url-generator.ts
385
+ var FIREBASE_STORAGE_BASE = "https://firebasestorage.googleapis.com/v0/b/charzing-d1600.firebasestorage.app/o";
386
+ function normalizeStorageBrand(firestoreBrandId) {
387
+ const storageMapping = {
388
+ "mercedes-benz": "BENZ",
389
+ "mercedes-maybach": "BENZ",
390
+ "hyundai": "HYUNDAI",
391
+ "kia": "KIA",
392
+ "tesla": "TESLA",
393
+ "BMW": "BMW",
394
+ "MINI": "MINI",
395
+ "audi": "AUDI",
396
+ "PORSCHE": "PORSCHE"
279
397
  };
398
+ return storageMapping[firestoreBrandId] || firestoreBrandId.toUpperCase();
280
399
  }
281
- function generateFallbackUrls(params, options = {}) {
282
- const urls = [];
283
- if (params.trim) {
284
- urls.push(generateVehicleImageUrl(params, options));
400
+ function normalizeStorageModel(firestoreModelId) {
401
+ if (firestoreModelId === firestoreModelId.toUpperCase()) {
402
+ return firestoreModelId;
285
403
  }
286
- urls.push(generateVehicleImageUrl({
287
- brand: params.brand,
288
- model: params.model,
289
- year: params.year
290
- }, options));
291
- return urls;
404
+ return firestoreModelId.toUpperCase();
405
+ }
406
+ function normalizeStorageTrim(trim) {
407
+ if (!trim) return "";
408
+ return trim.toLowerCase().replace(/[\s\-]/g, "_").replace(/[^a-z0-9_]/g, "");
409
+ }
410
+ function generateImageFilename(params) {
411
+ const { brandId, modelId, year, trim } = params;
412
+ const brandLower = brandId.toLowerCase();
413
+ const modelLower = modelId.toLowerCase().replace(/[\s]/g, "-");
414
+ const trimPart = trim ? `__${normalizeStorageTrim(trim)}` : "";
415
+ return `${brandLower}__${modelLower}__${year}${trimPart}.png`;
416
+ }
417
+ function generateStoragePath(params) {
418
+ const { brandId, modelId, year } = params;
419
+ const storageBrand = normalizeStorageBrand(brandId);
420
+ const storageModel = normalizeStorageModel(modelId);
421
+ const filename = generateImageFilename(params);
422
+ return `vehicle-images/${storageBrand}/${storageModel}/${year}/${filename}`;
423
+ }
424
+ function generateVehicleImageUrl(params) {
425
+ const path = generateStoragePath(params);
426
+ const encodedPath = encodeURIComponent(path);
427
+ return `${FIREBASE_STORAGE_BASE}/${encodedPath}?alt=media`;
292
428
  }
293
429
  export {
294
430
  SUPPORTED_BRANDS,
295
431
  convertKoreanToEnglish,
296
432
  findBestModelMatch,
297
- generateFallbackUrls,
298
433
  generateImageFilename,
434
+ generateStoragePath,
299
435
  generateVehicleImageUrl,
300
436
  getBrandMapping,
437
+ getBrandModels,
301
438
  getBrandNameEnglish,
302
439
  getBrandNameKorean,
440
+ getCachedBrandCount,
441
+ getCachedModelCount,
442
+ getBrandMapping2 as getDynamicBrandMapping,
443
+ getModelMapping2 as getDynamicModelMapping,
303
444
  getModelMapping,
304
445
  getModelSlug,
305
446
  getStorageBrandName,
306
- getVehicleImagePath,
307
- isValidImageFilename,
447
+ initializeBrandMappings,
448
+ initializeModelMappings,
449
+ isInitialized as isBrandMappingInitialized,
450
+ isInitialized2 as isModelMappingInitialized,
308
451
  normalizeBrandId,
309
452
  normalizeModelId,
310
- parseImageFilename,
453
+ resetCache as resetBrandCache,
454
+ resetCache2 as resetModelCache,
311
455
  toSlug
312
456
  };
313
457
  //# sourceMappingURL=index.mjs.map