/* BASIC2026 portable PDF bookmark (outline) reader and editor. */
#define B2026_PDF_BOOKMARK_MAX 10000
#define B2026_PDF_BOOKMARK_DEPTH_MAX 64

typedef struct {
    char *title;
    int page,level,parent,open,resolved,object_index;
} B2026PdfBookmark;

typedef struct {
    B2026PdfBookmark *items;
    int count,capacity;
} B2026PdfBookmarkList;

static void b2026_pdf_bm_free(B2026PdfBookmarkList *list){
    int i;if(list==NULL)return;for(i=0;i<list->count;++i)free(list->items[i].title);free(list->items);memset(list,0,sizeof(*list));
}

static int b2026_pdf_bm_push(B2026PdfBookmarkList *list,B2026PdfBookmark *item){
    if(list->count>=B2026_PDF_BOOKMARK_MAX)return 0;if(list->count==list->capacity){int cap=list->capacity?list->capacity*2:32;B2026PdfBookmark*p;if(cap>B2026_PDF_BOOKMARK_MAX)cap=B2026_PDF_BOOKMARK_MAX;p=(B2026PdfBookmark*)realloc(list->items,(size_t)cap*sizeof(*p));if(p==NULL)return 0;list->items=p;list->capacity=cap;}list->items[list->count++]=*item;return 1;
}

static int b2026_pdf_bm_page_from_ref(B2026PdfDocument *doc,int number,int generation){
    int oi=b2026_pdf_object_lookup(doc,number,generation),i;if(oi<0)return 0;for(i=0;i<doc->page_count;++i)if(doc->page_objects[i]==oi)return i+1;return 0;
}

static int b2026_pdf_bm_dest_page(B2026PdfDocument *doc,const unsigned char *body,size_t n,const char *key){
    B2026PdfKeyRange r;size_t p;char *end;long number,generation;if(!b2026_pdf_tf_find_key(body,n,key,&r)||!r.found)return 0;p=b2026_pdf_tf_space(body,r.value_end,r.value_start);if(p<r.value_end&&body[p]=='[')p=b2026_pdf_tf_space(body,r.value_end,p+1U);if(p>=r.value_end||!isdigit(body[p]))return 0;number=strtol((const char*)body+p,&end,10);if(end==(char*)body+p||number<=0||number>INT_MAX)return 0;p=b2026_pdf_tf_space(body,r.value_end,(size_t)(end-(char*)body));if(p>=r.value_end||!isdigit(body[p]))return 0;generation=strtol((const char*)body+p,&end,10);if(end==(char*)body+p||generation<0||generation>INT_MAX)return 0;p=b2026_pdf_tf_space(body,r.value_end,(size_t)(end-(char*)body));if(p>=r.value_end||body[p]!='R')return 0;return b2026_pdf_bm_page_from_ref(doc,(int)number,(int)generation);
}

static int b2026_pdf_bm_item_page(B2026PdfDocument *doc,B2026PdfObject *item){
    int page=b2026_pdf_bm_dest_page(doc,item->body,item->body_length,"/Dest");B2026PdfKeyRange r;int number,generation,oi;if(page>0)return page;if(!b2026_pdf_tf_find_key(item->body,item->body_length,"/A",&r)||!r.found)return 0;if(b2026_pdf_tf_ref(item->body+r.value_start,r.value_end-r.value_start,&number,&generation)){oi=b2026_pdf_object_lookup(doc,number,generation);if(oi<0)return 0;return b2026_pdf_bm_dest_page(doc,doc->objects[oi].body,doc->objects[oi].body_length,"/D");}return b2026_pdf_bm_dest_page(doc,item->body+r.value_start,r.value_end-r.value_start,"/D");
}

static int b2026_pdf_bm_walk(B2026PdfDocument *doc,int first,int level,int parent,B2026PdfBookmarkList *list,unsigned char *seen){
    int oi=first,steps=0;if(level>B2026_PDF_BOOKMARK_DEPTH_MAX)return 0;while(oi>=0){B2026PdfObject*o;B2026PdfBookmark item;int child_number,child_generation,next_number,next_generation,child=-1,next=-1,count=0,ordinal;if(oi>=doc->object_count||seen[oi]||++steps>B2026_PDF_BOOKMARK_MAX)return 0;seen[oi]=1U;o=&doc->objects[oi];memset(&item,0,sizeof(item));item.title=b2026_pdf_dict_string(doc,o->body,o->body_length,"/Title");if(item.title==NULL)item.title=b2026_strdup_simple("");if(item.title==NULL)return 0;item.page=b2026_pdf_bm_item_page(doc,o);item.resolved=item.page>0;item.level=level;item.parent=parent;item.open=1;item.object_index=oi;if(b2026_pdf_dict_integer(o->body,o->body_length,"/Count",&count)&&count<0)item.open=0;if(!b2026_pdf_bm_push(list,&item)){free(item.title);return 0;}ordinal=list->count;if(b2026_pdf_named_ref(o->body,o->body_length,"/First",&child_number,&child_generation))child=b2026_pdf_object_lookup(doc,child_number,child_generation);if(child>=0&&!b2026_pdf_bm_walk(doc,child,level+1,ordinal,list,seen))return 0;if(b2026_pdf_named_ref(o->body,o->body_length,"/Next",&next_number,&next_generation))next=b2026_pdf_object_lookup(doc,next_number,next_generation);oi=next;}return 1;
}

static int b2026_pdf_bm_collect(B2026PdfDocument *doc,B2026PdfBookmarkList *list){
    int number,generation,root,first_number,first_generation,first;unsigned char*seen;if(doc->root_object<0)return 0;if(!b2026_pdf_named_ref(doc->objects[doc->root_object].body,doc->objects[doc->root_object].body_length,"/Outlines",&number,&generation))return 1;root=b2026_pdf_object_lookup(doc,number,generation);if(root<0)return 0;if(!b2026_pdf_named_ref(doc->objects[root].body,doc->objects[root].body_length,"/First",&first_number,&first_generation))return 1;first=b2026_pdf_object_lookup(doc,first_number,first_generation);if(first<0)return 0;seen=(unsigned char*)calloc((size_t)doc->object_count,1U);if(seen==NULL)return 0;seen[root]=1U;number=b2026_pdf_bm_walk(doc,first,1,0,list,seen);free(seen);return number;
}

static void b2026_pdf_bm_pair_number(const char*target,const char*jp,const char*en,double value){hash_set_number(target,jp,value);hash_set_number(target,en,value);}
static void b2026_pdf_bm_pair_string(const char*target,const char*jp,const char*en,const char*value){hash_set_string(target,jp,value!=NULL?value:"");hash_set_string(target,en,value!=NULL?value:"");}
static void b2026_pdf_bm_index_key(char*out,size_t cap,int index,const char*field){snprintf(out,cap,"%d,%s",index,field);}

static int b2026_pdf_bm_to_hash(const char*target,B2026PdfDocument*doc,B2026PdfBookmarkList*list){
    int i;char key[128];HashVariable*h=ensure_local_hash(get_hash_frame(target),target);if(h==NULL)return 0;clear_hash_entries(h);b2026_pdf_bm_pair_number(target,"成功","ok",1.0);b2026_pdf_bm_pair_string(target,"状態","status",list->count?"OK":"EMPTY");b2026_pdf_bm_pair_number(target,"件数","count",(double)list->count);b2026_pdf_bm_pair_number(target,"総ページ数","pageCount",(double)doc->page_count);for(i=0;i<list->count;++i){B2026PdfBookmark*b=&list->items[i];b2026_pdf_bm_index_key(key,sizeof(key),i+1,"タイトル");hash_set_string(target,key,b->title);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"title");hash_set_string(target,key,b->title);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"ページ");hash_set_number(target,key,(double)b->page);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"page");hash_set_number(target,key,(double)b->page);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"階層");hash_set_number(target,key,(double)b->level);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"level");hash_set_number(target,key,(double)b->level);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"親番号");hash_set_number(target,key,(double)b->parent);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"parent");hash_set_number(target,key,(double)b->parent);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"開く");hash_set_number(target,key,(double)b->open);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"open");hash_set_number(target,key,(double)b->open);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"移動先取得");hash_set_number(target,key,(double)b->resolved);b2026_pdf_bm_index_key(key,sizeof(key),i+1,"destinationResolved");hash_set_number(target,key,(double)b->resolved);}return 1;
}

static int b2026_pdf_bm_error(int code,const char*operation,const char*path,const char*detail){
    char message[4096];snprintf(message,sizeof(message),"PDF.%s failed: path=%s: %s",operation,path!=NULL?path:"",detail!=NULL?detail:"unknown error / 不明なエラー");basic_set_error_logged(code,message,operation,current_source_path,current_line,"PDF");b2026_try_append_log_line("__PDF_ERROR.log",message);return 0;
}

static int b2026_pdf_bookmark_get(const char*target,int argc,const B2026PdfArg*args){
    B2026PdfDocument doc;B2026PdfBookmarkList list;HashVariable*h;int rc;memset(&doc,0,sizeof(doc));memset(&list,0,sizeof(list));if(target==NULL||*target=='\0'||(h=ensure_local_hash(get_hash_frame(target),target))==NULL)return 0;clear_hash_entries(h);if(argc!=1||args[0].kind!=1||args[0].text==NULL){b2026_pdf_bm_error(4390,"GETBOOKMARKS","","PDF path is required / PDFパスが必要です");return 0;}rc=b2026_pdf_load(&doc,args[0].text,0);if(rc<=0){b2026_pdf_bm_pair_number(target,"成功","ok",0.0);b2026_pdf_bm_pair_string(target,"状態","status","FAILED");b2026_pdf_bm_error(4391,"GETBOOKMARKS",args[0].text,b2026_pdf_load_error(rc));b2026_pdf_document_free(&doc);return 0;}if(!b2026_pdf_bm_collect(&doc,&list)||!b2026_pdf_bm_to_hash(target,&doc,&list)){b2026_pdf_bm_error(4392,"GETBOOKMARKS",args[0].text,"bookmark tree is damaged or too deep / ブックマークツリーが破損しているか深すぎます");b2026_pdf_bm_free(&list);b2026_pdf_document_free(&doc);return 0;}b2026_pdf_bm_free(&list);b2026_pdf_document_free(&doc);return 1;
}

static HashEntry*b2026_pdf_bm_input_entry(HashVariable*h,int index,const char*jp,const char*en){char key[128];HashEntry*e;b2026_pdf_bm_index_key(key,sizeof(key),index,jp);e=find_hash_entry(h,key);if(e!=NULL)return e;b2026_pdf_bm_index_key(key,sizeof(key),index,en);return find_hash_entry(h,key);}
static int b2026_pdf_bm_input_number(HashVariable*h,int index,const char*jp,const char*en,double fallback,double*out,int required){HashEntry*e=b2026_pdf_bm_input_entry(h,index,jp,en);if(e==NULL){if(required)return 0;*out=fallback;return 1;}if(e->value.type!=TYPE_NUMBER||!isfinite(e->value.val.number)||floor(e->value.val.number)!=e->value.val.number)return 0;*out=e->value.val.number;return 1;}

static int b2026_pdf_bm_from_hash(HashVariable*h,int page_count,B2026PdfBookmarkList*list,int*open_panel){
    HashEntry*ce=b2026_pdf_tf_entry(h,"件数","count"),*pe=b2026_pdf_tf_entry(h,"パネル表示","openPanel");int count,i,last_level=0,stack[B2026_PDF_BOOKMARK_DEPTH_MAX+1];if(ce==NULL||ce->value.type!=TYPE_NUMBER||!isfinite(ce->value.val.number)||floor(ce->value.val.number)!=ce->value.val.number||ce->value.val.number<1.0||ce->value.val.number>B2026_PDF_BOOKMARK_MAX)return 0;count=(int)ce->value.val.number;*open_panel=pe!=NULL&&pe->value.type==TYPE_NUMBER&&isfinite(pe->value.val.number)&&pe->value.val.number!=0.0;memset(stack,0,sizeof(stack));for(i=1;i<=count;++i){HashEntry*te=b2026_pdf_bm_input_entry(h,i,"タイトル","title");double page,level,open;B2026PdfBookmark item;if(te==NULL||te->value.type==TYPE_NUMBER||!b2026_pdf_bm_input_number(h,i,"ページ","page",0.0,&page,1)||!b2026_pdf_bm_input_number(h,i,"階層","level",1.0,&level,0)||!b2026_pdf_bm_input_number(h,i,"開く","open",1.0,&open,0))return 0;if(page<1.0){page=1.0;b2026_pdf_log_warning("ブックマークのページ番号を1ページ目へ補正しました。 / Bookmark page was clamped to page 1.");}else if(page>(double)page_count){page=(double)page_count;b2026_pdf_log_warning("ブックマークのページ番号を最終ページへ補正しました。 / Bookmark page was clamped to the last page.");}if(level<1.0||level>B2026_PDF_BOOKMARK_DEPTH_MAX||(i==1&&level!=1.0)||(i>1&&level>(double)(last_level+1)))return 0;memset(&item,0,sizeof(item));item.title=b2026_strdup_simple(hash_value_as_text(&te->value));if(item.title==NULL||*item.title=='\0'||strlen(item.title)>4096U){free(item.title);return 0;}item.page=(int)page;item.level=(int)level;item.open=open!=0.0;item.resolved=1;item.parent=item.level>1?stack[item.level-1]:0;if(!b2026_pdf_bm_push(list,&item)){free(item.title);return 0;}stack[item.level]=i;last_level=item.level;}return 1;
}

static int b2026_pdf_bm_build(B2026PdfDocument*doc,B2026PdfBookmarkList*list,int open_panel){
    int root,i;char ref[64];root=b2026_pdf_tf_add_body(doc,"<< >>");if(root<0)return 0;for(i=0;i<list->count;++i){list->items[i].object_index=b2026_pdf_tf_add_body(doc,"<< >>");if(list->items[i].object_index<0)return 0;}for(i=0;i<list->count;++i){B2026PdfBookmark*b=&list->items[i];B2026PdfTextBuffer body;char*title=b2026_pdf_metadata_pdf_hex(b->title);int j,prev=0,next=0,first=0,last=0,desc=0,parent_index=b->parent?list->items[b->parent-1].object_index:root;char temp[256];if(title==NULL)return 0;for(j=i-1;j>=0;--j)if(list->items[j].level==b->level&&list->items[j].parent==b->parent){prev=list->items[j].object_index;break;}for(j=i+1;j<list->count;++j){if(list->items[j].level<b->level)break;if(list->items[j].level==b->level&&list->items[j].parent==b->parent){next=list->items[j].object_index;break;}}for(j=i+1;j<list->count&&list->items[j].level>b->level;++j){++desc;if(list->items[j].parent==i+1){if(!first)first=list->items[j].object_index;last=list->items[j].object_index;}}memset(&body,0,sizeof(body));snprintf(temp,sizeof(temp),"<< /Title %s /Parent %d 0 R /Dest [%d 0 R /Fit]",title,doc->objects[parent_index].old_number,doc->objects[doc->page_objects[b->page-1]].old_number);free(title);if(!b2026_pdf_text_append_bytes(&body,temp,strlen(temp)))goto item_fail;if(prev){snprintf(temp,sizeof(temp)," /Prev %d 0 R",doc->objects[prev].old_number);if(!b2026_pdf_text_append_bytes(&body,temp,strlen(temp)))goto item_fail;}if(next){snprintf(temp,sizeof(temp)," /Next %d 0 R",doc->objects[next].old_number);if(!b2026_pdf_text_append_bytes(&body,temp,strlen(temp)))goto item_fail;}if(first){snprintf(temp,sizeof(temp)," /First %d 0 R /Last %d 0 R /Count %d",doc->objects[first].old_number,doc->objects[last].old_number,b->open?desc:-desc);if(!b2026_pdf_text_append_bytes(&body,temp,strlen(temp)))goto item_fail;}if(!b2026_pdf_text_append_bytes(&body," >>",3U))goto item_fail;free(doc->objects[b->object_index].body);doc->objects[b->object_index].body=(unsigned char*)b2026_pdf_text_take(&body);doc->objects[b->object_index].body_length=strlen((char*)doc->objects[b->object_index].body);continue;item_fail:free(body.data);return 0;}
    {B2026PdfTextBuffer body;char temp[256];int first=list->items[0].object_index,last=first;memset(&body,0,sizeof(body));for(i=1;i<list->count;++i)if(list->items[i].level==1)last=list->items[i].object_index;snprintf(temp,sizeof(temp),"<< /Type /Outlines /First %d 0 R /Last %d 0 R /Count %d >>",doc->objects[first].old_number,doc->objects[last].old_number,list->count);if(!b2026_pdf_text_append_bytes(&body,temp,strlen(temp)))return 0;free(doc->objects[root].body);doc->objects[root].body=(unsigned char*)b2026_pdf_text_take(&body);doc->objects[root].body_length=strlen((char*)doc->objects[root].body);}
    snprintf(ref,sizeof(ref),"%d 0 R",doc->objects[root].old_number);if(!b2026_pdf_tf_set_key(doc,doc->root_object,"/Outlines",ref,0))return 0;if(open_panel&&!b2026_pdf_tf_set_key(doc,doc->root_object,"/PageMode","/UseOutlines",0))return 0;return 1;
}

static int b2026_pdf_bm_verify(const char*path,B2026PdfBookmarkList*expected,int expect_empty){
    B2026PdfDocument doc;B2026PdfBookmarkList actual;int rc,i,ok=0;memset(&doc,0,sizeof(doc));memset(&actual,0,sizeof(actual));rc=b2026_pdf_load(&doc,path,0);if(rc<=0||!b2026_pdf_bm_collect(&doc,&actual))goto done;if(expect_empty){ok=actual.count==0;goto done;}if(actual.count!=expected->count)goto done;for(i=0;i<actual.count;++i)if(strcmp(actual.items[i].title,expected->items[i].title)!=0||actual.items[i].page!=expected->items[i].page||actual.items[i].level!=expected->items[i].level||actual.items[i].open!=expected->items[i].open)goto done;ok=1;done:b2026_pdf_bm_free(&actual);b2026_pdf_document_free(&doc);return ok;
}

static const char*b2026_pdf_bookmark_edit(const char*operation,int argc,const B2026PdfArg*args){
    int remove=strcmp(operation,"REMOVEBOOKMARKS")==0,separate,source_index,rc,i,open_panel=0;const char*source;char*output=NULL,*stage=NULL;HashVariable*h=NULL;B2026PdfDocument doc;B2026PdfBookmarkList list;B2026PdfPageList pages;const char*result;memset(&doc,0,sizeof(doc));memset(&list,0,sizeof(list));memset(&pages,0,sizeof(pages));if(remove){if(argc!=1&&argc!=2)return b2026_pdf_fail(4393,"","REMOVEBOOKMARKS requires path$ or output$, source$ / ブックマーク削除にはPDFパス、または保存先と入力元が必要です");separate=argc==2;}else{if(argc!=2&&argc!=3)return b2026_pdf_fail(4393,"","SETBOOKMARKS requires path$, bookmarks{} or output$, source$, bookmarks{} / ブックマーク設定の引数が正しくありません");separate=argc==3;if(!b2026_pdf_tf_hash(&args[argc-1],&h))return b2026_pdf_fail(4393,"","bookmark hash was not found / ブックマーク連想配列がありません");}source_index=separate?1:0;if(args[0].kind!=1||args[0].text==NULL||args[source_index].kind!=1||args[source_index].text==NULL)return b2026_pdf_fail(4393,"","PDF path must be a string / PDFパスは文字列で指定してください");output=b2026_pdf_output_path(args[0].text);source=args[source_index].text;if(output==NULL)goto memory_fail;rc=b2026_pdf_load(&doc,source,1);if(rc<=0){b2026_pdf_bm_error(4394,operation,source,b2026_pdf_load_error(rc));goto fail;}if(doc.root_object<0){b2026_pdf_bm_error(4395,operation,source,"document catalog is missing / 文書Catalogがありません");goto fail;}if(remove){char*page_mode;if(!b2026_pdf_tf_set_key(&doc,doc.root_object,"/Outlines",NULL,1)){b2026_pdf_bm_error(4396,operation,source,"catalog could not be updated / Catalogを更新できません");goto fail;}page_mode=b2026_pdf_dict_name(doc.objects[doc.root_object].body,doc.objects[doc.root_object].body_length,"/PageMode");if(page_mode!=NULL&&strcmp(page_mode,"UseOutlines")==0&&!b2026_pdf_tf_set_key(&doc,doc.root_object,"/PageMode",NULL,1)){free(page_mode);b2026_pdf_bm_error(4396,operation,source,"bookmark page mode could not be removed / ブックマーク表示モードを削除できません");goto fail;}free(page_mode);}else{if(!b2026_pdf_bm_from_hash(h,doc.page_count,&list,&open_panel)){b2026_pdf_bm_error(4397,operation,source,"bookmark data is invalid / ブックマークデータが正しくありません");goto fail;}if(!b2026_pdf_bm_build(&doc,&list,open_panel)){b2026_pdf_bm_error(4398,operation,source,"bookmark tree could not be built / ブックマークツリーを構築できません");goto fail;}}/* The writer creates fresh Catalog and Pages objects.  Do not copy the old keys into the generated Catalog, because duplicate /Pages entries make strict readers use a stale reference. */if(!b2026_pdf_tf_set_key(&doc,doc.root_object,"/Type",NULL,1)||!b2026_pdf_tf_set_key(&doc,doc.root_object,"/Pages",NULL,1)){b2026_pdf_bm_error(4396,operation,source,"catalog could not be normalized / Catalogを正規化できません");goto fail;}for(i=0;i<doc.page_count;++i)if(!b2026_pdf_pages_push(&pages,&doc,doc.page_objects[i],-1))goto memory_fail;stage=b2026_atomic_temp_path(output);if(stage==NULL||!b2026_pdf_write_output(stage,&doc,1,&pages,0,1,1)){b2026_pdf_bm_error(4399,operation,output,"bookmark PDF could not be staged or exceeded 2 GiB / ブックマークPDFを準備できないか2GiB上限を超えました");goto fail;}if(!b2026_pdf_bm_verify(stage,&list,remove)){b2026_pdf_bm_error(4400,operation,output,"post-write bookmark verification failed / 保存後のブックマーク検証に失敗しました");goto fail;}if(!b2026_atomic_commit_file(stage,output)){b2026_pdf_bm_error(4401,operation,output,"verified PDF could not be committed / 検証済みPDFを保存先へ反映できません");goto fail;}free(stage);stage=NULL;result=runtime_temp_string_copy(output);free(output);free(pages.items);b2026_pdf_bm_free(&list);b2026_pdf_document_free(&doc);return result;memory_fail:b2026_pdf_bm_error(4402,operation,source,"out of memory / メモリ不足です");fail:if(stage!=NULL){b2026_atomic_discard_file(stage);free(stage);}free(output);free(pages.items);b2026_pdf_bm_free(&list);b2026_pdf_document_free(&doc);return runtime_temp_string_copy("");
}
