alter table public.knowledge_chunks
    add column if not exists content_tsv tsvector
    generated always as (to_tsvector('simple', coalesce(content, ''))) stored;

create index if not exists knowledge_chunks_content_tsv_gin
on public.knowledge_chunks
using gin (content_tsv);

-- Build a tsquery using OR logic so any matching token scores a hit.
-- websearch_to_tsquery uses AND which requires ALL words in the query
-- (including stop words like "what", "apa", "saja") to appear in the chunk.
-- Instead, we tokenize via to_tsvector and join unique lexemes with | (OR).
create or replace function public.match_knowledge_fts(
    query_text text,
    match_count int DEFAULT 7
)
returns table (
    content text,
    source text,
    filename text,
    page_number integer,
    lexical_score float
)
language sql
stable
as $$
    with query_terms as (
        select to_tsquery(
            'simple',
            string_agg(lexeme, ' | ' order by lexeme)
        ) as tsq
        from (
            select distinct unnest(tsvector_to_array(
                to_tsvector('simple', query_text)
            )) as lexeme
        ) tokens
    )
    select
        kc.content,
        kc.source,
        kc.filename,
        kc.page_number,
        ts_rank_cd(kc.content_tsv, query_terms.tsq) as lexical_score
    from public.knowledge_chunks kc
    cross join query_terms
    where kc.content_tsv @@ query_terms.tsq
    order by lexical_score desc, kc.id asc
    limit match_count;
$$;
