import {
  AddressSchema,
  AssetClassSchema,
  OutputReferenceSchema,
} from '@3rd-eye-labs/cardano-offchain-common';
import { TSchema, Data } from '@evolution-sdk/evolution';
import { DEFAULT_SCHEMA_OPTIONS } from '../../types/evolution-schema-options';
import { option as O, function as F } from 'fp-ts';
import { Schema } from 'effect';
import { RationalSchema } from '../../types/rational';

const OpaqueData = Schema.typeSchema(Data.DataSchema);

export const StableswapOrderDatumSchema = TSchema.Struct({
  iasset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  owner: TSchema.ByteArray,
  destination: AddressSchema,
  destinationInlineDatum: TSchema.NullOr(OpaqueData),
  maxExecutionFee: TSchema.Integer,
  maxFeeRatio: RationalSchema,
});
export type StableswapOrderDatum = typeof StableswapOrderDatumSchema.Type;

const StableswapOrderRedeemerSchema = TSchema.Union(
  TSchema.Literal('BatchProcessStableswapOrders', { flatInUnion: true }),
  TSchema.Struct(
    {
      BatchAuxiliary: TSchema.Struct(
        {
          ownInputIndex: TSchema.Integer,
          mainOrderInputIndex: TSchema.Integer,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('CancelStableswapOrder', { flatInUnion: true }),
  TSchema.Literal('UpdateFees', { flatInUnion: true }),
  TSchema.Literal('UpgradeVersion', { flatInUnion: true }),
);

export type StableswapOrderRedeemer = typeof StableswapOrderRedeemerSchema.Type;

/**
 * This is used when order does not require any specific output datum.
 */
const StableswapOutputDatumSchema = TSchema.Tuple([
  TSchema.ByteArray,
  OutputReferenceSchema,
]);

export type StableswapOutputDatum = typeof StableswapOutputDatumSchema.Type;

export function serialiseStableswapOrderDatum(d: StableswapOrderDatum): string {
  return Data.withSchema(
    StableswapOrderDatumSchema,
    DEFAULT_SCHEMA_OPTIONS,
  ).toCBORHex(d);
}

export function parseStableswapOrderDatum(
  datum: string,
): O.Option<StableswapOrderDatum> {
  try {
    return O.some(
      Data.withSchema(
        StableswapOrderDatumSchema,
        DEFAULT_SCHEMA_OPTIONS,
      ).fromCBORHex(datum),
    );
  } catch (_) {
    return O.none;
  }
}

export function parseStableswapOrderDatumOrThrow(
  datum: string,
): StableswapOrderDatum {
  return F.pipe(
    parseStableswapOrderDatum(datum),
    O.match(() => {
      throw new Error('Expected a Stableswap Order datum.');
    }, F.identity),
  );
}

export function serialiseStableswapOutputDatum(
  d: StableswapOutputDatum,
): string {
  return Data.withSchema(
    StableswapOutputDatumSchema,
    DEFAULT_SCHEMA_OPTIONS,
  ).toCBORHex(d);
}

export function serialiseStableswapOrderRedeemer(
  r: StableswapOrderRedeemer,
): string {
  return Data.withSchema(
    StableswapOrderRedeemerSchema,
    DEFAULT_SCHEMA_OPTIONS,
  ).toCBORHex(r);
}

export function parseStableswapOrderRedeemer(
  redeemerCborHex: string,
): O.Option<StableswapOrderRedeemer> {
  try {
    return O.some(
      Data.withSchema(
        StableswapOrderRedeemerSchema,
        DEFAULT_SCHEMA_OPTIONS,
      ).fromCBORHex(redeemerCborHex),
    );
  } catch (_) {
    return O.none;
  }
}

export function parseStableswapOrderRedeemerOrThrow(
  redeemerCborHex: string,
): StableswapOrderRedeemer {
  return F.pipe(
    parseStableswapOrderRedeemer(redeemerCborHex),
    O.match(() => {
      throw new Error('Expected a Stableswap Order redeemer.');
    }, F.identity),
  );
}
