/* BASIC2026 secure PDF redaction.
 * A redacted page is rendered, painted in RGB pixels, and replaced by a new
 * image-only page.  The original page object and its content/resources are
 * therefore not reachable from the output PDF. */
typedef struct {
    int page;
    double left,top,width,height;
} B2026PdfRedactArea;

static void b2026_pdf_redact_report(const char *target,const char *path,int ok,
                                    int pages,int areas,double dpi,int quality,int removed,
                                    const char *status,const char *error){
    b2026_pdf_orc_hash_clear(target);
    b2026_pdf_orc_pair_number(target,"成功","ok",ok?1.0:0.0);
    b2026_pdf_orc_pair_text(target,"保存先","path",ok&&path!=NULL?path:"");
    b2026_pdf_orc_pair_number(target,"処理ページ数","processedPages",(double)pages);
    b2026_pdf_orc_pair_number(target,"墨消し領域数","redactionAreas",(double)areas);
    b2026_pdf_orc_pair_number(target,"解像度","dpi",dpi);
    b2026_pdf_orc_pair_number(target,"画質","quality",(double)quality);
    b2026_pdf_orc_pair_number(target,"付随情報削除","associatedDataRemoved",removed?1.0:0.0);
    b2026_pdf_orc_pair_text(target,"状態","status",status!=NULL?status:(ok?"OK":"ERROR"));
    b2026_pdf_orc_pair_text(target,"エラー内容","errorMessage",error!=NULL?error:"");
}

static HashEntry *b2026_pdf_redact_index_entry(HashVariable *h,int index,
                                                const char *jp,const char *en){
    char key[128];HashEntry *e;
    if(h==NULL)return NULL;
    snprintf(key,sizeof(key),"%d,%s",index,jp);e=find_hash_entry(h,key);
    if(e!=NULL)return e;
    snprintf(key,sizeof(key),"%d,%s",index,en);return find_hash_entry(h,key);
}

static int b2026_pdf_redact_entry_number(HashVariable *h,int index,const char *jp,
                                          const char *en,double *out){
    HashEntry *e=b2026_pdf_redact_index_entry(h,index,jp,en);
    if(e==NULL||e->value.type!=TYPE_NUMBER||!isfinite(e->value.val.number))return 0;
    *out=e->value.val.number;return 1;
}

static int b2026_pdf_redact_areas(HashVariable *h,B2026PdfRedactArea **out,int *count){
    HashEntry *e=b2026_pdf_tf_entry(h,"件数","count");B2026PdfRedactArea *a;double n;int i;
    *out=NULL;*count=0;
    if(e==NULL||e->value.type!=TYPE_NUMBER||!isfinite(e->value.val.number))return 0;
    n=e->value.val.number;if(n<1.0||n>100000.0||floor(n)!=n)return 0;
    a=(B2026PdfRedactArea*)calloc((size_t)n,sizeof(*a));if(a==NULL)return 0;
    for(i=0;i<(int)n;++i){double page;
        if(!b2026_pdf_redact_entry_number(h,i+1,"ページ","page",&page)||
           !b2026_pdf_redact_entry_number(h,i+1,"左","left",&a[i].left)||
           !b2026_pdf_redact_entry_number(h,i+1,"上","top",&a[i].top)||
           !b2026_pdf_redact_entry_number(h,i+1,"横幅","width",&a[i].width)||
           !b2026_pdf_redact_entry_number(h,i+1,"縦幅","height",&a[i].height)||
           page<1.0||page>(double)INT_MAX||floor(page)!=page){free(a);return 0;}
        a[i].page=(int)page;
    }
    *out=a;*count=(int)n;return 1;
}

static int b2026_pdf_redact_color(HashVariable *h,unsigned char rgb[3]){
    const char *s=b2026_pdf_tf_text(h,"色","color");unsigned int r,g,b;char tail;
    rgb[0]=rgb[1]=rgb[2]=0;if(s==NULL||*s=='\0'||b2026_strcasecmp(s,"BLACK")==0||strcmp(s,"黒")==0)return 1;
    if(b2026_strcasecmp(s,"WHITE")==0||strcmp(s,"白")==0){rgb[0]=rgb[1]=rgb[2]=255;return 1;}
    if(strlen(s)==7U&&s[0]=='#'&&sscanf(s+1,"%2x%2x%2x%c",&r,&g,&b,&tail)==3){rgb[0]=(unsigned char)r;rgb[1]=(unsigned char)g;rgb[2]=(unsigned char)b;return 1;}
    if(sscanf(s,"%u,%u,%u%c",&r,&g,&b,&tail)==3&&r<=255U&&g<=255U&&b<=255U){rgb[0]=(unsigned char)r;rgb[1]=(unsigned char)g;rgb[2]=(unsigned char)b;return 1;}
    return 0;
}

static int b2026_pdf_redact_settings(HashVariable *h,double *dpi,double *unit_scale,
                                      unsigned char rgb[3],int *quality,int *remove_data){
    const char *unit=b2026_pdf_tf_text(h,"単位","unit");HashEntry *e;double q;
    if(!b2026_pdf_orc_number(h,"解像度","dpi",300.0,72.0,1200.0,dpi)||
       !b2026_pdf_orc_number(h,"画質","quality",95.0,1.0,100.0,&q)||
       !b2026_pdf_redact_color(h,rgb))return 0;
    *quality=(int)floor(q+0.5);
    if(unit==NULL||b2026_strcasecmp(unit,"POINT")==0||b2026_strcasecmp(unit,"PT")==0||strcmp(unit,"ポイント")==0)*unit_scale=1.0;
    else if(b2026_strcasecmp(unit,"MM")==0||strcmp(unit,"ミリ")==0)*unit_scale=72.0/25.4;
    else if(b2026_strcasecmp(unit,"INCH")==0||b2026_strcasecmp(unit,"IN")==0||strcmp(unit,"インチ")==0)*unit_scale=72.0;
    else return 0;
    e=b2026_pdf_tf_entry(h,"付随情報削除","removeAssociatedData");
    if(e==NULL)*remove_data=1;
    else if(e->value.type!=TYPE_NUMBER||!isfinite(e->value.val.number))return 0;
    else *remove_data=e->value.val.number!=0.0;
    return 1;
}

static int b2026_pdf_redact_validate(B2026PdfDocument *doc,B2026PdfRedactArea *areas,
                                     int area_count,double unit_scale,unsigned char **selected,
                                     int **map,int *page_count){
    unsigned char *s;int *m;int i,count=0;
    s=(unsigned char*)calloc((size_t)doc->page_count,1U);m=(int*)malloc((size_t)doc->page_count*sizeof(*m));
    if(s==NULL||m==NULL){free(s);free(m);return 0;}
    for(i=0;i<doc->page_count;++i)m[i]=-1;
    for(i=0;i<area_count;++i){double box[4],w,h,l,t,r,b;int p=areas[i].page-1,rot;
        if(p<0||p>=doc->page_count||areas[i].left<0.0||areas[i].top<0.0||areas[i].width<=0.0||areas[i].height<=0.0)goto fail;
        if(!b2026_pdf_page_box(doc,doc->page_objects[p],"/CropBox",box)&&
           !b2026_pdf_page_box(doc,doc->page_objects[p],"/MediaBox",box))goto fail;
        w=box[2]-box[0];h=box[3]-box[1];rot=((b2026_pdf_page_inherited_integer(doc,doc->page_objects[p],"/Rotate",0)%360)+360)%360;
        if(rot==90||rot==270){double q=w;w=h;h=q;}
        l=areas[i].left*unit_scale;t=areas[i].top*unit_scale;r=l+areas[i].width*unit_scale;b=t+areas[i].height*unit_scale;
        if(!isfinite(l)||!isfinite(t)||!isfinite(r)||!isfinite(b)||l<0.0||t<0.0||r>w+0.000001||b>h+0.000001)goto fail;
        s[p]=1U;
    }
    for(i=0;i<doc->page_count;++i)if(s[i])m[i]=count++;
    *selected=s;*map=m;*page_count=count;return 1;
fail:free(s);free(m);return 0;
}

static void b2026_pdf_redact_paint(B2026PdfRaster *r,const B2026PdfRedactArea *a,
                                   double dpi,double unit_scale,const unsigned char rgb[3]){
    double scale=dpi*unit_scale/72.0;int x0=(int)floor(a->left*scale),y0=(int)floor(a->top*scale);
    int x1=(int)ceil((a->left+a->width)*scale),y1=(int)ceil((a->top+a->height)*scale),x,y;
    if(x0<0)x0=0;if(y0<0)y0=0;if(x1>r->width)x1=r->width;if(y1>r->height)y1=r->height;
    for(y=y0;y<y1;++y)for(x=x0;x<x1;++x){unsigned char *p=r->rgb+((size_t)y*(size_t)r->width+(size_t)x)*3U;p[0]=rgb[0];p[1]=rgb[1];p[2]=rgb[2];}
}

static int b2026_pdf_redact_strip_associated(B2026PdfDocument *doc){
    static const char *page_keys[]={"/Annots","/AA","/Thumb","/Metadata"};
    static const char *root_keys[]={"/Type","/Pages","/AcroForm","/Metadata","/Names","/Outlines","/PageLabels","/OCProperties","/StructTreeRoot","/MarkInfo","/OpenAction","/AA","/Requirements"};
    int i,k;
    for(i=0;i<doc->page_count;++i)for(k=0;k<(int)(sizeof(page_keys)/sizeof(page_keys[0]));++k)
        if(!b2026_pdf_san_remove_key(doc,doc->page_objects[i],page_keys[k],NULL))return 0;
    if(doc->root_object>=0)for(k=0;k<(int)(sizeof(root_keys)/sizeof(root_keys[0]));++k)
        if(!b2026_pdf_san_remove_key(doc,doc->root_object,root_keys[k],NULL))return 0;
    for(i=0;i<doc->object_count;++i)if(!b2026_pdf_san_remove_key(doc,i,"/Metadata",NULL))return 0;
    doc->info_object=-1;doc->metadata_object=-1;return 1;
}

static int b2026_pdf_redact_prepare_catalog(B2026PdfDocument *doc){
    if(doc->root_object<0)return 1;
    if(!b2026_pdf_san_remove_key(doc,doc->root_object,"/Type",NULL)||
       !b2026_pdf_san_remove_key(doc,doc->root_object,"/Pages",NULL)||
       !b2026_pdf_san_remove_key(doc,doc->root_object,"/AcroForm",NULL))return 0;
    return 1;
}

/* Break the old page-tree back references before reachable-object selection.
 * Otherwise /Parent reaches the old /Pages /Kids array and can pull a replaced
 * page into the output as an invisible, unreachable object.  The writer sees
 * this placeholder /Parent and rewrites it to the new page tree. */
static int b2026_pdf_redact_detach_page_tree(B2026PdfDocument *doc){
    int i;for(i=0;i<doc->page_count;++i)
        if(!b2026_pdf_tf_set_key(doc,doc->page_objects[i],"/Parent","0 0 R",0))return 0;
    return 1;
}

static int b2026_pdf_redact_verify(const char *path,int expected_pages,const unsigned char *selected,
                                   int remove_data){
    B2026PdfDocument d;B2026PdfTextSettings ts;int rc,i,partial=0,page_objects=0;B2026PdfSanitizeSettings ss;
    memset(&d,0,sizeof(d));memset(&ts,0,sizeof(ts));memset(&ss,0,sizeof(ss));
    rc=b2026_pdf_load(&d,path,0);if(rc<=0||d.page_count!=expected_pages)goto fail;
    for(i=0;i<d.object_count;++i)if(d.objects[i].is_page)++page_objects;
    if(page_objects!=expected_pages)goto fail;
    ts.coordinate_order=1;ts.separator="\n";ts.tolerance=-1.0;
    for(i=0;i<expected_pages;++i)if(selected[i]){char *text=b2026_pdf_text_page(&d,i,&ts,path,&partial);int empty=text!=NULL&&*text=='\0';free(text);if(!empty)goto fail;}
    b2026_pdf_document_free(&d);
    if(remove_data){ss.annotations=1;ss.flatten_forms=1;ss.javascript=1;ss.attachments=1;ss.actions=1;ss.metadata=1;ss.bookmarks=1;ss.page_labels=1;ss.thumbnails=1;ss.layers=1;ss.keep_links=0;ss.keep_tags=0;if(!b2026_pdf_san_verify(path,expected_pages,&ss))return 0;}
    return 1;
fail:b2026_pdf_document_free(&d);return 0;
}

/* Write selected replacement pages one at a time.  Keeping every 300 dpi RGB
 * page in memory and storing it uncompressed exceeds 2 GiB for ordinary
 * book-length PDFs.  The pixels are redacted before JPEG encoding, so the
 * original pixels never enter the replacement page stream. */
typedef struct {
    unsigned char *data;
    size_t used,capacity;
    int failed;
} B2026PdfRedactJpegBuffer;

static void b2026_pdf_redact_jpeg_callback(void *context,void *data,int size){
    B2026PdfRedactJpegBuffer *b=(B2026PdfRedactJpegBuffer*)context;size_t needed,capacity;unsigned char *next;
    if(b==NULL||data==NULL||size<=0||b->failed)return;
    if((size_t)size>SIZE_MAX-b->used||(needed=b->used+(size_t)size)>(size_t)B2026_PDF_EDIT_MAX_BYTES){b->failed=1;return;}
    if(needed>b->capacity){capacity=b->capacity>0U?b->capacity:65536U;while(capacity<needed){size_t grown=capacity*2U;if(grown<capacity||grown>(size_t)B2026_PDF_EDIT_MAX_BYTES){capacity=needed;break;}capacity=grown;}next=(unsigned char*)realloc(b->data,capacity);if(next==NULL){b->failed=1;return;}b->data=next;b->capacity=capacity;}
    memcpy(b->data+b->used,data,(size_t)size);b->used=needed;
}

/* Return 1 on success, -2 for renderer failure, -3 for JPEG failure,
 * -4 for the 2 GiB limit, and -1 for other staging failures. */
static int b2026_pdf_redact_image_pdf(const char *target,const char *source,B2026PdfDocument *doc,
                                      const unsigned char *selected,const int *map,int count,
                                      const B2026PdfRedactArea *areas,int area_count,double dpi,
                                      double unit_scale,const unsigned char rgb[3],int quality){
    char *stage=NULL;FILE *fp=NULL;long *offsets=NULL,xref;B2026PdfRedactJpegBuffer jpeg;B2026PdfRenderSession render_session;
    int total=2+count*3,index,page,i,j,rc=-1;char detail[512];B2026PdfRaster raster;
    memset(&raster,0,sizeof(raster));memset(&jpeg,0,sizeof(jpeg));memset(&render_session,0,sizeof(render_session));
    if(!saveimage_ensure_directories_safe(target,detail,sizeof(detail)))goto done;
    if(!b2026_pdf_render_session_open(&render_session,source)){rc=-2;goto done;}
    stage=b2026_atomic_temp_path(target);offsets=(long*)calloc((size_t)total+1U,sizeof(*offsets));
    if(stage==NULL||offsets==NULL||(fp=b2026_fopen(stage,"wb"))==NULL)goto done;
    if(fprintf(fp,"%%PDF-1.4\n%%\xE2\xE3\xCF\xD3\n")<0)goto done;
    offsets[1]=ftell(fp);if(fprintf(fp,"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")<0)goto done;
    offsets[2]=ftell(fp);if(fprintf(fp,"2 0 obj\n<< /Type /Pages /Count %d /Kids [",count)<0)goto done;
    for(index=0;index<count;++index)if(fprintf(fp," %d 0 R",5+index*3)<0)goto done;
    if(fprintf(fp," ] >>\nendobj\n")<0)goto done;
    for(index=0;index<count;++index){int im=3+index*3,ct=4+index*3,pg=5+index*3,rot;double box[4]={0,0,612,792},w,h;char stream[160];
        page=-1;for(i=0;i<doc->page_count;++i)if(selected[i]&&map[i]==index){page=i;break;}if(page<0)goto done;
        if(!b2026_pdf_render_session_page(&render_session,source,page+1,dpi,&raster)){rc=-2;goto done;}
        for(j=0;j<area_count;++j)if(areas[j].page==page+1)b2026_pdf_redact_paint(&raster,&areas[j],dpi,unit_scale,rgb);
        jpeg.used=0U;jpeg.failed=0;if(!stbi_write_jpg_to_func(b2026_pdf_redact_jpeg_callback,&jpeg,raster.width,raster.height,3,raster.rgb,quality)||jpeg.failed||jpeg.used==0U){rc=-3;goto done;}
        if(!b2026_pdf_page_box(doc,doc->page_objects[page],"/CropBox",box))b2026_pdf_page_box(doc,doc->page_objects[page],"/MediaBox",box);
        w=box[2]-box[0];h=box[3]-box[1];rot=((b2026_pdf_page_inherited_integer(doc,doc->page_objects[page],"/Rotate",0)%360)+360)%360;if(rot==90||rot==270){double t=w;w=h;h=t;}if(w<=0.0||h<=0.0){w=612.0;h=792.0;}
        offsets[im]=ftell(fp);if(fprintf(fp,"%d 0 obj\n<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length %lu >>\nstream\n",im,raster.width,raster.height,(unsigned long)jpeg.used)<0||fwrite(jpeg.data,1U,jpeg.used,fp)!=jpeg.used)goto done;
        if(fprintf(fp,"\nendstream\nendobj\n")<0)goto done;
        snprintf(stream,sizeof(stream),"q %.17g 0 0 %.17g 0 0 cm /Im%d Do Q\n",w,h,index+1);
        offsets[ct]=ftell(fp);if(fprintf(fp,"%d 0 obj\n<< /Length %lu >>\nstream\n",ct,(unsigned long)strlen(stream))<0||fwrite(stream,1U,strlen(stream),fp)!=strlen(stream)||fprintf(fp,"endstream\nendobj\n")<0)goto done;
        offsets[pg]=ftell(fp);if(fprintf(fp,"%d 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 %.17g %.17g] /Resources << /XObject << /Im%d %d 0 R >> >> /Contents %d 0 R >>\nendobj\n",pg,w,h,index+1,im,ct)<0)goto done;
        b2026_pdf_raster_free(&raster);
        if(ftell(fp)<0||(unsigned long long)ftell(fp)>B2026_PDF_EDIT_MAX_BYTES){rc=-4;goto done;}
    }
    xref=ftell(fp);if(xref<0||(unsigned long long)xref>B2026_PDF_EDIT_MAX_BYTES){rc=-4;goto done;}
    if(fprintf(fp,"xref\n0 %d\n0000000000 65535 f \n",total+1)<0)goto done;
    for(i=1;i<=total;++i)if(fprintf(fp,"%010ld 00000 n \n",offsets[i])<0)goto done;
    if(fprintf(fp,"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%ld\n%%%%EOF\n",total+1,xref)<0||fflush(fp)!=0||ferror(fp))goto done;
    if(fclose(fp)!=0){fp=NULL;goto done;}fp=NULL;if(!b2026_atomic_commit_file(stage,target))goto done;rc=1;
done:
    if(fp!=NULL)fclose(fp);b2026_pdf_raster_free(&raster);b2026_pdf_render_session_close(&render_session);if(rc!=1&&stage!=NULL)b2026_atomic_discard_file(stage);free(stage);free(jpeg.data);free(offsets);return rc;
}

static const char *b2026_pdf_redact(const char *target,int argc,const B2026PdfArg *args){
    B2026PdfDocument docs[2];B2026PdfPageList pages;B2026PdfRedactArea *areas=NULL;
    HashVariable *area_hash=NULL,*settings=NULL;unsigned char *selected=NULL,rgb[3];int *map=NULL;
    const char *output="",*source="",*detail="";char *image_path=NULL,*stage=NULL;double dpi=300.0,unit_scale=1.0;
    int area_count=0,redacted_pages=0,remove_data=1,quality=95,rc=0,image_rc=0,i,ok=0,preserve_catalog=0;
    memset(docs,0,sizeof(docs));memset(&pages,0,sizeof(pages));
    if((argc!=3&&argc!=4&&argc!=7&&argc!=8)||args[0].kind!=1||args[1].kind!=1||args[0].text==NULL||args[1].text==NULL){detail="invalid paths or parameter count / パスまたはパラメーター数が正しくありません";goto fail;}
    output=args[0].text;source=args[1].text;
    if(argc==3||argc==4){
        if(!b2026_pdf_tf_hash(&args[2],&area_hash)||(argc==4&&!b2026_pdf_tf_hash(&args[3],&settings))){detail="area/settings associative array does not exist / 領域または設定の連想配列がありません";goto fail;}
        if(!b2026_pdf_redact_areas(area_hash,&areas,&area_count)){detail="invalid redaction area array / 墨消し領域の連想配列が正しくありません";goto fail;}
    }else{
        double page;if(argc==8&&!b2026_pdf_tf_hash(&args[7],&settings)){detail="settings associative array does not exist / 設定の連想配列がありません";goto fail;}
        for(i=2;i<7;++i)if(args[i].kind!=0||!isfinite(args[i].number)){detail="page and rectangle values must be finite numbers / ページと領域は有限の数値で指定してください";goto fail;}
        page=args[2].number;if(page<1.0||page>(double)INT_MAX||floor(page)!=page){detail="page number must be a positive integer / ページ番号は正の整数で指定してください";goto fail;}
        areas=(B2026PdfRedactArea*)calloc(1U,sizeof(*areas));if(areas==NULL){detail="out of memory / メモリ不足です";goto fail;}
        areas[0].page=(int)page;areas[0].left=args[3].number;areas[0].top=args[4].number;areas[0].width=args[5].number;areas[0].height=args[6].number;area_count=1;
    }
    if(!b2026_pdf_redact_settings(settings,&dpi,&unit_scale,rgb,&quality,&remove_data)){detail="invalid redaction settings / 墨消し設定が正しくありません";goto fail;}
    rc=b2026_pdf_load(&docs[0],source,4);if(rc<=0){detail=b2026_pdf_load_error(rc);goto fail;}
    if(!b2026_pdf_redact_validate(&docs[0],areas,area_count,unit_scale,&selected,&map,&redacted_pages)){detail="redaction rectangle is outside the displayed page or invalid / 墨消し領域が表示ページの範囲外または不正です";goto fail;}
    image_path=b2026_atomic_temp_path(output);if(image_path==NULL){detail="redacted image pages could not be staged / 墨消し済み画像ページを準備できません";goto fail;}
    image_rc=b2026_pdf_redact_image_pdf(image_path,source,&docs[0],selected,map,redacted_pages,areas,area_count,dpi,unit_scale,rgb,quality);
    if(image_rc!=1){
        if(image_rc==-2)detail="configured PDF renderer failed or timed out / 設定されたPDFレンダラーが失敗またはタイムアウトしました";
        else if(image_rc==-3)detail="redacted page JPEG encoding failed / 墨消し済みページのJPEG圧縮に失敗しました";
        else if(image_rc==-4)detail="compressed redacted image pages exceeded the 2 GiB limit / 圧縮後の墨消し済み画像ページが2GiB上限を超えました";
        else detail="redacted image pages could not be staged / 墨消し済み画像ページを準備できません";
        goto fail;
    }
    if(remove_data&&redacted_pages==docs[0].page_count){
        if(!b2026_pdf_redact_verify(image_path,docs[0].page_count,selected,1)){detail="post-write secure-redaction verification failed / 墨消し後の安全性検証に失敗しました";goto fail;}
        if(!b2026_atomic_commit_file(image_path,output)){detail="verified redacted PDF could not be committed / 検証済み墨消しPDFを保存先へ反映できません";goto fail;}
        free(image_path);image_path=NULL;ok=1;b2026_pdf_redact_report(target,output,1,redacted_pages,area_count,dpi,quality,1,"OK","");goto done;
    }
    rc=b2026_pdf_load(&docs[1],image_path,0);if(rc<=0||docs[1].page_count!=redacted_pages){detail="staged redacted pages failed structural verification / 墨消し済み画像ページの構造検証に失敗しました";goto fail;}
    if(remove_data){if(!b2026_pdf_redact_strip_associated(&docs[0])){detail="associated data could not be removed safely / 付随情報を安全に削除できません";goto fail;}preserve_catalog=0;}
    else{if(!b2026_pdf_redact_prepare_catalog(&docs[0])){detail="source catalog could not be prepared / 文書Catalogを準備できません";goto fail;}preserve_catalog=1;b2026_pdf_log_warning("付随情報を維持したため墨消し範囲外のメタデータ等が残る可能性があります。 / Associated data was retained and may contain information outside the redacted areas.");}
    if(!b2026_pdf_redact_detach_page_tree(&docs[0])){detail="source page tree could not be detached safely / 元のページツリー参照を安全に分離できません";goto fail;}
    for(i=0;i<docs[0].page_count;++i){B2026PdfDocument *d=selected[i]?&docs[1]:&docs[0];int oi=selected[i]?docs[1].page_objects[map[i]]:docs[0].page_objects[i];if(!b2026_pdf_pages_push(&pages,d,oi,-1)){detail="out of memory / メモリ不足です";goto fail;}}
    stage=b2026_atomic_temp_path(output);if(stage==NULL||!b2026_pdf_write_output(stage,docs,2,&pages,0,1,preserve_catalog)){detail="redacted PDF could not be written atomically or exceeded 2 GiB / 墨消しPDFを原子的に保存できないか2GiB上限を超えました";goto fail;}
    if(!b2026_pdf_redact_verify(stage,docs[0].page_count,selected,remove_data)){detail="post-write secure-redaction verification failed / 墨消し後の安全性検証に失敗しました";goto fail;}
    if(!b2026_atomic_commit_file(stage,output)){detail="verified redacted PDF could not be committed / 検証済み墨消しPDFを保存先へ反映できません";goto fail;}
    free(stage);stage=NULL;ok=1;b2026_pdf_redact_report(target,output,1,redacted_pages,area_count,dpi,quality,remove_data,"OK","");
    goto done;
fail:
    if(stage!=NULL)b2026_atomic_discard_file(stage);
    b2026_pdf_orc_error(4360,"REDACT",source,detail);
    b2026_pdf_redact_report(target,"",0,redacted_pages,area_count,dpi,quality,0,"ERROR",detail);
done:
    if(image_path!=NULL)b2026_atomic_discard_file(image_path);free(image_path);free(stage);free(areas);free(selected);free(map);free(pages.items);
    b2026_pdf_document_free(&docs[1]);b2026_pdf_document_free(&docs[0]);
    return runtime_temp_string_copy(ok?output:"");
}
