// OfflineSyncManager.mm
#import "OfflineSyncManager.h"
#import "LocalStorage.h"
#import "NetworkManager.h"
#import "APIClient.h"
#import "APIConfig.h"

static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";

static NSString * const kSyncHistoryKey = @"SYNC_HISTORY_QUEUE";

static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";

static NSString * const kSyncSelectedUnitsKey = @"SYNC_SELECTEDUNITS";

@implementation OfflineSyncManager

#pragma mark - Singleton

+ (instancetype)shared {
    static OfflineSyncManager *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[OfflineSyncManager alloc] init];
    });
    return instance;
}

#pragma mark - Queue Helpers (CRITICAL)
- (NSMutableArray *)safeLoadQueue {
    id stored = [LocalStorage loadArrayForKey:kOfflineQueueKey];

    NSLog(@"📦 [OfflineSync] Raw stored queue: %@", stored);

    if (![stored isKindOfClass:[NSArray class]]) {
        NSLog(@"🧹 [OfflineSync] Queue corrupted — resetting");
        [LocalStorage removeObjectForKey:kOfflineQueueKey];
        return [NSMutableArray array];
    }

    NSMutableArray *validItems = [NSMutableArray array];
    for (id obj in stored) {
        if ([obj isKindOfClass:[NSDictionary class]]) {
            [validItems addObject:obj];
        }
    }

    NSLog(@"📦 [OfflineSync] Valid queue items: %@", validItems);

    return validItems;
}

- (void)saveQueue:(NSArray *)queue {
    [LocalStorage saveArray:queue forKey:kOfflineQueueKey];
}

#pragma mark - Public Queue APIs

- (void)queueAddIssue:(NSDictionary *)payload
              display:(NSDictionary *)display
               images:(NSArray<UIImage *> *)images
           fieldNames:(NSDictionary *)fieldNames {

    [self enqueueAction:@"ADD_ISSUE"
                payload:payload
                 images:images
            displayInfo:display
             fieldNames:fieldNames];
}

- (void)queueUpdateIssue:(NSDictionary *)payload
                 display:(NSDictionary *)display
                  images:(NSArray<UIImage *> *)images
              fieldNames:(NSDictionary *)fieldNames {

    [self enqueueAction:@"UPDATE_ISSUE"
                payload:payload
                 images:images
            displayInfo:display
             fieldNames:fieldNames];
}

- (void)queueAddInfo:(NSString *)issueID
             remarks:(NSString *)remarks
         displayInfo:(NSDictionary *)displayInfo
              images:(NSArray<UIImage *> *)images
            mediaURL:(NSURL * _Nullable)mediaURL
           mediaType:(NSString * _Nullable)mediaType {

    NSMutableArray *queue = [self safeLoadQueue];

    NSArray *imagePaths = [self saveImagesToDisk:images];
    NSString *mediaPath = [self saveMediaToDisk:mediaURL];

    NSMutableDictionary *item = [NSMutableDictionary dictionary];
    item[@"id"] = NSUUID.UUID.UUIDString;
    item[@"type"] = @"ADD_INFO";

    // 🔥 API payload
    item[@"issue_id"] = issueID ?: @"";
    item[@"remarks"] = remarks ?: @"";

    // 🔥 Files
    item[@"imagePaths"] = imagePaths ?: @[];
    item[@"timestamp"] = @((NSInteger)[NSDate date].timeIntervalSince1970);

    if (mediaPath) item[@"mediaPath"] = mediaPath;
    if (mediaType) item[@"mediaType"] = mediaType;

    // 🔥 UI display fields (CRITICAL)
    item[@"projectName"] = displayInfo[@"projectName"] ?: @"Unknown Project";
    item[@"unitName"] = displayInfo[@"unitName"] ?: @"-";
    item[@"createdBy"] = displayInfo[@"createdBy"] ?: @"-";
    item[@"issueDisplay"] = displayInfo[@"issueDisplay"] ?: @"Add Info";
    item[@"createdAt"] = displayInfo[@"createdAt"] ?: [self todayDateString];

    [queue addObject:item];
    [self saveQueue:queue];

    NSLog(@"📥 [OfflineSync] Queued ADD_INFO (%lu)", (unsigned long)queue.count);
}

#pragma mark - Core Queue Add
- (void)enqueueAction:(NSString *)type
              payload:(NSDictionary *)payload
               images:(NSArray<UIImage *> *)images
          displayInfo:(NSDictionary *)displayInfo
           fieldNames:(NSDictionary *)fieldNames
{
    NSMutableArray *queue = [self safeLoadQueue];
    NSArray *imagePaths = [self saveImagesToDisk:images];

    NSMutableDictionary *item = [NSMutableDictionary dictionary];
    item[@"id"] = NSUUID.UUID.UUIDString;
    item[@"type"] = type;
    item[@"payload"] = payload ?: @{};
    item[@"imagePaths"] = imagePaths ?: @[];
    item[@"timestamp"] = @((NSInteger)[NSDate date].timeIntervalSince1970);

    // 🔥 UI/display info
    item[@"projectName"] = displayInfo[@"projectName"] ?: @"Unknown Project";
    item[@"unitName"] = displayInfo[@"unitName"] ?: @"-";
    item[@"createdBy"] = displayInfo[@"createdBy"] ?: @"-";
    item[@"issueDisplay"] = displayInfo[@"issueDisplay"] ?: type;
    item[@"createdAt"] = displayInfo[@"createdAt"] ?: [self todayDateString];

    // 🔥 Add the fieldNames dictionary
    item[@"fieldNames"] = fieldNames ?: @{};

    [queue addObject:item];
    [self saveQueue:queue];

    NSLog(@"📥 [OfflineSync] Queued %@ (%lu)", type, (unsigned long)queue.count);
}

#pragma mark - Processing Queue
- (void)processNextItemWithProgress:(void (^)(NSInteger step,
                                              NSInteger total,
                                              NSString *title))progress
                         completion:(void (^)(BOOL finished))completion {

    NSMutableArray *queue = [self safeLoadQueue];
    NSInteger total = queue.count;

    if (total == 0) {
        if (completion) completion(YES);
        return;
    }

    __block NSInteger step = 0;
    __weak typeof(self) weakSelf = self;

    // 🔑 MUST be __block
    __block void (^processNextItem)(void);

    processNextItem = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) return;

        if (queue.count == 0) {
            NSLog(@"✅ [OfflineSync] Queue empty — finishing");
            
            if (completion) completion(YES);

            // 🔑 BREAK the self-retain cycle
            processNextItem = nil;
            return;
        }

        NSDictionary *item = queue.firstObject;
        step++;

        if (progress) {
            NSString *title = item[@"issueDisplay"] ?: item[@"type"] ?: @"Syncing";
            progress(step, total, title);
        }

        NSString *taskKey = [NSString stringWithFormat:@"process_%@", item[@"id"]];

        [strongSelf safeProcessItem:item taskKey:taskKey completion:^(BOOL success) {

            __strong typeof(weakSelf) strongSelf2 = weakSelf;
            if (!strongSelf2) return;

            // Save to history
            NSMutableDictionary *historyItem = [item mutableCopy];
            historyItem[@"syncedAt"] = [strongSelf2 todayDateString];
            historyItem[@"status"] = success ? @"SUCCESS" : @"FAILED";

            NSMutableArray *history = [strongSelf2 safeLoadHistory];
            [history insertObject:historyItem atIndex:0];
            [strongSelf2 saveHistoryQueue:history];

            // Cleanup files
            for (NSString *path in item[@"imagePaths"]) {
                [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
            }
            if (item[@"mediaPath"]) {
                [[NSFileManager defaultManager] removeItemAtPath:item[@"mediaPath"] error:nil];
            }

            // Remove item
            [queue removeObjectAtIndex:0];
            [strongSelf2 saveQueue:queue];

            // Next iteration
            dispatch_async(dispatch_get_main_queue(), ^{
                if (processNextItem) {
                    processNextItem();
                }
            });
        }];
    };

    // Start
    processNextItem();
}

- (void)safeProcessItem:(NSDictionary *)item
               taskKey:(NSString *)taskKey
            completion:(void (^)(BOOL success))completion {

    __weak typeof(self) weakSelf = self;

    __block BOOL finished = NO;

    // Centralize completion + logging
    void (^finalize)(BOOL) = ^(BOOL success) {
        if (finished) return;
        finished = YES;

        // Log status
        NSMutableDictionary *status =
        [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
        status[taskKey] = success ? @"SUCCESS" : @"FAILED";
        [LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];

        if (completion) completion(success);
    };

    // Timeout guard (in case API never returns)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60.0 * NSEC_PER_SEC)),
                   dispatch_get_main_queue(), ^{
        if (!finished) {
            NSLog(@"⚠️ [OfflineSync] Timeout for item: %@", item[@"type"]);
            finalize(NO);
        }
    });

    NSString *type = item[@"type"] ?: @"";

    NSArray<UIImage *> *images = [self loadImagesFromPaths:item[@"imagePaths"]];
    NSURL *mediaURL = item[@"mediaPath"] ? [NSURL fileURLWithPath:item[@"mediaPath"]] : nil;

    if ([type isEqualToString:@"ADD_INFO"]) {

        if (images.count == 0 && !mediaURL) {

            [APIClient submitAddInfo:item[@"issue_id"]
                             remarks:item[@"remarks"]
                           completion:^(BOOL success, NSDictionary *res, NSError *err) {
                if (!success) NSLog(@"⚠️ ADD_INFO failed: %@", err ?: res);
                finalize(success);
            }];

        } else {

            [APIClient uploadAddInfo:item[@"issue_id"]
                             remarks:item[@"remarks"]
                              images:images
                            mediaURL:mediaURL
                           mediaType:item[@"mediaType"]
                          completion:^(BOOL success, NSDictionary *res, NSError *err) {
                if (!success) NSLog(@"⚠️ ADD_INFO upload failed: %@", err ?: res);
                finalize(success);
            }];
        }
        return;
    }

    if ([type isEqualToString:@"ADD_ISSUE"]) {

        [APIClient submitAddIssue:item[@"payload"]
                           images:images
                        completion:^(BOOL success, NSDictionary *res, NSError *err) {

            [weakSelf logAddIssueDebugWithItem:item images:images res:res err:err];
            if (!success) NSLog(@"⚠️ ADD_ISSUE failed: %@", err ?: res);
            finalize(success);
        }];
        return;
    }

    if ([type isEqualToString:@"UPDATE_ISSUE"]) {

        [APIClient submitUpdateIssue:item[@"payload"]
                              images:images
                           completion:^(BOOL success, NSDictionary *res, NSError *err) {
            if (!success) NSLog(@"⚠️ UPDATE_ISSUE failed: %@", err ?: res);
            finalize(success);
        }];
        return;
    }

    // Unknown type — still finalize
    NSLog(@"⚠️ [OfflineSync] Unknown type: %@", type);
    finalize(NO);
}

#pragma mark - Disk Handling
- (NSArray *)saveImagesToDisk:(NSArray<UIImage *> *)images {

    NSMutableArray *paths = [NSMutableArray array];
    if (![images isKindOfClass:[NSArray class]]) return paths;

    NSString *dir = [self offlineDirectory];

    for (id obj in images) {
        if (![obj isKindOfClass:[UIImage class]]) continue;

        NSString *path = [dir stringByAppendingPathComponent:
                          [NSString stringWithFormat:@"%@.jpg", NSUUID.UUID.UUIDString]];

        NSData *data = UIImageJPEGRepresentation(obj, 0.8);
        if (data && [data writeToFile:path atomically:YES]) {
            [paths addObject:path];
        }
    }
    return paths;
}

- (NSString *)saveMediaToDisk:(NSURL *)url {

    if (!url) return nil;

    NSString *dir = [self offlineDirectory];
    NSString *path = [dir stringByAppendingPathComponent:
                      [NSString stringWithFormat:@"%@.%@",
                       NSUUID.UUID.UUIDString, url.pathExtension]];

    NSData *data = [NSData dataWithContentsOfURL:url];
    if ([data writeToFile:path atomically:YES]) {
        return path;
    }
    return nil;
}

- (NSArray *)loadImagesFromPaths:(NSArray *)paths {

    NSMutableArray *images = [NSMutableArray array];
    for (NSString *path in paths) {
        UIImage *img = [UIImage imageWithContentsOfFile:path];
        if (img) [images addObject:img];
    }
    return images;
}

- (NSString *)offlineDirectory {

    NSString *dir = NSSearchPathForDirectoriesInDomains(
        NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    dir = [dir stringByAppendingPathComponent:@"offline_files"];

    if (![[NSFileManager defaultManager] fileExistsAtPath:dir]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:dir
                                  withIntermediateDirectories:YES
                                                   attributes:nil
                                                        error:nil];
    }
    return dir;
}

#pragma mark - Status
- (BOOL)hasPendingItems {
    return ([self safeLoadQueue].count > 0);
}

#pragma mark - Pending Screen Helper
- (NSArray *)pendingItemsForUI {

    NSArray *queue = [self safeLoadQueue];
    NSMutableDictionary *grouped = [NSMutableDictionary dictionary];

    for (NSDictionary *item in queue) {

        NSString *project = item[@"projectName"] ?: @"Unknown Project";
        NSMutableArray *items = grouped[project] ?: [NSMutableArray array];

        NSDictionary *uiItem = @{
            @"unitName": item[@"unitName"] ?: @"-",
            @"createdBy": item[@"createdBy"] ?: @"-",
            @"createdOn": item[@"createdAt"] ?: [self formatTimestamp:item[@"timestamp"]],
            @"type": item[@"issueDisplay"] ?: item[@"type"] ?: @"-",
            @"raw": item
        };

        [items addObject:uiItem];
        grouped[project] = items;
    }

    NSMutableArray *sections = [NSMutableArray array];
    for (NSString *project in grouped) {
        [sections addObject:@{
            @"project": project,
            @"items": grouped[project]
        }];
    }

    return sections;
}

#pragma mark - History Helpers
- (NSMutableArray *)safeLoadHistory {
    id stored = [LocalStorage loadArrayForKey:kSyncHistoryKey];

    if (![stored isKindOfClass:[NSArray class]]) {
        [LocalStorage removeObjectForKey:kSyncHistoryKey];
        return [NSMutableArray array];
    }

    NSMutableArray *valid = [NSMutableArray array];
    for (id obj in stored) {
        if ([obj isKindOfClass:[NSDictionary class]]) {
            [valid addObject:obj];
        }
    }

    return valid;
}

- (void)saveHistory:(NSArray *)history {
    [LocalStorage saveArray:history forKey:kSyncHistoryKey];
}

- (NSArray *)historyItemsForUI {

    NSArray *history = [self loadHistoryQueue];
    NSMutableArray *sections = [NSMutableArray array];

    for (NSDictionary *item in history) {

        NSDictionary *uiItem = @{
            @"unitName": item[@"unitName"] ?: @"-",
            @"createdBy": item[@"createdBy"] ?: @"-",
            @"createdOn": item[@"createdAt"] ?: @"-",
            @"uploadedOn": item[@"syncedAt"] ?: @"-",
            @"type": item[@"issueDisplay"] ?: @"-",
            @"status": item[@"status"] ?: @"FAILED",
            @"raw": item
        };

        [sections addObject:uiItem];
    }

    return sections;
}

- (NSInteger)historyItemCount {
    return [self safeLoadHistory].count;
}

- (NSMutableArray *)loadHistoryQueue {
    id stored = [LocalStorage loadArrayForKey:kSyncHistoryKey];

    if (![stored isKindOfClass:[NSArray class]]) {
        return [NSMutableArray array];
    }

    NSMutableArray *valid = [NSMutableArray array];
    for (id obj in stored) {
        if ([obj isKindOfClass:[NSDictionary class]]) {
            [valid addObject:obj];
        }
    }
    return valid;
}

- (void)saveHistoryQueue:(NSArray *)queue {
    [LocalStorage saveArray:queue forKey:kSyncHistoryKey];
}

- (void)retryHistoryItem:(NSDictionary *)historyItem {

    NSMutableDictionary *queueItem = [historyItem mutableCopy];
    [queueItem removeObjectForKey:@"status"];
    [queueItem removeObjectForKey:@"syncedAt"];

    NSMutableArray *queue = [self safeLoadQueue];
    [queue addObject:queueItem];
    [self saveQueue:queue];

    NSMutableArray *history = [self loadHistoryQueue];
    [history removeObject:historyItem];
    [self saveHistoryQueue:history];
}

- (NSString *)formatTimestamp:(NSNumber *)ts {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:ts.doubleValue];
    NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
    fmt.dateFormat = @"yyyy-MM-dd";
    return [fmt stringFromDate:date];
}

- (NSString *)todayDateString {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
    formatter.dateFormat = @"yyyy-MM-dd";
    return [formatter stringFromDate:[NSDate date]];
}

- (void)clearOfflineQueue {
    [LocalStorage removeObjectForKey:kOfflineQueueKey];
    [LocalStorage removeObjectForKey:kSyncHistoryKey];
    NSLog(@"🧹 [OfflineSync] Offline queue cleared");
}

#pragma mark - Download Units
- (void)startDownloadUnitImagesWithProgress:(void (^)(NSInteger completed, NSInteger total))progress
                                  completion:(void (^)(BOOL success))completion {

    if (![NetworkManager.sharedManager isConnected]) {
        if (completion) completion(NO);
        return;
    }

    NSArray *selectedUnits = [self buildOfflineUnitPayloadForCurrentConfig] ?: @[];

    // ------------------------------
    // Build sequential tasks
    // ------------------------------
    NSMutableArray<void (^)(void (^next)(void))> *tasks = [NSMutableArray array];

    // Task 1 — requestIssues
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task1" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 1");

            [APIClient requestIssues:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 1 — requestIssues\nTime: %@\nError: %@\nResponse: %@\n",
                    [NSDate date], err, res];
                [self appendOfflineLog:logText];
              
                if (!success) {
                    NSLog(@"⚠️ Task 1 failed: %@", err);
                    logSuccess(NO);
                } else {
                    if (res)
                    NSLog(@"✅ Task 1 succeeded");
                    logSuccess(YES);
                }
                safeNext();
            }];

        } next:next];
    }];

    // Task 2 — requestDashboardInfo
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task2" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 2");

            [APIClient requestDashboardInfo:^(BOOL success, NSDictionary *res, NSError *err) {
              NSString *logText = [NSString stringWithFormat:
                  @"\n🚀 Task 2 — requestDashboardInfo\nTime: %@\nError: %@\nResponse: %@\n",
                  [NSDate date], err, res];
              [self appendOfflineLog:logText];
                if (!success) {
                    NSLog(@"⚠️ Task 2 failed: %@", err);
                    logSuccess(NO);
                } else {
                    if (res)
                    NSLog(@"✅ Task 2 succeeded");
                    logSuccess(YES);
                }
                safeNext();
            }];

        } next:next];
    }];

    // Task 3 — requestGetUnitPlan (already has logs)
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task3" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 3");

            [APIClient requestGetUnitPlan:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 3 — requestGetUnitPlan\nTime: %@\nError: %@\nResponse: %@\n",
                    [NSDate date], err, res];
                [self appendOfflineLog:logText];

                if (err || ![res isKindOfClass:[NSDictionary class]]) {
                    NSLog(@"⚠️ Task 3 failed: %@", err);
                    logSuccess(NO);
                    safeNext();
                    return;
                }

                id dataArray = res[@"Data"];
                NSDictionary *objData = @{};
                if ([dataArray isKindOfClass:[NSArray class]] && ((NSArray *)dataArray).count > 0) {
                    id firstItem = ((NSArray *)dataArray)[0];
                    if ([firstItem isKindOfClass:[NSDictionary class]]) objData = firstItem;
                }

                NSLog(@"✅ Task 3 succeeded");
                logSuccess(YES);
                safeNext();
            }];

        } next:next];
    }];

    // Task 4 — requestGeneralSettings
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task4" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 4");

            [APIClient requestGeneralSettings:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 4 — requestGeneralSettings\nTime: %@\nError: %@\nResponse: %@\n",
                    [NSDate date], err, res];
                [self appendOfflineLog:logText];
              
                if (!success) {
                    NSLog(@"⚠️ Task 4 failed: %@", err);
                    logSuccess(NO);
                } else {
                    if (res)
                    NSLog(@"✅ Task 4 succeeded");
                    logSuccess(YES);
                }
                safeNext();
            }];

        } next:next];
    }];

    // Task 5 — requestCommonArea
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task5" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 5");
            [APIClient requestCommonArea:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 5 — requestCommonArea\nTime: %@\nError: %@\nResponse: %@\n",
                [NSDate date], err, res];
              
                [self appendOfflineLog:logText];
                if (!success) {
                    NSLog(@"⚠️ Task 5 failed: %@", err);
                    logSuccess(NO);
                } else {
                    if (res)
                    NSLog(@"✅ Task 5 succeeded");
                    logSuccess(YES);
                }
                safeNext();
            }];

        } next:next];
    }];

    // Task 6 — requestDefectMatrix
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task6" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {

            NSLog(@"🚀 Starting Task 6 — requestDefectMatrix");

            [APIClient requestDefectMatrix:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 6 — requestDefectMatrix\nTime: %@\nError: %@\nResponse: %@\n",
                    [NSDate date], err, res];
                [self appendOfflineLog:logText];
              
                if (!res || err) {
                    NSLog(@"⚠️ Task 6 failed: %@", err);
                    logSuccess(NO);
                    safeNext();
                    return;
                }

                NSDictionary *data = res[@"Data"];
                if (![data isKindOfClass:[NSDictionary class]]) {
                    NSLog(@"⚠️ Task 6 failed: Data invalid");
                    logSuccess(NO);
                    safeNext();
                    return;
                }

                NSArray *priority        = data[@"priority"] ?: @[];
                NSArray *contractor      = data[@"contractor"] ?: @[];
                NSArray *newIssueStatus  = data[@"new_issue_status"] ?: @[];
                NSArray *startWorkStatus = data[@"start_work_status"] ?: @[];
                NSArray *subContractor   = data[@"sub_contractor"] ?: @[];
                NSArray *locationStatus  = data[@"location_status"] ?: @[];
                NSString *notMe          = data[@"not_me"] ?: @"";

                NSArray *defectMatrix = data[@"defect_matrix"];
                if ([defectMatrix isKindOfClass:[NSArray class]]) {
                    for (NSDictionary *entry in defectMatrix) {
                        NSArray *locationIds = entry[@"location_id"];
                        if (![locationIds isKindOfClass:[NSArray class]] || locationIds.count == 0) continue;

                        NSArray *category = entry[@"category"] ?: @[];
                        NSDictionary *settings = @{
                            @"category": category,
                            @"priority": priority,
                            @"contractor": contractor,
                            @"new_issue_status": newIssueStatus,
                            @"start_work_status": startWorkStatus,
                            @"sub_contractor": subContractor,
                            @"location_status": locationStatus,
                            @"not_me": notMe
                        };

                        for (id locationId in locationIds) {
                            NSString *locationKey = [NSString stringWithFormat:@"LOCATION_SETTINGS_%@", locationId];
                            [LocalStorage saveDictionary:settings forKey:locationKey];
                            NSLog(@"✅ Task 6 — saved settings for location: %@", locationId);
                        }
                    }
                }

                NSLog(@"✅ Task 6 succeeded");
                logSuccess(YES);
                safeNext();
            }];

        } next:next];
    }];
  
    // Task 7 — requestAccessItem
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task7" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 7");
            [APIClient requestAccessItem:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 7 — requestAccessItem\nTime: %@\nError: %@\nResponse: %@\n",
                [NSDate date], err, res];
              
                [self appendOfflineLog:logText];
                if (!success) {
                    NSLog(@"⚠️ Task 7 failed: %@", err);
                    logSuccess(NO);
                } else {
                    if (res)
                    NSLog(@"✅ Task 7 succeeded");
                    logSuccess(YES);
                }
                safeNext();
            }];
          } next:next];
      }];

      // Task 8 — requestGeneralInfo
      [tasks addObject:^(void (^next)(void)) {
          [self safeSequentialTask:@"task8" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
              NSLog(@"🚀 Starting Task 4");

              [APIClient requestGeneralInfo:^(BOOL success, NSDictionary *res, NSError *err) {
                  NSString *logText = [NSString stringWithFormat:
                      @"\n🚀 Task 8 — requestGeneralInfo\nTime: %@\nError: %@\nResponse: %@\n",
                      [NSDate date], err, res];
                  [self appendOfflineLog:logText];
                
                  if (!success) {
                      NSLog(@"⚠️ Task 8 failed: %@", err);
                      logSuccess(NO);
                  } else {
                      if (res)
                      NSLog(@"✅ Task 8 succeeded");
                      logSuccess(YES);
                  }
                  safeNext();
              }];
          } next:next];
      }];

       // Task 9 — requestClientAnnouncement
      [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task9" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 9");

            [APIClient requestClientAnnouncement:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 9 — requestClientAnnouncement\nTime: %@\nError: %@\nResponse: %@\n",
                    [NSDate date], err, res];
                [self appendOfflineLog:logText];
              
                if (!success) {
                    NSLog(@"⚠️ Task 9 failed: %@", err);
                    logSuccess(NO);
                } else {
                    if (res)
                    NSLog(@"✅ Task 9 succeeded");
                    logSuccess(YES);
                }
                safeNext();
            }];
        } next:next];
    }];

    // Task 10 — requestAppointment
    [tasks addObject:^(void (^next)(void)) {
        [self safeSequentialTask:@"task10" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
            NSLog(@"🚀 Starting Task 10");

            [APIClient requestAppointment:^(BOOL success, NSDictionary *res, NSError *err) {
                NSString *logText = [NSString stringWithFormat:
                    @"\n🚀 Task 10 — requestAppointment\nTime: %@\nError: %@\nResponse: %@\n",
                    [NSDate date], err, res];
                [self appendOfflineLog:logText];
              
                if (!success) {
                    NSLog(@"⚠️ Task 10 failed: %@", err);
                    logSuccess(NO);
                } else {
                    if (res)
                    NSLog(@"✅ Task 10 succeeded");
                    logSuccess(YES);
                }
                safeNext();
            }];
        } next:next];
    }];

    // Task 11 — Image downloads
    if (selectedUnits.count > 0) {
        [self fetchOfflineImageListForSelectedUnits:selectedUnits completion:^(NSArray<NSURL *> *urls, NSError *error) {
            if (error || urls.count == 0) {
                NSLog(@"⚠️ No images to download");
            } else {
                // Flatten all images into tasks
                for (NSInteger i = 0; i < urls.count; i++) {
                    NSURL *url = urls[i];

                    [tasks addObject:^(void (^next)(void)) {
                        NSString *urlKey = [NSString stringWithFormat:@"image_%ld", (long)i];

                        [APIClient downloadAndCacheImage:url completion:^(BOOL success, NSString *filePath) {

                            // Save status to local storage (include file path for debugging)
                            NSMutableDictionary *status =
                                [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];

                            status[urlKey] = @{
                                @"status": success ? @"SUCCESS" : @"FAILED",
                                @"filePath": filePath ?: @""
                            };

                            [LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];

                            if (!success) {
                                NSLog(@"⚠️ Image download failed for URL: %@", url);
                            } else {
                                NSLog(@"✅ Image downloaded: %@", filePath);
                            }

                            // Continue with next task
                            if (next) next();
                        }];
                    }];
                }
            }

            // ------------------------------
            // Start recursive execution
            // ------------------------------
            __block NSInteger currentIndex = 0;
            NSInteger totalTasks = tasks.count;

            __block void (^runNextTask)(void);
            runNextTask = ^{
                if (currentIndex >= totalTasks) {
                    [self saveLastOfflineSyncDate];
                    [self saveOfflineDownloadStatusToFile];
                    if (completion) completion(YES);
                    runNextTask = nil;
                    return;
                }

                void (^task)(void (^)(void)) = tasks[currentIndex];
                currentIndex++;

                if (progress) progress(currentIndex, totalTasks);

                dispatch_async(dispatch_get_main_queue(), ^{
                    task(runNextTask);
                });
            };

            runNextTask();
        }];
        return;
    }

    // If no selected units, just run tasks 1–6
    __block NSInteger currentIndex = 0;
    NSInteger totalTasks = tasks.count;

    __block void (^runNextTask)(void);
    runNextTask = ^{
        if (currentIndex >= totalTasks) {
            [self saveLastOfflineSyncDate];
            [self saveOfflineDownloadStatusToFile];
            if (completion) completion(YES);
            runNextTask = nil;
            return;
        }

        void (^task)(void (^)(void)) = tasks[currentIndex];
        currentIndex++;

        if (progress) progress(currentIndex, totalTasks);

        dispatch_async(dispatch_get_main_queue(), ^{
            task(runNextTask);
        });
    };
    runNextTask();
}

#pragma mark - Image Download Helpers
- (NSArray *)buildImageSyncPayloadFromUnits:(NSArray *)units {
    NSMutableDictionary<NSString *, NSMutableArray *> *grouped = @{}.mutableCopy;

    for (NSDictionary *unit in units) {
        NSString *projectId = unit[@"project_id"];
        NSString *planId = unit[@"plan_id"];
        if (!projectId || !planId) continue;

        if (!grouped[projectId]) grouped[projectId] = [NSMutableArray array];
        [grouped[projectId] addObject:planId];
    }

    NSMutableArray *result = [NSMutableArray array];
    for (NSString *projectId in grouped) {
        [result addObject:@{ @"project_id": projectId, @"plan_id": grouped[projectId] }];
    }

    return result;
}

- (void)fetchOfflineImageListForSelectedUnits:(NSArray *)units
                                   completion:(void (^)(NSArray<NSURL *> *, NSError *))completion {

    NSArray *payload = [self buildImageSyncPayloadFromUnits:units];
    if (payload.count == 0) {
        completion(@[], nil);
        return;
    }

    [APIClient requestArrayImage:payload completion:^(BOOL success, NSDictionary *res, NSError *error) {
        if (!success) {
            completion(nil, error);
            return;
        }

        NSMutableOrderedSet<NSURL *> *urls = [NSMutableOrderedSet orderedSet];
        NSDictionary *fileImage = res[@"Data"][@"file_image"];

        for (NSDictionary *img in fileImage[@"plan_image"]) {
            if (img[@"link"]) [urls addObject:[NSURL URLWithString:img[@"link"]]];
        }
        for (NSDictionary *img in fileImage[@"issue_image"]) {
            if (img[@"link"]) [urls addObject:[NSURL URLWithString:img[@"link"]]];
        }

        for (NSURL *url in urls) {
            NSLog(@"URL for image downloads: %@", url.absoluteString);
        }
        completion(urls.array, nil);
    }];
}

- (NSArray *)buildOfflineUnitPayloadForCurrentConfig {
    NSString *projectId = [APIConfig projectId];
    NSString *planId = [APIConfig drawingPlanId];

    if (!projectId || !planId) {
        NSLog(@"⚠️ Missing projectId or planId for image sync");
        return @[];
    }

    NSArray *result = @[
        @{
            @"project_id": projectId,
            @"plan_id": @[planId]   // API expects array
        }
    ];

    NSLog(@"🔄 Image sync payload (single unit): %@", result);
    return result;
}

#pragma mark - Safe Sequential API Helper

- (void)safeSequentialTask:(NSString *)taskKey
                     block:(void (^)(void (^next)(void), void (^success)(BOOL)))block
                     next:(void (^)(void))next {

    __weak typeof(self) weakSelf = self;

    // Safe next ensures we never get stuck
    void (^safeNext)(void) = ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            if (next) next();
        });
    };

    // Success logger
    void (^logSuccess)(BOOL) = ^(BOOL success){
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) return;

        NSMutableDictionary *status =
            [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy];
        if (!status) status = [NSMutableDictionary dictionary];

        status[taskKey] = success ? @"SUCCESS" : @"FAILED";
        [LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
    };

    // Execute the block with safeNext and success callback
    block(safeNext, logSuccess);
}

- (void)saveLastOfflineSyncDate {
    NSDate *now = [NSDate date];
    NSTimeInterval timestamp = [now timeIntervalSince1970];

    [LocalStorage saveObject:@(timestamp)
                      forKey:kLastOfflineSyncDateKey];
}

#pragma mark - Pending Items Helpers

- (BOOL)isAPISuccess:(NSDictionary *)res error:(NSError *)err {
    if (err || ![res isKindOfClass:[NSDictionary class]]) {
        return NO;
    }

    NSDictionary *appData = res[@"AppData"];
    if (![appData isKindOfClass:[NSDictionary class]]) {
        return NO;
    }

    NSString *status = appData[@"status"];
    return [status isKindOfClass:[NSString class]] &&
           [[status lowercaseString] isEqualToString:@"success"];
}

- (void)logAddIssueDebugWithItem:(NSDictionary *)item
                          images:(NSArray<UIImage *> *)images
                             res:(NSDictionary *)res
                            err:(NSError *)err {

    NSLog(@"\n🚀 [ADD_ISSUE] ================================");
    NSLog(@"📤 Payload: %@", item[@"payload"]);

    NSLog(@"📸 Images count: %lu", (unsigned long)images.count);

    NSMutableArray *imageSizes = [NSMutableArray array];
    for (UIImage *img in images) {
        CGSize size = img.size;
        [imageSizes addObject:
         [NSString stringWithFormat:@"%.0fx%.0f", size.width, size.height]];
    }
    NSLog(@"📸 Image sizes: %@", imageSizes);

    if (err) {
        NSLog(@"❌ API Error: %@", err);
    } else {
        NSLog(@"📥 API Response: %@", res);
    }

    NSDictionary *appData = res[@"AppData"];
    NSLog(@"📥 AppData: %@", appData);

    NSLog(@"🏁 Status evaluated as: %@",
          [self isAPISuccess:res error:err] ? @"SUCCESS" : @"FAILED");

    NSLog(@"============================================\n");
}

- (void)saveOfflineDownloadStatusToFile {
    NSMutableDictionary *status = [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
    
    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:status options:NSJSONWritingPrettyPrinted error:&error];
    
    if (!jsonData) {
        NSLog(@"⚠️ Failed to serialize offline download status: %@", error);
        return;
    }
    
    NSString *documentsDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [documentsDir stringByAppendingPathComponent:@"OfflineDownloadStatus.txt"];
    
    BOOL success = [jsonData writeToFile:filePath options:NSDataWritingAtomic error:&error];
    if (success) {
        NSLog(@"✅ Offline download status saved to file: %@", filePath);
    } else {
        NSLog(@"⚠️ Failed to write offline download status to file: %@", error);
    }
}

- (NSString *)offlineLogFilePath {
    NSString *docsDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [docsDir stringByAppendingPathComponent:@"OfflineSyncLog.txt"];
    NSLog(@"📄 Offline log file path: %@", filePath); // <-- added log
    return filePath;
}

- (void)appendOfflineLog:(NSString *)text {
    NSString *filePath = [self offlineLogFilePath];
    NSFileManager *fm = [NSFileManager defaultManager];

    if (![fm fileExistsAtPath:filePath]) {
        [text writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"🆕 Created offline log file at path: %@", filePath); // log when file created
    } else {
        NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
        [handle seekToEndOfFile];
        [handle writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
        [handle closeFile];
        NSLog(@"✏️ Appended to offline log file: %@", filePath); // log when appending
    }
}

@end
