// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { bcs } from '../../bcs/index.js';
import type { Experimental_SuiClientTypes } from '../types.js';

export function parseTransactionEffects({
	effects,
	epoch,
	objectTypes,
}: {
	effects: Uint8Array;
	objectTypes: Record<string, string>;
	epoch?: string | null;
}): Experimental_SuiClientTypes.TransactionEffects {
	const parsed = bcs.TransactionEffects.parse(effects);

	switch (parsed.$kind) {
		case 'V1':
			return parseTransactionEffectsV1({ bytes: effects, effects: parsed.V1, epoch, objectTypes });
		case 'V2':
			return parseTransactionEffectsV2({ bytes: effects, effects: parsed.V2, epoch, objectTypes });
		default:
			throw new Error(
				`Unknown transaction effects version: ${(parsed as { $kind: string }).$kind}`,
			);
	}
}

function parseTransactionEffectsV1(_: {
	bytes: Uint8Array;
	effects: NonNullable<(typeof bcs.TransactionEffects.$inferType)['V1']>;
	epoch?: string | null;
	objectTypes: Record<string, string>;
}): Experimental_SuiClientTypes.TransactionEffects {
	throw new Error('V1 effects are not supported yet');
}

function parseTransactionEffectsV2({
	bytes,
	effects,
	epoch,
	objectTypes,
}: {
	bytes: Uint8Array;
	effects: NonNullable<(typeof bcs.TransactionEffects.$inferType)['V2']>;
	epoch?: string | null;
	objectTypes: Record<string, string>;
}): Experimental_SuiClientTypes.TransactionEffects {
	const changedObjects = effects.changedObjects.map(
		([id, change]): Experimental_SuiClientTypes.ChangedObject => {
			return {
				id,
				inputState: change.inputState.$kind === 'Exist' ? 'Exists' : 'DoesNotExist',
				inputVersion: change.inputState.Exist?.[0][0] ?? null,
				inputDigest: change.inputState.Exist?.[0][1] ?? null,
				inputOwner: change.inputState.Exist?.[1] ?? null,
				outputState:
					change.outputState.$kind === 'NotExist' ? 'DoesNotExist' : change.outputState.$kind,
				outputVersion:
					change.outputState.$kind === 'PackageWrite'
						? change.outputState.PackageWrite?.[0]
						: change.outputState.ObjectWrite
							? effects.lamportVersion
							: null,
				outputDigest:
					change.outputState.$kind === 'PackageWrite'
						? change.outputState.PackageWrite?.[1]
						: (change.outputState.ObjectWrite?.[0] ?? null),
				outputOwner: change.outputState.ObjectWrite ? change.outputState.ObjectWrite[1] : null,
				idOperation: change.idOperation.$kind,
				objectType: objectTypes[id] ?? null,
			};
		},
	);

	return {
		bcs: bytes,
		digest: effects.transactionDigest,
		version: 2,
		status:
			effects.status.$kind === 'Success'
				? {
						success: true,
						error: null,
					}
				: {
						success: false,
						// TODO: add command
						error: effects.status.Failed.error.$kind,
					},
		epoch: epoch ?? null,
		gasUsed: effects.gasUsed,
		transactionDigest: effects.transactionDigest,
		gasObject:
			effects.gasObjectIndex === null ? null : (changedObjects[effects.gasObjectIndex] ?? null),
		eventsDigest: effects.eventsDigest,
		dependencies: effects.dependencies,
		lamportVersion: effects.lamportVersion,
		changedObjects,
		unchangedSharedObjects: effects.unchangedSharedObjects.map(
			([objectId, object]): Experimental_SuiClientTypes.UnchangedSharedObject => {
				return {
					kind: object.$kind,
					objectId: objectId,
					version:
						object.$kind === 'ReadOnlyRoot'
							? object.ReadOnlyRoot[0]
							: (object[object.$kind] as string | null),
					digest: object.$kind === 'ReadOnlyRoot' ? object.ReadOnlyRoot[1] : null,
					objectType: objectTypes[objectId] ?? null,
				};
			},
		),
		auxiliaryDataDigest: effects.auxDataDigest,
	};
}
