/* BASIC2026 portable external PDF action remover. */
typedef struct {
    int external_links_removed,external_actions_removed,annotations_removed;
    int internal_links_preserved,verified;
} B2026PdfExternalLinkStats;

static void b2026_pdf_el_pair_number(const char*t,const char*jp,const char*en,double v){if(t==NULL)return;hash_set_number(t,jp,v);hash_set_number(t,en,v);}
static void b2026_pdf_el_pair_string(const char*t,const char*jp,const char*en,const char*v){if(t==NULL)return;hash_set_string(t,jp,v!=NULL?v:"");hash_set_string(t,en,v!=NULL?v:"");}
static void b2026_pdf_el_report(const char*t,const char*path,int ok,const B2026PdfExternalLinkStats*s){HashVariable*h;if(t==NULL||*t=='\0')return;h=ensure_local_hash(get_hash_frame(t),t);clear_hash_entries(h);b2026_pdf_el_pair_number(t,"成功","ok",ok?1.0:0.0);b2026_pdf_el_pair_string(t,"保存先","path",ok&&path!=NULL?path:"");b2026_pdf_el_pair_number(t,"外部リンク削除数","externalLinksRemoved",s!=NULL?(double)s->external_links_removed:0.0);b2026_pdf_el_pair_number(t,"外部動作削除数","externalActionsRemoved",s!=NULL?(double)s->external_actions_removed:0.0);b2026_pdf_el_pair_number(t,"注釈削除数","annotationsRemoved",s!=NULL?(double)s->annotations_removed:0.0);b2026_pdf_el_pair_number(t,"内部リンク維持数","internalLinksPreserved",s!=NULL?(double)s->internal_links_preserved:0.0);b2026_pdf_el_pair_string(t,"検証結果","verification",ok&&s!=NULL&&s->verified?"PASS":"FAIL");}

static int b2026_pdf_el_external_kind(const unsigned char*b,size_t n){char*k=b2026_pdf_dict_name(b,n,"/S");int yes=k!=NULL&&(strcmp(k,"URI")==0||strcmp(k,"GoToR")==0||strcmp(k,"GoToE")==0||strcmp(k,"Launch")==0||strcmp(k,"SubmitForm")==0||strcmp(k,"ImportData")==0);free(k);return yes;}
static int b2026_pdf_el_object_has(B2026PdfDocument*d,int oi,int depth);

static int b2026_pdf_el_value_has(B2026PdfDocument*d,const unsigned char*v,size_t n,int depth){size_t p=0U,first;if(v==NULL||depth>32)return 0;first=b2026_pdf_tf_space(v,n,0U);if(first<n&&v[first]=='<'&&first+1U<n&&v[first+1U]=='<'&&b2026_pdf_el_external_kind(v+first,n-first))return 1;while(p<n){size_t q;int num,gen,oi;p=b2026_pdf_tf_space(v,n,p);if(p>=n)break;if(v[p]=='('||(v[p]=='<'&&(p+1U>=n||v[p+1U]!='<'))){p=b2026_pdf_tf_value_end(v,n,p,0);continue;}if(v[p]=='<'&&p+1U<n&&v[p+1U]=='<'){q=b2026_pdf_tf_value_end(v,n,p,0);if(p!=first&&q>p&&b2026_pdf_el_value_has(d,v+p,q-p,depth+1))return 1;p=(p==first)?p+2U:q;continue;}if(b2026_pdf_tf_ref(v+p,n-p,&num,&gen)&&(oi=b2026_pdf_object_lookup(d,num,gen))>=0){if(b2026_pdf_el_object_has(d,oi,depth+1))return 1;q=b2026_pdf_tf_value_end(v,n,p,0);p=q>p?q:p+1U;continue;}++p;}return 0;}
static int b2026_pdf_el_object_has(B2026PdfDocument*d,int oi,int depth){B2026PdfObject*o;if(depth>32||oi<0||oi>=d->object_count)return 0;o=&d->objects[oi];if(b2026_pdf_el_external_kind(o->body,o->body_length))return 1;{B2026PdfKeyRange r;if(b2026_pdf_tf_find_key(o->body,o->body_length,"/Next",&r)&&r.found&&b2026_pdf_el_value_has(d,o->body+r.value_start,r.value_end-r.value_start,depth+1))return 1;}return 0;}
static int b2026_pdf_el_key_has(B2026PdfDocument*d,int oi,const char*key,int*present){B2026PdfObject*o;B2026PdfKeyRange r;if(present!=NULL)*present=0;if(oi<0||oi>=d->object_count)return 0;o=&d->objects[oi];if(!b2026_pdf_tf_find_key(o->body,o->body_length,key,&r))return 0;if(!r.found)return 1;if(present!=NULL)*present=1;return b2026_pdf_el_value_has(d,o->body+r.value_start,r.value_end-r.value_start,0)?2:1;}
static int b2026_pdf_el_owner_external(B2026PdfDocument*d,const unsigned char*b,size_t n){B2026PdfKeyRange r;if(b2026_pdf_tf_find_key(b,n,"/A",&r)&&r.found&&b2026_pdf_el_value_has(d,b+r.value_start,r.value_end-r.value_start,0))return 1;if(b2026_pdf_tf_find_key(b,n,"/AA",&r)&&r.found&&b2026_pdf_el_value_has(d,b+r.value_start,r.value_end-r.value_start,0))return 1;return 0;}

static int b2026_pdf_el_filter_annotations(B2026PdfDocument*d,B2026PdfExternalLinkStats*s){int page;for(page=0;page<d->page_count;++page){int pi=d->page_objects[page];B2026PdfObject*p=&d->objects[pi];B2026PdfKeyRange r;B2026PdfTextBuffer out;char*a=NULL;size_t n,q,end;int kept=0;memset(&out,0,sizeof(out));if(!b2026_pdf_tf_find_key(p->body,p->body_length,"/Annots",&r))return 0;if(!r.found)continue;a=b2026_pdf_tf_value_copy(d,p->body+r.value_start,r.value_end-r.value_start,1);if(a==NULL)return 0;n=strlen(a);q=b2026_pdf_tf_space((unsigned char*)a,n,0U);if(q>=n||a[q]!='[')goto fail;end=b2026_pdf_tf_value_end((unsigned char*)a,n,q,0);if(!b2026_pdf_text_append_char(&out,'['))goto fail;++q;while(q<end){size_t z;const unsigned char*body=NULL;size_t body_n=0U;int oi=-1,is_link=0,external=0,num,gen,remove_annotation;q=b2026_pdf_tf_space((unsigned char*)a,end,q);if(q>=end||a[q]==']')break;z=b2026_pdf_tf_value_end((unsigned char*)a,end,q,0);if(z<=q)goto fail;if(b2026_pdf_tf_ref((unsigned char*)a+q,z-q,&num,&gen)&&(oi=b2026_pdf_object_lookup(d,num,gen))>=0){body=d->objects[oi].body;body_n=d->objects[oi].body_length;}else if(a[q]=='<'&&q+1U<z&&a[q+1U]=='<'){body=(unsigned char*)a+q;body_n=z-q;}if(body!=NULL){is_link=b2026_pdf_dict_name_is(body,body_n,"/Subtype","Link");external=b2026_pdf_el_owner_external(d,body,body_n);}remove_annotation=external&&(is_link||oi<0);if(remove_annotation){++s->annotations_removed;if(is_link)++s->external_links_removed;}else{if(!b2026_pdf_text_append_char(&out,' ')||!b2026_pdf_text_append_bytes(&out,a+q,z-q))goto fail;++kept;}q=z;}if(!b2026_pdf_text_append_bytes(&out," ]",2U))goto fail;if(kept){if(!b2026_pdf_tf_set_key(d,pi,"/Annots",out.data,0))goto fail;}else if(!b2026_pdf_tf_set_key(d,pi,"/Annots",NULL,1))goto fail;free(out.data);free(a);continue;fail:free(out.data);free(a);return 0;}return 1;}

static int b2026_pdf_el_strip(B2026PdfDocument*d,B2026PdfExternalLinkStats*s){static const char*keys[]={"/OpenAction","/A","/AA","/Next"};int i,k;if(!b2026_pdf_el_filter_annotations(d,s))return 0;for(i=0;i<d->object_count;++i)for(k=0;k<4;++k){int present=0,kind=b2026_pdf_el_key_has(d,i,keys[k],&present);if(kind==0)return 0;if(present&&kind==2){if(!b2026_pdf_tf_set_key(d,i,keys[k],NULL,1))return 0;++s->external_actions_removed;}}for(i=0;i<d->object_count;++i)if(b2026_pdf_el_external_kind(d->objects[i].body,d->objects[i].body_length)){static const char*ak[]={"/S","/URI","/F","/D","/Next"};for(k=0;k<5;++k)if(!b2026_pdf_san_remove_key(d,i,ak[k],NULL))return 0;++s->external_actions_removed;}return 1;}

static int b2026_pdf_el_count_internal(B2026PdfDocument*d,int*out){B2026PdfLinkList l;int i;memset(&l,0,sizeof(l));if(!b2026_pdf_link_collect(d,0,&l))return 0;*out=0;for(i=0;i<l.count;++i)if(strcmp(l.items[i].type,"INTERNAL")==0)++*out;b2026_pdf_link_free(&l);return 1;}
static int b2026_pdf_el_verify(const char*path,int pages,int internal,int had_form,int had_attachments,int had_bookmarks,int had_info){B2026PdfDocument d;int rc,i,after=0;memset(&d,0,sizeof(d));rc=b2026_pdf_load(&d,path,0);if(rc<=0||d.page_count!=pages)goto fail;if(had_form&&!b2026_pdf_memfind(d.bytes,d.length,"/AcroForm",9U))goto fail;if(had_attachments&&!b2026_pdf_memfind(d.bytes,d.length,"/EmbeddedFiles",14U))goto fail;if(had_bookmarks&&!b2026_pdf_memfind(d.bytes,d.length,"/Outlines",9U))goto fail;if(had_info&&d.info_object<0)goto fail;for(i=0;i<d.object_count;++i){int k;static const char*keys[]={"/OpenAction","/A","/AA","/Next"};if(b2026_pdf_el_external_kind(d.objects[i].body,d.objects[i].body_length))goto fail;for(k=0;k<4;++k){int present=0,kind=b2026_pdf_el_key_has(&d,i,keys[k],&present);if(kind==0||(present&&kind==2))goto fail;}}if(!b2026_pdf_el_count_internal(&d,&after)||after!=internal)goto fail;b2026_pdf_document_free(&d);return 1;fail:b2026_pdf_document_free(&d);return 0;}

static const char*b2026_pdf_external_links_remove(const char*target,int argc,const B2026PdfArg*args){int separate,si,rc,i,internal=0,had_form,had_attachments,had_bookmarks,had_info;const char*source,*result;char*output=NULL,*stage=NULL;B2026PdfDocument doc;B2026PdfPageList pages;B2026PdfExternalLinkStats stats;memset(&doc,0,sizeof(doc));memset(&pages,0,sizeof(pages));memset(&stats,0,sizeof(stats));if((argc!=1&&argc!=2)||args[0].kind!=1||args[0].text==NULL||(argc==2&&(args[1].kind!=1||args[1].text==NULL))){b2026_pdf_el_report(target,"",0,&stats);return b2026_pdf_fail(4433,"","REMOVEEXTERNALLINKS requires path$ or output$, source$ / 外部リンク削除にはPDFパス、または保存先と入力元が必要です");}separate=argc==2;si=separate?1:0;source=args[si].text;output=b2026_pdf_output_path(args[0].text);if(output==NULL)goto memory_fail;rc=b2026_pdf_load(&doc,source,4);if(rc<=0){b2026_pdf_fail(4434,source,b2026_pdf_load_error(rc));goto fail;}if(b2026_pdf_memfind(doc.bytes,doc.length,"/ByteRange",10U)!=NULL){b2026_pdf_fail(4435,source,"digitally signed PDF cannot be modified / 電子署名付きPDFは変更できません");goto fail;}had_form=b2026_pdf_memfind(doc.bytes,doc.length,"/AcroForm",9U)!=NULL;had_attachments=b2026_pdf_memfind(doc.bytes,doc.length,"/EmbeddedFiles",14U)!=NULL;had_bookmarks=b2026_pdf_memfind(doc.bytes,doc.length,"/Outlines",9U)!=NULL;had_info=doc.info_object>=0;if(!b2026_pdf_el_count_internal(&doc,&internal)||!b2026_pdf_el_strip(&doc,&stats))goto memory_fail;stats.internal_links_preserved=internal;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))goto memory_fail;for(i=0;i<doc.page_count;++i)if(!b2026_pdf_pages_push(&pages,&doc,doc.page_objects[i],-1))goto memory_fail;if(!b2026_pdf_select_reachable_objects(&doc,1,&pages,1)||!b2026_pdf_san_reenable_annotations(&doc))goto memory_fail;stage=b2026_atomic_temp_path(output);if(stage==NULL||!b2026_pdf_write_output(stage,&doc,1,&pages,0,0,1)){b2026_pdf_fail(4436,output,"external-link-free PDF could not be staged or exceeded 2 GiB / 外部リンク削除済みPDFを準備できないか2GiB上限を超えました");goto fail;}if(!b2026_pdf_el_verify(stage,doc.page_count,internal,had_form,had_attachments,had_bookmarks,had_info)){b2026_pdf_fail(4437,output,"post-write external-link removal verification failed / 外部リンク削除後の検証に失敗しました");goto fail;}stats.verified=1;if(!b2026_atomic_commit_file(stage,output)){b2026_pdf_fail(4438,output,"verified PDF could not be committed / 検証済みPDFを保存先へ反映できません");goto fail;}free(stage);stage=NULL;b2026_pdf_el_report(target,output,1,&stats);result=runtime_temp_string_copy(output);free(output);free(pages.items);b2026_pdf_document_free(&doc);return result;memory_fail:b2026_pdf_fail(4439,source,"out of memory or unsupported PDF action structure / メモリ不足または未対応のPDF動作構造です");fail:if(stage!=NULL){b2026_atomic_discard_file(stage);free(stage);}b2026_pdf_el_report(target,"",0,&stats);free(output);free(pages.items);b2026_pdf_document_free(&doc);return runtime_temp_string_copy("");}
