/* Portable PDF text search.  It reuses the built-in PDF text extractor and
 * never invokes an OS PDF framework, OCR engine, regular-expression engine,
 * or external command. */
typedef struct {
    int case_sensitive;
    int ignore_width;
    int ignore_whitespace;
    const char *page_spec;
} B2026PdfSearchSettings;

static void b2026_pdf_search_pair_number(const char *target,const char *jp,const char *en,double value){
    if(target==NULL)return;hash_set_number(target,jp,value);hash_set_number(target,en,value);
}
static void b2026_pdf_search_pair_text(const char *target,const char *jp,const char *en,const char *value){
    if(target==NULL)return;hash_set_string(target,jp,value!=NULL?value:"");hash_set_string(target,en,value!=NULL?value:"");
}
static void b2026_pdf_search_clear(const char *target){
    if(target!=NULL&&*target!='\0'){HashVariable *h=ensure_local_hash(get_hash_frame(target),target);clear_hash_entries(h);}
}
static void b2026_pdf_search_result(const char *target,int ok,int complete,int found,const char *status,
                                    int page_count,int searched_count,int text_count,int matched_count,
                                    const unsigned char *matched,const char *page_spec){
    int i,index=0;char key[96];
    if(target==NULL)return;
    b2026_pdf_search_pair_number(target,"成功","ok",ok?1.0:0.0);
    b2026_pdf_search_pair_number(target,"完全","complete",complete?1.0:0.0);
    b2026_pdf_search_pair_number(target,"発見","found",found?1.0:0.0);
    b2026_pdf_search_pair_text(target,"状態","status",status);
    b2026_pdf_search_pair_number(target,"総ページ数","pageCount",(double)page_count);
    b2026_pdf_search_pair_number(target,"検索ページ数","searchedPageCount",(double)searched_count);
    b2026_pdf_search_pair_number(target,"文字ページ数","textPageCount",(double)text_count);
    b2026_pdf_search_pair_number(target,"該当ページ数","matchedPageCount",(double)matched_count);
    b2026_pdf_search_pair_text(target,"ページ指定","pageSpec",page_spec);
    if(matched!=NULL)for(i=0;i<page_count;++i)if(matched[i]){
        ++index;snprintf(key,sizeof(key),"ページ,%d",index);hash_set_number(target,key,(double)(i+1));
        snprintf(key,sizeof(key),"page,%d",index);hash_set_number(target,key,(double)(i+1));
    }
}
static double b2026_pdf_search_fail(const char *target,int code,const char *path,const char *detail,const char *status){
    char message[2048];
    b2026_pdf_search_result(target,0,0,0,status!=NULL?status:"FAILED",0,0,0,0,NULL,"");
    snprintf(message,sizeof(message),"PDF.SEARCHTEXT failed: path=%s: %s / 【PDF文字検索エラー】%s",
             path!=NULL?path:"",detail!=NULL?detail:"unknown error",detail!=NULL?detail:"処理できません");
    basic_set_error_logged(code,message,"PDF.SEARCHTEXT",current_source_path,current_line,"PDF");
    b2026_try_append_log_line("__PDF_ERROR.log",message);
    return -1.0;
}
static int b2026_pdf_search_bool(HashVariable *h,const char *jp,const char *en,int default_value,int *out){
    HashEntry *e=b2026_pdf_tf_entry(h,jp,en);if(e==NULL){*out=default_value;return 1;}
    if(e->value.type!=TYPE_NUMBER||!isfinite(e->value.val.number))return 0;*out=e->value.val.number!=0.0;return 1;
}
static int b2026_pdf_search_settings(const B2026PdfArg *arg,B2026PdfSearchSettings *s){
    HashVariable *h=NULL;HashEntry *e;
    s->case_sensitive=1;s->ignore_width=1;s->ignore_whitespace=0;s->page_spec="ALL";
    if(arg==NULL)return 1;if(!b2026_pdf_tf_hash(arg,&h))return 0;
    if(!b2026_pdf_search_bool(h,"大文字小文字を区別","caseSensitive",1,&s->case_sensitive)||
       !b2026_pdf_search_bool(h,"半角全角を無視","ignoreWidth",1,&s->ignore_width)||
       !b2026_pdf_search_bool(h,"空白を無視","ignoreWhitespace",0,&s->ignore_whitespace))return 0;
    e=b2026_pdf_tf_entry(h,"対象ページ","pages");if(e!=NULL){if(e->value.type==TYPE_NUMBER)return 0;s->page_spec=hash_value_as_text(&e->value);}
    return s->page_spec!=NULL&&*s->page_spec!='\0';
}

static unsigned int b2026_pdf_search_halfwidth_kana(unsigned int cp){
    static const unsigned short map[]={
        0x3002,0x300C,0x300D,0x3001,0x30FB,0x30F2,0x30A1,0x30A3,0x30A5,0x30A7,
        0x30A9,0x30E3,0x30E5,0x30E7,0x30C3,0x30FC,0x30A2,0x30A4,0x30A6,0x30A8,
        0x30AA,0x30AB,0x30AD,0x30AF,0x30B1,0x30B3,0x30B5,0x30B7,0x30B9,0x30BB,
        0x30BD,0x30BF,0x30C1,0x30C4,0x30C6,0x30C8,0x30CA,0x30CB,0x30CC,0x30CD,
        0x30CE,0x30CF,0x30D2,0x30D5,0x30D8,0x30DB,0x30DE,0x30DF,0x30E0,0x30E1,
        0x30E2,0x30E4,0x30E6,0x30E8,0x30E9,0x30EA,0x30EB,0x30EC,0x30ED,0x30EF,
        0x30F3,0x3099,0x309A
    };
    return cp>=0xFF61U&&cp<=0xFF9FU?(unsigned int)map[cp-0xFF61U]:cp;
}
static unsigned int b2026_pdf_search_kana_compose(unsigned int base,unsigned int mark){
    if(mark==0x3099U){
        if(base==0x30A6U)return 0x30F4U;
        if(base>=0x30ABU&&base<=0x30B3U&&(base&1U))return base+1U;
        if(base>=0x30B5U&&base<=0x30C2U&&((base-0x30B5U)%2U)==0U)return base+1U;
        if(base>=0x30C4U&&base<=0x30C9U&&((base-0x30C4U)%2U)==0U)return base+1U;
        if(base>=0x30CFU&&base<=0x30DBU&&((base-0x30CFU)%3U)==0U)return base+1U;
        if(base==0x30EFU)return 0x30F7U;if(base==0x30F0U)return 0x30F8U;
        if(base==0x30F1U)return 0x30F9U;if(base==0x30F2U)return 0x30FAU;
    }else if(mark==0x309AU&&base>=0x30CFU&&base<=0x30DBU&&((base-0x30CFU)%3U)==0U)return base+2U;
    return 0U;
}
static unsigned int b2026_pdf_search_width_fold(unsigned int cp){
    if(cp>=0xFF01U&&cp<=0xFF5EU)return cp-0xFEE0U;
    if(cp==0x3000U)return 0x20U;
    if(cp>=0xFF61U&&cp<=0xFF9FU)return b2026_pdf_search_halfwidth_kana(cp);
    if(cp==0xFFE0U)return 0x00A2U;if(cp==0xFFE1U)return 0x00A3U;if(cp==0xFFE2U)return 0x00ACU;
    if(cp==0xFFE3U)return 0x00AFU;if(cp==0xFFE4U)return 0x00A6U;if(cp==0xFFE5U)return 0x00A5U;
    if(cp==0xFFE6U)return 0x20A9U;if(cp==0xFFE8U)return 0x2502U;if(cp==0xFFE9U)return 0x2190U;
    if(cp==0xFFEAU)return 0x2191U;if(cp==0xFFEBU)return 0x2192U;if(cp==0xFFECU)return 0x2193U;
    if(cp==0xFFEDU)return 0x25A0U;if(cp==0xFFEEU)return 0x25CBU;return cp;
}
static unsigned int b2026_pdf_search_case_fold(unsigned int cp){
    if(cp>='A'&&cp<='Z')return cp+0x20U;
    if(cp>=0x00C0U&&cp<=0x00D6U)return cp+0x20U;if(cp>=0x00D8U&&cp<=0x00DEU)return cp+0x20U;
    if(cp==0x0178U)return 0x00FFU;if(cp==0x0130U)return 0x0069U;if(cp==0x212AU)return 0x006BU;if(cp==0x212BU)return 0x00E5U;
    if(cp>=0x0391U&&cp<=0x03A1U)return cp+0x20U;if(cp>=0x03A3U&&cp<=0x03ABU)return cp+0x20U;if(cp==0x03C2U)return 0x03C3U;
    if(cp>=0x0410U&&cp<=0x042FU)return cp+0x20U;if(cp>=0x0400U&&cp<=0x040FU)return cp+0x50U;
    return cp;
}
static int b2026_pdf_search_space(unsigned int cp){return cp==0x20U||cp==0x09U||cp==0x0AU||cp==0x0DU;}
static char *b2026_pdf_search_normalize(const char *text,const B2026PdfSearchSettings *s){
    const unsigned char *p=(const unsigned char*)(text!=NULL?text:"");B2026PdfTextBuffer out;memset(&out,0,sizeof(out));
    while(*p){int span=utf8_char_span(p),next_span=0;unsigned int cp,next=0U,combined=0U;if(span<1)span=1;cp=utf8_decode_codepoint(p,span);p+=(size_t)span;
        if(s->ignore_width){cp=b2026_pdf_search_width_fold(cp);if(*p){next_span=utf8_char_span(p);if(next_span<1)next_span=1;next=utf8_decode_codepoint(p,next_span);next=b2026_pdf_search_width_fold(next);combined=b2026_pdf_search_kana_compose(cp,next);if(combined!=0U){cp=combined;p+=(size_t)next_span;}}}
        if(!s->case_sensitive)cp=b2026_pdf_search_case_fold(cp);if(s->ignore_whitespace&&b2026_pdf_search_space(cp))continue;
        if(!b2026_pdf_text_append_codepoint(&out,cp)){free(out.data);return NULL;}
    }
    return b2026_pdf_text_take(&out);
}
static char *b2026_pdf_search_page_spec(const unsigned char *matched,int pages){
    B2026PdfTextBuffer out;int i;char number[32];memset(&out,0,sizeof(out));
    for(i=0;i<pages;++i)if(matched[i]){if(out.used>0U&&!b2026_pdf_text_append_char(&out,','))goto fail;snprintf(number,sizeof(number),"%d",i+1);if(!b2026_pdf_text_append_bytes(&out,number,strlen(number)))goto fail;}
    return b2026_pdf_text_take(&out);fail:free(out.data);return NULL;
}

static double b2026_pdf_search_text(const char *target,int argc,B2026PdfArg *args){
    B2026PdfDocument doc;B2026PdfTextSettings extract;B2026PdfSearchSettings settings;B2026PdfArg page_arg;
    const char *path="",*needle="";char *normalized_needle=NULL,*page_spec=NULL;unsigned char *selected=NULL,*matched=NULL;
    int rc,i,partial=0,searched=0,text_pages=0,matched_pages=0;double result=-1.0;const char *status="FAILED";
    memset(&doc,0,sizeof(doc));b2026_pdf_search_clear(target);basic_clear_error();
    if((argc!=2&&argc!=3)||args[0].kind!=1||args[1].kind!=1||args[0].text==NULL||args[1].text==NULL||
       (argc==3&&args[2].kind!=3)||!b2026_pdf_search_settings(argc==3?&args[2]:NULL,&settings))
        return b2026_pdf_search_fail(target,4350,"","path$, search-text$ [, settings{}] is required / PDFパス、検索文字列、任意の設定が必要です","FAILED");
    path=args[0].text;needle=args[1].text;if(*needle=='\0')return b2026_pdf_search_fail(target,4351,path,"search text must not be empty / 検索文字列は空にできません","FAILED");
    normalized_needle=b2026_pdf_search_normalize(needle,&settings);if(normalized_needle==NULL||*normalized_needle=='\0'){
        free(normalized_needle);return b2026_pdf_search_fail(target,4351,path,"normalized search text is empty / 正規化後の検索文字列が空です","FAILED");}
    rc=b2026_pdf_load(&doc,path,0);if(rc<=0){const char *detail=b2026_pdf_load_error(rc);int timed_out=g_last_error.timeout!=0;
        free(normalized_needle);b2026_pdf_document_free(&doc);if(timed_out){char message[2048];
            b2026_pdf_search_result(target,0,0,0,"TIMEOUT",0,0,0,0,NULL,"");
            snprintf(message,sizeof(message),"PDF.SEARCHTEXT timeout: path=%s / 【PDF文字検索タイムアウト】PDFの読み込みがタイムアウトしました",path);
            b2026_try_append_log_line("__PDF_ERROR.log",message);return -1.0;}
        return b2026_pdf_search_fail(target,4352,path,detail,"FAILED");}
    memset(&page_arg,0,sizeof(page_arg));page_arg.kind=1;page_arg.text=settings.page_spec;
    if(!b2026_pdf_parse_page_spec(&page_arg,doc.page_count,&selected)){b2026_pdf_document_free(&doc);free(normalized_needle);return b2026_pdf_search_fail(target,4353,path,"invalid target pages / 対象ページの指定が正しくありません","FAILED");}
    matched=(unsigned char*)calloc((size_t)doc.page_count,1U);if(matched==NULL)goto memory_fail;
    extract.coordinate_order=1;extract.space=1;extract.line_break=1;extract.tolerance=-1.0;extract.separator="\f";
    for(i=0;i<doc.page_count;++i)if(selected[i]){char *text,*normalized;++searched;text=b2026_pdf_text_page(&doc,i,&extract,path,&partial);if(text==NULL)goto memory_fail;
        if(*text!='\0')++text_pages;normalized=b2026_pdf_search_normalize(text,&settings);free(text);if(normalized==NULL)goto memory_fail;
        if(strstr(normalized,normalized_needle)!=NULL){matched[i]=1;++matched_pages;}free(normalized);
    }
    page_spec=b2026_pdf_search_page_spec(matched,doc.page_count);if(page_spec==NULL)goto memory_fail;
    status=partial?"PARTIAL":(matched_pages>0?"FOUND":(text_pages>0?"NOT_FOUND":"NO_TEXT"));
    b2026_pdf_search_result(target,1,partial?0:1,matched_pages>0,status,doc.page_count,searched,text_pages,matched_pages,matched,page_spec);
    result=(double)matched_pages;goto done;
memory_fail:
    b2026_pdf_search_fail(target,4354,path,"memory or search limit exceeded / メモリまたは検索上限を超えました","FAILED");result=-1.0;
done:
    free(page_spec);free(matched);free(selected);free(normalized_needle);b2026_pdf_document_free(&doc);return result;
}
