import { getScriptTypeFromAddress } from "../common";
import { BatchMode, InscriptionFile, ScriptType } from "../types";
import { estimateTxFee } from "./baseTx";

export function estimateInscriptionFee(
  fileSize: number,
  count: number,
  feeRate: number,
  envelopeSize = 100,
  batchMode: BatchMode = null,
): number {
  const fileFee = Math.ceil((feeRate * (fileSize + envelopeSize)) / 4);
  const commitCount = batchMode ? 1 : count;
  let inputTypes = Array(2).fill(ScriptType.P2TR);
  let outputTypes = Array(3).fill(ScriptType.P2TR);
  const commitFee =
    estimateTxFee(inputTypes, outputTypes, feeRate) * commitCount;

  inputTypes = Array(1).fill(ScriptType.P2TR);
  outputTypes = Array(1).fill(ScriptType.P2TR);
  let revealFee = estimateTxFee(inputTypes, outputTypes, feeRate) * count;

  if (batchMode === "separate-outputs") {
    outputTypes = Array(count).fill(ScriptType.P2TR);
    revealFee = estimateTxFee(inputTypes, outputTypes, feeRate);
  } else if (batchMode === "shared-output") {
    revealFee = estimateTxFee(inputTypes, outputTypes, feeRate);
  }

  return commitFee + revealFee + fileFee;
}

export function estimateDirectInscriptionFee(
  feeRate: number,
  receiverAddress: string,
  files: InscriptionFile[] = [],
  delegates = 0,
  parentCount = 0,
  delegatesAverageSize = 33,
  parentAverageSize = 33,
): number {
  const isFiles = files?.length > 0;
  const isDelegates = !!delegates;
  if ((isFiles && isDelegates) || (!isFiles && !isDelegates)) {
    throw new Error("Use either 'files' or 'delegates'");
  }
  const receiverScriptType = getScriptTypeFromAddress(receiverAddress);

  let envelopeSize = 0;
  if (isFiles) {
    for (let i = 0; i < files.length; i++) {
      envelopeSize += getEnvelopeSize(
        files[i],
        delegates,
        parentCount,
        delegatesAverageSize,
        parentAverageSize,
        parentCount > 0 || i !== 0, // only false on first iter
      );
    }
  }
  if (isDelegates) {
    for (let i = 0; i < delegates; i++) {
      envelopeSize += getEnvelopeSize(
        null,
        delegates,
        parentCount,
        delegatesAverageSize,
        parentAverageSize,
        parentCount > 0 || i !== 0, // only false on first iter
      );
    }
  }
  const inscriptionFee = Math.ceil(((65 + envelopeSize) / 4) * feeRate);

  const outputCount = (files?.length || 0) + parentCount + 1 + delegates; // add fee output
  const inputTypes = [
    ScriptType.P2TR,
    ...Array(parentCount).fill(ScriptType.P2TR_INSCRIPTION),
  ];
  const outputTypes = Array(outputCount - 1).fill(receiverScriptType);
  outputTypes.push(ScriptType.P2WPKH);

  const revealFee = estimateTxFee(inputTypes, outputTypes, feeRate);
  return Math.ceil(revealFee + inscriptionFee);
}

export function getEnvelopeSize(
  file: InscriptionFile | null,
  delegates = 0,
  parentCount = 0,
  delegatesAverageSize = 33,
  parentAverageSize = 33,
  addPointer = true,
): number {
  if (!delegates && !file) {
    throw new Error("must provide delegates or files");
  }
  const parentFieldsSize = parentCount * (1 + 1 + parentAverageSize); // 1 (tag) + 1 (push) + 33/32 (inscription ID)
  const pointerFieldSize = addPointer ? 4 : 0;
  // delegates
  if (!file) {
    const envelopeSize =
      2 + // OP_0 OP_IF
      4 + // "ord" push
      parentFieldsSize +
      pointerFieldSize +
      1 + // tag
      1 + // push
      delegatesAverageSize + // delegate inscription ID
      1; // OP_ENDIF
    return envelopeSize;
  }

  const contentTypeSize = file.mimeType?.length || 0;
  const contentTypeField = contentTypeSize > 0 ? 1 + 1 + contentTypeSize : 0;

  const numFileChunks = Math.ceil(file.size / 520);
  const lastChunkSize = file.size % 520 || 520;
  let filePushOps = (numFileChunks - 1) * 3;
  filePushOps +=
    lastChunkSize <= 75
      ? 1 // OP_PUSHBYTES_X (1 byte)
      : lastChunkSize <= 255
        ? 2 // OP_PUSHDATA1 (2 bytes)
        : 3; // OP_PUSHDATA2 (3 bytes)

  const metadataSize = file.metadataSize || 0;
  const numMetaChunks = Math.ceil(metadataSize / 520);
  const lastMetaChunkSize = metadataSize % 520 || 520;
  let metadataPushOps = (numMetaChunks - 1) * 3;
  metadataPushOps +=
    lastMetaChunkSize <= 75
      ? 1 // OP_PUSHBYTES_X (1 byte)
      : lastMetaChunkSize <= 255
        ? 2 // OP_PUSHDATA1 (2 bytes)
        : 3; // OP_PUSHDATA2 (3 bytes)

  const envelopeSize =
    2 + // OP_FALSE OP_IF
    4 + // "ord" push
    parentFieldsSize +
    numMetaChunks +
    metadataPushOps +
    metadataSize +
    contentTypeField +
    pointerFieldSize +
    1 + // OP_PUSH 0
    filePushOps +
    file.size + // file content
    1; // OP_ENDIF

  return envelopeSize;
}
