import {
  addAssets,
  Address,
  Assets,
  calculateMinLovelaceFromUTxO,
  Credential,
  credentialToAddress,
  Data,
  Datum,
  LucidEvolution,
  Network,
  OutputDatum,
  OutRef,
  paymentCredentialOf,
  ProtocolParameters,
  Script,
  ScriptHash,
  scriptHashToCredential,
  stakeCredentialOf,
  TxBuilder,
  UTxO,
} from '@lucid-evolution/lucid';
import { ScriptReference } from '../types/system-params';
import { match, P } from 'ts-pattern';

/**
 * Returns the inline datum.
 * Throws when the UTXO doesn't have an inline datum
 * (i.e. in case it has hash datum or no datum).
 */
export function getInlineDatumOrThrow(utxo: UTxO): Datum {
  if (utxo.datum != null) {
    return utxo.datum;
  }

  throw new Error(
    'Expected an inline datum for OutRef: ' +
      JSON.stringify({
        txHash: utxo.txHash,
        outputIndex: utxo.outputIndex,
      } as OutRef),
  );
}

export async function addrDetails(
  lucid: LucidEvolution,
): Promise<[Credential, Credential | undefined]> {
  const addr = await lucid.wallet().address();

  let stakeCredential = undefined;
  try {
    stakeCredential = stakeCredentialOf(addr);
  } catch (_) {
    // No stake credential
  }

  return [paymentCredentialOf(addr), stakeCredential];
}

export function createScriptAddress(
  network: Network,
  scriptHash: ScriptHash,
  stakeCredential?: Credential,
): Address {
  return credentialToAddress(
    network,
    scriptHashToCredential(scriptHash),
    stakeCredential,
  );
}

export async function scriptRef(
  ref: ScriptReference,
  lucid: LucidEvolution,
): Promise<UTxO> {
  const utxos = await lucid.utxosByOutRef([
    { txHash: ref.input.transactionId, outputIndex: ref.input.index },
  ]);
  if (utxos.length === 0) throw Error('Unable to locate script ref.');
  return utxos[0];
}

/**
 * Complete, sign with wallet, submit a tx and wait for confirmation.
 */
export async function submitTx(
  lucid: LucidEvolution,
  tx: TxBuilder,
): Promise<void> {
  const txHash = await tx
    .complete()
    .then((t) => t.sign.withWallet().complete())
    .then((t) => t.submit());
  await lucid.awaitTx(txHash);
}

export function balance(utxos: UTxO[]): Assets {
  return utxos.reduce((acc, utxo) => addAssets(acc, utxo.assets), {});
}

/**
 * Estimate the min lovelace for a UTXO so it can be created.
 */
export function estimateUtxoMinLovelace(
  protocolParameters: ProtocolParameters,
  destinationAddr: string,
  assets: Assets,
  destinationDatum: OutputDatum | undefined = undefined,
  scriptRef: Script | undefined = undefined,
): bigint {
  return calculateMinLovelaceFromUTxO(protocolParameters.coinsPerUtxoByte, {
    address: destinationAddr,
    datumHash: match(destinationDatum)
      .with({ kind: 'hash', value: P.select() }, (dHash) => dHash)
      .otherwise(() => null),
    datum: match(destinationDatum)
      .with({ kind: 'inline', value: P.select() }, (data) => Data.to(data))
      .otherwise(() => null),
    assets: assets,
    // Use dummy tx hash and out idx
    txHash: '0000000000000000000000000000000000000000000000000000000000000000',
    outputIndex: 0,
    scriptRef: scriptRef ?? null,
  } satisfies UTxO);
}
