#if !defined(_WIN32)
#define _POSIX_C_SOURCE 200809L
#endif
/* Complete C11 owner-thread host; requires the adjacent portable_native_events.js.
 * Linux: see README for script-enabled release-capi build and -pthread linking.
 * Pass the shared JS file as argv[1], or run from the core repository root. */
#include "sqs_map.h"
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(_WIN32)
#include <pthread.h>
#include <time.h>
#endif
static void ok(int32_t status) { if(status!=SQSM_MAP_OK){fprintf(stderr,"status=%" PRId32 ": %s\n",status,sqsm_map_last_error());exit(1);} }
static int contains(sqsm_map_bytes_t out,const char *text){char *s=malloc((size_t)out.len+1);assert(s);memcpy(s,out.data,(size_t)out.len);s[out.len]=0;int found=strstr(s,text)!=NULL;free(s);return found;}
static void free_out(sqsm_map_bytes_t out){ok(sqsm_map_bytes_free(out.allocation));}
static sqsm_map_bytes_t pump(uint64_t session){sqsm_map_bytes_t out={0};ok(sqsm_map_script_session_pump_json(session,32,&out));return out;}
static uint64_t start(uint64_t map,const char *s){uint64_t id=0;ok(sqsm_map_script_session_start(map,(const uint8_t*)s,strlen(s),&id));assert(id);return id;}
static void counts(uint64_t session,unsigned count,const char *state){sqsm_map_bytes_t out=pump(session);char needle[80];snprintf(needle,sizeof needle,"\"invocations\":%u",count);assert(contains(out,needle)&&contains(out,state));free_out(out);}
static void poll(uint64_t map){sqsm_map_bytes_t out={0};ok(sqsm_map_poll_events_json(map,&out));free_out(out);}
static void release(uint64_t s){ok(sqsm_map_script_session_stop(s));ok(sqsm_map_script_session_stop(s));ok(sqsm_map_script_session_release(s));assert(sqsm_map_script_session_release(s)==SQSM_MAP_STALE_HANDLE);}
static void fail_pump(uint64_t map,const char *code,int32_t status,int trigger){uint64_t s=start(map,code);sqsm_map_bytes_t out={0};if(trigger){out=pump(s);free_out(out);static double bearing=123;ok(sqsm_map_set_native_bearing(map,bearing++));}int32_t actual=sqsm_map_script_session_pump_json(s,32,&out);if(actual!=status)fprintf(stderr,"expected %d got %d: %s\n%s\n",status,actual,sqsm_map_last_error(),code);assert(actual==status&&out.data==NULL&&out.len==0&&out.allocation==0);ok(sqsm_map_script_session_state_json(s,&out));assert(contains(out,"\"state\":\"failed\""));free_out(out);release(s);poll(map);}
#if !defined(_WIN32)
struct ended {uint64_t map,session;};
static void *owner_exit(void *p){struct ended *out=p;ok(sqsm_map_create(32,32,&out->map));out->session=start(out->map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.viewchange',()=>{});export default 1;");sqsm_map_bytes_t bytes=pump(out->session);free_out(bytes);return NULL;}
static void *wrong_thread(void *p){uint64_t s=*(uint64_t*)p;sqsm_map_bytes_t out={0};assert(sqsm_map_script_session_pump_json(s,1,&out)==SQSM_MAP_CONFLICT);assert(!out.data&&!out.len&&!out.allocation);assert(sqsm_map_script_session_stop(s)==SQSM_MAP_CONFLICT);assert(sqsm_map_script_session_release(s)==SQSM_MAP_CONFLICT);return NULL;}
#endif
int main(int argc,char **argv){
 uint64_t map=0;ok(sqsm_map_create(64,64,&map));
 if(!(sqsm_map_runtime_flags()&SQSM_MAP_SCRIPT_SESSIONS)){uint64_t s=9;assert(sqsm_map_script_session_api_version()==0);assert(sqsm_map_script_session_start(map,(const uint8_t*)"export default 1;",17,&s)==SQSM_MAP_CAPABILITY_UNAVAILABLE&&s==0);ok(sqsm_map_destroy(map));puts("pure map: retained session capability unavailable, ABI usable: PASS");return 0;}
 assert(sqsm_map_script_api_version()==1&&sqsm_map_script_session_api_version()==1);
 const char *path=argc>1?argv[1]:"crates/sqs-map-capi/examples/portable_native_events.js";
 FILE *file=fopen(path,"rb");assert(file);assert(fseek(file,0,SEEK_END)==0);long len=ftell(file);assert(len>0&&len<=128*1024);rewind(file);char *source=malloc((size_t)len+1);assert(source);assert(fread(source,1,(size_t)len,file)==(size_t)len);source[len]=0;fclose(file);
 uint64_t s=start(map,source);counts(s,0,"\"state\":\"running\"");
#if !defined(_WIN32)
 struct ended ended={0};pthread_t owner;assert(pthread_create(&owner,NULL,owner_exit,&ended)==0);assert(pthread_join(owner,NULL)==0);sqsm_map_bytes_t stale={0};assert(sqsm_map_script_session_state_json(ended.session,&stale)==SQSM_MAP_STALE_HANDLE&&!stale.data);assert(sqsm_map_destroy(ended.map)==SQSM_MAP_STALE_HANDLE);
 pthread_t thread;assert(pthread_create(&thread,NULL,wrong_thread,&s)==0);assert(pthread_join(thread,NULL)==0);
 struct timespec idle={2,100000000};nanosleep(&idle,NULL); // idle sessions outlive the per-pump deadline
#endif
 ok(sqsm_map_set_native_bearing(map,10));counts(s,3,"\"state\":\"running\"");
 ok(sqsm_map_set_native_bearing(map,20));counts(s,4,"\"state\":\"running\"");
 ok(sqsm_map_set_native_bearing(map,30));counts(s,4,"\"state\":\"running\"");
 const char *point="{\"type\":\"Point\",\"coordinates\":[0,0]}";uint64_t layer=0;ok(sqsm_map_add_geojson(map,(const uint8_t*)point,strlen(point),&layer));counts(s,5,"\"state\":\"running\"");ok(sqsm_map_remove_layer(map,layer));counts(s,6,"\"state\":\"running\"");
 sqsm_map_bytes_t events={0};ok(sqsm_map_poll_events_json(map,&events));assert(contains(events,"sqs.nativeviewchange")&&contains(events,"sqs.layerschange"));free_out(events);
 release(s);s=start(map,source);counts(s,0,"\"state\":\"running\"");ok(sqsm_map_resize(map,80,64));counts(s,3,"\"state\":\"running\"");ok(sqsm_map_script_session_stop(s));ok(sqsm_map_resize(map,64,64));counts(s,3,"\"state\":\"cancelled\"");release(s);poll(map);
 const char *wait="import {map} from 'sqsmap';export const sessionMode='persistent';await map.once('sqs.viewchange');export default 9;";
 s=start(map,wait);counts(s,0,"\"state\":\"initializing\"");ok(sqsm_map_set_native_bearing(map,45));counts(s,1,"\"state\":\"running\"");release(s);poll(map);
 s=start(map,"import {map} from 'sqsmap';map.setBearing(42);await map.sqs.flush();export default await map.getBearing();");sqsm_map_bytes_t out=pump(s);assert(contains(out,"\"state\":\"succeeded\"")&&contains(out,"\"value\":42"));free_out(out);release(s);poll(map);
 s=start(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.viewchange',()=>map.sqs.disposeSession());export default 1;");out=pump(s);free_out(out);ok(sqsm_map_resize(map,65,64));counts(s,1,"\"state\":\"cancelled\"");release(s);poll(map);
 fail_pump(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.viewchange',async()=>{await Promise.resolve();throw new Error('callback')});",SQSM_MAP_INVALID_ARGUMENT,1);
 fail_pump(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.viewchange',()=>{throw undefined});",SQSM_MAP_INVALID_ARGUMENT,1);
 fail_pump(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.viewchange',()=>{while(true){}});",SQSM_MAP_RESOURCE_LIMIT,1);
 fail_pump(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.viewchange',()=>{const spin=()=>Promise.resolve().then(spin);spin();});",SQSM_MAP_RESOURCE_LIMIT,1);
 fail_pump(map,"import {map} from 'sqsmap';export const sessionMode='persistent';for(let i=0;i<33;i++)map.on('sqs.viewchange',()=>{});",SQSM_MAP_RESOURCE_LIMIT,0);
 fail_pump(map,"import {map} from 'sqsmap';export const sessionMode='persistent';for(let i=0;i<257;i++)await map.getBearing();",SQSM_MAP_RESOURCE_LIMIT,0);
 fail_pump(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('load',()=>{});",SQSM_MAP_CAPABILITY_UNAVAILABLE,0);
 s=start(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.layerschange',()=>{});export default 1;");out=pump(s);free_out(out);
 for(unsigned i=0;i<65;i++){ok(sqsm_map_add_geojson(map,(const uint8_t*)point,strlen(point),&layer));ok(sqsm_map_remove_layer(map,layer));poll(map);}assert(sqsm_map_script_session_pump_json(s,32,&out)==SQSM_MAP_RESOURCE_LIMIT);assert(!out.data);ok(sqsm_map_script_session_state_json(s,&out));assert(contains(out,"RESOURCE_LIMIT"));free_out(out);release(s);
 s=start(map,"import {map} from 'sqsmap';export const sessionMode='persistent';map.on('sqs.viewchange',async()=>{await new Promise(()=>{});});export default 1;");out=pump(s);free_out(out);
 for(unsigned i=0;i<32;i++){ok(sqsm_map_set_native_bearing(map,200+i));out=pump(s);free_out(out);}ok(sqsm_map_set_native_bearing(map,250));assert(sqsm_map_script_session_pump_json(s,32,&out)==SQSM_MAP_RESOURCE_LIMIT&&!out.data);release(s);poll(map);
 s=start(map,"import {map} from 'sqsmap';export const sessionMode='persistent';const waiting=map.once('sqs.viewchange');await map.sqs.disposeSession();await waiting;");counts(s,0,"\"state\":\"cancelled\"");release(s);
 uint64_t kept[4];for(unsigned i=0;i<4;i++)kept[i]=start(map,source);uint64_t fifth=99;assert(sqsm_map_script_session_start(map,(const uint8_t*)source,strlen(source),&fifth)==SQSM_MAP_RESOURCE_LIMIT&&fifth==0);for(unsigned i=0;i<4;i++)release(kept[i]);
 for(unsigned i=0;i<16;i++){s=start(map,source);out=pump(s);free_out(out);release(s);}
 s=start(map,source);out=pump(s);ok(sqsm_map_destroy(map));assert(contains(out,"\"state\":\"running\""));free_out(out);counts(s,0,"\"state\":\"cancelled\"");release(s);free(source);
 puts("retained native script C ABI: real GPU, shared listeners/once/off, independent queues, initialization, idle, stop/restart/destroy, errors/budgets, thread/handle/output ownership: PASS");return 0;
}
