package jnpf.attendance.schedule;

import java.io.Serializable;
import java.time.LocalTime;
import java.util.Objects;

/**
 * 第六步：候选固定班次发现与过滤参数。
 */
public final class CommonFixedShiftDiscoveryConfig implements Serializable {

    /** 进入候选池：出现天数 ≥ 该值 */
    private final int poolMinAppearanceDays;
    /** 进入候选池：出现次数 ≥ 该值 */
    private final int poolMinAppearanceCount;
    /** 进入候选池：出现天数占比 ≥ 该值（0～1） */
    private final double poolMinDayRatio;

    /** 合法班次最短时长（分钟），小于则剔除（如 09:00-09:30 共 30 分钟） */
    private final int legalMinShiftMinutes;
    /** 合法班次最长时长（分钟） */
    private final int legalMaxShiftMinutes;

    /** 营业开始（含） */
    private final LocalTime businessOpen;
    /** 营业结束（不含）；若为 00:00 表示次日 0 点即 24:00 营业结束 */
    private final LocalTime businessCloseExclusive;

    /** 样本量 ≥ 该值时，最终筛选要求「出现天数占比 ≥ finalMinDayRatioWhenSampleLarge」 */
    private final int sampleLargeThresholdDays;
    private final double finalMinDayRatioWhenSampleLarge;

    /** 样本量处于 [mediumLow, large) 时，最终筛选要求「出现天数 ≥ finalMinDaysWhenSampleMedium」 */
    private final int sampleMediumLowDays;
    private final int sampleMediumHighDays;
    private final int finalMinDaysWhenSampleMedium;

    public CommonFixedShiftDiscoveryConfig(
            int poolMinAppearanceDays,
            int poolMinAppearanceCount,
            double poolMinDayRatio,
            int legalMinShiftMinutes,
            int legalMaxShiftMinutes,
            LocalTime businessOpen,
            LocalTime businessCloseExclusive,
            int sampleLargeThresholdDays,
            double finalMinDayRatioWhenSampleLarge,
            int sampleMediumLowDays,
            int sampleMediumHighDays,
            int finalMinDaysWhenSampleMedium) {
        if (poolMinAppearanceDays < 0
                || poolMinAppearanceCount < 0
                || poolMinDayRatio < 0
                || legalMinShiftMinutes < 0
                || legalMaxShiftMinutes < legalMinShiftMinutes) {
            throw new IllegalArgumentException("invalid CommonFixedShiftDiscoveryConfig");
        }
        this.poolMinAppearanceDays = poolMinAppearanceDays;
        this.poolMinAppearanceCount = poolMinAppearanceCount;
        this.poolMinDayRatio = poolMinDayRatio;
        this.legalMinShiftMinutes = legalMinShiftMinutes;
        this.legalMaxShiftMinutes = legalMaxShiftMinutes;
        this.businessOpen = businessOpen == null ? LocalTime.MIDNIGHT : businessOpen;
        this.businessCloseExclusive =
                businessCloseExclusive == null ? LocalTime.MIDNIGHT : businessCloseExclusive;
        this.sampleLargeThresholdDays = Math.max(1, sampleLargeThresholdDays);
        this.finalMinDayRatioWhenSampleLarge = finalMinDayRatioWhenSampleLarge;
        this.sampleMediumLowDays = Math.max(1, sampleMediumLowDays);
        this.sampleMediumHighDays = Math.max(sampleMediumLowDays, sampleMediumHighDays);
        this.finalMinDaysWhenSampleMedium = Math.max(1, finalMinDaysWhenSampleMedium);
    }

    public static CommonFixedShiftDiscoveryConfig defaults() {
        return new CommonFixedShiftDiscoveryConfig(
                2,
                3,
                0.30d,
                60,
                12 * 60,
                LocalTime.MIDNIGHT,
                LocalTime.MIDNIGHT,
                10,
                0.60d,
                5,
                9,
                2);
    }

    public int getPoolMinAppearanceDays() {
        return poolMinAppearanceDays;
    }

    public int getPoolMinAppearanceCount() {
        return poolMinAppearanceCount;
    }

    public double getPoolMinDayRatio() {
        return poolMinDayRatio;
    }

    public int getLegalMinShiftMinutes() {
        return legalMinShiftMinutes;
    }

    public int getLegalMaxShiftMinutes() {
        return legalMaxShiftMinutes;
    }

    public LocalTime getBusinessOpen() {
        return businessOpen;
    }

    public LocalTime getBusinessCloseExclusive() {
        return businessCloseExclusive;
    }

    public int getSampleLargeThresholdDays() {
        return sampleLargeThresholdDays;
    }

    public double getFinalMinDayRatioWhenSampleLarge() {
        return finalMinDayRatioWhenSampleLarge;
    }

    public int getSampleMediumLowDays() {
        return sampleMediumLowDays;
    }

    public int getSampleMediumHighDays() {
        return sampleMediumHighDays;
    }

    public int getFinalMinDaysWhenSampleMedium() {
        return finalMinDaysWhenSampleMedium;
    }

    /** 营业窗口内 [open, close) 分钟；close 为 MIDNIGHT 表示全天 0～1440。 */
    public boolean isWindowWithinBusiness(int startMinuteInclusive, int endMinuteExclusive) {
        int open = HalfHourSlotLabel.localTimeToMinute(businessOpen);
        int close = HalfHourSlotLabel.localTimeToMinute(businessCloseExclusive);
        if (businessCloseExclusive.equals(LocalTime.MIDNIGHT) && businessOpen.equals(LocalTime.MIDNIGHT)) {
            return startMinuteInclusive >= 0 && endMinuteExclusive <= 24 * 60;
        }
        if (close > open) {
            return startMinuteInclusive >= open && endMinuteExclusive <= close;
        }
        // 跨日营业简化为全天
        return startMinuteInclusive >= 0 && endMinuteExclusive <= 24 * 60;
    }
}
