package com.njcn.gather.tool.wave.service.impl;

import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.tool.wave.component.WaveFileComponent;
import com.njcn.gather.tool.wave.component.WaveVectorComponent;
import com.njcn.gather.tool.wave.pojo.bo.WaveDataDetail;
import com.njcn.gather.tool.wave.pojo.dto.AnalogDTO;
import com.njcn.gather.tool.wave.pojo.dto.ComtradeCfgDTO;
import com.njcn.gather.tool.wave.pojo.dto.EigenvalueDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveVectorGroupDTO;
import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam;
import com.njcn.gather.tool.wave.pojo.param.WaveParseParam;
import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO;
import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeVectorResultVO;
import com.njcn.gather.tool.wave.pojo.vo.WaveParseResultVO;
import com.njcn.gather.tool.wave.pojo.vo.WavePointVO;
import com.njcn.gather.tool.wave.service.WaveService;
import com.njcn.gather.tool.wave.utils.WaveUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 波形解析服务实现。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WaveServiceImpl implements WaveService {

    /** 文本波形默认最大返回点数。 */
    private static final int DEFAULT_MAX_POINT_COUNT = 2000;
    /** 自动识别分隔符标记。 */
    private static final String AUTO_SEPARATOR = "AUTO";
    /** COMTRADE 默认解析类型。 */
    private static final int DEFAULT_PARSE_TYPE = 1;
    /** 向量计算固定使用原始波形解析类型。 */
    private static final int RAW_WAVE_PARSE_TYPE = 3;
    /** COMTRADE 简化显示解析类型。 */
    private static final int SIMPLIFIED_DISPLAY_PARSE_TYPE = 4;
    /** 暂降判定默认阈值。 */
    private static final float DEFAULT_SAG_THRESHOLD_RATE = 0.9F;
    /** 暂降底部平台默认浮动带。 */
    private static final float DEFAULT_BOTTOM_THRESHOLD_RATE = 0.02F;
    /** PT/CT 默认变比。 */
    private static final double DEFAULT_RATIO = 1D;
    /** PT 默认接线方式，0 表示星形。 */
    private static final int DEFAULT_PT_TYPE = 0;

    /** 波形文件解析组件。 */
    private final WaveFileComponent waveFileComponent;
    /** 波形向量计算组件。 */
    private final WaveVectorComponent waveVectorComponent;

    @Override
    public WaveParseResultVO parse(WaveParseParam param) {
        if (param == null || StrUtil.isBlank(param.getWaveformText())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "波形文本不能为空");
        }

        boolean containsXAxis = Boolean.TRUE.equals(param.getContainsXAxis());
        int skipHeaderLines = sanitizeSkipHeaderLines(param.getSkipHeaderLines());
        int maxPointCount = sanitizeMaxPointCount(param.getMaxPointCount());
        BigDecimal samplingInterval = sanitizeSamplingInterval(param.getSamplingInterval());
        int xColumnIndex = sanitizeColumnIndex(param.getXColumnIndex(), 0);
        int yColumnIndex = sanitizeColumnIndex(param.getYColumnIndex(), containsXAxis ? 1 : 0);

        List<WavePointVO> sourcePoints = new ArrayList<>();
        int ignoredLineCount = 0;
        int nonBlankLineIndex = 0;
        String[] lines = param.getWaveformText().split("\\r?\\n");
        for (String line : lines) {
            if (StrUtil.isBlank(line)) {
                continue;
            }
            if (nonBlankLineIndex++ < skipHeaderLines) {
                continue;
            }
            String[] columns = splitColumns(line, param.getSeparator());
            if (columns.length == 0) {
                ignoredLineCount++;
                continue;
            }
            try {
                if (containsXAxis) {
                    sourcePoints.add(buildPoint(columns, xColumnIndex, yColumnIndex));
                } else {
                    sourcePoints.addAll(buildSingleColumnPoints(columns, samplingInterval, sourcePoints.size()));
                }
            } catch (Exception ex) {
                ignoredLineCount++;
                log.debug("波形行解析失败，line={}, reason={}", line, ex.getMessage());
            }
        }

        if (sourcePoints.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "未解析到有效波形点位");
        }

        List<WavePointVO> displayPoints = downSample(sourcePoints, maxPointCount);
        return buildTextResult(sourcePoints, displayPoints, ignoredLineCount, containsXAxis);
    }

    @Override
    public WaveComtradeResultVO parseComtrade(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param) {
        if (cfgStream == null || datStream == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "cfg 或 dat 文件不能为空");
        }

        WaveComtradeParseParam resolvedParam = param == null ? new WaveComtradeParseParam() : param;
        try (InputStream cfgInputStream = cfgStream; InputStream datInputStream = datStream) {
            int parseType = sanitizeParseType(resolvedParam.getParseType());
            boolean simplifiedDisplay = parseType == SIMPLIFIED_DISPLAY_PARSE_TYPE;
            WaveDataDTO waveDataDTO = waveFileComponent.getComtrade(cfgInputStream, datInputStream, parseType);
            applyWaveMetadata(waveDataDTO, resolvedParam);

            boolean needRms = shouldCalculateRms(resolvedParam) || simplifiedDisplay;
            boolean buildDetails = shouldBuildDetails(resolvedParam);
            boolean calculateEigenvalue = Boolean.TRUE.equals(resolvedParam.getCalculateEigenvalue());

            if (needRms || buildDetails || calculateEigenvalue) {
                waveDataDTO = waveFileComponent.getValidData(waveDataDTO);
            }
            if (simplifiedDisplay) {
                applySimplifiedDisplay(waveDataDTO);
            }

            WaveComtradeResultVO result = new WaveComtradeResultVO();
            fillComtradeSummary(result, waveDataDTO);
            result.setWaveData(waveDataDTO);
            if (buildDetails) {
                List<WaveDataDetail> waveDataDetails = WaveUtil.filterWaveData(waveDataDTO);
                result.setWaveDataDetails(waveDataDetails);
            }
            if (calculateEigenvalue) {
                List<EigenvalueDTO> eigenvalues = waveFileComponent.getEigenvalue(
                        waveDataDTO,
                        resolvedParam.getDynamicThreshold() == null || resolvedParam.getDynamicThreshold()
                );
                result.setEigenvalues(eigenvalues);
            }
            return result;
        } catch (BusinessException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("COMTRADE 波形解析失败", ex);
            throw new BusinessException(CommonResponseEnum.FAIL, "COMTRADE 波形解析失败");
        }
    }

    private void fillComtradeSummary(WaveComtradeResultVO result, WaveDataDTO waveDataDTO) {
        if (result == null || waveDataDTO == null) {
            return;
        }

        ComtradeCfgDTO cfgDTO = waveDataDTO.getComtradeCfgDTO();
        if (cfgDTO != null) {
            result.setTotalChannels(cfgDTO.getNChannelNum());
        }
        result.setPhaseCount(resolvePhaseCount(waveDataDTO, cfgDTO));
        result.setUnit(resolveDisplayUnit(waveDataDTO));
    }

    private Integer resolvePhaseCount(WaveDataDTO waveDataDTO, ComtradeCfgDTO cfgDTO) {
        if (waveDataDTO.getIPhasic() != null) {
            return waveDataDTO.getIPhasic();
        }
        return cfgDTO == null ? null : cfgDTO.getNPhasic();
    }

    private String resolveDisplayUnit(WaveDataDTO waveDataDTO) {
        boolean hasVoltage = false;
        boolean hasCurrent = false;
        ComtradeCfgDTO cfgDTO = waveDataDTO.getComtradeCfgDTO();
        if (cfgDTO != null && cfgDTO.getLstAnalogDTO() != null) {
            for (AnalogDTO analogDTO : cfgDTO.getLstAnalogDTO()) {
                if (analogDTO == null || StrUtil.isBlank(analogDTO.getSzUnitName())) {
                    continue;
                }
                if (isVoltageChannel(analogDTO)) {
                    hasVoltage = true;
                } else {
                    hasCurrent = true;
                }
            }
        }
        if (!hasVoltage && !hasCurrent && waveDataDTO.getWaveTitle() != null) {
            for (String title : waveDataDTO.getWaveTitle()) {
                if (StrUtil.isBlank(title) || "x".equalsIgnoreCase(title)) {
                    continue;
                }
                if (title.substring(0, 1).equalsIgnoreCase("U")) {
                    hasVoltage = true;
                } else {
                    hasCurrent = true;
                }
            }
        }
        if (hasVoltage && hasCurrent) {
            return "kV/A";
        }
        if (hasVoltage) {
            return "kV";
        }
        if (hasCurrent) {
            return "A";
        }
        return null;
    }

    @Override
    public WaveComtradeVectorResultVO parseComtradeVector(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param) {
        if (cfgStream == null || datStream == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "cfg 或 dat 文件不能为空");
        }

        WaveComtradeParseParam resolvedParam = param == null ? new WaveComtradeParseParam() : param;
        try (InputStream cfgInputStream = cfgStream; InputStream datInputStream = datStream) {
            WaveDataDTO waveDataDTO = waveFileComponent.getComtrade(cfgInputStream, datInputStream, RAW_WAVE_PARSE_TYPE);
            applyWaveMetadata(waveDataDTO, resolvedParam);

            List<WaveVectorGroupDTO> vectorGroups = waveVectorComponent.calculateVectors(waveDataDTO);
            WaveComtradeVectorResultVO result = new WaveComtradeVectorResultVO();
            result.setMonitorName(waveDataDTO.getMonitorName());
            result.setTime(waveDataDTO.getTime());
            result.setSamplePerCycle(waveDataDTO.getComtradeCfgDTO().getFinalSampleRate());
            result.setCycleCount(resolveCycleCount(vectorGroups));
            result.setVectorGroups(vectorGroups);
            return result;
        } catch (BusinessException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("COMTRADE 向量计算失败", ex);
            throw new BusinessException(CommonResponseEnum.FAIL, "COMTRADE 向量计算失败");
        }
    }

    private Integer resolveCycleCount(List<WaveVectorGroupDTO> vectorGroups) {
        if (vectorGroups == null || vectorGroups.isEmpty() || vectorGroups.get(0).getVectorSeries() == null) {
            return 0;
        }
        return vectorGroups.get(0).getVectorSeries().size();
    }

    private void applyWaveMetadata(WaveDataDTO waveDataDTO, WaveComtradeParseParam param) {
        waveDataDTO.setPt(resolveCfgRatio(waveDataDTO, true, defaultIfNull(param.getPt(), DEFAULT_RATIO)));
        waveDataDTO.setCt(resolveCfgRatio(waveDataDTO, false, defaultIfNull(param.getCt(), DEFAULT_RATIO)));
        waveDataDTO.setPtType(param.getPtType() == null ? DEFAULT_PT_TYPE : param.getPtType());
        waveDataDTO.setMonitorName(StrUtil.blankToDefault(param.getMonitorName(), "未命名测点"));
    }

    private double resolveCfgRatio(WaveDataDTO waveDataDTO, boolean voltage, double fallbackRatio) {
        if (waveDataDTO == null || waveDataDTO.getComtradeCfgDTO() == null
                || waveDataDTO.getComtradeCfgDTO().getLstAnalogDTO() == null) {
            return fallbackRatio;
        }
        for (AnalogDTO analogDTO : waveDataDTO.getComtradeCfgDTO().getLstAnalogDTO()) {
            if (!hasValidCfgRatio(analogDTO) || isVoltageChannel(analogDTO) != voltage) {
                continue;
            }
            // cfg 模拟量通道的 primary/secondary 是变比权威来源。
            return analogDTO.getFPrimary() / analogDTO.getFSecondary();
        }
        return fallbackRatio;
    }

    private boolean hasValidCfgRatio(AnalogDTO analogDTO) {
        return analogDTO != null
                && analogDTO.getFPrimary() != null
                && analogDTO.getFSecondary() != null
                && analogDTO.getFPrimary() > 0
                && analogDTO.getFSecondary() > 0;
    }

    private boolean isVoltageChannel(AnalogDTO analogDTO) {
        return !StrUtil.equalsIgnoreCase(analogDTO.getSzUnitName(), "A");
    }

    private boolean shouldCalculateRms(WaveComtradeParseParam param) {
        return param.getCalculateRms() == null || param.getCalculateRms();
    }

    private boolean shouldBuildDetails(WaveComtradeParseParam param) {
        return param.getBuildDetails() == null || param.getBuildDetails();
    }

    private void applySimplifiedDisplay(WaveDataDTO waveDataDTO) {
        if (waveDataDTO == null || waveDataDTO.getListWaveData() == null || waveDataDTO.getListRmsData() == null) {
            return;
        }
        SimplifiedRange range = resolveSimplifiedRange(waveDataDTO);
        if (range == null) {
            return;
        }
        maskBottomMiddle(waveDataDTO.getListWaveData(), range);
        maskBottomMiddle(waveDataDTO.getListRmsData(), range);
    }

    private SimplifiedRange resolveSimplifiedRange(WaveDataDTO waveDataDTO) {
        List<List<Float>> rmsData = waveDataDTO.getListRmsData();
        if (rmsData == null || rmsData.isEmpty()) {
            return null;
        }
        int valueColumnCount = resolveValueColumnCount(rmsData);
        if (valueColumnCount <= 0) {
            return null;
        }
        int baseIndex = resolveBaseIndex(waveDataDTO, rmsData);
        if (baseIndex < 0 || baseIndex >= rmsData.size()) {
            return null;
        }
        float[] baseValues = resolveBaseValues(rmsData.get(baseIndex), valueColumnCount);
        if (baseValues == null) {
            return null;
        }

        int startIndex = -1;
        int recoverIndex = -1;
        float bottomValue = Float.MAX_VALUE;
        for (int i = baseIndex; i < rmsData.size(); i++) {
            Float minValue = minValue(rmsData.get(i), valueColumnCount);
            if (minValue == null) {
                continue;
            }
            boolean sag = isSag(rmsData.get(i), baseValues, valueColumnCount);
            if (startIndex < 0 && sag) {
                startIndex = i;
            }
            if (startIndex >= 0 && recoverIndex < 0 && minValue < bottomValue) {
                bottomValue = minValue;
            }
            if (startIndex >= 0 && !sag) {
                recoverIndex = i;
                break;
            }
        }
        if (startIndex < 0 || recoverIndex < 0 || startIndex >= recoverIndex || bottomValue == Float.MAX_VALUE) {
            return null;
        }

        float bottomThreshold = bottomValue + resolveBottomBand(baseValues);
        int bottomStartIndex = -1;
        int bottomEndIndex = -1;
        for (int i = startIndex; i <= recoverIndex; i++) {
            Float minValue = minValue(rmsData.get(i), valueColumnCount);
            if (minValue == null || minValue > bottomThreshold) {
                continue;
            }
            if (bottomStartIndex < 0) {
                bottomStartIndex = i;
            }
            bottomEndIndex = i;
        }
        if (bottomStartIndex < 0 || bottomEndIndex < 0 || bottomStartIndex >= bottomEndIndex) {
            return null;
        }
        return new SimplifiedRange(bottomStartIndex, bottomEndIndex);
    }

    private int resolveValueColumnCount(List<List<Float>> rows) {
        int count = 0;
        for (List<Float> row : rows) {
            if (row != null && row.size() > 1) {
                count = Math.max(count, row.size() - 1);
            }
        }
        return count;
    }

    private int resolveBaseIndex(WaveDataDTO waveDataDTO, List<List<Float>> rmsData) {
        Integer sampleRate = waveDataDTO.getComtradeCfgDTO() == null ? null : waveDataDTO.getComtradeCfgDTO().getFinalSampleRate();
        int preferred = sampleRate == null ? 0 : sampleRate + 2;
        return preferred < rmsData.size() ? preferred : 0;
    }

    private float[] resolveBaseValues(List<Float> row, int valueColumnCount) {
        if (row == null || row.size() <= valueColumnCount) {
            return null;
        }
        float[] baseValues = new float[valueColumnCount];
        for (int i = 0; i < valueColumnCount; i++) {
            Float value = row.get(i + 1);
            if (value == null || value <= 0) {
                return null;
            }
            baseValues[i] = value;
        }
        return baseValues;
    }

    private boolean isSag(List<Float> row, float[] baseValues, int valueColumnCount) {
        if (row == null || row.size() <= valueColumnCount) {
            return false;
        }
        for (int i = 0; i < valueColumnCount; i++) {
            Float value = row.get(i + 1);
            if (value != null && value < baseValues[i] * DEFAULT_SAG_THRESHOLD_RATE) {
                return true;
            }
        }
        return false;
    }

    private Float minValue(List<Float> row, int valueColumnCount) {
        if (row == null || row.size() <= valueColumnCount) {
            return null;
        }
        Float minValue = null;
        for (int i = 0; i < valueColumnCount; i++) {
            Float value = row.get(i + 1);
            if (value == null) {
                continue;
            }
            minValue = minValue == null ? value : Math.min(minValue, value);
        }
        return minValue;
    }

    private float resolveBottomBand(float[] baseValues) {
        float minBaseValue = Float.MAX_VALUE;
        for (float baseValue : baseValues) {
            minBaseValue = Math.min(minBaseValue, baseValue);
        }
        if (minBaseValue == Float.MAX_VALUE) {
            return 0F;
        }
        return minBaseValue * DEFAULT_BOTTOM_THRESHOLD_RATE;
    }

    private void maskBottomMiddle(List<List<Float>> rows, SimplifiedRange range) {
        if (rows == null || range == null) {
            return;
        }
        for (int i = 0; i < rows.size(); i++) {
            if (i <= range.bottomStartIndex || i >= range.bottomEndIndex) {
                continue;
            }
            List<Float> row = rows.get(i);
            if (row == null || row.size() <= 1) {
                continue;
            }
            for (int j = 1; j < row.size(); j++) {
                row.set(j, null);
            }
        }
    }

    private int sanitizeParseType(Integer parseType) {
        if (parseType == null || parseType < 0 || parseType > SIMPLIFIED_DISPLAY_PARSE_TYPE) {
            return DEFAULT_PARSE_TYPE;
        }
        return parseType;
    }

    private double defaultIfNull(Double value, double defaultValue) {
        if (value == null || value <= 0) {
            return defaultValue;
        }
        return value;
    }

    private WavePointVO buildPoint(String[] columns, int xColumnIndex, int yColumnIndex) {
        BigDecimal yValue = parseNumber(readColumn(columns, yColumnIndex));
        BigDecimal xValue = parseNumber(readColumn(columns, xColumnIndex));
        return new WavePointVO(xValue, yValue);
    }

    private List<WavePointVO> buildSingleColumnPoints(String[] columns, BigDecimal samplingInterval, int startIndex) {
        List<WavePointVO> points = new ArrayList<>();
        for (int i = 0; i < columns.length; i++) {
            BigDecimal yValue = parseNumber(columns[i]);
            BigDecimal xValue = samplingInterval.multiply(BigDecimal.valueOf(startIndex + i));
            points.add(new WavePointVO(xValue, yValue));
        }
        return points;
    }

    private String readColumn(String[] columns, int columnIndex) {
        if (columnIndex < 0 || columnIndex >= columns.length) {
            throw new IllegalArgumentException("列下标超出范围");
        }
        return columns[columnIndex];
    }

    private BigDecimal parseNumber(String value) {
        if (StrUtil.isBlank(value)) {
            throw new IllegalArgumentException("数值不能为空");
        }
        return new BigDecimal(value.trim());
    }

    private String[] splitColumns(String line, String separator) {
        String trimmedLine = line.trim();
        if (StrUtil.isBlank(trimmedLine)) {
            return new String[0];
        }
        String[] parts;
        if (StrUtil.isBlank(separator) || AUTO_SEPARATOR.equalsIgnoreCase(separator)) {
            parts = trimmedLine.split("[,;\\s]+");
        } else if ("TAB".equalsIgnoreCase(separator)) {
            parts = trimmedLine.split("\\t+");
        } else if ("SPACE".equalsIgnoreCase(separator)) {
            parts = trimmedLine.split("\\s+");
        } else {
            parts = trimmedLine.split(Pattern.quote(separator));
        }
        return Arrays.stream(parts)
                .map(String::trim)
                .filter(StrUtil::isNotBlank)
                .toArray(String[]::new);
    }

    private List<WavePointVO> downSample(List<WavePointVO> sourcePoints, int maxPointCount) {
        if (sourcePoints.size() <= maxPointCount) {
            return sourcePoints;
        }

        List<WavePointVO> result = new ArrayList<>();
        int step = (int) Math.ceil((double) sourcePoints.size() / maxPointCount);
        for (int i = 0; i < sourcePoints.size(); i += step) {
            result.add(sourcePoints.get(i));
        }
        WavePointVO lastPoint = sourcePoints.get(sourcePoints.size() - 1);
        if (!result.contains(lastPoint) && result.size() < maxPointCount) {
            result.add(lastPoint);
        } else if (!result.isEmpty()) {
            result.set(result.size() - 1, lastPoint);
        }
        return result;
    }

    private WaveParseResultVO buildTextResult(List<WavePointVO> sourcePoints, List<WavePointVO> displayPoints,
                                              int ignoredLineCount, boolean containsXAxis) {
        BigDecimal minX = sourcePoints.get(0).getX();
        BigDecimal maxX = sourcePoints.get(0).getX();
        BigDecimal minY = sourcePoints.get(0).getY();
        BigDecimal maxY = sourcePoints.get(0).getY();
        BigDecimal sumY = BigDecimal.ZERO;

        for (WavePointVO point : sourcePoints) {
            if (point.getX().compareTo(minX) < 0) {
                minX = point.getX();
            }
            if (point.getX().compareTo(maxX) > 0) {
                maxX = point.getX();
            }
            if (point.getY().compareTo(minY) < 0) {
                minY = point.getY();
            }
            if (point.getY().compareTo(maxY) > 0) {
                maxY = point.getY();
            }
            sumY = sumY.add(point.getY());
        }

        WaveParseResultVO result = new WaveParseResultVO();
        result.setContainsXAxis(containsXAxis);
        result.setSourcePointCount(sourcePoints.size());
        result.setDisplayPointCount(displayPoints.size());
        result.setIgnoredLineCount(ignoredLineCount);
        result.setSampled(sourcePoints.size() != displayPoints.size());
        result.setMinX(minX);
        result.setMaxX(maxX);
        result.setMinY(minY);
        result.setMaxY(maxY);
        result.setAverageY(sumY.divide(BigDecimal.valueOf(sourcePoints.size()), 6, RoundingMode.HALF_UP));
        result.setPoints(displayPoints);
        return result;
    }

    private int sanitizeSkipHeaderLines(Integer skipHeaderLines) {
        if (skipHeaderLines == null || skipHeaderLines < 0) {
            return 0;
        }
        return skipHeaderLines;
    }

    private int sanitizeMaxPointCount(Integer maxPointCount) {
        if (maxPointCount == null || maxPointCount <= 0) {
            return DEFAULT_MAX_POINT_COUNT;
        }
        return maxPointCount;
    }

    private int sanitizeColumnIndex(Integer columnIndex, int defaultValue) {
        if (columnIndex == null || columnIndex < 0) {
            return defaultValue;
        }
        return columnIndex;
    }

    private BigDecimal sanitizeSamplingInterval(BigDecimal samplingInterval) {
        if (samplingInterval == null || samplingInterval.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ONE;
        }
        return samplingInterval;
    }

    private static final class SimplifiedRange {

        /** 暂降底部平台起始下标。 */
        private final int bottomStartIndex;
        /** 暂降底部平台结束下标。 */
        private final int bottomEndIndex;

        private SimplifiedRange(int bottomStartIndex, int bottomEndIndex) {
            this.bottomStartIndex = bottomStartIndex;
            this.bottomEndIndex = bottomEndIndex;
        }
    }
}
