#import "StreamChatReactNative.h"
#import <React/RCTLog.h>

#if __has_include(<React/RCTLog.h>)
#import <React/RCTLog.h>
#import <React/RCTImageLoader.h>
#else
#import "RCTLog.h"
#import "RCTImageLoader.h"
#endif

static NSString *const moduleName = @"StreamChatReactNative";

// These helpers are declared `static` so their symbols keep internal linkage and
// are not exported from the framework. Otherwise they collide (duplicate symbol
// link error) with libraries that vendor the same image-resizer sources, e.g.
// @bam.tech/react-native-image-resizer. See issue #3227.
static bool saveImage(NSString *fullPath, UIImage *image, NSString *format, float quality);
static NSString *generateFilePath(NSString *ext, NSString *outputPath);
static UIImage *rotateImage(UIImage *inputImage, float rotationDegrees);
static float getScaleForProportionalResize(CGSize theSize, CGSize intoSize, bool onlyScaleDown, bool maximize);
static UIImage *scaleImage(UIImage *image, CGSize toSize, NSString *mode, bool onlyScaleDown, UIColor *backgroundColor);
static NSDictionary *transformImage(UIImage *image, int rotation, CGSize newSize, NSString *fullPath, NSString *format, int quality, NSDictionary *options);

@implementation StreamChatReactNative

@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

RCT_REMAP_METHOD(createResizedImage, uri:(NSString *)uri width:(double)width height:(double)height format:(NSString *)format quality:(double)quality mode:(NSString *)mode onlyScaleDown:(BOOL)onlyScaleDown rotation:(nonnull NSNumber *)rotation outputPath:(NSString *)outputPath backgroundColor:(NSNumber *)backgroundColor resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
    [self createResizedImage:uri width:width height:height format:format quality:quality mode:mode onlyScaleDown:onlyScaleDown rotation:rotation outputPath:outputPath backgroundColor:backgroundColor resolve:resolve reject:reject];
}

- (void)createResizedImage:(NSString *)uri width:(double)width height:(double)height format:(NSString *)format quality:(double)quality mode:(NSString *)mode onlyScaleDown:(BOOL)onlyScaleDown rotation:(nonnull NSNumber *)rotation outputPath:(NSString *)outputPath backgroundColor:(NSNumber *)backgroundColor resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @try {
            CGSize newSize = CGSizeMake(width, height);

            //Set image extension
            NSString *extension = @"jpg";
            if ([format isEqualToString:@"PNG"]) {
                extension = @"png";
            }

            NSString* fullPath;
            @try {
                fullPath = generateFilePath(extension, outputPath);
            } @catch (NSException *exception) {
                [NSException raise:moduleName format:@"Invalid output path."];
            }

            // Nil unless the caller asked for a backdrop. Converted here (on the JS-side
            // argument) rather than in scaleImage so the ARGB decoding lives in one place.
            UIColor *fillColor = backgroundColor == nil ? nil : [RCTConvert UIColor:backgroundColor];

            NSMutableDictionary *options = [@{@"mode": mode, @"onlyScaleDown": [NSNumber numberWithBool:onlyScaleDown]} mutableCopy];
            if (fillColor != nil) {
                options[@"backgroundColor"] = fillColor;
            }

            RCTImageLoader *loader = [self.bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES];
            NSURLRequest *request = [RCTConvert NSURLRequest:uri];
            [loader loadImageWithURLRequest:request
                                       size:newSize
                                      scale:1
                                    clipped:NO
                                 resizeMode:RCTResizeModeContain
                              progressBlock:nil
                           partialLoadBlock:nil
                            completionBlock:^(NSError *error, UIImage *image) {
                if (error) {
                    RCTLogError(@"%@", [NSString stringWithFormat:@"Code : %@ / Message : %@", [NSString stringWithFormat: @"%ld", (long)error.code], error.description]);
                    reject([NSString stringWithFormat: @"%ld", (long)error.code], error.description, nil);
                    return;
                }
                NSDictionary * response =  transformImage(image, [rotation integerValue], newSize, fullPath, format, (int)quality, options);
                resolve(response);
            }];
        } @catch (NSException *exception) {
            RCTLogError(@"%@", [NSString stringWithFormat:@"Code : %@ / Message : %@", exception.name, exception.reason]);
            reject(exception.name, exception.reason, nil);
        }
    });
}



static bool saveImage(NSString * fullPath, UIImage * image, NSString * format, float quality)
{
    NSData* data = nil;
    if ([format isEqualToString:@"JPEG"]) {
        data = UIImageJPEGRepresentation(image, quality / 100.0);
    } else if ([format isEqualToString:@"PNG"]) {
        data = UIImagePNGRepresentation(image);
    }

    if (data == nil) {
        return NO;
    }

    NSFileManager* fileManager = [NSFileManager defaultManager];
    return [fileManager createFileAtPath:fullPath contents:data attributes:nil];
}

static NSString * generateFilePath(NSString * ext, NSString * outputPath)
{
    NSString* directory;

    if ([outputPath length] == 0) {
        NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        directory = [paths firstObject];
    } else {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        if ([outputPath hasPrefix:documentsDirectory]) {
            directory = outputPath;
        } else {
            directory = [documentsDirectory stringByAppendingPathComponent:outputPath];
        }

        NSError *error;
        [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error];
        if (error) {
            NSLog(@"Error creating documents subdirectory: %@", error);
            @throw [NSException exceptionWithName:@"InvalidPathException" reason:[NSString stringWithFormat:@"Error creating documents subdirectory: %@", error] userInfo:nil];
        }
    }

    NSString* name = [[NSUUID UUID] UUIDString];
    NSString* fullName = [NSString stringWithFormat:@"%@.%@", name, ext];
    NSString* fullPath = [directory stringByAppendingPathComponent:fullName];

    return fullPath;
}

static UIImage * rotateImage(UIImage *inputImage, float rotationDegrees)
{

    // We want only fixed 0, 90, 180, 270 degree rotations.
    const int rotDiv90 = (int)round(rotationDegrees / 90);
    const int rotQuadrant = rotDiv90 % 4;
    const int rotQuadrantAbs = (rotQuadrant < 0) ? rotQuadrant + 4 : rotQuadrant;

    // Return the input image if no rotation specified.
    if (0 == rotQuadrantAbs) {
        return inputImage;
    } else {
        // Rotate the image by 80, 180, 270.
        UIImageOrientation orientation = UIImageOrientationUp;

        switch(rotQuadrantAbs) {
            case 1:
                orientation = UIImageOrientationRight; // 90 deg CW
                break;
            case 2:
                orientation = UIImageOrientationDown; // 180 deg rotation
                break;
            default:
                orientation = UIImageOrientationLeft; // 90 deg CCW
                break;
        }

        return [[UIImage alloc] initWithCGImage: inputImage.CGImage
                                          scale: 1.0
                                    orientation: orientation];
    }
}

static float getScaleForProportionalResize(CGSize theSize, CGSize intoSize, bool onlyScaleDown, bool maximize)
{
    float    sx = theSize.width;
    float    sy = theSize.height;
    float    dx = intoSize.width;
    float    dy = intoSize.height;
    float    scale    = 1;

    if( sx != 0 && sy != 0 )
    {
        dx    = dx / sx;
        dy    = dy / sy;

        // if maximize is true, take LARGER of the scales, else smaller
        if (maximize) {
            scale = MAX(dx, dy);
        } else {
            scale = MIN(dx, dy);
        }

        if (onlyScaleDown) {
            scale = MIN(scale, 1);
        }
    }
    else
    {
        scale     = 0;
    }
    return scale;
}


// returns a resized image keeping aspect ratio and considering
// any :image scale factor.
// The returned image is an unscaled image (scale = 1.0)
// so no additional scaling math needs to be done to get its pixel dimensions
static UIImage* scaleImage (UIImage* image, CGSize toSize, NSString* mode, bool onlyScaleDown, UIColor* backgroundColor)
{

    // Need to do scaling corrections
    // based on scale, since UIImage width/height gives us
    // a possibly scaled image (dimensions in points)
    // Idea taken from RNCamera resize code
    CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);

    // using this instead of ImageHelpers allows us to consider
    // rotation variations
    CGSize newSize;

    if ([mode isEqualToString:@"stretch"]) {
        // Distort aspect ratio
        int width = toSize.width;
        int height = toSize.height;

        if (onlyScaleDown) {
            width = MIN(width, imageSize.width);
            height = MIN(height, imageSize.height);
        }

        newSize = CGSizeMake(width, height);
    } else {
        // Either "contain" (default) or "cover": preserve aspect ratio
        bool maximize = [mode isEqualToString:@"cover"];
        float scale = getScaleForProportionalResize(imageSize, toSize, onlyScaleDown, maximize);
        newSize = CGSizeMake(roundf(imageSize.width * scale), roundf(imageSize.height * scale));
    }

    // A non-opaque context initialises to white (tested on iOS 26). Encoders
    // without an alpha channel (JPEG) then drop the alpha and keep the RGB, which
    // is why transparent areas come out white. When a backdrop is requested we
    // make the context opaque and fill it first, so the image composites onto the
    // colour in the same render pass - no extra bitmap, and an opaque context is
    // cheaper than an alpha one.
    BOOL opaque = (backgroundColor != nil);
    UIGraphicsBeginImageContextWithOptions(newSize, opaque, 1.0);
    if (opaque) {
        [backgroundColor setFill];
        UIRectFill(CGRectMake(0, 0, newSize.width, newSize.height));
    }
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

static NSDictionary * transformImage(UIImage *image,
                              int rotation,
                              CGSize newSize,
                              NSString* fullPath,
                              NSString* format,
                              int quality,
                              NSDictionary* options)
{
    if (image == nil) {
        [NSException raise:moduleName format:@"Can't retrieve the file from the path."];
    }

    // Rotate image if rotation is specified.
    if (0 != (int)rotation) {
        image = rotateImage(image, rotation);
        if (image == nil) {
            [NSException raise:moduleName format:@"Can't rotate the image."];
        }
    }

    // Do the resizing
    UIImage * scaledImage = scaleImage(
                                       image,
                                       newSize,
                                       options[@"mode"],
                                       [[options objectForKey:@"onlyScaleDown"] boolValue],
                                       options[@"backgroundColor"]
                                       );

    if (scaledImage == nil) {
        [NSException raise:moduleName format:@"Can't resize the image."];
    }
    // Compress and save the image
    if (!saveImage(fullPath, scaledImage, format, quality)) {
        [NSException raise:moduleName format:@"Can't save the image. Check your compression format and your output path"];
    }

    NSURL *fileUrl = [[NSURL alloc] initFileURLWithPath:fullPath];
    NSString *fileName = fileUrl.lastPathComponent;
    NSError *attributesError = nil;
    NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:&attributesError];
    NSNumber *fileSize = fileAttributes == nil ? 0 : [fileAttributes objectForKey:NSFileSize];
    NSDictionary *response = @{@"path": fullPath,
                               @"uri": fileUrl.absoluteString,
                               @"name": fileName,
                               @"size": fileSize == nil ? @(0) : fileSize,
                               @"width": @(scaledImage.size.width),
                               @"height": @(scaledImage.size.height),
    };

    return response;
}

// Don't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
    return std::make_shared<facebook::react::NativeStreamChatReactNativeSpecJSI>(params);
}
#endif
@end