import { FetchHttpClient } from "@effect/platform";
import { Array as _Array, Effect, pipe, Schedule, Schema } from "effect";
import * as CoreAddress from "../../../Address.js";
import * as CoreAssets from "../../../Assets.js";
import * as Bytes from "../../../Bytes.js";
import * as PlutusData from "../../../Data.js";
import * as DatumHash from "../../../DatumHash.js";
import * as InlineDatum from "../../../InlineDatum.js";
import * as NativeScripts from "../../../NativeScripts.js";
import * as PlutusV1 from "../../../PlutusV1.js";
import * as PlutusV2 from "../../../PlutusV2.js";
import * as PlutusV3 from "../../../PlutusV3.js";
import * as PolicyId from "../../../PolicyId.js";
import * as PoolKeyHash from "../../../PoolKeyHash.js";
import * as Redeemer from "../../../Redeemer.js";
import * as Transaction from "../../../Transaction.js";
import * as TransactionHash from "../../../TransactionHash.js";
import * as AssetsUnit from "../../../Unit.js";
import * as CoreUTxO from "../../../UTxO.js";
import * as Provider from "../Provider.js";
import * as HttpUtils from "./HttpUtils.js";
import * as Kupo from "./Kupo.js";
import * as Ogmios from "./Ogmios.js";
const TIMEOUT = 10_000;
/**
 * Wrap errors into ProviderError
 */
const wrapError = operation => cause => Effect.fail(new Provider.ProviderError({
  message: `Kupmios ${operation} failed`,
  cause
}));
// Internal utility functions (not exported)
const toProtocolParameters = result => {
  return {
    minFeeA: result.minFeeCoefficient,
    minFeeB: result.minFeeConstant.ada.lovelace,
    maxTxSize: result.maxTransactionSize.bytes,
    maxValSize: result.maxValueSize.bytes,
    keyDeposit: BigInt(result.stakeCredentialDeposit.ada.lovelace),
    poolDeposit: BigInt(result.stakePoolDeposit.ada.lovelace),
    drepDeposit: BigInt(result.delegateRepresentativeDeposit.ada.lovelace),
    govActionDeposit: BigInt(result.governanceActionDeposit.ada.lovelace),
    priceMem: result.scriptExecutionPrices.memory[0] / result.scriptExecutionPrices.memory[1],
    priceStep: result.scriptExecutionPrices.cpu[0] / result.scriptExecutionPrices.cpu[1],
    maxTxExMem: BigInt(result.maxExecutionUnitsPerTransaction.memory),
    maxTxExSteps: BigInt(result.maxExecutionUnitsPerTransaction.cpu),
    coinsPerUtxoByte: BigInt(result.minUtxoDepositCoefficient),
    collateralPercentage: result.collateralPercentage,
    maxCollateralInputs: result.maxCollateralInputs,
    minFeeRefScriptCostPerByte: result.minFeeReferenceScripts.base,
    costModels: {
      PlutusV1: Object.fromEntries(result.plutusCostModels["plutus:v1"].map((value, index) => [index.toString(), value])),
      PlutusV2: Object.fromEntries(result.plutusCostModels["plutus:v2"].map((value, index) => [index.toString(), value])),
      PlutusV3: Object.fromEntries(result.plutusCostModels["plutus:v3"].map((value, index) => [index.toString(), value]))
    }
  };
};
const toAssets = value => {
  let assets = CoreAssets.fromLovelace(BigInt(value.coins));
  for (const unit of Object.keys(value.assets)) {
    const cleanUnit = unit.replace(".", "");
    // Parse policyId (first 56 chars) and assetName (rest)
    const policyIdHex = cleanUnit.slice(0, 56);
    const assetNameHex = cleanUnit.slice(56);
    assets = CoreAssets.addByHex(assets, policyIdHex, assetNameHex, BigInt(value.assets[unit]));
  }
  return assets;
};
const retrieveDatumEffect = (kupoUrl, kupoHeader) => (datum_type, datum_hash) => Effect.gen(function* () {
  if (datum_type === "inline" && datum_hash) {
    const pattern = `${kupoUrl}/datums/${datum_hash}`;
    const schema = Kupo.DatumSchema;
    return yield* pipe(HttpUtils.get(pattern, schema, kupoHeader), Effect.flatMap(Effect.fromNullable), Effect.map(result => {
      // Parse the datum hex string to PlutusData
      const data = PlutusData.fromCBORHex(result.datum);
      return new InlineDatum.InlineDatum({
        data
      });
    }), Effect.retry(Schedule.compose(Schedule.exponential(50), Schedule.recurs(5))), Effect.timeout(5_000), Effect.catchAll(wrapError("retrieveDatum")));
  } else if (datum_type === "hash" && datum_hash) {
    const hashBytes = Bytes.fromHex(datum_hash);
    return new DatumHash.DatumHash({
      hash: hashBytes
    });
  }
  return undefined;
});
const getScriptEffect = (kupoUrl, kupoHeader) => script_hash => Effect.gen(function* () {
  if (script_hash) {
    const pattern = `${kupoUrl}/scripts/${script_hash}`;
    const schema = Kupo.ScriptSchema;
    return yield* pipe(HttpUtils.get(pattern, schema, kupoHeader), Effect.flatMap(Effect.fromNullable), Effect.retry(Schedule.compose(Schedule.exponential(50), Schedule.recurs(5))), Effect.timeout(5_000), Effect.map(({
      language,
      script
    }) => {
      // Convert script hex to bytes - Kupo returns scripts already properly encoded
      const scriptBytes = Bytes.fromHex(script);
      // Create the proper Script type based on language
      switch (language) {
        case "native":
          {
            // Parse the native script from CBOR
            return NativeScripts.fromCBORBytes(scriptBytes);
          }
        case "plutus:v1":
          {
            // Kupo returns scripts in single CBOR-encoded format (59xxxx...)
            // which is the correct format for PlutusV1/V2/V3 bytes field
            return new PlutusV1.PlutusV1({
              bytes: scriptBytes
            });
          }
        case "plutus:v2":
          {
            return new PlutusV2.PlutusV2({
              bytes: scriptBytes
            });
          }
        case "plutus:v3":
          {
            return new PlutusV3.PlutusV3({
              bytes: scriptBytes
            });
          }
        default:
          throw new Error(`Unknown script language: ${language}`);
      }
    }), Effect.catchAll(wrapError("getScript")));
  } else return undefined;
});
const kupmiosUtxosToUtxos = (kupoURL, kupoHeader) => utxos => {
  const getDatum = retrieveDatumEffect(kupoURL, kupoHeader);
  const getScript = getScriptEffect(kupoURL, kupoHeader);
  return Effect.forEach(utxos, utxo => {
    return pipe(Effect.all([getDatum(utxo.datum_type, utxo.datum_hash), getScript(utxo.script_hash)]), Effect.map(([datumOption, scriptRef]) => {
      const transactionId = TransactionHash.fromHex(utxo.transaction_id);
      const address = CoreAddress.fromBech32(utxo.address);
      return new CoreUTxO.UTxO({
        transactionId,
        index: BigInt(utxo.output_index),
        address,
        assets: toAssets(utxo.value),
        datumOption,
        scriptRef
      });
    }));
  }, {
    concurrency: 10
  });
};
// Exported effect functions used by KupmiosProvider
export const getProtocolParametersEffect = /*#__PURE__*/Effect.fn("getProtocolParameters")(function* (ogmiosUrl, headers) {
  const data = {
    jsonrpc: "2.0",
    method: "queryLedgerState/protocolParameters",
    params: {},
    id: null
  };
  const schema = Ogmios.JSONRPCSchema(Ogmios.ProtocolParametersSchema);
  const {
    result
  } = yield* pipe(HttpUtils.postJson(ogmiosUrl, data, schema, headers?.ogmiosHeader), Effect.timeout(TIMEOUT), Effect.catchAll(wrapError("getProtocolParameters")), Effect.provide(FetchHttpClient.layer));
  return toProtocolParameters(result);
});
export const getUtxosEffect = (kupoUrl, headers) => Effect.fn("getUtxos")(function* (addressOrCredential) {
  let pattern;
  if (addressOrCredential instanceof CoreAddress.Address) {
    const addressStr = CoreAddress.toBech32(addressOrCredential);
    pattern = `${kupoUrl}/matches/${addressStr}?unspent`;
  } else {
    pattern = `${kupoUrl}/matches/${Bytes.toHex(addressOrCredential.hash)}/*?unspent`;
  }
  const toUtxos = kupmiosUtxosToUtxos(kupoUrl, headers?.kupoHeader);
  const schema = Schema.Array(Kupo.UTxOSchema);
  const utxos = yield* pipe(HttpUtils.get(pattern, schema, headers?.kupoHeader), Effect.flatMap(u => toUtxos(u)), Effect.timeout(TIMEOUT), Effect.catchAll(wrapError("getUtxos")), Effect.provide(FetchHttpClient.layer));
  return utxos;
});
export const getUtxoByUnitEffect = (kupoUrl, headers) => Effect.fn("getUtxoByUnit")(function* (unit) {
  const {
    assetName,
    policyId
  } = AssetsUnit.fromUnit(unit);
  const policyIdHex = PolicyId.toHex(policyId);
  const assetNameHex = assetName ? Bytes.toHex(assetName.bytes) : undefined;
  const pattern = `${kupoUrl}/matches/${policyIdHex}.${assetNameHex ? `${assetNameHex}` : "*"}?unspent`;
  const toUtxos = kupmiosUtxosToUtxos(kupoUrl, headers?.kupoHeader);
  const schema = Schema.Array(Kupo.UTxOSchema);
  const utxos = yield* pipe(HttpUtils.get(pattern, schema, headers?.kupoHeader), Effect.flatMap(u => toUtxos(u)), Effect.timeout(TIMEOUT), Effect.catchAll(wrapError("getUtxoByUnit")), Effect.provide(FetchHttpClient.layer));
  if (utxos.length > 1) {
    return yield* Effect.fail(new Provider.ProviderError({
      cause: new Error("Unit needs to be an NFT or only held by one address."),
      message: "Multiple UTxOs found for unit"
    }));
  }
  if (utxos.length === 0) {
    return yield* Effect.fail(new Provider.ProviderError({
      cause: new Error("No UTxO found for unit"),
      message: "UTxO not found"
    }));
  }
  return utxos[0];
});
export const getUtxosByOutRefEffect = (kupoUrl, headers) => Effect.fn("getUtxosByOutRef")(function* (inputs) {
  const queryHashes = [...new Set(inputs.map(input => TransactionHash.toHex(input.transactionId)))];
  const mkPattern = txHash => `${kupoUrl}/matches/*@${txHash}?unspent`;
  const schema = Schema.Array(Kupo.UTxOSchema);
  const toUtxos = kupmiosUtxosToUtxos(kupoUrl, headers?.kupoHeader);
  const program = Effect.forEach(queryHashes, txHash => pipe(HttpUtils.get(mkPattern(txHash), schema, headers?.kupoHeader), Effect.flatMap(u => toUtxos(u)), Effect.timeout(TIMEOUT), Effect.catchAll(wrapError("getUtxosByOutRef"))));
  const utxos = yield* pipe(program, Effect.provide(FetchHttpClient.layer));
  return _Array.flatten(utxos).filter(utxo => inputs.some(input => TransactionHash.toHex(utxo.transactionId) === TransactionHash.toHex(input.transactionId) && Number(utxo.index) === Number(input.index)));
});
export const submitTxEffect = (ogmiosUrl, headers) => Effect.fn("submitTx")(function* (tx) {
  const txCborHex = Transaction.toCBORHex(tx);
  const data = {
    jsonrpc: "2.0",
    method: "submitTransaction",
    params: {
      transaction: {
        cbor: txCborHex
      }
    },
    id: null
  };
  const schema = Ogmios.JSONRPCSchema(Schema.Struct({
    transaction: Schema.Struct({
      id: Schema.String
    })
  }));
  const {
    result
  } = yield* pipe(HttpUtils.postJson(ogmiosUrl, data, schema, headers?.ogmiosHeader), Effect.timeout(TIMEOUT), Effect.catchAll(cause => {
    // TODO: This is a workaround to extract meaningful error messages from Ogmios.
    // Ogmios returns errors with a `description` field, but we don't have a proper
    // error schema defined. The proper fix would be:
    // 1. Define an OgmiosError schema that captures the actual error response shape
    // 2. Use schema-validated error decoding instead of runtime duck-typing
    // 3. Apply consistent error extraction across all provider methods
    const errorMessage = cause instanceof Error ? cause.message : typeof cause === "object" && cause !== null && "description" in cause ? String(cause.description) : "Kupmios submitTx failed";
    return Effect.fail(new Provider.ProviderError({
      cause,
      message: `Kupmios submitTx failed: ${errorMessage}`
    }));
  }), Effect.provide(FetchHttpClient.layer));
  // Parse and return the transaction hash
  return Schema.decodeSync(TransactionHash.FromHex)(result.transaction.id);
});
export const getUtxosWithUnitEffect = (kupoUrl, headers) => Effect.fn("getUtxosWithUnit")(function* (addressOrCredential, unit) {
  const isAddress = addressOrCredential instanceof CoreAddress.Address;
  const queryPredicate = isAddress ? CoreAddress.toBech32(addressOrCredential) : Bytes.toHex(addressOrCredential.hash);
  const {
    assetName,
    policyId
  } = AssetsUnit.fromUnit(unit);
  const policyIdHex = PolicyId.toHex(policyId);
  const assetNameHex = assetName ? Bytes.toHex(assetName.bytes) : undefined;
  const pattern = `${kupoUrl}/matches/${queryPredicate}${isAddress ? "" : "/*"}?unspent&policy_id=${policyIdHex}${assetNameHex ? `&asset_name=${assetNameHex}` : ""}`;
  const schema = Schema.Array(Kupo.UTxOSchema);
  const toUtxos = kupmiosUtxosToUtxos(kupoUrl, headers?.kupoHeader);
  const utxos = yield* pipe(HttpUtils.get(pattern, schema, headers?.kupoHeader), Effect.flatMap(u => toUtxos(u)), Effect.timeout(TIMEOUT), Effect.catchAll(wrapError("getUtxosWithUnit")), Effect.provide(FetchHttpClient.layer));
  return utxos;
});
export const evaluateTxEffect = (ogmiosUrl, headers) => Effect.fn("evaluateTx")(function* (tx, additionalUTxOs) {
  const txCborHex = Transaction.toCBORHex(tx);
  // Prepare request data
  const data = {
    jsonrpc: "2.0",
    method: "evaluateTransaction",
    params: {
      transaction: {
        cbor: txCborHex
      },
      additionalUtxo: Ogmios.toOgmiosUTxOs(additionalUTxOs)
    },
    id: null
  };
  // Define the schema
  const schema = Ogmios.JSONRPCSchema(Schema.Array(Ogmios.RedeemerSchema));
  // Perform the request and handle the response
  const {
    result
  } = yield* pipe(HttpUtils.postJson(ogmiosUrl, data, schema, headers?.ogmiosHeader), Effect.provide(FetchHttpClient.layer), Effect.timeout(TIMEOUT), Effect.catchAll(wrapError("evaluateTx")));
  const evalRedeemers = result.map(item => {
    // Map Ogmios terminology to Core terminology
    const purpose = item.validator.purpose;
    let tag;
    if (purpose === "publish") tag = "cert";else if (purpose === "withdraw") tag = "reward";else tag = purpose;
    return {
      ex_units: new Redeemer.ExUnits({
        mem: BigInt(item.budget.memory),
        steps: BigInt(item.budget.cpu)
      }),
      redeemer_index: item.validator.index,
      redeemer_tag: tag
    };
  });
  return evalRedeemers;
});
export const awaitTxEffect = (kupoUrl, headers) => Effect.fn("awaitTx")(function* (txHash, checkInterval = 5000, timeout = 160_000) {
  const txHashHex = TransactionHash.toHex(txHash);
  const pattern = `${kupoUrl}/matches/*@${txHashHex}?unspent`;
  const schema = Schema.Array(Kupo.UTxOSchema).annotations({
    identifier: "Array<KupmiosSchema.KupoUTxO>"
  });
  const result = yield* pipe(HttpUtils.get(pattern, schema, headers?.kupoHeader), Effect.provide(FetchHttpClient.layer), Effect.repeat({
    schedule: Schedule.exponential(checkInterval),
    until: result => result.length > 0
  }), Effect.timeout(timeout), Effect.catchAllCause(cause => Effect.fail(new Provider.ProviderError({
    cause,
    message: "Kupmios awaitTx failed"
  }))), Effect.as(true));
  return result;
});
export const getDelegationEffect = (ogmiosUrl, headers) => Effect.fn("getDelegation")(function* (rewardAddress) {
  const data = {
    jsonrpc: "2.0",
    method: "queryLedgerState/rewardAccountSummaries",
    params: {
      keys: [rewardAddress]
    },
    id: null
  };
  const schema = Ogmios.JSONRPCSchema(Ogmios.Delegation);
  const {
    result
  } = yield* pipe(HttpUtils.postJson(ogmiosUrl, data, schema, headers?.ogmiosHeader), Effect.provide(FetchHttpClient.layer), Effect.timeout(TIMEOUT), Effect.catchAll(wrapError("getDelegation")));
  const delegation = result?.[0] ?? null;
  return {
    poolId: delegation?.stakePool?.id ? yield* Schema.decode(PoolKeyHash.FromBech32)(delegation.stakePool.id).pipe(Effect.mapError(cause => new Provider.ProviderError({
      cause,
      message: "Kupmios getDelegation failed"
    }))) : null,
    rewards: BigInt(delegation?.rewards?.ada?.lovelace ?? 0)
  };
});
export const getDatumEffect = (kupoUrl, headers) => Effect.fn("getDatum")(function* (datumHash) {
  const datumHashHex = Bytes.toHex(datumHash.hash);
  const pattern = `${kupoUrl}/datums/${datumHashHex}`;
  const schema = Kupo.DatumSchema;
  const result = yield* pipe(HttpUtils.get(pattern, schema, headers?.kupoHeader), Effect.provide(FetchHttpClient.layer), Effect.timeout(TIMEOUT), Effect.flatMap(Effect.fromNullable), Effect.catchAll(wrapError("getDatum")));
  return Schema.decodeSync(PlutusData.FromCBORHex())(result.datum);
});
//# sourceMappingURL=KupmiosEffects.js.map