@aiquants/fuzzy-search
Version:
Advanced fuzzy search library with Levenshtein distance, n-gram indexing, and Web Worker support
2 lines • 25.1 kB
JavaScript
import{useCallback as b,useEffect as z,useMemo as C,useRef as B,useState as T}from"react";var P={threshold:.4,caseSensitive:!1,learningWeight:1.2,debounceMs:300,multiTermOperator:"and",autoSearchOnIndexRebuild:!0,customWeights:{},ngramSize:2,minNgramOverlap:1,sortBy:"relevance",sortOrder:"desc",enableIndexFiltering:!0,enableLevenshtein:!0,parallelSearchStrategy:"balanced",indexWorkerOptions:{strategy:"hybrid",threshold:.4,ngramOverlapThreshold:.3,minCandidatesRatio:.1,maxCandidatesRatio:.5,jaroWinklerPrefix:.1,maxResults:1e3,relevanceFieldWeight:.2,relevancePerfectMatchBonus:.1},levenshteinWorkerOptions:{threshold:.3,lengthSimilarityThreshold:.1,partialMatchBonus:.1,lengthDiffPenalty:1,maxResults:1e3,relevanceFieldWeight:.15}};var i="[fuzzy-search]",w=class w{constructor(){this.indexWorker=null;this.levenshteinWorker=null;this.initializationPromise=null;this.referenceCount=0}static getInstance(){return w.instance||(w.instance=new w),w.instance}async getWorkers(e){return this.referenceCount++,this.indexWorker&&this.levenshteinWorker?(console.log(`${i} \u{1F527} Using existing global Workers (ref count: ${this.referenceCount})`),{indexWorker:this.indexWorker,levenshteinWorker:this.levenshteinWorker}):(this.initializationPromise||(this.initializationPromise=this.initializeWorkers(e)),await this.initializationPromise,{indexWorker:this.indexWorker,levenshteinWorker:this.levenshteinWorker})}async initializeWorkers(e){if(!(typeof Worker>"u"))try{if(!this.indexWorker){let r=e.workerUrls?.indexWorker?new URL(e.workerUrls.indexWorker,window.location.origin):new URL("./worker/indexWorker.mjs",import.meta.url);console.log(i,"\u{1F527} Global Index Worker URL:",r.toString()),this.indexWorker=new Worker(r,{type:"module"}),console.log(i,"\u{1F527} Global Index Worker: Initialized successfully")}if(!this.levenshteinWorker){let r=e.workerUrls?.levenshteinWorker?new URL(e.workerUrls.levenshteinWorker,window.location.origin):new URL("./worker/levenshteinWorker.mjs",import.meta.url);console.log(i,"\u{1F527} Global Levenshtein Worker URL:",r.toString()),this.levenshteinWorker=new Worker(r,{type:"module"}),console.log(i,"\u{1F527} Global Levenshtein Worker: Initialized successfully")}}catch(r){console.warn(i,"\u26A0\uFE0F Failed to initialize global Workers:",r)}}releaseReference(){this.referenceCount--,console.log(`${i} \u{1F527} Worker reference released (ref count: ${this.referenceCount})`)}dispose(){this.indexWorker&&(this.indexWorker.terminate(),this.indexWorker=null),this.levenshteinWorker&&(this.levenshteinWorker.terminate(),this.levenshteinWorker=null),this.initializationPromise=null,this.referenceCount=0,console.log(i,"\u{1F527} Global Workers disposed")}};w.instance=null;var $=w,D=class{constructor(){this.listeners=new Map}on(e,r){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e)?.add(r)}off(e,r){let t=this.listeners.get(e);t&&t.delete(r)}emit(e,r){let t=this.listeners.get(e);t&&t.forEach(n=>{try{n(r)}catch(s){console.error(`${i} \u274C Error in event listener for ${e}:`,s)}})}removeAllListeners(){this.listeners.clear()}},F=class{constructor(e=P){this.defaultOptions=e;this.indexWorker=null;this.levenshteinWorker=null;this.searchHistory=[];this.learningData={fieldWeights:{},queryPatterns:{},userPreferences:{preferredThreshold:.4,frequentFields:[],searchStyle:"fuzzy",lastUsedOptions:{}},performanceMetrics:{averageSearchTime:{},indexBuildTime:0}};this.requestId=0;this.pendingRequests=new Map;this.eventEmitter=new D;this.isIndexBuilt=!1;this.lastIndexedDataHash=null;this.stats={totalSearches:0,averageSearchTime:0,popularQueries:[],performanceBySize:{},workers:{index:{stats:{}},levenshtein:{stats:{}}}};this.loadLearningData(),this.workersInitialized=this.initializeWorkers(),setInterval(()=>this.cleanupPendingRequests(),3e4)}async initializeWorkers(){if(!(typeof Worker>"u"))try{let r=await $.getInstance().getWorkers(this.defaultOptions);this.indexWorker=r.indexWorker,this.levenshteinWorker=r.levenshteinWorker,this.indexWorker&&(this.indexWorker.onmessage=this.handleIndexWorkerMessage.bind(this),this.indexWorker.onerror=this.handleIndexWorkerError.bind(this),this.indexWorker.addEventListener("error",t=>{console.error(i,"\u{1F527} Index Worker Error (Event Listener):",t)})),this.levenshteinWorker&&(this.levenshteinWorker.onmessage=this.handleLevenshteinWorkerMessage.bind(this),this.levenshteinWorker.onerror=this.handleLevenshteinWorkerError.bind(this),this.levenshteinWorker.addEventListener("error",t=>{console.error(i,"\u{1F527} Levenshtein Worker Error (Event Listener):",t)})),this.eventEmitter.emit("workers:initialized",{indexWorker:!!this.indexWorker,levenshteinWorker:!!this.levenshteinWorker}),await this.testWorkerConnectivity()}catch(e){console.warn(i,"\u26A0\uFE0F Failed to initialize Workers:",e)}}async testWorkerConnectivity(){try{let e=[];this.indexWorker&&e.push(this.pingWorker(this.indexWorker,"index")),this.levenshteinWorker&&e.push(this.pingWorker(this.levenshteinWorker,"levenshtein")),await Promise.all(e)}catch(e){console.error(i,"\u{1F527} Worker connectivity test failed:",e)}}async pingWorker(e,r){let n=`ping-${r}-${Date.now()}`;await new Promise((s,c)=>{let a=setTimeout(()=>{console.warn(`${i} \u{1F527} ${r==="index"?"Index":"Levenshtein"} Worker: Ping timeout`),e.removeEventListener("message",o),c(new Error(`${r==="index"?"Index":"Levenshtein"} Worker ping timeout`))},5e3),o=p=>{let g=p.data;g.type==="pong"&&g.id===n&&(clearTimeout(a),e.removeEventListener("message",o),console.log(`${i} \u{1F527} ${r==="index"?"Index":"Levenshtein"} Worker: Ping successful`),s())};e.addEventListener("message",o),console.log(`${i} \u{1F527} ${r==="index"?"Index":"Levenshtein"} Worker: Sending ping message:`,n),e.postMessage({type:"ping",id:n})})}handleIndexWorkerMessage(e){let r=e.data;if(console.log(i,"\u{1F527} Index Worker message received:",r.type,r),r.type==="indexSearchResults"){let t=this.pendingRequests.get(r.id);t&&(this.pendingRequests.delete(r.id),r.error?t.reject(new Error(r.error)):t.resolve(r.results||[])),this.eventEmitter.emit("indexWorker:searchCompleted",{requestId:r.id,resultsCount:r.results?.length||0,processingTime:r.processingTime})}else if(r.type==="error"){let t=this.pendingRequests.get(r.id);t&&(this.pendingRequests.delete(r.id),t.reject(new Error(r.error||"Index Worker error"))),console.error(i,"\u274C Index Worker reported error:",{requestId:r.id,message:r.error,processingTime:r.processingTime}),this.eventEmitter.emit("indexWorker:error",{requestId:r.id,error:r.error,processingTime:r.processingTime})}else r.type==="indexReady"?(console.log(i,"\u2705 Index Worker: Index built successfully",r),r.stats&&(this.stats.workers.index.stats=r.stats,this.eventEmitter.emit("worker:statsUpdated",{worker:"index",stats:this.stats.workers.index.stats})),this.eventEmitter.emit("indexWorker:message",r)):r.type==="pong"||r.type==="stats"&&r.stats&&(this.stats.workers.index.stats=r.stats,this.eventEmitter.emit("worker:statsUpdated",{worker:"index",stats:this.stats.workers.index.stats}));this.eventEmitter.emit("indexWorker:message",r)}handleIndexWorkerError(e){console.error(i,"\u{1F527} Index Worker Error:",{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno,error:e.error,type:e.type}),this.eventEmitter.emit("indexWorker:error",e)}handleLevenshteinWorkerMessage(e){let r=e.data;if(console.log(i,"\u{1F527} Levenshtein Worker message received:",r.type,"stats present:",!!r.stats),r.type==="levenshteinSearch"){let t=this.pendingRequests.get(r.id);t&&(this.pendingRequests.delete(r.id),r.error?t.reject(new Error(r.error)):t.resolve(r.results||[])),r.stats&&(this.stats.workers.levenshtein.stats=r.stats,this.eventEmitter.emit("worker:statsUpdated",{worker:"levenshtein",stats:this.stats.workers.levenshtein.stats})),this.eventEmitter.emit("levenshteinWorker:searchCompleted",{requestId:r.id,processingTime:r.processingTime,workerType:r.workerType,operationMeta:r.operationMeta})}else r.type==="pong"||r.type==="stats"&&r.stats&&(this.stats.workers.levenshtein.stats=r.stats,this.eventEmitter.emit("worker:statsUpdated",{worker:"levenshtein",stats:this.stats.workers.levenshtein.stats}));this.eventEmitter.emit("levenshteinWorker:message",r)}handleLevenshteinWorkerError(e){console.error(i,"\u{1F527} Levenshtein Worker Error:",{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno,error:e.error,type:e.type}),this.eventEmitter.emit("levenshteinWorker:error",e)}isLocalStorageAvailable(){try{return typeof window<"u"&&window.localStorage!==void 0}catch{return!1}}loadLearningData(){try{if(this.isLocalStorageAvailable()){let e=localStorage.getItem("fuzzy-search-learning-data");e?this.learningData=JSON.parse(e):this.initializeDefaultLearningData()}else this.initializeDefaultLearningData()}catch(e){console.warn(i,"\u26A0\uFE0F Failed to load learning data:",e),this.initializeDefaultLearningData()}}initializeDefaultLearningData(){this.learningData={fieldWeights:{},queryPatterns:{},userPreferences:{preferredThreshold:this.defaultOptions.threshold,frequentFields:[],searchStyle:"fuzzy",lastUsedOptions:{}},performanceMetrics:{averageSearchTime:{},indexBuildTime:0}}}saveLearningData(){try{this.isLocalStorageAvailable()&&localStorage.setItem("fuzzy-search-learning-data",JSON.stringify(this.learningData))}catch(e){console.warn(i,"\u26A0\uFE0F Failed to save learning data:",e)}}async search(e,r,t,n){let s=performance.now(),c={...this.defaultOptions,...n};console.log(`${i} \u{1F680} FuzzySearchManager.search called: query="${e}", items.length=${r.length}`),this.eventEmitter.emit("search:start",{query:e,options:c});try{await this.workersInitialized;let a=this.generateDataHash(r,t);if(!this.isIndexBuilt||this.lastIndexedDataHash!==a?(console.log(`${i} \u{1F527} Building index: isIndexBuilt=${this.isIndexBuilt}, dataChanged=${this.lastIndexedDataHash!==a}`),await this.buildIndex(r,t,c),this.isIndexBuilt=!0,this.lastIndexedDataHash=a):console.log(`${i} \u{1F527} Index already built for current data, skipping rebuild`),!(this.indexWorker&&this.levenshteinWorker))throw new Error("Web Workers are not available or not initialized.");let o=await this.searchParallel(e,r,t,c),p=performance.now()-s;this.updateStats(e,p),this.updateLearningData(e,r.length,p);let g={results:o,totalItems:r.length,processingTime:p,query:e,options:c};return this.eventEmitter.emit("search:complete",g),o}catch(a){let o=a instanceof Error?a:new Error("Unknown search error");throw this.eventEmitter.emit("search:error",{error:o,query:e}),o}}async buildIndex(e,r,t){if(!this.indexWorker)throw new Error("Index Worker not available");let n=`build-index-${++this.requestId}`;console.log(`${i} \u{1F4E4} Building index: requestId=${n}`);let s={type:"buildIndex",id:n,items:e,searchFields:r,options:t};return new Promise((c,a)=>{let o=null,p=W=>{let f=W;f.type==="indexReady"&&f.id===n&&(console.log(`${i} \u{1F3AF} buildIndex: Match found! Resolving promise for requestId=${n}`),this.eventEmitter.off("indexWorker:message",p),this.eventEmitter.off("indexWorker:message",g),o&&(clearTimeout(o),o=null),c())},g=W=>{let f=W;f.type==="error"&&f.id===n&&(console.warn(`${i} \u274C buildIndex: Worker reported error for requestId=${n}`,f.error),this.eventEmitter.off("indexWorker:message",p),this.eventEmitter.off("indexWorker:message",g),o&&(clearTimeout(o),o=null),a(new Error(f.error||"Index Worker error")))};this.eventEmitter.on("indexWorker:message",p),this.eventEmitter.on("indexWorker:message",g),this.indexWorker?.postMessage(s),o=setTimeout(()=>{this.eventEmitter.off("indexWorker:message",p),this.eventEmitter.off("indexWorker:message",g),o=null,a(new Error("Index build timeout"))},6e4)})}async searchParallel(e,r,t,n){console.log(`${i} \u{1F310} searchParallel: query="${e}", items.length=${r.length}`);let{enableIndexFiltering:s,enableLevenshtein:c}=n;if(!(s||c))return console.log(`${i} \u26A0\uFE0F Both search stages are disabled. Returning empty results.`),[];let a=s?this.runIndexStage(e,r,t,n):Promise.resolve({stage:"index",results:[],processingTime:0,confidence:0,candidatesProcessed:0}),o=c?this.runLevenshteinStage(e,r,t,n):Promise.resolve({stage:"levenshtein",results:[],processingTime:0,confidence:0,candidatesProcessed:0}),[p,g]=await Promise.all([a,o]),W=this.mergeParallelResults(p,g,n.parallelSearchStrategy||"balanced",n);return console.log(`${i} \u{1F527} Parallel search completed: Index=${p.results.length}, Levenshtein=${g.results.length}, Merged=${W.results.length}`),W.results}async runIndexStage(e,r,t,n){let s=`index-${++this.requestId}`;console.log(`${i} \u{1F4E4} Index Stage: requestId=${s}`);let c={type:"indexSearch",id:s,query:e,items:r,searchFields:t,options:n};return this.executeWorkerStage(this.indexWorker,c,{stage:"index",confidence:.8,timeoutMessage:"Index search timeout",unavailableMessage:"Index Worker not available"})}async runLevenshteinStage(e,r,t,n){let s=`levenshtein-${++this.requestId}`;console.log(`${i} \u{1F4E4} Levenshtein Stage: requestId=${s}`);let c={type:"levenshteinSearch",id:s,query:e,items:r,searchFields:t,options:n};return this.executeWorkerStage(this.levenshteinWorker,c,{stage:"levenshtein",confidence:.9,timeoutMessage:"Levenshtein search timeout",unavailableMessage:"Levenshtein Worker not available"})}executeWorkerStage(e,r,t){if(!e)throw new Error(t.unavailableMessage);return new Promise((n,s)=>{let c=setTimeout(()=>{this.pendingRequests.has(r.id)&&(this.pendingRequests.delete(r.id),s(new Error(t.timeoutMessage)))},3e4);this.pendingRequests.set(r.id,{resolve:a=>{clearTimeout(c),n({stage:t.stage,results:a,processingTime:0,confidence:t.confidence,candidatesProcessed:a.length})},reject:a=>{clearTimeout(c),s(a)},timestamp:Date.now()}),e.postMessage(r)})}mergeParallelResults(e,r,t,n){let s=[];switch(t){case"index-first":s=e.results.length>0?e.results:r.results;break;case"levenshtein-first":s=r.results.length>0?r.results:e.results;break;default:s=this.mergeByScore([...e.results,...r.results],n);break}return{results:s,totalProcessingTime:e.processingTime+r.processingTime,indexStage:e,levenshteinStage:r,mergeStrategy:t,mergeQuality:.8}}mergeByScore(e,r){let t=new Map;e.forEach(a=>{if(a.originalIndex!==void 0){let o=t.get(a.originalIndex);(!o||a.score>o.score)&&t.set(a.originalIndex,a)}});let n=Array.from(t.values()),s=r.sortBy||P.sortBy,c=r.sortOrder||P.sortOrder;return s==="original"?n.sort((a,o)=>{let p=a.originalIndex??0,g=o.originalIndex??0;return c==="asc"?p-g:g-p}):n.sort((a,o)=>c==="asc"?a.score-o.score:o.score-a.score)}generateDataHash(e,r){return`${e.length}|${r.join(",")}|${JSON.stringify(e.slice(0,3))}`}updateStats(e,r){this.stats.totalSearches++,this.stats.averageSearchTime=(this.stats.averageSearchTime*(this.stats.totalSearches-1)+r)/this.stats.totalSearches}updateLearningData(e,r,t){this.learningData.queryPatterns[e]=(this.learningData.queryPatterns[e]??0)+1;let n=this.getPerformanceSizeKey(r);this.learningData.performanceMetrics.averageSearchTime[n]=t,this.stats.totalSearches%10===0&&this.saveLearningData()}getPerformanceSizeKey(e){return e<1e3?"small":e<1e4?"medium":e<1e5?"large":"xlarge"}cleanupPendingRequests(){let e=Date.now(),r=3e4;this.pendingRequests.forEach((t,n)=>{e-t.timestamp>r&&(t.reject(new Error("Request timeout")),this.pendingRequests.delete(n))})}updateSearchHistory(e,r,t=0,n=0){let s={query:e,timestamp:Date.now(),selectedItemId:r,resultCount:t,processingTime:n,searchTime:n,successful:t>0};this.searchHistory.unshift(s),this.searchHistory.length>1e3&&(this.searchHistory=this.searchHistory.slice(0,500))}async getStats(e=5e3){return await Promise.all([this.waitForWorkerStats(this.indexWorker,"index",e),this.waitForWorkerStats(this.levenshteinWorker,"levenshtein",e)]),this.stats}getCurrentStats(){return this.stats}async resetStats(e=5e3){let r=[this.resetWorkerStats(this.indexWorker,"index",e),this.resetWorkerStats(this.levenshteinWorker,"levenshtein",e)];this.stats={totalSearches:0,averageSearchTime:0,popularQueries:[],performanceBySize:{},workers:{index:{stats:this.createEmptyWorkerStats("index")},levenshtein:{stats:this.createEmptyWorkerStats("levenshtein")}}},this.searchHistory=[],await Promise.all(r),console.log(i,"\u{1F4CA} FuzzySearchManager: All statistics have been reset"),this.eventEmitter.emit("stats:reset",{timestamp:Date.now(),resetBy:"manager"})}waitForWorkerStats(e,r,t){return e?new Promise(n=>{let s=setTimeout(()=>{console.warn(`${i} \u26A0\uFE0F getStats: Timeout waiting for ${r} worker.`),this.eventEmitter.off("worker:statsUpdated",c),n()},t),c=a=>{let{worker:o}=a;o===r&&(clearTimeout(s),this.eventEmitter.off("worker:statsUpdated",c),n())};this.eventEmitter.on("worker:statsUpdated",c),e.postMessage({type:"getStats"})}):Promise.resolve()}resetWorkerStats(e,r,t){return e?new Promise(n=>{let s=setTimeout(()=>{console.warn(`${i} \u26A0\uFE0F resetStats: Timeout waiting for ${r} worker.`),e.removeEventListener("message",c),n()},t),c=o=>{o.data.type==="resetStats"&&(clearTimeout(s),e.removeEventListener("message",c),console.log(`${i} \u{1F4CA} ${r} worker statistics have been reset`),n())};e.addEventListener("message",c);let a={type:"resetStats",id:`resetStats-${Date.now()}-${r}`};e.postMessage(a)}):Promise.resolve()}createEmptyWorkerStats(e){return{basic:{totalSearches:0,totalProcessingTime:0,averageSearchTime:0,lastSearchTime:0,errorCount:0,timestamp:Date.now()},workerSpecific:{workerType:e,operationCount:0,operationDistribution:{},specificMetrics:{}},analysis:{fieldPerformance:{},performanceInsights:{recentAverageProcessingTime:0,recentAverageResultCount:0,throughputPerSecond:0,totalHistoryEntries:0,mostCommonQueryLength:0,bestPerformingField:"",similarityDistribution:{highSimilarity:0,mediumSimilarity:0,lowSimilarity:0,veryLowSimilarity:0}},recentHistory:[]}}}getSearchHistory(){return[...this.searchHistory]}on(e,r){this.eventEmitter.on(e,r)}off(e,r){this.eventEmitter.off(e,r)}async prebuildIndex(e,r,t){console.log(`${i} \u{1F3D7}\uFE0F Pre-building index for ${e.length} items`);try{await this.workersInitialized;let n=this.generateDataHash(e,r);!this.isIndexBuilt||this.lastIndexedDataHash!==n?(console.log(`${i} \u{1F527} Pre-building index: isIndexBuilt=${this.isIndexBuilt}, dataChanged=${this.lastIndexedDataHash!==n}`),await this.rebuildIndex(e,r,t),console.log(`${i} \u2705 Index pre-built successfully for ${e.length} items`)):console.log(`${i} \u{1F527} Index already built for current data, skipping pre-build`)}catch(n){throw console.error(i,"\u{1F527} Error during index pre-building:",n),n}}async rebuildIndex(e,r,t){console.log(`${i} \u{1F504} Starting forced index rebuild for ${e.length} items`);let n={...this.defaultOptions,...t};try{await this.workersInitialized,this.isIndexBuilt=!1,this.lastIndexedDataHash=null,await this.buildIndex(e,r,n),console.log(`${i} \u2705 Index rebuild completed successfully for ${e.length} items`),this.eventEmitter.emit("index:rebuilt",{itemCount:e.length,searchFields:r,timestamp:Date.now()})}catch(s){throw console.error(i,"\u274C Failed to rebuild index:",s),this.eventEmitter.emit("index:rebuildError",{error:s instanceof Error?s.message:String(s),timestamp:Date.now()}),s}}dispose(){$.getInstance().releaseReference(),this.indexWorker=null,this.levenshteinWorker=null,this.pendingRequests.clear(),this.eventEmitter.removeAllListeners(),this.saveLearningData()}};var m="[fuzzy-search]",R=(u,e)=>{if(typeof u=="string"&&u.trim())return u;if(u instanceof Error&&u.message.trim())return u.message;if(typeof u=="object"&&u!==null){if("error"in u)return R(u.error,e);if("message"in u)return R(u.message,e)}return e};function te(u,e,r){let[t,n]=T(""),[s,c]=T([]),[a,o]=T(!1),[p,g]=T(!1),[W,f]=T(null),[M,L]=T([]),[A,H]=T({totalSearches:0,averageSearchTime:0,itemCount:u.length,resultsCount:0,workerStats:void 0}),y=C(()=>({...P,...r}),[r]),d=B(null),q=B(t);z(()=>{q.current=t},[t]);let I=C(()=>e.map(l=>String(l)),[e]);z(()=>{d.current||(d.current=new F(y),console.log(m,"\u{1F527} FuzzySearchManager created (initial setup)"));let l=()=>{if(d.current){let S=d.current.getCurrentStats(),x={totalSearches:S?.totalSearches??0,averageSearchTime:S?.averageSearchTime??0,itemCount:u.length,resultsCount:s.length,workerStats:{index:S?.workers.index.stats,levenshtein:S?.workers.levenshtein.stats}};H(x)}},h=S=>{let x=R(S,"Index Worker error");console.error(`${m} \u274C Index worker error event`,S),f(x),g(!1)},v=S=>{let x=R(S,"Index rebuild error");console.error(`${m} \u274C Index rebuild error event`,S),f(x),g(!1)},k=S=>{let x=R(S,"Search execution failed");console.error(`${m} \u274C Search error event`,S),f(x)};return d.current.on("worker:statsUpdated",l),d.current.on("search:complete",l),d.current.on("indexWorker:error",h),d.current.on("index:rebuildError",v),d.current.on("search:error",k),()=>{d.current&&(d.current.off("worker:statsUpdated",l),d.current.off("search:complete",l),d.current.off("indexWorker:error",h),d.current.off("index:rebuildError",v),d.current.off("search:error",k))}});let E=b(async l=>{if(d.current){console.log(`${m} \u{1F50D} performSearch called with query: "${l}"`);try{if(console.log(`${m} \u{1F4C8} Setting isSearching to true for query: "${l}"`),o(!0),f(null),!l.trim()){c(u.map((O,U)=>({item:O,score:1,baseScore:1,matchedFields:[],relevanceReason:"\u5168\u4EF6\u8868\u793A",originalIndex:U}))),console.log(`${m} \u{1F4C8} Setting isSearching to false for empty query: "${l}"`),o(!1);return}console.log(`${m} \u{1F680} Starting search with query: "${l}", items count: ${u.length}`);let h=performance.now(),v=await d.current.search(l,u,I,y),S=performance.now()-h;console.log(`${m} \u2705 Search completed with ${v.length} results in ${S}ms`),c(v);let x={query:l,timestamp:Date.now(),resultCount:v.length,processingTime:S,searchTime:S,successful:v.length>0};L(O=>{let U=O.filter(K=>K.query!==l);return[x,...U].slice(0,50)})}catch(h){console.log(`${m} \u274C Search error for query: "${l}"`,h),f(h instanceof Error?h.message:"Search failed"),c([])}finally{console.log(`${m} \u{1F4C8} Setting isSearching to false (finally) for query: "${l}"`),o(!1)}}},[u,I,y]);z(()=>{if(!d.current||u.length===0)return;let l=!1,h=y.autoSearchOnIndexRebuild??!0;return console.log(`${m} \u{1F3D7}\uFE0F Data changed, pre-building index for ${u.length} items`),g(!0),d.current.prebuildIndex(u,I,y).then(()=>{!l&&h&&(console.log(`${m} \u{1F501} Re-running search after index rebuild`),E(q.current))}).catch(v=>{console.warn(m,"\u{1F527} Failed to pre-build index:",v),f(R(v,"\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u4E8B\u524D\u69CB\u7BC9\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"))}).finally(()=>{l||g(!1)}),()=>{l=!0}},[u,I,y,E]),z(()=>()=>{d.current&&(console.log(m,"\u{1F527} FuzzySearchManager disposed (component unmount)"),d.current.dispose(),d.current=null)},[]),z(()=>{console.log(`${m} \u{1F3AF} useEffect triggered - searchTerm: "${t}", debounceMs: ${y.debounceMs}`);let l=setTimeout(()=>{console.log(`${m} \u23F0 Timeout executing performSearch for: "${t}"`),E(t)},y.debounceMs);return()=>{console.log(`${m} \u{1F6AB} Timeout cleared for: "${t}"`),clearTimeout(l)}},[t,y,E]),z(()=>{t.trim()||c(u.map((h,v)=>({item:h,score:1,baseScore:1,matchedFields:[],relevanceReason:"\u5168\u4EF6\u8868\u793A",originalIndex:v})))},[u,t]);let j=b((l,h)=>{console.log(m,"\u{1F3AF} Selected item:",l,"at index:",h)},[]),Q=b(()=>{n(""),f(null)},[]),_=b(()=>{L([])},[]),G=b(async()=>{if(d.current){await d.current.resetStats(),L([]),f(null);let l=d.current.getCurrentStats(),h={totalSearches:l?.totalSearches??0,averageSearchTime:l?.averageSearchTime??0,itemCount:u.length,resultsCount:s.length,workerStats:{index:l?.workers.index.stats,levenshtein:l?.workers.levenshtein.stats}};H(h),console.log(`${m} \u{1F4CA} useFuzzySearch: Statistics have been reset and refreshed`)}},[u.length,s.length]),Z=b((l,h=5)=>{let v=M.filter(k=>k.query.toLowerCase().includes(l.toLowerCase())&&k.query!==l).sort((k,S)=>S.timestamp-k.timestamp).slice(0,h).map(k=>k.query);return Array.from(new Set(v))},[M]),J=b(async l=>{if(!d.current){console.warn(`${m} \u26A0\uFE0F Search manager is not initialized`);return}console.log(`${m} \u{1F504} Rebuilding index for ${u.length} items`),g(!0),f(null);try{let h=l?{...y,...l}:y;await d.current.rebuildIndex(u,I,h),console.log(`${m} \u2705 Index rebuild completed successfully`)}catch(h){let v=h instanceof Error?h.message:"\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u518D\u69CB\u7BC9\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F";throw console.error(m,"\u274C Index rebuild failed:",v),f(v),h}finally{g(!1)}},[u,I,y]);return{searchTerm:t,setSearchTerm:n,filteredItems:s,isSearching:a,isIndexBuilding:p,error:W,searchHistory:M,options:y,selectItem:j,clearSearch:Q,clearHistory:_,resetStats:G,getSearchSuggestions:Z,performanceMetrics:A,rebuildIndex:J}}function ne(u=10){let[e,r]=T([]),t=b(s=>{s.trim()&&r(c=>{let a=c.filter(p=>p.query!==s);return[{query:s,timestamp:Date.now(),resultCount:0,processingTime:0,searchTime:0,successful:!1},...a].slice(0,u)})},[u]),n=b(()=>{r([])},[]);return{searchHistory:e,addToHistory:t,clearHistory:n}}export{te as useFuzzySearch,ne as useSearchHistory};
//# sourceMappingURL=react.mjs.map