"use strict";
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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Network = void 0;
const threads_crypto_1 = require("@textile/threads-crypto");
const loglevel_1 = __importDefault(require("loglevel"));
const threads_core_1 = require("@textile/threads-core");
const threads_encoding_1 = require("@textile/threads-encoding");
const store_1 = require("./store");
const logger = loglevel_1.default.getLogger('network');
const ed25519 = threads_crypto_1.keys.supportedKeys.ed25519;
/**
 * Network is the Network interface for Thread orchestration.
 */
class Network {
    /**
     * Create a new network Network.
     * @param store The store to use for caching keys and log information.
     * @param client A client connected to a remote network peer.
     * @param identity Identity represents an entity with a public key capable of signing a message.
     * @note If an identity is not provided, the a random PKI identity is used. This might not be what you want!
     * It is not easy/possible to migrate identities after the fact. Please supply an identity argument if
     * you wish to persist/retrieve user data later.
     */
    constructor(store, client, identity) {
        this.client = client;
        this.identity = identity;
        this.store = store instanceof store_1.LogStore ? store : store_1.LogStore.fromDatastore(store);
    }
    /**
     * Obtain a token for interacting with the remote network API. Will attempt to return a cached token if available.
     * @param identity The generic identity to use for signing and validation. Will default to the  identity specified
     * at construction if available, otherwise a new Identity is required here.
     */
    getToken(identity = this.identity) {
        return __awaiter(this, void 0, void 0, function* () {
            const existing = this.client.context.get('authorization');
            if (existing !== undefined)
                return existing;
            if (identity === undefined)
                throw new Error('Identity required.');
            this.identity = identity;
            return this.client.getToken(identity);
        });
    }
    /**
     * getHostID returns the network's (remote) host peer ID.
     */
    getHostID() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.client.getHostID();
        });
    }
    /**
     * createThread with id.
     * This will overwrite any existing thread information.
     * @param id The Thread id.
     * @param opts The set of keys to use when creating the Thread. All keys are "optional", though if no replicator key
     * is provided, one will be created. Similarly, if no log key is provided, then the "host" Peer ID will be used.
     * If no ReadKey is provided, the network will be unable to write records (but it may be able to return records).
     */
    createThread(id, opts) {
        return __awaiter(this, void 0, void 0, function* () {
            const logInfo = yield this.deriveLogKeys(opts === null || opts === void 0 ? void 0 : opts.logKey);
            // Don't send along readKey, or log's privKey information
            const threadKey = (opts === null || opts === void 0 ? void 0 : opts.threadKey) || threads_core_1.ThreadKey.fromRandom();
            const newOpts = {
                threadKey: new threads_core_1.ThreadKey(threadKey.service),
                logKey: logInfo.pubKey,
            };
            const info = yield this.client.createThread(id, newOpts);
            // Now we want to store or create read key
            info.key = new threads_core_1.ThreadKey(threadKey.service, Buffer.from(threadKey.read || threads_crypto_1.randomBytes(32)));
            logger.debug('caching thread + log information');
            yield this.store.addThread(info);
            yield this.store.addLog(id, logInfo);
            return info;
        });
    }
    /**
     * addThread from a multiaddress.
     * @param addr The Thread multiaddress.
     * @param opts The set of keys to use when adding the Thread.
     */
    addThread(addr, opts) {
        return __awaiter(this, void 0, void 0, function* () {
            const logInfo = yield this.deriveLogKeys(opts === null || opts === void 0 ? void 0 : opts.logKey);
            // Don't send along readKey, or log's privKey information
            const threadKey = opts === null || opts === void 0 ? void 0 : opts.threadKey;
            if (threadKey === undefined)
                throw new Error('Missing Thread key(s)');
            const newOpts = {
                threadKey: new threads_core_1.ThreadKey(threadKey.service),
                logKey: logInfo.pubKey,
            };
            const info = yield this.client.addThread(addr, newOpts);
            // Now we want to store full key information
            info.key = threadKey;
            logger.debug('caching thread + log information');
            yield this.store.addThread(info);
            yield this.store.addLog(info.id, logInfo);
            return info;
        });
    }
    /**
     * getThread with id.
     * @param id The Thread ID.
     */
    getThread(id) {
        return __awaiter(this, void 0, void 0, function* () {
            const info = yield this.client.getThread(id);
            // Merge local thread info with remote thread info
            const local = yield this.store.threadInfo(id);
            return Object.assign(Object.assign({}, info), local);
        });
    }
    /**
     * pullThread for new records.
     * Logs owned by this host are traversed on the (possibly remote) network client. Remotely addressed logs are pulled
     * from the network on the (possible remote) client and forwarded to this peer.
     * @param id The Thread ID.
     */
    pullThread(id) {
        return __awaiter(this, void 0, void 0, function* () {
            logger.debug(`pulling thread ${id.toString()}`);
            // @note: Not need to worry about safety here, the remote peer will handle that for us.
            return this.client.pullThread(id);
        });
    }
    /**
     * deleteThread with id.
     * @param id The Thread ID.
     */
    deleteThread(id) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.client.deleteThread(id);
            return this.store.deleteThread(id);
        });
    }
    /**
     * addReplicator to a thread.
     * @param id The Thread ID.
     * @param addr The multiaddress of the replicator peer.
     */
    addReplicator(id, addr) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.client.addReplicator(id, addr);
        });
    }
    /**
     * createRecord with body.
     * @param id The Thread ID.
     * @param body The body to add as content.
     */
    createRecord(id, body) {
        var _a, _b, _c;
        return __awaiter(this, void 0, void 0, function* () {
            const block = threads_core_1.Block.encoder(body, 'dag-cbor');
            const info = yield this.getThread(id);
            // Get (or create a new set of) log keys
            const logInfo = yield this.getOwnLog(info, true);
            if (info.key === undefined)
                throw new Error('Missing key info.');
            if (info.key.read === undefined)
                throw new Error('Missing network key.');
            const event = yield threads_encoding_1.createEvent(block, info.key.read);
            if (!logInfo.privKey)
                throw new Error('Missing private key.');
            // If we have head information for this log, use head CID
            const prev = logInfo.head;
            // Use supplied identity if available, otherwise, default to log private key
            // Using log private key assumes the log owner is also the identity owner, which might not always be the case.
            // In most cases, there _will_ be an available identity because it is required for `getToken`.
            const pubKey = (_c = (_b = (_a = this.identity) === null || _a === void 0 ? void 0 : _a.public) !== null && _b !== void 0 ? _b : logInfo.pubKey) !== null && _c !== void 0 ? _c : logInfo.privKey.public;
            const record = yield threads_encoding_1.createRecord(event, {
                privKey: logInfo.privKey,
                servKey: info.key.service,
                prev,
                pubKey,
            });
            yield this.client.addRecord(id, logInfo.id, record);
            const res = {
                record,
                threadID: id,
                logID: logInfo.id,
            };
            return res;
        });
    }
    /**
     * addRecord to the given log.
     * @param id The Thread ID.
     * @param logID The Log ID.
     * @param rec The log record to add.
     */
    addRecord(id, logID, rec) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.client.addRecord(id, logID, rec);
        });
    }
    /**
     * getRecord returns the record at cid.
     * @param id The Thread ID.
     * @param rec The record's CID.
     */
    getRecord(id, rec) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.client.getRecord(id, rec);
        });
    }
    /**
     * subscribe to new record events in the given threads.
     * @param cb The callback to call on each new thread record.
     * @param threads The variadic set of threads to subscribe to.
     */
    subscribe(cb, threads = []) {
        return this.client.subscribe(cb, threads);
    }
    /**
     * deriveLogKeys returns a set of log keys from the given inputs.
     * @param key Optional public or private log key.
     */
    deriveLogKeys(key) {
        return __awaiter(this, void 0, void 0, function* () {
            let pubKey;
            let privKey;
            if (!key) {
                privKey = yield ed25519.generateKeyPair();
                pubKey = privKey.public;
            }
            else if (key.public) {
                privKey = key;
                pubKey = privKey.public;
            }
            else {
                pubKey = key;
            }
            const info = {
                id: yield threads_core_1.LogID.fromPublicKey(pubKey),
                privKey,
                pubKey,
            };
            return info;
        });
    }
    getOwnLog(info, create) {
        return __awaiter(this, void 0, void 0, function* () {
            const logs = info.logs || new Set();
            for (const log of logs.values()) {
                const local = yield this.store.logInfo(info.id, log.id);
                const merged = Object.assign(Object.assign({}, log), local);
                if (merged.privKey)
                    return merged;
            }
            if (create) {
                return this.deriveLogKeys();
            }
            return;
        });
    }
}
exports.Network = Network;
//# sourceMappingURL=network.js.map