
    (<i3                    x   d Z ddlmZ ddlZddlZddlmZ ddlmZ erddl	m
Z
  ee      Z ej                  d      Z ej                  d      Z ej                  d	      Z ej                  d
      Z ej                  d      ZddZddZdddZddZ	 	 	 	 	 	 	 	 	 	 	 	 ddZ	 	 	 d	 	 	 	 	 	 	 	 	 	 	 ddZy)uG   utils/session_search.py — FTS5 기반 세션 메시지 전문 검색.    )annotationsN)TYPE_CHECKING)
get_logger)SessionStorez"[^"]*"z\b(AND|OR|NOT)\bz[()\\+*]z\b(\w+-\w[\w-]*)\bz {2,}c                f   | syg dfd}t         j                  ||       }t        j                  d|      }t        j                  d|      }t        j                  d |      }t              D ]  \  }}|j                  d| d|      } t        j                  d|      j                         S )u  FTS5 특수문자를 처리하여 안전한 쿼리 문자열을 반환한다.

    - 인용구("...")는 보호 (FTS5 phrase query)
    - (, ), +, * 제거
    - AND/OR/NOT 단어 경계 제거 ("ANDROID" 보호)
    - 하이픈 단어(chat-send)를 인용 처리
     c                h    j                  | j                  d             dt              dz
   dS )Nr    Q    )appendgrouplen)mquoted_partss    E/home/jay/workspace/.worktrees/task-2057-dev2/utils/session_search.py_stashz#sanitize_fts5_query.<locals>._stash&   s3    AGGAJ's<(1,-T22     c                ,    d| j                  d       dS )N"r   )r   )r   s    r   <lambda>z%sanitize_fts5_query.<locals>.<lambda>-   s    1QWWQZL): r   r
   r   )r   zre.Match[str]returnstr)	_QUOTED_PHRASE_REsub_BOOL_OP_RE_SPECIAL_CHARS_RE_HYPHEN_WORD_RE	enumeratereplace_MULTI_SPACE_REstrip)queryr   safeipartr   s        @r   sanitize_fts5_queryr(      s      L3   /D??3%D  d+D:DAD\* 34||eA3dOT23 sD)//11r   c                    t               }| }	 ||v rt        j                  d|       |S |j                  |       |j	                  |      }||S |j                  d      }|s|S |}Z)u_   위임 체인을 역방향 추적하여 루트 세션 ID를 반환한다. 순환 방지 포함.z(Cycle detected in session lineage at: %sparent_session_id)setloggerwarningaddget_sessionget)
session_iddbvisitedcurrentrowparents         r   _resolve_lineage_rootr7   5   sr    GG
gNNEwONGnnW%;N,-N r   c                   t        |       |k  r| S |s| d| S d}|j                         j                         D ]G  }| j                         j	                  |j                         j                  d            }|dk7  sE|} n |dk(  r| d| S |dz  }t        d||z
        }t        t        |       ||z         }t        d||z
        }| || S )uT   쿼리 매칭 위치를 중심으로 max_chars 이하 텍스트 창을 추출한다.Nr      r   )r   r#   splitlowerfindmaxmin)		full_textr$   	max_charspostokenidxhalfstartends	            r   _truncate_around_matchesrH   H   s    
9~")$$
C$$& oo$$U[[]%8%8%=>"9C	 by)$$>D3:E
c)nei/
0C3?#EU3r   c                2    dj                  d | D              S )uI   세션 메시지 목록을 '[role] content' 텍스트로 직렬화한다.
c              3  r   K   | ]/  }d |j                  dd       d|j                  d      xs d  1 yw)[roleunknownz] contentr   N)r0   ).0r   s     r   	<genexpr>z'_format_conversation.<locals>.<genexpr>b   s9     `RSqvy12"QUU95E5K4LM`s   57)join)messagess    r   _format_conversationrT   `   s    99`W_```r   c           	        d}| g}|r2ddj                  dt        |      z         d}|j                  |       d| d|dz   d	}t        j                  |j
                        }t        j                  |_        	 |j                  ||      j                         }	|j                          t               }
g }|	D ]  }|d
   }|r||k(  r||
v r|
j                  |       |j                  |      }t        t        |      | d      }|j!                  |t#        ||      ||d   |d   d       t        |      |k\  s |S  |S # |j                          w xY w)u5   FTS5 SQL 쿼리 실행 후 결과를 구조화한다.r   z AND m.role IN (,?)z
        SELECT m.session_id, snippet(messages_fts, 0, '<b>', '</b>', '...', 32) AS snip, rank
        FROM messages_fts
        JOIN messages m ON messages_fts.rowid = m.id
        WHERE messages_fts MATCH ?z%
        ORDER BY rank
        LIMIT    z
    r1   i  )rA   sniprank)r1   root_session_idsummarysnippetscore)rR   r   extendsqlite3connectdb_pathRowrow_factoryexecutefetchallcloser+   r.   get_messagesrH   rT   r   r7   )clean_queryr2   role_filterlimitcurrent_session_idrole_clauseparamssqlconnrowsseenresultsr5   sidmsgsr]   s                   r   _execute_fts_searchrw   e   sr    K'=F(#K8H2H)I(J!Lk"# $/- 0qyk C ??2::&D{{D||C(113

UDG |$#);";$;s#*+?+E{^ab!#8b#A"v;V	
 w<5 N-, N7 	

s   ; E Ec                    t        |       }|sdg iS 	 t        |||||      }d|iS # t        $ r%}t        j	                  d||       dg icY d}~S d}~ww xY w)u@   FTS5 검색을 수행하고 결과를 포맷하여 반환한다.rt   z FTS5 search error (query=%r): %sN)r(   rw   	Exceptionr,   r-   )r$   r2   rk   rl   rm   rj   rt   excs           r   search_sessionsr{      sm     &e,K2%k2{EK]^
 w	  9;L2s   & 	AA	AA)r$   r   r   r   )r1   r   r2   'SessionStore'r   r   )i )r@   r   r$   r   rA   intr   r   )rS   
list[dict]r   r   )rj   r   r2   r|   rk   list[str] | Nonerl   r}   rm   
str | Noner   r~   )N   N)r$   r   r2   r|   rk   r   rl   r}   rm   r   r   dict)__doc__
__future__r   rera   typingr   utils.loggerr   utils.session_storer   __name__r,   compiler   r   r   r   r"   r(   r7   rH   rT   rw   r{    r   r   <module>r      s   M " 	    #0	H	 BJJz* bjj,-BJJ{+ "**23"**X&28& 0a
888 "8 	8
 #8 8| %)%)    "  	 
 #  
 r   