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());
  });
};

import { bytesAtOffset, bytesEqual, flexBytesAtOffset } from "../utils/bytes.js";
import { bmtHash } from "./bmt.js";
import { recoverAddress, sign } from "./signer.js";
import { keccak256Hash } from "../utils/hash.js";
import { SPAN_SIZE } from "./span.js";
import { serializeBytes } from "./serialize.js";
import { BeeError } from "../utils/error.js";
import { makeContentAddressedChunk, MAX_PAYLOAD_SIZE, MIN_PAYLOAD_SIZE, assertValidChunkData } from "./cac.js";
import { bytesToHex } from "../utils/hex.js";
import * as socAPI from "../modules/soc.js";
import * as chunkAPI from "../modules/chunk.js";
import { assertAddress } from "../utils/type.js";
const IDENTIFIER_SIZE = 32;
const SIGNATURE_SIZE = 65;
const SOC_IDENTIFIER_OFFSET = 0;
const SOC_SIGNATURE_OFFSET = SOC_IDENTIFIER_OFFSET + IDENTIFIER_SIZE;
const SOC_SPAN_OFFSET = SOC_SIGNATURE_OFFSET + SIGNATURE_SIZE;
const SOC_PAYLOAD_OFFSET = SOC_SPAN_OFFSET + SPAN_SIZE;

function recoverChunkOwner(data) {
  const cacData = data.slice(SOC_SPAN_OFFSET);
  const chunkAddress = bmtHash(cacData);
  const signature = bytesAtOffset(data, SOC_SIGNATURE_OFFSET, SIGNATURE_SIZE);
  const identifier = bytesAtOffset(data, SOC_IDENTIFIER_OFFSET, IDENTIFIER_SIZE);
  const digest = keccak256Hash(identifier, chunkAddress);
  const ownerAddress = recoverAddress(signature, digest);
  return ownerAddress;
}
/**
 * Verifies if the data is a valid single owner chunk
 *
 * @param data    The chunk data
 * @param address The address of the single owner chunk
 *
 * @returns a single owner chunk or throws error
 */


export function makeSingleOwnerChunkFromData(data, address) {
  const ownerAddress = recoverChunkOwner(data);
  const identifier = bytesAtOffset(data, SOC_IDENTIFIER_OFFSET, IDENTIFIER_SIZE);
  const socAddress = keccak256Hash(identifier, ownerAddress);

  if (!bytesEqual(address, socAddress)) {
    throw new BeeError('SOC Data does not match given address!');
  }

  const signature = () => bytesAtOffset(data, SOC_SIGNATURE_OFFSET, SIGNATURE_SIZE);

  const span = () => bytesAtOffset(data, SOC_SPAN_OFFSET, SPAN_SIZE);

  const payload = () => flexBytesAtOffset(data, SOC_PAYLOAD_OFFSET, MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE);

  return {
    data,
    identifier: () => identifier,
    signature,
    span,
    payload,
    address: () => socAddress,
    owner: () => ownerAddress
  };
}
export function makeSOCAddress(identifier, address) {
  return keccak256Hash(identifier, address);
}
/**
 * Creates a single owner chunk object
 *
 * @param chunk       A chunk object used for the span and payload
 * @param identifier  The identifier of the chunk
 * @param signer      The singer interface for signing the chunk
 */

export function makeSingleOwnerChunk(chunk, identifier, signer) {
  return __awaiter(this, void 0, void 0, function* () {
    const chunkAddress = chunk.address();
    assertValidChunkData(chunk.data, chunkAddress);
    const digest = keccak256Hash(identifier, chunkAddress);
    const signature = yield sign(signer, digest);
    const data = serializeBytes(identifier, signature, chunk.span(), chunk.payload());
    const address = makeSOCAddress(identifier, signer.address);
    return {
      data,
      identifier: () => identifier,
      signature: () => signature,
      span: () => chunk.span(),
      payload: () => chunk.payload(),
      address: () => address,
      owner: () => signer.address
    };
  });
}
/**
 * Helper function to upload a chunk.
 *
 * It uses the Chunk API and calculates the address before uploading.
 *
 * @param ky              Ky instance
 * @param chunk           A chunk object
 * @param postageBatchId  Postage BatchId that will be assigned to uploaded data
 * @param options         Upload options
 */

export function uploadSingleOwnerChunk(ky, chunk, postageBatchId, options) {
  return __awaiter(this, void 0, void 0, function* () {
    const owner = bytesToHex(chunk.owner());
    const identifier = bytesToHex(chunk.identifier());
    const signature = bytesToHex(chunk.signature());
    const data = serializeBytes(chunk.span(), chunk.payload());
    return socAPI.upload(ky, owner, identifier, signature, data, postageBatchId, options);
  });
}
/**
 * Helper function to create and upload SOC.
 *
 * @param ky              Ky instance
 * @param signer          The singer interface for signing the chunk
 * @param postageBatchId
 * @param identifier      The identifier of the chunk
 * @param data            The chunk data
 * @param options
 */

export function uploadSingleOwnerChunkData(ky, signer, postageBatchId, identifier, data, options) {
  return __awaiter(this, void 0, void 0, function* () {
    assertAddress(postageBatchId);
    const cac = makeContentAddressedChunk(data);
    const soc = yield makeSingleOwnerChunk(cac, identifier, signer);
    return uploadSingleOwnerChunk(ky, soc, postageBatchId, options);
  });
}
/**
 * Helper function to download SOC.
 *
 * @param url           The url of the Bee service
 * @param ownerAddress  The singer interface for signing the chunk
 * @param identifier    The identifier of the chunk
 */

export function downloadSingleOwnerChunk(ky, ownerAddress, identifier) {
  return __awaiter(this, void 0, void 0, function* () {
    const address = makeSOCAddress(identifier, ownerAddress);
    const data = yield chunkAPI.download(ky, bytesToHex(address));
    return makeSingleOwnerChunkFromData(data, address);
  });
}