_optNum.mjs

import get from 'lodash-es/get.js'
import isnum from './isnum.mjs'
import cst from './_const.mjs'


//本檔為內部使用, 不由index匯出


/**
 * 取設定物件之數值選項, 無效時回傳預設值; 供各函數之數值選項共用同一套規則, 不各自手寫檢查
 *
 * 數字與數字字串皆可(isnum), 字串先轉為數字再判定, 例如'12.34'為12.34、'5'為5、' 7 '為7; 空字串、純空白字串、非數字字串、NaN、null、布林等皆非isnum, 一律用預設
 * 以Number轉換後判定, 不用cdbl: cdbl把Infinity轉為有限之Number.MAX_VALUE, 會使有限與否之判定失效
 * 低於下界(min, minOpen為true時須大於min)者用預設, below為'clamp'時改夾至下界
 * +Infinity依意圖處置: timer之計時器毫秒夾至計時器上限(同delay, 見_const.mjs), inf為'keep'者保留(例如容許誤差之無限大即任何變化皆容許), 其餘用預設; NaN用預設, -Infinity視為低於下界
 * int為true時須為整數(Infinity依上一條處置)
 * timer為true時有效值再以Math.min夾至計時器上限: 超大值之意圖為「很久」, 夾至上限最接近其意圖, 不視為無效
 *
 * @param {Object} opt 輸入設定物件
 * @param {String} key 輸入選項鍵名
 * @param {Number} def 輸入無效時之預設值
 * @param {Object} [rule={}] 輸入規則物件
 * @param {Number} [rule.min=-Infinity] 輸入下限
 * @param {Boolean} [rule.minOpen=false] 輸入是否不含下限(須大於min)
 * @param {String} [rule.below='def'] 輸入低於下限時之處置, 'def'為用預設, 'clamp'為夾至下限(minOpen為true時仍用預設)
 * @param {Boolean} [rule.int=false] 輸入是否須為整數
 * @param {Boolean} [rule.timer=false] 輸入是否為計時器毫秒(夾至上限)
 * @param {String} [rule.inf='def'] 輸入非計時器之+Infinity之處置, 'def'為用預設, 'keep'為保留
 * @returns {Number} 回傳數字
 */
function optNum(opt, key, def, rule = {}) {
    let min = get(rule, 'min', -Infinity)
    let minOpen = get(rule, 'minOpen', false) === true
    let clamp = get(rule, 'below', 'def') === 'clamp'
    let int = get(rule, 'int', false) === true
    let timer = get(rule, 'timer', false) === true
    let keepInf = get(rule, 'inf', 'def') === 'keep'

    //v, 數字或數字字串(isnum已排除NaN、空字串與純空白字串), 字串轉為數字
    let v = get(opt, key, null)
    if (!isnum(v)) {
        return def
    }
    v = Number(v)

    //check min
    if (minOpen ? !(v > min) : !(v >= min)) {
        return (clamp && !minOpen && Number.isFinite(min)) ? min : def
    }

    //check Infinity
    if (v === Infinity) {
        if (timer) {
            return cst.TIMER_TIME_MAX
        }
        return keepInf ? Infinity : def
    }
    if (!Number.isFinite(v)) {
        return def
    }

    //check int
    if (int && !Number.isInteger(v)) {
        return def
    }

    //timer
    if (timer) {
        v = Math.min(v, cst.TIMER_TIME_MAX)
    }

    return v
}


export default optNum