"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeFeedWriter = exports.makeFeedReader = exports.downloadFeedUpdate = exports.getFeedUpdateChunkReference = exports.updateFeed = exports.findNextIndex = void 0;
const hash_1 = require("../utils/hash");
const serialize_1 = require("../chunk/serialize");
const feed_1 = require("../modules/feed");
const soc_1 = require("../chunk/soc");
const types_1 = require("../types");
const bytes_1 = require("../utils/bytes");
const error_1 = require("../utils/error");
const hex_1 = require("../utils/hex");
const uint64_1 = require("../utils/uint64");
const chunkAPI = __importStar(require("../modules/chunk"));
const eth_1 = require("../utils/eth");
const type_1 = require("../utils/type");
const identifier_1 = require("./identifier");
const reference_1 = require("../utils/reference");
const TIMESTAMP_PAYLOAD_OFFSET = 0;
const TIMESTAMP_PAYLOAD_SIZE = 8;
const REFERENCE_PAYLOAD_OFFSET = TIMESTAMP_PAYLOAD_SIZE;
function findNextIndex(ky, owner, topic, options) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const feedUpdate = yield (0, feed_1.fetchLatestFeedUpdate)(ky, owner, topic, options);
            return (0, hex_1.makeHexString)(feedUpdate.feedIndexNext, types_1.FEED_INDEX_HEX_LENGTH);
        }
        catch (e) {
            if (e instanceof error_1.BeeResponseError && e.status === 404) {
                return (0, hex_1.bytesToHex)((0, bytes_1.makeBytes)(8));
            }
            throw e;
        }
    });
}
exports.findNextIndex = findNextIndex;
function updateFeed(ky, signer, topic, reference, postageBatchId, options, index = 'latest') {
    var _a;
    return __awaiter(this, void 0, void 0, function* () {
        const ownerHex = (0, eth_1.makeHexEthAddress)(signer.address);
        const nextIndex = index === 'latest' ? yield findNextIndex(ky, ownerHex, topic, options) : index;
        const identifier = (0, identifier_1.makeFeedIdentifier)(topic, nextIndex);
        const at = (_a = options === null || options === void 0 ? void 0 : options.at) !== null && _a !== void 0 ? _a : Date.now() / 1000.0;
        const timestamp = (0, uint64_1.writeUint64BigEndian)(at);
        const payloadBytes = (0, serialize_1.serializeBytes)(timestamp, reference);
        return (0, soc_1.uploadSingleOwnerChunkData)(ky, signer, postageBatchId, identifier, payloadBytes, options);
    });
}
exports.updateFeed = updateFeed;
function getFeedUpdateChunkReference(owner, topic, index) {
    const identifier = (0, identifier_1.makeFeedIdentifier)(topic, index);
    return (0, hash_1.keccak256Hash)(identifier, owner);
}
exports.getFeedUpdateChunkReference = getFeedUpdateChunkReference;
function downloadFeedUpdate(ky, owner, topic, index) {
    return __awaiter(this, void 0, void 0, function* () {
        const address = getFeedUpdateChunkReference(owner, topic, index);
        const addressHex = (0, hex_1.bytesToHex)(address);
        const data = yield chunkAPI.download(ky, addressHex);
        const soc = (0, soc_1.makeSingleOwnerChunkFromData)(data, address);
        const payload = soc.payload();
        const timestampBytes = (0, bytes_1.bytesAtOffset)(payload, TIMESTAMP_PAYLOAD_OFFSET, TIMESTAMP_PAYLOAD_SIZE);
        const timestamp = (0, uint64_1.readUint64BigEndian)(timestampBytes);
        const reference = (0, reference_1.makeBytesReference)(payload, REFERENCE_PAYLOAD_OFFSET);
        return {
            timestamp,
            reference,
        };
    });
}
exports.downloadFeedUpdate = downloadFeedUpdate;
function makeFeedReader(ky, type, topic, owner) {
    return {
        type,
        owner,
        topic,
        download(options) {
            return __awaiter(this, void 0, void 0, function* () {
                if (!(options === null || options === void 0 ? void 0 : options.index)) {
                    return (0, feed_1.fetchLatestFeedUpdate)(ky, owner, topic, Object.assign(Object.assign({}, options), { type }));
                }
                const update = yield downloadFeedUpdate(ky, (0, hex_1.hexToBytes)(owner), topic, options.index);
                return {
                    reference: (0, hex_1.bytesToHex)(update.reference),
                    feedIndex: options.index,
                    feedIndexNext: '',
                };
            });
        },
    };
}
exports.makeFeedReader = makeFeedReader;
function makeFeedWriter(ky, type, topic, signer) {
    const upload = (postageBatchId, reference, options) => __awaiter(this, void 0, void 0, function* () {
        (0, type_1.assertAddress)(postageBatchId);
        const canonicalReference = (0, reference_1.makeBytesReference)(reference);
        return updateFeed(ky, signer, topic, canonicalReference, postageBatchId, Object.assign(Object.assign({}, options), { type }));
    });
    return Object.assign(Object.assign({}, makeFeedReader(ky, type, topic, (0, eth_1.makeHexEthAddress)(signer.address))), { upload });
}
exports.makeFeedWriter = makeFeedWriter;
