/*
 * SPDX-License-Identifier: MIT
 */
package org.ta4j.core.criteria.commissions;

import org.ta4j.core.BarSeries;
import org.ta4j.core.Position;
import org.ta4j.core.TradingRecord;
import org.ta4j.core.criteria.AbstractAnalysisCriterion;
import org.ta4j.core.num.NaN;
import org.ta4j.core.num.Num;
import org.ta4j.core.num.NumFactory;

/**
 * Analysis criterion that totals execution fees across a trading record.
 *
 * <p>
 * This criterion is intentionally distinct from {@link CommissionsCriterion}.
 * The commissions criterion models costs using the configured transaction cost
 * model, while this criterion returns actual, recorded execution fees when a
 * {@link TradingRecord} exposes them. This keeps modeled cost analytics stable
 * while enabling fee-aware live tracking (partial fills, maker/taker mixes, and
 * exchange fee changes).
 * </p>
 *
 * <p>
 * When a trading record does not expose execution fees, this criterion falls
 * back to the transaction cost model (matching {@link CommissionsCriterion}
 * semantics).
 * </p>
 *
 * @since 0.22.2
 */
public class TotalFeesCriterion extends AbstractAnalysisCriterion {

    @Override
    public Num calculate(BarSeries series, Position position) {
        NumFactory factory = series.numFactory();
        if (position.isClosed()) {
            Num cost = position.getEntry().getCostModel().calculate(position);
            return toSeriesNum(factory, cost);
        }
        if (position.isOpened()) {
            int endIndex = series.getEndIndex();
            Num cost = position.getEntry().getCostModel().calculate(position, endIndex);
            return toSeriesNum(factory, cost);
        }
        return factory.zero();
    }

    @Override
    public Num calculate(BarSeries series, TradingRecord tradingRecord) {
        NumFactory factory = series.numFactory();
        Num recordedFees = tradingRecord.getRecordedTotalFees();
        if (recordedFees != null) {
            return toSeriesNum(factory, recordedFees);
        }
        Num closedFees = tradingRecord.getPositions()
                .stream()
                .filter(Position::isClosed)
                .map(position -> calculate(series, position))
                .reduce(factory.zero(), Num::plus);

        Position current = tradingRecord.getCurrentPosition();
        if (current.isOpened()) {
            return closedFees.plus(calculate(series, current));
        }
        return closedFees;
    }

    @Override
    public boolean betterThan(Num v1, Num v2) {
        return v1.isLessThan(v2);
    }

    private Num toSeriesNum(NumFactory factory, Num value) {
        if (value == null) {
            return factory.zero();
        }
        if (value.isNaN()) {
            return NaN.NaN;
        }
        return factory.numOf(value.getDelegate());
    }
}
