"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateTransactionUseCase = void 0;
const uuid_1 = require("uuid");
const transaction_1 = require("../../../domain/entities/transaction");
const not_found_error_1 = require("../../../domain/errors/not-found-error");
const validation_error_1 = require("../../../domain/errors/validation-error");
const transaction_created_event_1 = require("../../../domain/events/transaction-created.event");
const transaction_data_1 = require("../../../domain/value-objects/transaction-data");
class CreateTransactionUseCase {
    transactionRepository;
    networkRepository;
    addressRepository;
    tokenRepository;
    eventPublisher;
    constructor(transactionRepository, networkRepository, addressRepository, tokenRepository, eventPublisher) {
        this.transactionRepository = transactionRepository;
        this.networkRepository = networkRepository;
        this.addressRepository = addressRepository;
        this.tokenRepository = tokenRepository;
        this.eventPublisher = eventPublisher;
    }
    async execute(input) {
        const network = await this.networkRepository.findById(input.networkId);
        if (!network) {
            throw new not_found_error_1.NotFoundError('Network', input.networkId);
        }
        const normalizedFrom = await this.resolveAddress(input.from);
        const normalizedTo = await this.resolveAddress(input.to);
        const normalizedData = await this.normalizeData(input.data);
        const dataToAddress = await this.findKnownRecipientInData(normalizedData);
        const involvedAddress = normalizedFrom ?? normalizedTo ?? dataToAddress;
        if (!involvedAddress) {
            throw new validation_error_1.ValidationError('At least one of from, to, or data.to must be a known address in the system', {
                from: ['Address not found'],
                to: ['Address not found'],
                data: ['No known recipient address found in data'],
            });
        }
        if (involvedAddress.type !== network.addressType) {
            throw new validation_error_1.ValidationError(`Involved address has type ${involvedAddress.type}, but network ${network.name} expects type ${network.addressType}`, {
                networkId: ['Transaction network does not match the involved address type'],
            });
        }
        if (input.interactedWith) {
            const token = await this.tokenRepository.findById(input.interactedWith);
            if (!token) {
                throw new not_found_error_1.NotFoundError('Token', input.interactedWith);
            }
            if (token.networkId !== network.id) {
                throw new validation_error_1.ValidationError(`Token ${token.symbol} belongs to network ${token.networkId}, but transaction network is ${network.id}`, {
                    interactedWith: ['Token network does not match transaction network'],
                });
            }
        }
        const fee = await this.resolveFee(input, network.id);
        const transaction = transaction_1.Transaction.create((0, uuid_1.v4)(), input.hash, input.status, input.block ?? null, input.networkId, input.timestamp, normalizedFrom?.id ?? input.from ?? null, normalizedTo?.id ?? input.to ?? null, input.interactedWith ?? null, input.value, fee, input.gasPrice ?? null, input.gasLimit ?? null, input.method, normalizedData);
        const saved = await this.transactionRepository.save(transaction);
        this.eventPublisher.publish(new transaction_created_event_1.TransactionCreatedEvent(saved));
        return saved;
    }
    /**
     * Resolves an address value to a known Address record.
     *
     * Third-party integrations often send the raw blockchain address, while the
     * rest of the system (balance calculation, notifications) expects the
     * internal address record id. This helper tries the id first, then falls
     * back to a lookup by raw address.
     */
    async resolveAddress(value) {
        if (!value) {
            return null;
        }
        const byId = await this.addressRepository.findById(value);
        if (byId) {
            return byId;
        }
        return this.addressRepository.findByAddress(value);
    }
    async normalizeData(data) {
        return Promise.all(data.map((path) => this.normalizeDataPath(path)));
    }
    async normalizeDataPath(path) {
        if ((0, transaction_data_1.isContractTokenTransferPath)(path) && path.to) {
            const address = await this.resolveAddress(path.to);
            if (address) {
                return { ...path, to: address.id };
            }
        }
        return path;
    }
    async findKnownRecipientInData(data) {
        for (const path of data) {
            if ((0, transaction_data_1.isContractTokenTransferPath)(path) && path.to) {
                const address = await this.resolveAddress(path.to);
                if (address) {
                    return address;
                }
            }
        }
        return null;
    }
    async resolveFee(input, networkId) {
        if (input.fee) {
            return input.fee;
        }
        if (input.method.toLowerCase() !== 'swap') {
            return null;
        }
        const networkTokens = await this.tokenRepository.findByNetworkId(networkId);
        const nativeToken = networkTokens.find((token) => token.isNative);
        if (!nativeToken || nativeToken.decimals <= 0) {
            return null;
        }
        const exponent = Math.max(0, nativeToken.decimals - 3);
        return (10n ** BigInt(exponent)).toString();
    }
}
exports.CreateTransactionUseCase = CreateTransactionUseCase;
//# sourceMappingURL=create-transaction.use-case.js.map