#include <emscripten/emscripten.h>
#ifdef SINTER_AVIF
#include <avif/avif.h>
#endif
#ifdef SINTER_HEIC
#include <libheif/heif.h>
#endif
#include <vector>
#include <memory>
#include <cstring>
#include <cstdlib>
#include <climits>
#include <cmath>
#include <string>
static std::string last_error;
#define API extern "C" EMSCRIPTEN_KEEPALIVE
API const char* native_error(){return last_error.c_str();}
API void* alloc(size_t n){return malloc(n);}
API void dealloc(void*p,size_t){free(p);}
#ifdef SINTER_DECODER
struct Reader {
 int kind,w=0,h=0,count=1,plays=1,index=0,rawW=0,rawH=0,angle=0,mirror=-1;bool animated=false; double duration=0;
#ifdef SINTER_AVIF
 avifCropRect crop={};
#endif
#ifdef SINTER_AVIF
 avifDecoder* avif=nullptr;
#else
 heif_context* heif=nullptr; heif_image* image=nullptr;
#endif
 std::vector<uint8_t> input,rgba;
 ~Reader(){
#ifdef SINTER_AVIF
if(avif)avifDecoderDestroy(avif);
#else
if(image)heif_image_release(image);if(heif)heif_context_free(heif);
#endif
}
};
API Reader* reader_open(const uint8_t*p,size_t n,int kind){try{
 auto r=std::make_unique<Reader>();r->kind=kind;r->input.assign(p,p+n);
#ifdef SINTER_AVIF
 {r->avif=avifDecoderCreate();if(!r->avif)return nullptr;r->avif->maxThreads=1;
  // Metadata is not retained; malformed optional EXIF must not reject valid AV1 pixels.
  r->avif->ignoreExif=AVIF_TRUE;r->avif->ignoreXMP=AVIF_TRUE;r->avif->codecChoice=AVIF_CODEC_CHOICE_AOM;r->avif->imageSizeLimit=134217728;
  auto result=avifDecoderSetIOMemory(r->avif,r->input.data(),n);if(result==AVIF_RESULT_OK)result=avifDecoderParse(r->avif);if(result!=AVIF_RESULT_OK){last_error=std::string(avifResultToString(result))+": "+r->avif->diag.error;return nullptr;}
  r->rawW=r->avif->image->width;r->rawH=r->avif->image->height;r->count=r->avif->imageCount;
  r->animated=r->avif->imageSequenceTrackPresent;r->crop={0,0,(uint32_t)r->rawW,(uint32_t)r->rawH};
  auto image=r->avif->image;if((image->transformFlags&AVIF_TRANSFORM_CLAP)&&!avifCropRectFromCleanApertureBox(&r->crop,&image->clap,r->rawW,r->rawH,&r->avif->diag))return nullptr;
  if(image->transformFlags&AVIF_TRANSFORM_IROT)r->angle=image->irot.angle;
  if(image->transformFlags&AVIF_TRANSFORM_IMIR)r->mirror=image->imir.axis;
  r->w=r->angle%2?r->crop.height:r->crop.width;r->h=r->angle%2?r->crop.width:r->crop.height;
  int repeat=r->avif->repetitionCount;r->plays=repeat==AVIF_REPETITION_COUNT_INFINITE?0:repeat<0?1:repeat+1;
 }
#else
 {r->heif=heif_context_alloc();if(!r->heif)return nullptr;
  if(heif_context_read_from_memory_without_copy(r->heif,r->input.data(),n,nullptr).code!=heif_error_Ok)return nullptr;
  heif_image_handle* handle=nullptr;if(heif_context_get_primary_image_handle(r->heif,&handle).code!=heif_error_Ok)return nullptr;
  auto err=heif_decode_image(handle,&r->image,heif_colorspace_RGB,heif_chroma_interleaved_RGBA,nullptr);heif_image_handle_release(handle);if(err.code!=heif_error_Ok)return nullptr;
  r->w=heif_image_get_width(r->image,heif_channel_interleaved);r->h=heif_image_get_height(r->image,heif_channel_interleaved);
 }
#endif
 if(r->w<=0||r->h<=0||r->count<=0||(uint64_t)r->w*r->h>134217728)return nullptr;
 r->rgba.resize((size_t)r->w*r->h*4);return r.release();
 }catch(...){return nullptr;}}
API void reader_close(Reader*r){delete r;}
API int reader_width(Reader*r){return r->w;}
API int reader_height(Reader*r){return r->h;}
API int reader_count(Reader*r){return r->count;}
API int reader_plays(Reader*r){return r->plays;}
API const uint8_t*reader_data(Reader*r){return r->rgba.data();}
API double reader_duration(Reader*r){return r->duration;}
API int reader_next(Reader*r){try{if(r->index>=r->count)return 0;
#ifdef SINTER_AVIF
 {if(avifDecoderNextImage(r->avif)!=AVIF_RESULT_OK)return -1;
  avifRGBImage rgb;avifRGBImageSetDefaults(&rgb,r->avif->image);rgb.depth=8;rgb.format=AVIF_RGB_FORMAT_RGBA;std::vector<uint8_t>raw((size_t)r->rawW*r->rawH*4);rgb.pixels=raw.data();rgb.rowBytes=r->rawW*4;
  if(avifImageYUVToRGB(r->avif->image,&rgb)!=AVIF_RESULT_OK)return -1;
  for(uint32_t y=0;y<r->crop.height;y++)for(uint32_t x=0;x<r->crop.width;x++){
   uint32_t dx=x,dy=y;switch(r->angle){case 1:dx=y;dy=r->crop.width-1-x;break;case 2:dx=r->crop.width-1-x;dy=r->crop.height-1-y;break;case 3:dx=r->crop.height-1-y;dy=x;break;}
   if(r->mirror==0)dy=r->h-1-dy;else if(r->mirror==1)dx=r->w-1-dx;
   memcpy(r->rgba.data()+((size_t)dy*r->w+dx)*4,raw.data()+((size_t)(y+r->crop.y)*r->rawW+x+r->crop.x)*4,4);
  }
  r->duration=r->avif->imageTiming.duration*1000000.;
 }
#else
 {int stride=0;const auto*p=heif_image_get_plane_readonly(r->image,heif_channel_interleaved,&stride);if(!p||stride<r->w*4)return -1;for(int y=0;y<r->h;y++)memcpy(r->rgba.data()+(size_t)y*r->w*4,p+(size_t)y*stride,r->w*4);}
#endif
 r->index++;return 1;}catch(...){return -1;}}

API int reader_animated(Reader*r){return r->animated;}
API double reader_timebase(Reader*r){
#ifdef SINTER_AVIF
return (double)r->avif->timescale;
#else
return 1.;
#endif
}
API double reader_numerator(Reader*r){
#ifdef SINTER_AVIF
return (double)r->avif->imageTiming.durationInTimescales;
#else
return 0.;
#endif
}
API double reader_denominator(Reader*r){return reader_timebase(r);}

#endif
#ifdef SINTER_ENCODER
struct Writer {
 int kind,w,h,count,added=0;bool animated=false;std::vector<uint8_t>out;
#ifdef SINTER_AVIF
 avifEncoder* avif=nullptr;
#else
 heif_context* heif=nullptr;heif_encoder* encoder=nullptr;
#endif
 ~Writer(){
#ifdef SINTER_AVIF
if(avif)avifEncoderDestroy(avif);
#else
if(encoder)heif_encoder_release(encoder);if(heif)heif_context_free(heif);
#endif
}
};
API Writer* writer_open(int kind,int w,int h,int count,int plays,int quality,int,int speed,int animated,double timebase){try{
 if(w<=0||h<=0||count<=0||plays<0||quality<1||quality>100)return nullptr;
 auto s=std::make_unique<Writer>();s->kind=kind;s->w=w;s->h=h;s->count=count;s->animated=animated;
#ifdef SINTER_AVIF
 {s->avif=avifEncoderCreate();if(!s->avif)return nullptr;s->avif->codecChoice=AVIF_CODEC_CHOICE_AOM;s->avif->maxThreads=1;s->avif->quality=quality;s->avif->qualityAlpha=quality;s->avif->speed=speed;if(!std::isfinite(timebase)||timebase<1||timebase>UINT32_MAX||std::floor(timebase)!=timebase)return nullptr;s->avif->timescale=(uint64_t)timebase;s->avif->repetitionCount=plays==0?AVIF_REPETITION_COUNT_INFINITE:plays-1;
 }
#else
 {if(count!=1)return nullptr;s->heif=heif_context_alloc();if(!s->heif)return nullptr;if(heif_context_get_encoder_for_format(s->heif,heif_compression_HEVC,&s->encoder).code!=heif_error_Ok)return nullptr;if(heif_encoder_set_lossy_quality(s->encoder,quality).code!=heif_error_Ok)return nullptr;}
#endif
 return s.release();}catch(...){return nullptr;}}
API int writer_add(Writer*s,const uint8_t*rgba,size_t n,double duration,uint32_t){try{
 if(n!=(size_t)s->w*s->h*4||s->added>=s->count)return 0;
#ifdef SINTER_AVIF
 {if(!std::isfinite(duration)||duration<0||duration>UINT32_MAX||std::floor(duration)!=duration)return 0;avifImage*image=avifImageCreate(s->w,s->h,8,AVIF_PIXEL_FORMAT_YUV444);if(!image)return 0;image->colorPrimaries=AVIF_COLOR_PRIMARIES_BT709;image->transferCharacteristics=AVIF_TRANSFER_CHARACTERISTICS_SRGB;image->matrixCoefficients=AVIF_MATRIX_COEFFICIENTS_BT601;
  avifRGBImage rgb;avifRGBImageSetDefaults(&rgb,image);rgb.depth=8;rgb.format=AVIF_RGB_FORMAT_RGBA;rgb.pixels=const_cast<uint8_t*>(rgba);rgb.rowBytes=s->w*4;
  auto err=avifImageRGBToYUV(image,&rgb);if(err==AVIF_RESULT_OK)err=avifEncoderAddImage(s->avif,image,duration,!s->animated?AVIF_ADD_IMAGE_FLAG_SINGLE:AVIF_ADD_IMAGE_FLAG_NONE);avifImageDestroy(image);if(err!=AVIF_RESULT_OK)return 0;
 }
#else
 {heif_image*image=nullptr;if(heif_image_create(s->w,s->h,heif_colorspace_RGB,heif_chroma_interleaved_RGBA,&image).code!=heif_error_Ok)return 0;
  auto err=heif_image_add_plane(image,heif_channel_interleaved,s->w,s->h,8);if(err.code==heif_error_Ok){int stride=0;auto*p=heif_image_get_plane(image,heif_channel_interleaved,&stride);if(!p||stride<s->w*4){heif_image_release(image);return 0;}for(int y=0;y<s->h;y++)memcpy(p+(size_t)y*stride,rgba+(size_t)y*s->w*4,s->w*4);err=heif_context_encode_image(s->heif,image,s->encoder,nullptr,nullptr);}heif_image_release(image);if(err.code!=heif_error_Ok)return 0;
 }
#endif
 s->added++;return 1;}catch(...){return 0;}}
#ifdef SINTER_HEIC
static heif_error write_bytes(heif_context*,const void*data,size_t n,void*user){auto*s=(Writer*)user;const auto*p=(const uint8_t*)data;try{s->out.insert(s->out.end(),p,p+n);return {heif_error_Ok,heif_suberror_Unspecified,""};}catch(...){return {heif_error_Memory_allocation_error,heif_suberror_Unspecified,"allocation failed"};}}
#endif
API int writer_finish(Writer*s){try{if(s->added!=s->count)return 0;
#ifdef SINTER_AVIF
{avifRWData out=AVIF_DATA_EMPTY;if(avifEncoderFinish(s->avif,&out)!=AVIF_RESULT_OK){avifRWDataFree(&out);return 0;}s->out.assign(out.data,out.data+out.size);avifRWDataFree(&out);}
#else
{heif_writer writer={1,write_bytes};if(heif_context_write(s->heif,&writer,s).code!=heif_error_Ok)return 0;}
#endif
return !s->out.empty();}catch(...){return 0;}}
API const uint8_t*writer_data(Writer*s){return s->out.data();}
API size_t writer_len(Writer*s){return s->out.size();}
API void writer_close(Writer*s){delete s;}

#endif
