//  APIClient.mm
#import "APIClient.h"
#import "APIConfig.h"
#import <CommonCrypto/CommonDigest.h>
#import "LocalStorage.h"

@implementation APIClient

+ (void)requestAccessItem:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"AccessItem" completion:completion]) {
        return;
    }

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/plan/getAccessItem"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"drawing_plan_id": [APIConfig drawingPlanId] ?: @"",
            @"information": [APIConfig deviceInfo],
        }
    };
  
    NSLog(@"🌍 SUBMIT URL: %@", url.absoluteString);
    NSLog(@"📦 SUBMIT Body JSON: %@", jsonBody);
    
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSLog(@"🔄 SUBMIT (NO REP) Request started...");

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        NSString *cacheKey = [NSString stringWithFormat:@"AccessItem_%@", [APIConfig projectId]];
        NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
        [LocalStorage saveDictionary:safeJson forKey:cacheKey];
        NSLog(@"💾 Saved to cache: %@", cacheKey);
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)requestGetUnitPlan:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
  
    if ([APIConfig handleOfflineForAPI:@"GetUnitPlan" completion:completion]) {
        return;
    }
  
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/plan/getLocationByUnit"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
  
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"drawing_plan_id": [APIConfig drawingPlanId] ?: @"",
        }
    };
  
    NSLog(@"🌍 SUBMIT URL: %@", url.absoluteString);
    NSLog(@"📦 SUBMIT Body JSON: %@", jsonBody);
    
    NSError *jsonError;
    NSData *body = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = body;

    // 🌍 Debug logs
    NSLog(@"🌍 [requestUnitPlan] URL: %@", url.absoluteString);
    NSLog(@"📦 [requestUnitPlan] Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);

    // 🌐 Perform request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ [requestUnitPlan] Network error: %@", error);
            if (completion) completion(NO, nil, error);
            return;
        }

        NSError *jsonErr;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];

        if (jsonErr) {
            NSLog(@"❌ [requestUnitPlan] JSON parse error: %@", jsonErr);
            if (completion) completion(NO, nil, jsonErr);
            return;
        }

        NSLog(@"✅ [requestUnitPlan] Response:\n%@", json);

        NSDictionary *appData = json[@"AppData"];
        if ([appData[@"status"] isEqualToString:@"success"]) {
            NSLog(@"📄 Successfully requested unit plan for ID");
        } else if (appData[@"error_code"] && [appData[@"error_code"] integerValue] == 503) {
            NSLog(@"⚠️ Server under maintenance (503)");
        } else {
            NSLog(@"⚠️ API returned failure: %@", appData[@"message"]);
        }

        // ✅ Call back on main thread
        if (completion) {
        NSString *cacheKey = [NSString stringWithFormat:@"GetUnitPlan_%@", [APIConfig projectId]];
        NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
        [LocalStorage saveDictionary:safeJson forKey:cacheKey];
        NSLog(@"💾 Saved to cache: %@", cacheKey);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(YES, json, nil);
            });
        }
    }];

    [task resume];
}

+ (void)requestSettingsByLocation:(NSString *)locationID
                    completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
 
    if ([APIConfig handleOfflineForAPI:@"SettingsByLocation" completion:completion]) {
        return;
    }

    NSURL *url = [APIConfig urlWithPath:@"/framework/issue/getGeneralSettingByLocation"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // 🧱 multipart/form-data setup
    NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField:@"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    // Helper block for adding form fields
    void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
                          dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:
                           @"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]
                          dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", value]
                          dataUsingEncoding:NSUTF8StringEncoding]];
    };

    // 🧩 Add POST fields
    appendFormField(@"data[os]", [APIConfig os] );
    appendFormField(@"data[project_id]", [APIConfig projectId]  ?: @"0");
    appendFormField(@"data[location_id]", locationID ?: @"0");

    NSString *deviceInfo = [APIConfig deviceInfoJSONString];
    appendFormField(@"data[information]", deviceInfo);
  
    // End boundary
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
                      dataUsingEncoding:NSUTF8StringEncoding]];
    request.HTTPBody = body;

    // 🌍 Debug logs
    NSLog(@"🌍 [requestSettingsByLocation] URL: %@", url.absoluteString);
    NSLog(@"📦 [requestSettingsByLocation] Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);

    // 🌐 Send request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ [requestSettingsByLocation] Network error: %@", error);
            if (completion) completion(NO, nil, error);
            return;
        }

        NSError *jsonErr;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
        if (jsonErr) {
            NSLog(@"❌ JSON parse error: %@", jsonErr);
            if (completion) completion(NO, nil, jsonErr);
            return;
        }

        NSLog(@"✅ [requestSettingsByLocation] Response:\n%@", json);

        NSDictionary *appData = json[@"AppData"];
        NSString *status = appData[@"status"];

        if ([status isEqualToString:@"success"]) {
            NSLog(@"📄 Successfully requested settings for location");
        }
        else if ([appData[@"error_code"] integerValue] == 503) {
            NSLog(@"⚠️ Maintenance mode (503)");
        }
        else {
            NSLog(@"⚠️ Failed: %@", appData[@"message"]);
        }

        if (completion) {
        NSString *cacheKey = [NSString stringWithFormat:@"SettingsByLocation_%@", [APIConfig projectId]];
        NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
        [LocalStorage saveDictionary:safeJson forKey:cacheKey];
        NSLog(@"💾 Saved to cache: %@", cacheKey);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(YES, json, nil);
            });
        }
    }];

    [task resume];
}

+ (void)submitAddIssue:(NSDictionary *)issueData
                images:(NSArray<UIImage *> * _Nullable)images
            completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/addIssue"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
  
    // 🧱 Multipart body
    NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
   forHTTPHeaderField:@"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    // Helper for adding form fields
    void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]
                          dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", value]
                          dataUsingEncoding:NSUTF8StringEncoding]];
    };

    // 🔹 Append form data fields
    for (NSString *key in issueData.allKeys) {
        id value = issueData[key];
        if ([value isKindOfClass:[NSNumber class]]) value = [(NSNumber *)value stringValue];
        if ([value isKindOfClass:[NSString class]]) {
            appendFormField([NSString stringWithFormat:@"data[%@]", key], value);
        }
    }

    // 🔹 Append OS info
    appendFormField(@"data[os]", [APIConfig os] );

    // 🔹 Append images
    if (images.count > 0) {
        for (int i = 0; i < images.count; i++) {
            id maybeImage = images[i];
            if (![maybeImage isKindOfClass:[UIImage class]]) {
                NSLog(@"⚠️ Skipping non-UIImage at index %d: %@", i, maybeImage);
                continue; // skip invalid entries
            }

            UIImage *image = (UIImage *)maybeImage;
            NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
            if (!imageData) continue;

            [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
                              dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithFormat:
                               @"Content-Disposition: form-data; name=\"data[image][%d]\"; filename=\"image%d.jpg\"\r\n", i, i]
                              dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:imageData];
            [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    }

    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
                      dataUsingEncoding:NSUTF8StringEncoding]];
    request.HTTPBody = body;

    // Debug
    NSLog(@"📦 [AddIssue] Uploading %lu image(s)", (unsigned long)images.count);

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"❌ [AddIssue] Network error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(NO, nil, error); });
            return;
        }

        NSError *jsonErr;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
        if (jsonErr) {
            NSString *raw = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"⚠️ [AddIssue] JSON parse error: %@\nRaw response: %@", jsonErr, raw);
            dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(NO, nil, jsonErr); });
            return;
        }

        NSLog(@"✅ [AddIssue] Response: %@", json);
        dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(YES, json, nil); });
    }];

    [task resume];
}

+ (void)submitUpdateIssue:(NSDictionary *)updateData
                images:(NSArray<UIImage *> * _Nullable)images
               completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion{
  
  NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/updateIssue"];
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  request.HTTPMethod = @"POST";
  
  // 🧱 Multipart body setup
  NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
  [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
 forHTTPHeaderField:@"Content-Type"];
  
  NSMutableData *body = [NSMutableData data];
  
  // Helper for form fields
  void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
                      dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:
                       @"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]
                      dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", value]
                      dataUsingEncoding:NSUTF8StringEncoding]];
  };
  
  // 🔹 Append update fields (all under data[])
  for (NSString *key in updateData.allKeys) {
    id value = updateData[key];
    if ([value isKindOfClass:[NSNumber class]]) value = [(NSNumber *)value stringValue];
    if ([value isKindOfClass:[NSString class]]) {
      appendFormField([NSString stringWithFormat:@"data[%@]", key], value);
    }
  }
  
  // 🔹 Always include OS
  appendFormField(@"data[plan_id]", [APIConfig drawingPlanId] );
  appendFormField(@"data[location_id]", updateData[@"location_id"]);
  appendFormField(@"data[issue_id]", updateData[@"issue_id"]);
  appendFormField(@"data[issue_setting_id]", updateData[@"issue_setting_id"]);
  appendFormField(@"data[position_x]", updateData[@"position_x"]);
  appendFormField(@"data[position_y]", updateData[@"position_y"]);
  appendFormField(@"data[comment]", updateData[@"comment"]);
  appendFormField(@"data[os]", updateData[@"os"]);
  
  // 🔹 Append image files
  for (int i = 0; i < images.count; i++) {
      id maybeImage = images[i];
      if (![maybeImage isKindOfClass:[UIImage class]]) {
        NSLog(@"⚠️ Skipping non-UIImage at index %d: %@", i, maybeImage);
        continue; // skip invalid entries
      }
      
      UIImage *image = (UIImage *)maybeImage;
      NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
      if (!imageData) continue;
      
      [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
                        dataUsingEncoding:NSUTF8StringEncoding]];
      [body appendData:[[NSString stringWithFormat:
                         @"Content-Disposition: form-data; name=\"data[image][%d]\"; filename=\"image%d.jpg\"\r\n", i, i]
                        dataUsingEncoding:NSUTF8StringEncoding]];
      [body appendData:[@"Content-Type: image/jpeg\r\n\r\n"
                        dataUsingEncoding:NSUTF8StringEncoding]];
      [body appendData:imageData];
      [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    }
    
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
                      dataUsingEncoding:NSUTF8StringEncoding]];
    request.HTTPBody = body;
    
    NSLog(@"📦 [UpdateIssue] Uploading %lu image(s)", (unsigned long)images.count);
    
    // 🔹 Execute request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
                                  dataTaskWithRequest:request
                                  completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
      
      if (error) {
        NSLog(@"❌ [UpdateIssue] Network error: %@", error);
        dispatch_async(dispatch_get_main_queue(), ^{
          if (completion) completion(NO, nil, error);
        });
        return;
      }
      
      NSError *jsonErr;
      NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
      if (jsonErr) {
        NSString *raw = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"⚠️ [UpdateIssue] JSON parse error: %@\nRaw: %@", jsonErr, raw);
        dispatch_async(dispatch_get_main_queue(), ^{
          if (completion) completion(NO, nil, jsonErr);
        });
        return;
      }
      
      NSLog(@"✅ [UpdateIssue] Response: %@", json);
      dispatch_async(dispatch_get_main_queue(), ^{
        if (completion) completion(YES, json, nil);
      });
    }];
    [task resume];
  }
  
+ (void)requestAppointment:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"Appointment" completion:completion]) {
        return;
    }

    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/appointment/getSlot"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"project_id": [APIConfig projectId] ,
            @"drawing_plan_id": [APIConfig drawingPlanId] 
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Appointment API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"Appointment_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
                }
        });
    }];

    [task resume];
}

+ (void)requestGeneralInfo:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"GeneralInfo" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/plan/getUnitInfo"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"drawing_plan_id": [APIConfig drawingPlanId] 
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ General Info API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"GeneralInfo_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
                }
        });
    }];

    [task resume];
}

+ (void)submitAppointment:(NSDictionary *)appointmentData
                        completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/appointment/makeAppointment"];

    NSLog(@"🌍 SUBMIT (NO REP) URL: %@", url.absoluteString);
    NSLog(@"📦 SUBMIT (NO REP) Body JSON: %@", appointmentData);

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSDictionary *bodyDict = @{@"data": appointmentData};
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:bodyDict options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSLog(@"🔄 SUBMIT (NO REP) Request started...");

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        NSLog(@"✅ SUBMIT (NO REP) API Response:\n%@", json);

        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)submitRepresentativeAppointment:(NSDictionary *)repData
                              completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/appointment/makeAppointment"];
    NSLog(@"🌍 SUBMIT URL: %@", url.absoluteString);

    // Create request
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // Multipart boundary
    NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
   forHTTPHeaderField:@"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    // Add repData fields as text fields
    for (NSString *key in repData) {
        NSString *value = [NSString stringWithFormat:@"%@", repData[key]]; // ensure string
        NSString *field = [NSString stringWithFormat:
            @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n",
            boundary, key, value];
        [body appendData:[field dataUsingEncoding:NSUTF8StringEncoding]];
    }

    // Close multipart
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    request.HTTPBody = body;

    NSLog(@"🔄 Request started with body (%lu bytes)", (unsigned long)body.length);

    // Send request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        NSLog(@"✅ API Response:\n%@", json);
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)requestCancelAppointment:(id)appointmentId
               reason:(NSString *)reason
            completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"CancelAppointment" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/appointment/cancelAppointment"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"project_id": [APIConfig projectId] ,
            @"appointment_id": appointmentId,
            @"reason": reason
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ request Cancel Appointment Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"CancelAppointment_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestDashboardInfo:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"DashboardInfo" completion:completion]) {
        return;
    }

  NSURL *url = [APIConfig urlWithPath:@"/framework/owner/plan/getIssueUpdateAppointmentInfo"];

  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  request.HTTPMethod = @"POST";
  
  // 🧱 Create multipart form-data body
  NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
  NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
  [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
  
  NSMutableData *body = [NSMutableData data];
  
  // Helper block to append fields
  void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", value] dataUsingEncoding:NSUTF8StringEncoding]];
  };
  
  // Add form fields
  appendFormField(@"data[os]", [APIConfig os] );
  appendFormField(@"data[drawing_plan_id]", [APIConfig drawingPlanId] );
  
  NSString *infoJson = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
  appendFormField(@"data[information]", infoJson);
  
  [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  
  request.HTTPBody = body;
  
  // 🌍 Debug logs
  NSLog(@"🌍 URL: %@", url.absoluteString);
  NSLog(@"📦 Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
  
  NSURLSessionDataTask *task = [[NSURLSession sharedSession]
                                dataTaskWithRequest:request
                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error) {
      NSLog(@"❌ Network error: %@", error);
      if (completion) completion(NO, nil, error);
      return;
    }
    
    NSError *jsonErr;
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
    NSLog(@"🧩 Dashboard Info response: %@", json);
    
    if (completion) {
    NSString *cacheKey = [NSString stringWithFormat:@"DashboardInfo_%@", [APIConfig projectId]];
    NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
    [LocalStorage saveDictionary:safeJson forKey:cacheKey];
    NSLog(@"💾 Saved to cache: %@", cacheKey);
      dispatch_async(dispatch_get_main_queue(), ^{
        completion(YES, json, jsonErr);
      });
    }
  }];
  [task resume];
}

+ (void)requestClientAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"ClientAnnouncement" completion:completion]) {
        return;
    }
    // ✅ Keep token in URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/clientAnnouncement"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // 🧱 multipart/form-data setup
    NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField:@"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", value] dataUsingEncoding:NSUTF8StringEncoding]];
    };

    // Add form fields (same as DashboardInfo)
    appendFormField(@"data[os]", [APIConfig os] );
    appendFormField(@"data[project_id]", [APIConfig projectId] );
    appendFormField(@"data[client_id]", [APIConfig clientId] );

    NSString *infoJson = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
    appendFormField(@"data[information]", infoJson);

    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    request.HTTPBody = body;

    // 🌍 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
          NSLog(@"❌ Network error: %@", error);
          if (completion) completion(NO, nil, error);
          return;
        }

        NSError *jsonErr;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
        NSLog(@"✅ ClientAnnouncement API response:\n%@", json);

        NSDictionary *appData = json[@"AppData"];
        NSArray *announcements = @[];

        if ([appData[@"status"] isEqualToString:@"success"]) {
            announcements = json[@"Data"];
            NSLog(@"📣 Got %lu announcements", (unsigned long)announcements.count);
        } else {
            NSLog(@"⚠️ Announcement request failed: %@", appData[@"message"]);
        }

        if (completion) {
            NSString *cacheKey = [NSString stringWithFormat:@"ClientAnnouncement_%@", [APIConfig projectId]];
            NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
            [LocalStorage saveDictionary:safeJson forKey:cacheKey];
            NSLog(@"💾 Saved to cache: %@", cacheKey);
            dispatch_async(dispatch_get_main_queue(), ^{
              completion(YES, json, jsonErr);
            });
        }
    }];
    [task resume];
}

+ (void)requestHistory:(NSString *)issueID
                    completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"History" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/getIssueHistory"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"issue_id": issueID,
            @"information": [APIConfig deviceInfo],
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Issues API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"History_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+(void)submitAddInfo:(NSString *)issueID
                    remarks:(NSString *)remarks
                    completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {


    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/addInfo"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"issue_id": issueID,
            @"remarks": remarks,
            @"os": [APIConfig os] ,
            @"full_response": @"false",
            @"information": [APIConfig deviceInfo],
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Add Info API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) completion(YES, jsonResponse, jsonParseError);
        });
    }];

    [task resume];
}

+ (void)uploadAddInfo:(NSString *)issueID
              remarks:(NSString *)remarks
               images:(NSArray<UIImage *> *)images
             mediaURL:(NSURL *)mediaURL
            mediaType:(NSString *)mediaType
           completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/addInfo"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
  
    // 🧱 Multipart body
    NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
   forHTTPHeaderField:@"Content-Type"];
   [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSMutableData *body = [NSMutableData data];

    // Helper for adding form fields
    void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
        [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]
                          dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"%@\r\n", value]
                          dataUsingEncoding:NSUTF8StringEncoding]];
    };

    // 🔹 Append OS info
    appendFormField(@"data[issue_id]", issueID ?: @"");
    appendFormField(@"data[remarks]", remarks ?: @"");
    appendFormField(@"data[os]", [APIConfig os] );

    // 🔹 Append images
    if (images.count > 0) {
        for (int i = 0; i < images.count; i++) {
            id maybeImage = images[i];
            if (![maybeImage isKindOfClass:[UIImage class]]) {
                NSLog(@"⚠️ Skipping non-UIImage at index %d: %@", i, maybeImage);
                continue; // skip invalid entries
            }

            UIImage *image = (UIImage *)maybeImage;
            NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
            if (!imageData) continue;

            [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
                              dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithFormat:
                               @"Content-Disposition: form-data; name=\"data[image][%d]\"; filename=\"image%d.jpg\"\r\n", i, i]
                              dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:imageData];
            [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    }

    // 🔹 Append media file if provided
    if (mediaURL) {
        NSData *fileData = [NSData dataWithContentsOfURL:mediaURL];
        if (fileData) {
            NSString *ext = mediaURL.pathExtension.lowercaseString;
            NSString *mimeType = @"application/octet-stream";

            if ([mediaType isEqualToString:@"Video"]) {
                mimeType = [NSString stringWithFormat:@"video/%@", ext];
            }

            NSInteger index = images.count;

            [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
                            dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithFormat:
                @"Content-Disposition: form-data; name=\"data[image][%ld]\"; filename=\"addinfo.%@\"\r\n",
                (long)index, ext]
                dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithFormat:
                @"Content-Type: %@\r\n\r\n", mimeType]
                dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:fileData];
            [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    }

    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
                      dataUsingEncoding:NSUTF8StringEncoding]];
    request.HTTPBody = body;

    // Debug
    NSLog(@"📦 [AddInfo] Uploading %lu image(s)", (unsigned long)images.count);

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"❌ [AddInfo] Network error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(NO, nil, error); });
            return;
        }

        NSError *jsonErr;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
        if (jsonErr) {
            NSString *raw = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"⚠️ [AddInfo] JSON parse error: %@\nRaw response: %@", jsonErr, raw);
            dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(NO, nil, jsonErr); });
            return;
        }

        NSLog(@"✅ [AddInfo] Response: %@", json);
        dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(YES, json, nil); });
    }];

    [task resume];
}

+ (void)requestIssues:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"Issues" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/getIssue"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"drawing_plan_id": [APIConfig drawingPlanId] ,
            @"information": [APIConfig deviceInfo],
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Issues API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"Issues_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestVoidIssue:(id)issueID
                remarks:(NSString *)remarks
            completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
    
    if ([APIConfig handleOfflineForAPI:@"VoidIssue" completion:completion]) {
        return;
    }
    
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/voidIssue"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
  
    // json checks
    NSArray *issueArray = issueID;
      if (![issueID isKindOfClass:[NSArray class]]) {
          issueArray = @[ issueID ?: @"" ];
      }
      NSString *remarksSafe = remarks ?: @"";

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"issue_id": issueArray,
            @"remarks": remarksSafe
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Void Issues API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"VoidIssue_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestDeleteIssue:(NSString *)issueID
               remarks:(NSString *)remarks
            completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"DeleteIssue" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/deleteIssue"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
  
    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"issue_id": issueID ?: @"",
            @"remarks": remarks ?: @""
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Delete Issues API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"DeleteIssue_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestCommonArea:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"CommonArea" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/plan/getCommonArea"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"project_id": [APIConfig projectId] ,
            @"information": [APIConfig deviceInfo],
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Common Area API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"CommonArea_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestGeneralSettings:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"GeneralSettings" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/issue/getGeneralSetting"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    
    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"project_id": [APIConfig projectId] ,
            @"information": [APIConfig deviceInfo]
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ General Settings API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"GeneralSettings_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestDefectMatrix:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

//    if ([APIConfig handleOfflineForAPI:@"DefectMatrix" completion:completion]) {
//        return;
//    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/issue/getGeneralSettingByUnit"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"project_id": [APIConfig projectId] ,
            @"drawing_plan_id": [APIConfig drawingPlanId] ,
            @"information": [APIConfig deviceInfo]
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Defect Matrix API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestProjectList:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
    if ([APIConfig handleOfflineForAPI:@"ProjectList" completion:completion]) {
        return;
    }
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/project/listProject"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
        }
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Project List API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"ProjectList_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestArrayImage:(NSArray *)imageArray
                completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
    
    // ✅ URL
    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/offline/syncImageArray"];
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    // ✅ JSON headers
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // ✅ Build JSON body
    NSDictionary *jsonBody = @{
        @"data": imageArray,
    };

    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url.absoluteString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Project List API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];

    [task resume];
}

+ (void)downloadAndCacheImage:(NSURL *)url
                    completion:(void (^)(BOOL success, NSString *filePath))completion {

    if (!url) {
        if (completion) completion(NO, nil);
        return;
    }

    // 1️⃣ Get cache directory
    NSString *cacheDir = [self offlineImageCacheDirectory];
    NSFileManager *fm = [NSFileManager defaultManager];

    if (![fm fileExistsAtPath:cacheDir]) {
        NSError *dirError = nil;
        [fm createDirectoryAtPath:cacheDir
      withIntermediateDirectories:YES
                       attributes:nil
                            error:&dirError];
        if (dirError) {
            NSLog(@"❌ Failed to create cache directory: %@", dirError);
            if (completion) completion(NO, nil);
            return;
        }
    }

    // 2️⃣ Generate unique file name (avoid collisions)
    NSString *fileName = [NSString stringWithFormat:@"%@_%@", url.lastPathComponent, [self md5Hash:url.absoluteString]];
    NSString *filePath = [cacheDir stringByAppendingPathComponent:fileName];

    // 3️⃣ Skip if already cached
    if ([fm fileExistsAtPath:filePath]) {
        NSLog(@"🟢 Cached file exists: %@", fileName);
        if (completion) completion(YES, filePath);
        return;
    }

    // 4️⃣ Build request
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"GET";
    request.timeoutInterval = 20; // per-file timeout

    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;

    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];

    // 5️⃣ Download task
    NSURLSessionDownloadTask *task =
    [session downloadTaskWithRequest:request
                   completionHandler:^(NSURL *location,
                                       NSURLResponse *response,
                                       NSError *error) {

        if (error || !location) {
            NSLog(@"❌ Download failed: %@ | %@", url, error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil);
            });
            return;
        }

        // 6️⃣ Move file to cache (on background queue)
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
            NSError *moveError = nil;
            [fm moveItemAtURL:location
                       toURL:[NSURL fileURLWithPath:filePath]
                       error:&moveError];

            if (moveError) {
                NSLog(@"❌ Save failed: %@", moveError);
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (completion) completion(NO, nil);
                });
                return;
            }

            NSLog(@"✅ Cached: %@", fileName);

            // 7️⃣ Completion on main queue
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(YES, filePath);
            });
        });
    }];

    [task resume];
}

+ (void)requestCompanyCode:(NSString *)companyCode
               completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion; {

    if ([APIConfig handleOfflineForAPI:@"CompanyCode" completion:completion]) {
        return;
    }

    NSString *urlString = @"https://kitadev.commudesk.com/api/companycode/get";
    NSURL *url = [NSURL URLWithString:urlString];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os],
            @"company_code": companyCode.length > 0
                ? [companyCode lowercaseString]
                : @"",
            @"information": [APIConfig deviceInfo],
        }
    };
  
    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", urlString);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Company Code API Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                NSString *cacheKey = [NSString stringWithFormat:@"CompanyCode_%@", [APIConfig projectId]];
                NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
                [LocalStorage saveDictionary:safeJson forKey:cacheKey];
                NSLog(@"💾 Saved to cache: %@", cacheKey);
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];
    [task resume];
}

+ (void)requestThirdPartyList:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"ThirdPartyList" completion:completion]) {
        return;
    }

    NSURL *url = [APIConfig urlWithPath:@"/framework/third-party/auth/listCode"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
  
    NSLog(@"🌍 Request URL: %@", url.absoluteString);
    
    NSError *jsonError;

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    NSLog(@"🔄 Request thirdPartyList started...");

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        NSString *cacheKey = [NSString stringWithFormat:@"AccessItem_%@", [APIConfig projectId]];
        NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
        [LocalStorage saveDictionary:safeJson forKey:cacheKey];
        NSLog(@"💾 Saved to cache: %@", cacheKey);
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)createThirdPartyLink:(NSString *)title
                    lotId:(NSString *)lotId
                  completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/third-party/auth/generateCode"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSDictionary *jsonBody = @{
        @"title": title ?: @"",
        @"unit_id": lotId ?: @"",
        @"status": @1,
        @"created_by": [APIConfig clientId],
    };
  
    // ✅ Convert dictionary → NSData
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }
    request.HTTPBody = bodyData;

    // 🧭 Debug logs
    NSLog(@"🌍 URL: %@", url);
    NSLog(@"📦 Body JSON: %@", jsonBody);

    // ✅ Make request
    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(NO, nil, error);
            });
            return;
        }

        NSError *jsonParseError;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];

        if (jsonParseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
        } else {
            NSLog(@"✅ Create Third Party Link Response:\n%@", jsonResponse);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                completion(YES, jsonResponse, jsonParseError);
            }
        });
    }];
    [task resume];
}

+ (void)updateThirdPartyLink:(NSDictionary *)payload
                  completion:(void(^)(NSDictionary * _Nullable response,NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/third-party/auth/update"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSError *jsonError;
    NSData *bodyData =
        [NSJSONSerialization dataWithJSONObject:payload
                                        options:0
                                          error:&jsonError];

    if (jsonError) {
        if (completion) completion(nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSLog(@"🌍 URL: %@", url);
    NSLog(@"📦 Body JSON: %@", payload);

    NSURLSessionDataTask *task =
    [[NSURLSession sharedSession] dataTaskWithRequest:request
                                    completionHandler:^(NSData *data,
                                                        NSURLResponse *response,
                                                        NSError *error) {

        if (error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *jsonResponse =
            [NSJSONSerialization JSONObjectWithData:data
                                            options:0
                                              error:&parseError];

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                completion(jsonResponse, parseError);
            }
        });
    }];

    [task resume];
}

+ (void)requestProfile:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"Profile" completion:completion]) {
        return;
    }

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/user"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"information": [APIConfig deviceInfo],
        }
    };
  
    NSLog(@"🌍 request profile: %@", url.absoluteString);
    NSLog(@"📦 request body json: %@", jsonBody);
    
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        NSString *cacheKey = [NSString stringWithFormat:@"Profile_%@", [APIConfig projectId]];
        NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
        [LocalStorage saveDictionary:safeJson forKey:cacheKey];
        NSLog(@"💾 Saved to cache: %@", cacheKey);
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)deleteProfile:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/accDeleteRequest"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
            @"information": [APIConfig deviceInfo],
        }
    };
  
    NSLog(@"🌍 delete profile: %@", url.absoluteString);
    NSLog(@"📦 delete body json: %@", jsonBody);
    
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)editProfile:(NSString *)name
        completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/editUser"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"name": name ?: @"",
            @"language": @"2",
            @"os": [APIConfig os] ,
            @"information": [APIConfig deviceInfo],
        }
    };
  
    NSLog(@"🌍 edit profile: %@", url.absoluteString);
    NSLog(@"📦 edit profile json: %@", jsonBody);
    
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)uploadProfileImage:(NSData *)imageData
                      name:(NSString *)name
                completion:(void(^)(BOOL success,
                                    NSDictionary * _Nullable response,
                                    NSError * _Nullable error))completion {

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/editUser"];

    NSString *boundary = @"----ProfileImageBoundary";
    NSMutableData *body = [NSMutableData data];

    void (^appendString)(NSString *) = ^(NSString *string) {
        [body appendData:[string dataUsingEncoding:NSUTF8StringEncoding]];
    };

    // name
    appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
    appendString(@"Content-Disposition: form-data; name=\"data[name]\"\r\n\r\n");
    appendString(name ?: @"");
    appendString(@"\r\n");

    // language
    appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
    appendString(@"Content-Disposition: form-data; name=\"data[language]\"\r\n\r\n");
    appendString(@"2");
    appendString(@"\r\n");

    // os
    appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
    appendString(@"Content-Disposition: form-data; name=\"data[os]\"\r\n\r\n");
    appendString([APIConfig os]);
    appendString(@"\r\n");

    // information
    appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
    appendString(@"Content-Disposition: form-data; name=\"data[information]\"\r\n\r\n");
    NSDictionary *info = [APIConfig deviceInfo];
    NSData *jsonData =
        [NSJSONSerialization dataWithJSONObject:info options:0 error:nil];

    NSString *infoString =
        [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

    appendString(infoString ?: @"");
    appendString(@"\r\n");

    // avatar
    if (imageData) {
        appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
        appendString(@"Content-Disposition: form-data; name=\"data[avatar]\"; filename=\"profile.jpg\"\r\n");
        appendString(@"Content-Type: image/jpeg\r\n\r\n");
        [body appendData:imageData];
        appendString(@"\r\n");
    }

    appendString([NSString stringWithFormat:@"--%@--\r\n", boundary]);

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
   forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    request.HTTPBody = body;

    NSLog(@"🌍 upload profile image: %@", url.absoluteString);

    NSURLSessionDataTask *task =
    [[NSURLSession sharedSession] dataTaskWithRequest:request
                                     completionHandler:^(NSData *data,
                                                         NSURLResponse *response,
                                                         NSError *error) {

        if (error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json =
        [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        dispatch_async(dispatch_get_main_queue(), ^{
            if (parseError) {
                completion(NO, nil, parseError);
            } else {
                completion(YES, json, nil);
            }
        });
    }];

    [task resume];
}

+ (void)requestNotification:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"Notification" completion:completion]) {
        return;
    }

    NSURL *url = [APIConfig urlWithPath:@"/framework/owner/notification"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
        }
    };

    NSLog(@"🌍 request notification: %@", url.absoluteString);
    NSLog(@"📦 request notification json: %@", jsonBody);
    
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        NSString *cacheKey = [NSString stringWithFormat:@"Notification_%@", [APIConfig projectId]];
        NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
        [LocalStorage saveDictionary:safeJson forKey:cacheKey];
        NSLog(@"💾 Saved to cache: %@", cacheKey);
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

+ (void)requestAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {

    if ([APIConfig handleOfflineForAPI:@"Announcement" completion:completion]) {
        return;
    }

    // NSURL *url = [APIConfig urlWithPath:@"/announcement"];
    //place holder, need to change back eventually
    NSURL *url = [NSURL URLWithString:@"https://kitadev.commudesk.com/api/announcement?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    NSDictionary *jsonBody = @{
        @"data": @{
            @"os": [APIConfig os] ,
        }
    };

    NSLog(@"🌍 announcement notification: %@", url.absoluteString);
    NSLog(@"📦 announcement notification json: %@", jsonBody);
    
    NSError *jsonError;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];

    if (jsonError) {
        NSLog(@"❌ JSON Serialization Error: %@", jsonError);
        if (completion) completion(NO, nil, jsonError);
        return;
    }

    request.HTTPBody = bodyData;

    NSURLSessionDataTask *task = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if (error) {
            NSLog(@"❌ Network Error: %@", error);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, error);
            });
            return;
        }

        NSError *parseError;
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

        if (parseError) {
            NSLog(@"⚠️ JSON Parse Error: %@", parseError);
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil, parseError);
            });
            return;
        }

        NSString *cacheKey = [NSString stringWithFormat:@"Announcement_%@", [APIConfig projectId]];
        NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
        [LocalStorage saveDictionary:safeJson forKey:cacheKey];
        NSLog(@"💾 Saved to cache: %@", cacheKey);
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(YES, json, nil);
        });
    }];

    [task resume];
}

#pragma mark - Helper Methods
+ (NSString *)multipartFormField:(NSString *)name value:(NSString *)value boundary:(NSString *)boundary {
    return [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"data[%@]\"\r\n\r\n%@\r\n", boundary, name, value];
}

+ (NSData *)multipartFormFile:(NSString *)filePath fieldName:(NSString *)fieldName fileName:(NSString *)fileName mimeType:(NSString *)mimeType boundary:(NSString *)boundary {
  NSMutableData *data = [NSMutableData data];
  NSData *fileData = [NSData dataWithContentsOfFile:filePath];
  
  [data appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  [data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"data[%@]\"; filename=\"%@\"\r\n", fieldName, fileName] dataUsingEncoding:NSUTF8StringEncoding]];
  [data appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimeType] dataUsingEncoding:NSUTF8StringEncoding]];
  [data appendData:fileData];
  [data appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  
  return data;
}

+ (NSString *)md5Hash:(NSString *)input {
    if (!input) return nil;

    const char *cStr = [input UTF8String];
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5(cStr, (CC_LONG)strlen(cStr), digest);

    NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
        [output appendFormat:@"%02x", digest[i]];

    return output;
}

+ (NSString *)offlineImageCacheDirectory {
    NSString *docsDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSString *cacheDir = [docsDir stringByAppendingPathComponent:@"OfflineUnitImages"];
    return cacheDir;
}

@end
