import { types } from '../actions/actionTypes'; import { createDebugHelpers } from '../utils/debug'; // 디버그 헬퍼 설정 const DEBUG_MODE = false; const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); const initialState = { searchDatas: {}, totalCount: {}, searchPerformed: false, initPerformed: false, searchTimestamp: null, shopperHouseData: null, shopperHouseSearchId: null, shopperHouseRelativeQueries: null, // ✨ relativeQueries는 독립적으로 저장 (searchId와 별개) preShopperHouseData: null, // 🔄 이전 shopperHouseData 저장 (sortingType 변경 시 사용) // 🔽 검색 메인 데이터 추가 searchMainData: { topSearchs: [], popularBrands: [], hotPicksForYou: [], tsvInfo: [], }, shopperHouseError: null, // ShopperHouse API 오류 정보 저장 (디버깅용) shopperHouseErrorPopup: { // ShopperHouse 에러 팝업 상태 (사용자 알림용) message: null, type: null, // 'API_FAILURE', 'NO_RESULTS', etc. visible: false, timestamp: null, originalError: null, }, // 🎯 [Phase 1] SearchPanel 모드 제어 명령 panelCommand: { type: null, // 명령 타입: 'SWITCH_TO_SEARCH_INPUT', 'SWITCH_TO_VOICE_INPUT' 등 source: null, // 명령 발생 출처: 'VoiceInputOverlay', 'SearchInputOverlay' 등 timestamp: null, // 명령 발생 시간 }, }; export const searchReducer = (state = initialState, action) => { switch (action.type) { case types.GET_SEARCH: { const newResults = action.payload.data.result.results; const updatedSearchDatas = action.append ? { ...state.searchDatas } : {}; newResults.forEach(({ type, docs }) => { if (action.append && updatedSearchDatas[type]) { for (let i = 0; i < docs.length; i++) { updatedSearchDatas[type][action.startIndex + i] = docs[i]; } } else { updatedSearchDatas[type] = docs; } }); const updatedTotalCount = action.append ? { ...state.totalCount } : {}; newResults.forEach(({ type, total_count }) => { updatedTotalCount[type] = total_count; }); return { ...state, searchDatas: updatedSearchDatas, totalCount: updatedTotalCount, searchPerformed: true, initPerformed: !action.append, // 일반 검색 시 ShopperHouse 데이터 초기화 shopperHouseData: null, shopperHouseSearchId: null, preShopperHouseData: null, // 🔄 이전 데이터도 초기화 }; } case types.RESET_SEARCH: return { ...state, searchDatas: {}, totalCount: {}, searchPerformed: false, initPerformed: false, searchTimestamp: null, // shopperHouseData, shopperHouseSearchId 유지 }; case types.RESET_VOICE_SEARCH: dlog('[VoiceInput]-searchReducer-RESET_VOICE_SEARCH'); dlog( JSON.stringify( { action: 'RESET_VOICE_SEARCH', shopperHouseData_cleared: true, shopperHouseSearchId_cleared: true, preShopperHouseData_cleared: true, shopperHouseRelativeQueries_preserved: state.shopperHouseRelativeQueries || '(없음)', relativeQueries_length: state.shopperHouseRelativeQueries?.length || 0, }, null, 2 ) ); return { ...state, shopperHouseData: null, shopperHouseSearchId: null, preShopperHouseData: null, // 🔄 이전 데이터도 초기화 // ✨ relativeQueries는 유지 (다음 PROMPT 모드에서 표시하기 위해) }; case types.SET_SEARCH_INIT_PERFORMED: return { ...state, initPerformed: action.payload, }; case types.UPDATE_SEARCH_TIMESTAMP: return { ...state, searchTimestamp: Date.now(), }; case types.BACKUP_SHOPPERHOUSE_DATA: { const newPreKey = action.payload?.results?.[0]?.searchId || 'null'; const oldPreKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null'; dlog('[ShopperHouse]-DIFF (after backup) preShopperHouseKey:', oldPreKey, '→', newPreKey); return { ...state, preShopperHouseData: action.payload, }; } case types.GET_SHOPPERHOUSE_SEARCH: { // ✅ 안전한 데이터 접근 const resultData = action.payload?.data?.result; if (!resultData) { console.error('[searchReducer] Invalid shopperHouse data structure'); return state; } const results = resultData.results || []; // searchId와 relativeQueries 추출 (첫 번째 result에서) const searchId = results.length > 0 ? results[0].searchId : null; const relativeQueries = results.length > 0 ? results[0].relativeQueries : null; const sortingType = results.length > 0 ? results[0].sortingType : null; // Key 정보 const newKey = searchId || 'null'; const preKey = state.preShopperHouseData?.results?.[0]?.searchId || 'null'; const preSortingType = state.preShopperHouseData?.results?.[0]?.sortingType || 'null'; // [VoiceInput] Redux에 저장 로그 dlog( '[ShopperHouse]-DIFF (after API) shopperHouseKey:', newKey, '| preShopperHouseKey:', preKey, '| sortingType:', sortingType || 'null', '| preSortingType:', preSortingType ); return { ...state, shopperHouseData: resultData, // 🔄 preShopperHouseData는 건드리지 않음 (API 호출 전에 이미 백업됨) shopperHouseSearchId: searchId, shopperHouseRelativeQueries: relativeQueries, // ✨ relativeQueries 별도 저장 // ShopperHouse 검색 시 일반 검색 데이터 초기화 searchDatas: {}, totalCount: {}, searchPerformed: false, initPerformed: false, }; } case types.SET_SHOPPERHOUSE_ERROR: dlog('[VoiceInput] ❌ Redux shopperHouseError 저장:', action.payload); return { ...state, shopperHouseError: action.payload, shopperHouseData: null, // 오류 발생 시 데이터 초기화 shopperHouseSearchId: null, preShopperHouseData: null, // 🔄 이전 데이터도 초기화 }; case types.SHOW_SHOPPERHOUSE_ERROR: dlog('[ShopperHouse] 🔴 Redux shopperHouseErrorPopup 표시:', action.payload); return { ...state, shopperHouseErrorPopup: { message: action.payload.message, type: action.payload.type, visible: action.payload.visible, timestamp: action.payload.timestamp, originalError: action.payload.originalError, }, }; case types.HIDE_SHOPPERHOUSE_ERROR: dlog('[ShopperHouse] ✅ Redux shopperHouseErrorPopup 숨김'); return { ...state, shopperHouseErrorPopup: { message: null, type: null, visible: false, timestamp: null, originalError: null, }, }; case types.CLEAR_SHOPPERHOUSE_DATA: dlog('[DEBUG] 🧹 Redux shopperHouseData 초기화 호출 - 호출 스택 추적:'); dlog('[DEBUG] 호출 위치:', new Error().stack?.split('\n')[1]?.trim() || '(스택 추적 불가)'); dlog( '[VoiceInput] 🧹 Redux shopperHouseData 초기화 (searchId & relativeQueries & preShopperHouseData는 유지)' ); dlog('[VoiceInput]-searchReducer-CLEAR_SHOPPERHOUSE_DATA'); dlog( JSON.stringify( { shopperHouseData_cleared: true, preShopperHouseData_preserved: !!state.preShopperHouseData, shopperHouseSearchId_preserved: state.shopperHouseSearchId || '(없음)', shopperHouseRelativeQueries_preserved: state.shopperHouseRelativeQueries || '(없음)', relativeQueries_length: state.shopperHouseRelativeQueries?.length || 0, }, null, 2 ) ); return { ...state, shopperHouseData: null, // 🔄 preShopperHouseData는 searchId처럼 유지! (다음 정렬 변경 시 사용) // preShopperHouseData: state.preShopperHouseData, // 명시적으로 유지 (spread로 자동 유지됨) // ✅ searchId는 2번째 발화에서 필요하므로 유지! // shopperHouseSearchId: null, // ❌ 제거됨 - searchId를 유지해야 2차 발화에서 searchId 포함 가능 // ✨ relativeQueries도 PROMPT 모드에서 표시하기 위해 유지! // shopperHouseRelativeQueries는 유지 shopperHouseError: null, // 데이터 초기화 시 오류도 초기화 }; // 🔽 검색 메인 데이터 처리 case types.GET_SEARCH_MAIN: { dlog('🔍 [searchReducer] GET_SEARCH_MAIN 받은 payload:', action.payload); // 여러 가능한 구조 확인 let resultData = null; if (action.payload?.result) { // payload.result 구조 resultData = action.payload.result; dlog('✅ [searchReducer] payload.result 구조 확인'); } else if (action.payload?.data?.result) { // payload.data.result 구조 resultData = action.payload.data.result; dlog('✅ [searchReducer] payload.data.result 구조 확인'); } else if (action.payload?.data) { // payload.data에 직접 데이터가 있는 경우 resultData = action.payload.data; dlog('✅ [searchReducer] payload.data 직접 구조 확인'); } if (!resultData) { console.error('[searchReducer] ❌ Invalid searchMain data structure'); console.error('받은 payload:', JSON.stringify(action.payload, null, 2)); return state; } dlog('[searchReducer] ✅ GET_SEARCH_MAIN success'); return { ...state, searchMainData: { topSearchs: resultData.topSearchs || [], popularBrands: resultData.popularBrands || [], hotPicksForYou: resultData.hotPicksForYou || [], tsvInfo: resultData.tsvInfo || [], }, }; } case types.CLEAR_SEARCH_MAIN_DATA: dlog('[searchReducer] 🧹 searchMainData 초기화'); return { ...state, searchMainData: { topSearchs: [], popularBrands: [], hotPicksForYou: [], tsvInfo: [], }, }; // 🎯 [Phase 1] SearchPanel 모드 제어 명령 case types.SWITCH_TO_SEARCH_INPUT_OVERLAY: dlog('[searchReducer] 🔄 SWITCH_TO_SEARCH_INPUT_OVERLAY 명령 저장', { source: action.payload?.source, timestamp: new Date().toISOString(), }); return { ...state, panelCommand: { type: 'SWITCH_TO_SEARCH_INPUT', source: action.payload?.source || 'unknown', timestamp: Date.now(), }, }; case types.CLEAR_PANEL_COMMAND: return { ...state, panelCommand: { type: null, source: null, timestamp: null, }, }; default: return state; } };