/* Portable PDF splitter.  Each part is written to a sibling staging file and
 * committed atomically, so an incomplete individual PDF never replaces an
 * existing destination. */
static int b2026_pdf_split_extension(const char *path,size_t *stem_length){
    size_t n;if(path==NULL)return 0;n=strlen(path);*stem_length=n;
    if(n>=4U&&path[n-4U]=='.'&&tolower((unsigned char)path[n-3U])=='p'&&
       tolower((unsigned char)path[n-2U])=='d'&&tolower((unsigned char)path[n-1U])=='f')*stem_length=n-4U;
    return n>0U;
}

static char *b2026_pdf_split_path(const char *base,int part,int width){
    size_t stem,n;char *path;int written;
    if(!b2026_pdf_split_extension(base,&stem)||stem==0U||base[stem-1U]=='/'||base[stem-1U]=='\\')return NULL;
    if(stem>SIZE_MAX-32U)return NULL;n=stem+(size_t)width+6U;path=(char*)malloc(n);
    if(path==NULL)return NULL;memcpy(path,base,stem);
    written=snprintf(path+stem,n-stem,"_%0*d.pdf",width,part);
    if(written<0||(size_t)written>=n-stem){free(path);return NULL;}return path;
}

static int b2026_pdf_split_materialize(B2026PdfDocument *doc,int page,const char *key){
    B2026PdfKeyRange direct;const unsigned char *value;size_t length;char *copy;
    if(!b2026_pdf_tf_find_key(doc->objects[page].body,doc->objects[page].body_length,key,&direct))return 0;
    if(direct.found)return 1;
    if(!b2026_pdf_tf_inherited_value(doc,page,key,&value,&length))return 1;
    copy=b2026_pdf_tf_value_copy(doc,value,length,0);if(copy==NULL)return 0;
    if(!b2026_pdf_tf_set_key(doc,page,key,copy,0)){free(copy);return 0;}free(copy);return 1;
}

static int b2026_pdf_split_array_commit(const char *target,char **paths,int count){
    Frame *frame;ArrayVariable *old,*result;int i;size_t slots;
    if(target==NULL)return 1;frame=get_array_frame(target);if(frame==NULL)return 0;
    result=(ArrayVariable*)calloc(1,sizeof(*result));if(result==NULL)return 0;
    slots=(size_t)count+(size_t)option_base;if(slots==0U||slots>SIZE_MAX/sizeof(B2026_Var)){free(result);return 0;}
    result->data=(B2026_Var*)calloc(slots,sizeof(B2026_Var));result->ref_count_ptr=(int*)malloc(sizeof(int));
    if(result->data==NULL||result->ref_count_ptr==NULL){free(result->data);free(result->ref_count_ptr);free(result);return 0;}
    *result->ref_count_ptr=1;strncpy(result->name,target,sizeof(result->name)-1U);result->size=option_base+count-1;result->read_count=count;basic_reset_canvas_pen_state(result);
    for(i=0;i<count;++i){result->data[option_base+i].type=1;result->data[option_base+i].data.ptr=b2026_strdup_simple(paths[i]);if(result->data[option_base+i].data.ptr==NULL){release_array_storage(result);free(result);return 0;}}
    old=find_local_array(frame,target);if(old!=NULL){ArrayVariable *next=old->next;release_array_storage(old);*old=*result;old->next=next;free(result);}else{result->next=frame->arrays;frame->arrays=result;}return 1;
}

static int b2026_pdf_split_verify(const char *path,int expected_pages){
    B2026PdfDocument check;int rc,i,ok=1;memset(&check,0,sizeof(check));
    rc=b2026_pdf_load(&check,path,0);if(rc<=0||check.page_count!=expected_pages)ok=0;
    for(i=0;ok&&i<check.page_count;++i){B2026PdfObject *page=&check.objects[check.page_objects[i]];int number,generation,parent;
        if(!b2026_pdf_named_ref(page->body,page->body_length,"/Parent",&number,&generation)||(parent=b2026_pdf_object_lookup(&check,number,generation))<0||
           !b2026_pdf_dict_name_is(check.objects[parent].body,check.objects[parent].body_length,"/Type","Pages"))ok=0;
    }
    b2026_pdf_document_free(&check);return ok;
}

static int b2026_pdf_split_array(const char *target,int argc,const B2026PdfArg *args){
    B2026PdfDocument doc;B2026PdfPageList pages;const char *source,*base;char **paths=NULL,**stages=NULL;int per=1,parts=0,width=3,part,i,rc,committed=0,ok=0;
    memset(&doc,0,sizeof(doc));memset(&pages,0,sizeof(pages));
    if(target!=NULL&&!b2026_pdf_split_array_commit(target,NULL,0)){b2026_pdf_fail(4199,"","out of memory while initializing the result array / 結果配列の初期化に必要なメモリを確保できません");return 0;}
    if((argc!=2&&argc!=3)||args[0].kind!=1||args[0].text==NULL||args[1].kind!=1||args[1].text==NULL||
       (argc==3&&(!b2026_pdf_arg_integer(&args[2],&per)||per<1))){b2026_pdf_fail(4190,"","SPLIT requires source$, output-base$ and an optional positive pages-per-file / 分割には入力PDF、出力基準パス、任意の1ファイルあたりページ数が必要です");return 0;}
    source=args[0].text;base=args[1].text;rc=b2026_pdf_load(&doc,source,1);if(rc<=0){b2026_pdf_fail(4191,source,b2026_pdf_load_error(rc));goto done;}
    if(b2026_pdf_memfind(doc.bytes,doc.length,"/Outlines",9U)!=NULL||b2026_pdf_memfind(doc.bytes,doc.length,"/PageLabels",11U)!=NULL||b2026_pdf_memfind(doc.bytes,doc.length,"/StructTreeRoot",15U)!=NULL)
        b2026_pdf_log_warning("分割PDFでは、しおり、ページラベル、タグ構造を再構築せず削除します。 / Bookmarks, page labels, and tagged-PDF structure are not rebuilt in split outputs.");
    /* The old /Parent page-tree reference reaches every sibling page.  Copy
     * inheritable attributes onto each page, then replace /Parent with an
     * unreachable placeholder.  The writer rewrites that placeholder to the
     * new Pages object; removing /Parent entirely creates a PDF that tolerant
     * readers accept but Preview rejects. */
    for(i=0;i<doc.page_count;++i){int page=doc.page_objects[i];
        if(!b2026_pdf_split_materialize(&doc,page,"/Resources")||
           !b2026_pdf_split_materialize(&doc,page,"/MediaBox")||
           !b2026_pdf_split_materialize(&doc,page,"/CropBox")||
           !b2026_pdf_split_materialize(&doc,page,"/Rotate")||
           !b2026_pdf_tf_set_key(&doc,page,"/Parent","0 0 R",0))goto memory_fail;
    }
    if(per>doc.page_count)per=doc.page_count;parts=(doc.page_count+per-1)/per;{int t=parts;while(t>=1000){++width;t/=10;}}
    paths=(char**)calloc((size_t)parts,sizeof(*paths));stages=(char**)calloc((size_t)parts,sizeof(*stages));if(paths==NULL||stages==NULL)goto memory_fail;
    for(part=0;part<parts;++part){int first=part*per,last=first+per;if(last>doc.page_count)last=doc.page_count;
        paths[part]=b2026_pdf_split_path(base,part+1,width);if(paths[part]==NULL){b2026_pdf_fail(4192,base,"output base path is invalid / 出力基準パスが正しくありません");goto done;}
        stages[part]=b2026_atomic_temp_path(paths[part]);if(stages[part]==NULL)goto memory_fail;
        memset(&pages,0,sizeof(pages));for(i=first;i<last;++i)if(!b2026_pdf_pages_push(&pages,&doc,doc.page_objects[i],-1))goto memory_fail;
        if(!b2026_pdf_write_output(stages[part],&doc,1,&pages,0,1,0)||!b2026_pdf_split_verify(stages[part],last-first)){free(pages.items);pages.items=NULL;pages.count=pages.capacity=0;b2026_pdf_fail(4193,paths[part],"split part could not be staged or failed structural verification / 分割PDFを準備できないか保存後の構造検証に失敗しました");goto done;}
        free(pages.items);pages.items=NULL;pages.count=pages.capacity=0;
    }
    for(part=0;part<parts;++part){if(!b2026_atomic_commit_file(stages[part],paths[part])){b2026_pdf_fail(4194,paths[part],committed>0?"a split part could not be committed; earlier complete parts remain saved / 分割PDFを保存先へ反映できません。先に完了した正常な分割ファイルは保存されています":"a split part could not be committed; existing destination was preserved / 分割PDFを保存先へ反映できず、既存ファイルは保持されました");goto done;}free(stages[part]);stages[part]=NULL;++committed;}
    if(!b2026_pdf_split_array_commit(target,paths,parts))goto memory_fail;ok=1;goto done;
memory_fail:b2026_pdf_fail(4199,source,"out of memory / メモリ不足です");
done:free(pages.items);if(stages!=NULL)for(part=0;part<parts;++part){if(stages[part]!=NULL){b2026_atomic_discard_file(stages[part]);free(stages[part]);}}if(paths!=NULL)for(part=0;part<parts;++part)free(paths[part]);free(stages);free(paths);b2026_pdf_document_free(&doc);return ok;
}
