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

static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";

static NSString * const kSelectedOfflineUnitsKey = @"SELECTED_OFFLINE_UNITS";

static NSString * const kSyncHistoryKey = @"SYNC_HISTORY_QUEUE";

static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";

@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 {

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

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

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

- (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 {
    
    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 fields (OG-style)
    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];

    [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;

    void (^processNext)(void);
    processNext = ^{
        NSMutableArray *currentQueue = [weakSelf safeLoadQueue];

        if (currentQueue.count == 0) {
            if (completion) completion(YES);
            return;
        }

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

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

        [weakSelf processItem:item completion:^(BOOL success) {

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

            NSMutableArray *history = [weakSelf safeLoadHistory];
            [history insertObject:historyItem atIndex:0];
            [weakSelf 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 processed item
            NSMutableArray *updatedQueue = [weakSelf safeLoadQueue];
            if (updatedQueue.count > 0) {
                [updatedQueue removeObjectAtIndex:0];
                [weakSelf saveQueue:updatedQueue];
            }

            // 🔁 Continue
            dispatch_async(dispatch_get_main_queue(), ^{
                processNext();
            });
        }];
    };

    processNext();
}

- (void)processItem:(NSDictionary *)item
         completion:(void (^)(BOOL success))completion {

    NSString *type = item[@"type"] ?: @"";
    
    // Load files
    NSArray<UIImage *> *images = [self loadImagesFromPaths:item[@"imagePaths"]];
    NSURL *mediaURL = item[@"mediaPath"] ? [NSURL fileURLWithPath:item[@"mediaPath"]] : nil;
  
    // Define per-item timeout
    NSTimeInterval timeout = 60.0; // seconds
    __block BOOL completed = NO;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (!completed) {
            NSLog(@"⚠️ [OfflineSync] Item timed out: %@, marking as FAILED", item[@"type"]);
            completed = YES;
            completion(NO);
        }
    });
  
    void (^finalize)(BOOL) = ^(BOOL success){
        if (!completed) {
            completed = YES;
            completion(success);
        }
    };

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

        if (images.count == 0 && !mediaURL) {
            // Simple ADD_INFO without attachments
            [APIClient submitAddInfo:item[@"issue_id"]
                             remarks:item[@"remarks"]
                           completion:^(NSDictionary *res, NSError *err) {
                finalize(err == nil);
            }];
        } else {
            // ADD_INFO with images/media
            [APIClient uploadAddInfo:item[@"issue_id"]
                             remarks:item[@"remarks"]
                              images:images
                            mediaURL:mediaURL
                           mediaType:item[@"mediaType"]
                          completion:^(NSDictionary *res, NSError *err) {
                finalize(err == nil);
            }];
        }
        return;
    }

    else if ([type isEqualToString:@"ADD_ISSUE"]) {
        [APIClient submitAddIssue:item[@"payload"]
                       images:images
                    completion:^(NSDictionary *res, NSError *err){
            finalize(err == nil);
        }];
        return;
    }

    else if ([type isEqualToString:@"UPDATE_ISSUE"]) {
        [APIClient submitUpdateIssue:item[@"payload"]
                        images:images
                     completion:^(NSDictionary *res, NSError *err){
            finalize(err == nil);
        }];
        return;
    }

    else {
        NSLog(@"⚠️ [OfflineSync] Unknown offline type: %@", type);
        finalize(NO);
        return;
    }
}

#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 {

    NSMutableArray *queue = [self safeLoadQueue];
    [queue addObject:historyItem];
    [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];
    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;
    }
    
    NSString *projectId = @"8";
    NSString *drawingPlanId = @"135";

    NSArray *selectedUnits = [LocalStorage loadArrayForKey:kSelectedOfflineUnitsKey];
    if (!selectedUnits) selectedUnits = @[];

    // 1️⃣ Build the sequential task queue
    NSMutableArray<void (^)(void (^next)(void))> *tasks = [NSMutableArray array];

    // 2️⃣ Add 6 independent API calls as tasks
    [tasks addObject:^(void (^next)(void)) {
        NSString *key = [NSString stringWithFormat:@"ISSUES_%@", drawingPlanId];
        [APIClient requestIssues:^(NSDictionary *res, NSError *err) {
            if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
            next();
        }];
    }];

    [tasks addObject:^(void (^next)(void)) {
        NSString *key = [NSString stringWithFormat:@"DASHBOARD_INFO_%@", drawingPlanId];
        [APIClient requestDashboardInfo:^(NSDictionary *res, NSError *err) {
            if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
            next();
        }];
    }];

    [tasks addObject:^(void (^next)(void)) {
        NSString *key = [NSString stringWithFormat:@"PLAN_UNIT_%@", drawingPlanId];
        [APIClient requestGetUnitPlan:^(NSDictionary *res, NSError *err) {
            if (res) {
                id dataArray = res[@"Data"];
                NSDictionary *objData = @{};
                
                if ([dataArray isKindOfClass:[NSArray class]] && [dataArray count] > 0) {
                    id firstItem = dataArray[0];
                    if ([firstItem isKindOfClass:[NSDictionary class]]) {
                        objData = firstItem;
                    }
                }
                [LocalStorage saveDictionary:objData forKey:key];
            }
            next();
        }];
    }];

    [tasks addObject:^(void (^next)(void)) {
        NSString *key = [NSString stringWithFormat:@"SETTINGS_%@", projectId];
        [APIClient requestGeneralSettings:^(NSDictionary *res, NSError *err) {
            if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
            next();
        }];
    }];

    [tasks addObject:^(void (^next)(void)) {
        NSString *key = [NSString stringWithFormat:@"COMMONAREA_%@", projectId];
        [APIClient requestCommonArea:^(NSDictionary *res, NSError *err) {
            if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
            next();
        }];
    }];

    [tasks addObject:^(void (^next)(void)) {

        [APIClient requestDefectMatrix:^(NSDictionary *res, NSError *err) {

            if (!res || err) {
                next();
                return;
            }

            NSDictionary *data = res[@"Data"];
            if (![data isKindOfClass:[NSDictionary class]]) {
                next();
                return;
            }

            // 1️⃣ Shared lists
            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"] ?: @"";

            // 2️⃣ Defect matrix expansion
            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
                    };

                    // 3️⃣ Save per location_id
                    for (id locationId in locationIds) {

                        NSString *locationKey =
                            [NSString stringWithFormat:@"LOCATION_SETTINGS_%@", locationId];

                        [LocalStorage saveDictionary:settings forKey:locationKey];
                    }
                }
            }
            next();
        }];
    }];


    // 3️⃣ Add image download task as the last task
    [tasks addObject:^(void (^next)(void)) {

        if (selectedUnits.count == 0) {
            next();
            return;
        }

        [self fetchOfflineImageListForSelectedUnits:selectedUnits completion:^(NSArray<NSURL *> *urls, NSError *error) {

            if (error || urls.count == 0) {
                next();
                return;
            }

            [self downloadImagesSequentially:urls progress:^(NSInteger completedImages, NSInteger totalImages) {

                // Map image progress into overall progress
                NSInteger overallCompleted = 6 + completedImages;
                NSInteger overallTotal = 6 + urls.count;
                if (progress) progress(overallCompleted, overallTotal);

            } next:^{
                next();
            }];
        }];
    }];

    // 4️⃣ Sequential executor
    __block NSInteger currentTaskIndex = 0;

    void (^runNextTask)(void);
    runNextTask = ^{
        if (currentTaskIndex >= tasks.count) {
            [self saveLastOfflineSyncDate];
            if (completion) completion(YES);
            return;
        }

        void (^task)(void (^next)(void)) = tasks[currentTaskIndex];
        currentTaskIndex++;

        // Update progress for API tasks
        if (progress && currentTaskIndex <= 6) {
            NSInteger overallTotal = 6 + selectedUnits.count;
            progress(currentTaskIndex, overallTotal);
        }

        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:^(NSDictionary *res, NSError *err) {
        if (err) {
            completion(nil, err);
            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"]]];
        }

        completion(urls.array, nil);
    }];
}

- (void)downloadImagesSequentially:(NSArray<NSURL *> *)urls
                           progress:(void (^)(NSInteger completed, NSInteger total))progress
                               next:(void (^)(void))next {

    __block NSInteger index = 0;
    NSInteger total = urls.count;

    if (total == 0) {
        if (next) next();
        return;
    }

    __block void (^downloadNext)(void);
    __weak __block void (^weakDownloadNext)(void);

    downloadNext = ^{
        if (index >= total) {
            if (next) next();
            return;
        }

        NSURL *url = urls[index];
         [APIClient downloadAndCacheImage:url completion:^(BOOL success) {
             index++;
             if (progress) progress(index, total);
             if (weakDownloadNext) weakDownloadNext();
         }];
    };

    weakDownloadNext = downloadNext;
    downloadNext();
}

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

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


@end
