import isenum from './isenum.mjs'
import iseint from './iseint.mjs'
import parseNumOpt from './_numOpt.mjs'
//本檔為內部使用, 不由index匯出
/**
* opt類數值參數之檢測與預設(單一擁有者), 供第221項第二批各函數共用, 不各自手寫
*
* 規格: 主控dspec〈輸入參數之檢測機制〉K3(opt類參數遇非預期之輸入視為未給、改用預設, 含超出範圍; opt.debug警告)、V5、§7.1
*
* 使用者2026-10-31: opt類參數(opt之鍵, 以及JSDoc標[參數=預設]或程式碼有回退之位置參數)給了非預期之輸入(含超出範圍)時, 視為未給、改用預設, 不報錯;
* 必須參數遇非預期之輸入才報錯(不使用本函數, 各函數自行判定)
*
* 規則:
* v為undefined或null視為未給, 回傳預設
* 有效(rule.kind為'enum'以isenum判定, 為'eint'以iseint判定, rule.chk為其opt, 如{ use: 'timer', gt: 0 })則回傳Number(v), 即去頭尾空白後Number(…)取值(K4), 其後只用轉換值
* 非預期(含超出範圍、Infinity、包裝物件、'0x10'等isenum/iseint拒收者)回傳預設, 不拋錯
* debug: 呼叫端傳入其opt(opt.debug === true, 非布林視為false)時, 非預期另輸出警告, 警告出口為opt.funLog(函數)或console.warn; 警告為固定詞彙,
* 只含函數名、參數名、種類描述與所採之預設(程式常數), 不含傳入之值(K9、V20); 同一函數、同一參數每個行程只警告一次(與_numOpt.mjs共用去重表, parseNumOpt.resetWarned()可重設)
* 位置參數無法帶debug者(呼叫端無opt)不傳opt, 即不警告
*
* @param {*} v 輸入原值(呼叫端已自opt讀出, 每個鍵只讀一次)
* @param {*} def 輸入預設值(未給或非預期時回傳)
* @param {Object} rule 輸入規則物件(建議於模組層建立一次, 不在熱路徑配置): { fn: 函數名, name: 參數名(如'opt.timeInterval'或'interval'), kind: 'enum'或'eint', chk: isenum/iseint之opt, defDesc: 預設值之警告描述(選用, 預設依def型別) }
* @param {*} [opt] 輸入呼叫端之opt(供讀取debug與funLog), 未給則不警告
* @returns {*} 回傳有效數字(Number)或預設值
*/
function optVal(v, def, rule, opt) {
//未給
if (v === undefined || v === null) {
return def
}
//判定, 有效則取值
let ok = rule.kind === 'eint' ? iseint(v, rule.chk) : isenum(v, rule.chk)
if (ok) {
return Number(v)
}
//非預期之輸入, 視為未給、改用預設; debug時警告
if (opt !== undefined && opt !== null) {
warnInvalid(rule, def, opt)
}
return def
}
//讀取opt.debug與opt.funLog(讀取拋錯一律視為未給; debug為true才讀funLog), 回傳{ debug, funLog }; 亦供轉交debug、funLog給下層函數之呼叫端使用(optVal.readDbg)
function readDbg(opt) {
let debug = false
let funLog = null
try {
if ((typeof opt === 'object' && opt !== null) || typeof opt === 'function') {
debug = opt.debug === true
if (debug) {
let f = opt.funLog
funLog = typeof f === 'function' ? f : null
}
}
}
catch (err) {
debug = false
funLog = null
}
return { debug, funLog }
}
//debug為true才警告
function warnInvalid(rule, def, opt) {
let { debug, funLog } = readDbg(opt)
if (!debug) {
return
}
emitWarn(rule, def, funLog)
}
//輸出警告(固定詞彙, 不含值; 去重與出口同_numOpt.mjs之warnOnce)
function emitWarn(rule, def, funLog) {
let msg = `[wsemi ${rule.fn}] invalid ${rule.name}: must be ${getDesc(rule)}, using default ${getDefDesc(rule, def)}`
parseNumOpt.warnOnce(`${rule.fn}|invalid|${rule.name}`, msg, { fn: rule.fn, reason: 'invalid', key: rule.name }, funLog)
}
/**
* 延後警告之收集器(optVal.defer): 判定時不讀呼叫端之opt, 業務鍵全部讀完後才至多讀一次opt.debug/funLog並依序警告(規格§4 V11: 每鍵只讀一次, 且debug之讀取不影響其後業務鍵之讀取)
*
* 用法: let col = optVal.defer(); x = col.take(v, def, rule); ...(其餘業務鍵); col.flush(opt)
* take之回傳值同optVal(v, def, rule)(不警告); flush於無非預期者時不讀opt; flush(opt, true)則一律讀一次(供轉交debug、funLog給下層), 回傳{ debug, funLog }
*
* @returns {Object} 回傳{ take, flush }
*/
function defer() {
let bad = []
let take = (v, def, rule) => {
let r = optVal(v, def, rule)
if (v !== undefined && v !== null) {
let ok = rule.kind === 'eint' ? iseint(v, rule.chk) : isenum(v, rule.chk)
if (!ok) {
bad.push({ rule, def })
}
}
return r
}
let flush = (opt, alwaysRead = false) => {
if (bad.length === 0 && !alwaysRead) {
return { debug: false, funLog: null }
}
let dbg = readDbg(opt)
if (dbg.debug) {
for (let b of bad) {
emitWarn(b.rule, b.def, dbg.funLog)
}
}
bad = []
return dbg
}
return { take, flush }
}
//規則之種類描述(固定詞彙, 取自程式常數): a valid number / a valid integer, 後接範圍
function getDesc(rule) {
let s = rule.kind === 'eint' ? 'a valid integer' : 'a valid number'
let c = rule.chk
if (c === undefined || c === null) {
return s
}
let ps = []
if (c.use !== undefined) {
ps.push(`use '${c.use}'`)
}
for (let k of ['min', 'max', 'gt', 'lt']) {
if (c[k] !== undefined) {
ps.push(`${k} ${c[k]}`)
}
}
return ps.length > 0 ? `${s} (${ps.join(', ')})` : s
}
//預設值之描述(程式常數): 數字、null、字串或rule.defDesc
function getDefDesc(rule, def) {
if (typeof rule.defDesc === 'string') {
return rule.defDesc
}
if (def === null) {
return 'null'
}
if (typeof def === 'number' || typeof def === 'boolean') {
return String(def)
}
if (typeof def === 'string') {
return `'${def}'`
}
return typeof def
}
optVal.getDesc = getDesc
optVal.readDbg = readDbg
optVal.defer = defer
export default optVal