// form/nc/nc-production/nc-production.js

// eslint-disable-next-line no-unused-vars
const chartRef = ref(null)
const chartOption = ref({})
const chartInitOption = ref({})
const ganttData = ref({})
// fromProduct, fromProduction, fromPurchase, fromSales, fromMaterial, fromEstimateNeed, fromUpperEstimateNeed, fromActionLimit, fromUpperActionLimit, fromNegativeEndBalance, fromUpperNegativeEndBalance = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
const maxWorkDrawCount = 9
let findText = ''
let areaName = 'product_need'
let findProductIdx = {}
let hasNeedWeekProductIdx = {}
let hasNeedProductIdx = {}
let productIdx = {}
let chartProductIdxArr = {}
// let workEstimatedRecordIdx = {}
let needToIdxArr = {}
let needToWeekIdxArr = {}
let needFromWeekIdxArr = {}
let upperIdxArr = {}
let lowerIdxArr = {}
let selectedItemArr = []
let activatedRowIdxArr = []
let gridItemByIdx = {}
let weeklyItemByIdx = {}
let currentDataByIdx = {}

let currentData, currentArea, stockChart, chartStartDate
let chartPref = {}
let prevChartSelected = []
const chartData = []
let gridData, gridDataWeekly, gridDataRowLookup
let gridDataProductIdxArr, gridDataProductIdx, gridDataWeeklyProductIdx, gridDataWeeklyProductIdxArr

let updateFunc
const parseBefore = peg.parseBefore
const needSort = nc.dynamicSort(['level', '>', 'product_id', '>', 'product_row', '>'])
const weeklyNeedSort = nc.dynamicSort(['level', '>', 'product_id', '>'])
function flattenProductNeed(source, target, key) {
	gridData = source
	if (!gridData) {
		return
	}
	if (!nc.isArray(gridData)) {
		for (const productArray of Object.values(gridData)) {
			for (let i = 0; i < productArray.length; i++) {
				productArray[i].product_row = i + 1
			}
		}
		gridData = Object.values(gridData).flat()
	}
	if (!gridData) {
		return
	}
	gridData.sort(needSort)
	gridDataRowLookup = {}
	let item, prev
	gridItemByIdx = {}
	for (let i = 0; i < gridData.length; i++) {
		item = gridData[i]
		item.idx = i + 1
		gridDataRowLookup[item.product_id + ':' + item.product_row] = i + 1
		gridItemByIdx[item.idx] = item
	}
	for (item of gridData) {
		if (item.from_product_id != null && item.from_product_row != null) {
			item.from_row = gridDataRowLookup[item.from_product_id + ':' + item.from_product_row]
			delete item.from_product_id
			delete item.from_product_row
		}
	}
	for (let i = 0; i < gridData.length; i++) {
		item = gridData[i]
		prev = gridData[i - 1]
		item.first_row = !prev || prev.product_id !== item.product_id || undefined
		if (item.first_row && i > 0) {
			gridData[i - 1].last_row = true
		} else {
			delete item.last_row
		}
	}
	if (gridData.length > 0) {
		gridData[gridData.length - 1].last_row = true
	}
	if (target) {
		target[key || 'product_need'] = gridData
	}
}

onMounted(() => {
	rec.reload_data = rec.reload_data || rec.constant?.reload_default >= 1
	nc.incrementalUpdate(state)
	updateFunc = nc.debounce(updateFindTextFilter, (rec.find_delay_seconds && rec.find_delay_seconds * 1000) || 1000, false)
	if (!state.grid?.nats_name) {
		update('update', 1) // first data load
	}
})

onUnmounted(() => {
	if (updateFunc) {
		updateFunc.cancel()
		updateFunc = null
	}
})

// eslint-disable-next-line no-unused-vars
function clickButton(action) {
	if (action === 'clear_find') {
		rec.find_text = ''
		prevCheckBoxValue = -1
		checkBoxClicked()
	} else if (action === 'add_order') {
		if (!rec.allow_save && rec.weekly_need) {
			return
		}
		selectedItemArr = nc.grid.activatedRecordArr(state, 'product_need_weekly')
		if (selectedItemArr.length > 0) {
			clearTimeout(addOrderErrorTimeout)
			clearCellError()
			const saveData = []
			const companyData = {}
			let hasError = false
			for (let item of selectedItemArr) {
				if (item.order_amount > 0 && item.company_id) {
					if (!companyData[item.company_id]) {
						companyData[item.company_id] = {
							company_id: item.company_id,
							ordr: []
						}
						saveData.push(companyData[item.company_id])
					}
					companyData[item.company_id].ordr.push({ product_id: item.product_id, order_amount: item.order_amount })
				} else {
					hasError = true
					item.show_cell_error = true
				}
			}
			if (hasError) {
				const { amountErrorCount, companyErrorCount } = updateAllowSave(null, true)
				const errorParts = []
				if (amountErrorCount > 0) {
					errorParts.push(`There are ${amountErrorCount} rows missing order amount`)
				}
				if (companyErrorCount > 0) {
					errorParts.push(`There are ${companyErrorCount} rows missing supplier company`)
				}
				const errorMessage =
					errorParts.length > 0
						? `${errorParts.join(' and ')}. Please fix before adding a new purchase order`
						: 'Please set order amount and supplier company id to all selected rows before adding a new purchase order'
				message.setInfo(state, nc.l(errorMessage), { callback: clearCellError })
				nc.grid.refreshCells(state, 'product_need_weekly')
				clearTimeout(addOrderErrorTimeout)
				addOrderErrorTimeout = setTimeout(clearCellError, rec.clear_error_timeout_seconds * 1000)
				return
			}
			if (saveData.length > 0) {
				nc.callServer(
					state,
					'save',
					{
						save: [{ table: 'order-purchase', save_preference: 'form/nc/nc-production/save-purchase-order.json', data: saveData }]
					},
					ret => {
						const saveArr = ret?.save && ret.save[0].data
						const orderNumArr = saveArr && saveArr.map(item => item.order_id)
						setTimeout(() => {
							if (orderNumArr && orderNumArr.length == 1) {
								message.setInfo(state, nc.l(`Saved purchase order '${orderNumArr[0]}'`))
							} else if (orderNumArr && orderNumArr.length > 1) {
								message.setInfo(state, nc.l(`Saved ${orderNumArr.length} purchase orders: ${orderNumArr.join(', ')}`))
							} else {
								message.setInfo(state, nc.l(`Purchase order save failed with error: ${ret.error}`))
							}
						}, 0)
					}
				)
			}
		}
	}
}

// work gantt chart
let prevChartType = rec.chart_type
let prevWorkPhaseIdx
function showWorkPhase(checkTab) {
	let item = currentData && selectedItemArr[0]
	if (!item) {
		return
	}
	if (prevWorkPhaseIdx === item.idx) {
		return
	}
	if (maxWorkDrawCount < 1) {
		return
	}
	prevWorkPhaseIdx = item.idx
	let workDrawCount = 0
	let phaseSource = null
	const phaseSourceArray = []
	function findPhaseSource(row) {
		if (row.phase_part && row.phase_part.length > 0) {
			return row
		}
		const productRowArray = gridDataProductIdxArr[row.product_id]
		for (const productRow of productRowArray || []) {
			if (productRow.phase_part && productRow.phase_part.length > 0) {
				return productRow
			}
		}
		return null
	}
	function addPhaseSource(source) {
		if (source == null) {
			return
		}
		if (phaseSource == null) {
			phaseSource = source
		}
		phaseSourceArray.push(source)
		workDrawCount++
	}
	item = rec.weekly_need ? item.calc : item
	addPhaseSource(findPhaseSource(item), false)
	// search chain when phase count limit not reached
	if (workDrawCount < maxWorkDrawCount) {
		let currentIdx = item.idx
		const visitedIdx = {}
		visitedIdx[currentIdx] = true
		while (upperIdxArr[currentIdx] && upperIdxArr[currentIdx].length > 0) {
			currentIdx = upperIdxArr[currentIdx][0]
			if (visitedIdx[currentIdx]) {
				console.error('showWorkPhase: upper chain visited same idx twice', currentDataByIdx[currentIdx])
				break
			}
			visitedIdx[currentIdx] = true
			const row = currentDataByIdx[currentIdx]
			if (row == null) {
				break
			}
			addPhaseSource(findPhaseSource(rec.weekly_need ? row.calc : row), true)
			if (workDrawCount >= maxWorkDrawCount) {
				break
			}
		}
	}
	if (workDrawCount == 0 || (checkTab && !rec.select_phase_view && rec.chart_type !== 'work_phase')) {
		ganttData.value = {}
		if (!rec.weekly_need && checkTab && rec.select_phase_view && rec.chart_type === 'work_phase') {
			rec.chart_type = prevChartType
		}
		return
	}
	if (!rec.weekly_need && rec.select_phase_view && rec.chart_type !== 'work_phase') {
		prevChartType = rec.chart_type
		rec.chart_type = 'work_phase'
	}
	const workArr = []
	rec.selected_production_lot = item.production_order_id || phaseSource.production_order_id
	let phase, work
	let totalHours = 0
	let totalExtraHours = 0
	let totalSetupHours = 0
	let totalAmount = 0
	let totalProducedAmount = 0
	let firstStartDt
	let workIdx = 0
	let lastWork
	let sourceIdx = 0
	let phaseIdx, firstRealWork, matchedWork
	for (const source of phaseSourceArray) {
		lastWork = null
		sourceIdx++
		phaseIdx = 0
		firstRealWork = null
		matchedWork = null
		for (const phaseItem of source.phase_part) {
			if (phaseItem == null) {
				console.error('show work phase: phase_part has undefined entry at index', phaseIdx)
				phaseIdx++
				continue
			}
			phaseIdx++
			phase = phaseItem
			workIdx++
			if (firstStartDt == null && phase.work_phase_number !== 0) {
				firstStartDt = Math.round(phase.start_dt) * 1000
			}
			work = nc.clone(phase)
			/* 			work = phase.record_id
				? nc.clone(workEstimatedRecordIdx[phase.record_id])
				: { resource_id: phase.resource_id, work_phase_number: phase.work_phase_number, product_id: phase.product_id, amount: phase.amount }
			work.calc = { phase_part: phase.part, work_hours: phase.work_hours } */
			work.calc = work.calc || { phase_part: phase.part, work_hours: phase.work_hours }
			if (phase.error) {
				work.calc.error = phase.error
			}
			work.idx = work.work_phase_number
			if (work.idx < workIdx) {
				work.idx = workIdx
			}
			work.label_text = ''
			if (sourceIdx >= 2 && phase.work_phase_number !== 0) {
				if (firstRealWork == null) {
					firstRealWork = work
				}
				if (item.work_phase_number === phase.work_phase_number) {
					matchedWork = work
				}
			}
			work.start_dt = phase.start_dt
			work.end_dt = phase.end_dt
			work.end2_dt = phase.end2_dt
			work.setup_hours = phase.setup_hours
			work.extra_hours = phase.extra_hours
			work.production_order_id = source.production_order_id || item.production_order_id || item.order_id || source.order_id + '-' + source.order_row
			if (source.product_id !== item.product_id) {
				work.calc.upper_product_id = source.product_id
			}
			if (phaseIdx === 1) {
				work.calc.show_product_id = source.product_id
			}
			work.work_phase_state = item.origin || source.origin ? parseBefore(item.origin || source.origin, ' ') : 'production' // 'sales -' --> 'sales'
			work.remaining_amount = phase.amount
			work.produced_amount = 0
			totalHours += phase.work_hours
			totalSetupHours += phase.setup_hours
			totalExtraHours += phase.extra_hours
			totalAmount += phase.amount
			totalProducedAmount += work.produced_amount
			workArr.push(work)
			lastWork = work
		}
		if (sourceIdx >= 2) {
			const targetWork = matchedWork || firstRealWork
			if (targetWork) {
				targetWork.calc.highlight = item.date
			}
		}
		if (lastWork) {
			work = nc.clone(lastWork)
			workIdx++
			work.work_phase_number = work.work_phase_number + 1 // delete work.work_phase_number
			work.idx = workIdx
			work.label_text = ''
			work.start_dt = lastWork.end2_dt
			work.end_dt = work.start_dt
			work.end2_dt = work.start_dt
			work.work_hours = totalHours
			work.setup_hours = totalSetupHours
			work.extra_hours = totalExtraHours
			work.remaining_amount = totalAmount
			work.produced_amount = totalProducedAmount
			work.calc = { tooltip_start_dt: firstStartDt, time_in_ms: false, show_product_id: source.product_id, linked_work_phase: source.calc?.linked_work_phase }
			work.production_order_id = lastWork.production_order_id
			work.resource_id = 'TOTAL'
			work.work_phase_state = 'delivery'
			work.description = 'delivery'
			work.extend = undefined
			workArr.push(work)
		}
	}
	// console.debug('phase graph array:', JSON.stringify(workArr))
	updateGanttData(workArr)
}

function updateGanttData(workArr) {
	if (state?.grid?.gridIdx?.resourceIdx) {
		const capacityIdx = state.grid.gridIdx.capacityIdx
		const resourceIdx = state.grid.gridIdx.resourceIdx
		const usedResource = {}
		const resource = [] // create new array, do not change original data
		const capacityIdx2 = {} // create new array, do not change original data
		let prevItem, res, capArr
		workArr.forEach(item => {
			if (item == null || item.resource_id == null) {
				console.error('show work phase: work array has invalid item', item)
				return
			}
			if (!usedResource[item.resource_id]) {
				usedResource[item.resource_id] = true
				res = resourceIdx[item.resource_id]
				capArr = capacityIdx[item.resource_id]
				if ((!res || !capArr) && prevItem) {
					res = resourceIdx[prevItem.resource_id]
					capArr = capacityIdx[prevItem.resource_id]
				}
				capacityIdx2[item.resource_id] = capArr
				resource.push({ start_dt: item.start_dt, resource_id: item.resource_id, resource_name: res ? res.resource_name : '', capArr: capArr })
				prevItem = item
			}
		})
		ganttData.value = { work_phase: workArr, resource: resource, capacityIdx: capacityIdx2 }
	}
}

let addOrderErrorTimeout
function clearCellError() {
	clearTimeout(addOrderErrorTimeout)
	if (gridDataWeekly) {
		for (let idx = 1; idx <= gridDataWeekly.length; idx++) {
			if (gridDataWeekly[idx - 1].show_cell_error) {
				delete gridDataWeekly[idx - 1].show_cell_error
			}
		}
	}
	nc.grid.refreshCells(state, 'product_need_weekly')
}

function updateAllowSave(arr, countProblems) {
	if (!rec.weekly_need) {
		rec.allow_save = false
	} else {
		const activated = arr || nc.grid.activatedRecordArr(state, 'product_need_weekly')
		rec.allow_save = activated && activated.length > 0
		rec.add_order_error = false
		let amountErrorCount = 0
		let companyErrorCount = 0
		if (activated) {
			for (let item of activated) {
				if (item.order_amount <= 0 || !item.company_id) {
					rec.add_order_error = true
					if (!countProblems) {
						return
					}
					if (item.order_amount <= 0) {
						amountErrorCount++
					}
					if (!item.company_id) {
						companyErrorCount++
					}
				}
			}
		}
		return { amountErrorCount, companyErrorCount }
	}
}

// eslint-disable-next-line no-unused-vars
function gridSelectionChanged(areaName) {
	updateAllowSave()
	const itemArr = nc.grid.activatedRecordArr(state, areaName)
	if (itemArr.length === 0) {
		prevWorkPhaseIdx = null
		if (areaName === 'product_need_weekly') {
			clearHighlights(gridDataWeekly)
			nc.grid.refreshCells(state, areaName)
		} else {
			clearHighlights(currentData)
			nc.grid.refreshCells(state, areaName)
		}
	} else {
		selectGridRow(areaName, itemArr)
	}
}

// eslint-disable-next-line no-unused-vars
function gridRowClicked(area, rowIndex, event, selected) {
	const itemArr = nc.grid.activatedRecordArr(state, area)
	if (itemArr.length === 0) {
		prevWorkPhaseIdx = null
		if (area === 'product_need_weekly') {
			clearHighlights(gridDataWeekly)
			nc.grid.refreshCells(state, area)
		} else {
			clearHighlights(currentData)
			nc.grid.refreshCells(state, area)
		}
	}
	if (itemArr.length > 0) {
		selectGridRow(area, itemArr)
	}
	if (gridDataWeekly && rec.weekly_need) {
		updateAllowSave(itemArr)
	}
}

// eslint-disable-next-line no-unused-vars
function gridRowDoubleClicked(area, rowIndex, event, column) {
	if (column?.field === 'from_row' && event.data?.from_row) {
		// console.debug(` ~ double clicked row ${rowIndex}, filtered row ${event.data.idx}, field: ${column?.field}, go to row: ${event.data.from_row}`)
		nc.grid.scrollToRow(state, area, event.data.from_row, 'scroll-to-view')
		gridRowClicked(area, event.data.from_row)
		return
	}
	if (column?.field === 'scope' && event.data?.scope) {
		const filteredIdx = nc.grid.filteredRecordArr(state, area).map(row => row.idx)
		let targetArr, visibleIdx
		if (event.data.scope.includes('↓')) {
			targetArr = lowerIdxArr[event.data.idx]
			if (targetArr && targetArr.length > 0) {
				visibleIdx = targetArr.find(idx => filteredIdx.includes(idx))
				if (visibleIdx != null) {
					// console.debug(` ~ double clicked row ${rowIndex}, filtered row ${event.data.idx}, field: ${column?.field}, go to lower row: ${visibleIdx}`)
					nc.grid.scrollToRow(state, area, visibleIdx, 'scroll-to-view')
					gridRowClicked(area, visibleIdx)
				}
			}
		}
		if (visibleIdx == null && event.data.scope.includes('↑')) {
			targetArr = upperIdxArr[event.data.idx]
			if (targetArr && targetArr.length > 0) {
				visibleIdx = targetArr.find(idx => filteredIdx.includes(idx))
				if (visibleIdx != null) {
					// console.debug(` ~ double clicked row ${rowIndex}, filtered row ${event.data.idx}, field: ${column?.field}, go to upper row: ${visibleIdx}`)
					nc.grid.scrollToRow(state, area, visibleIdx, 'scroll-to-view')
					gridRowClicked(area, visibleIdx)
				}
			}
		}
	}
}

function selectGridRow(areaName, itemArr, scrollType) {
	selectedItemArr = itemArr
	if (currentData) {
		activatedRowIdxArr.length = 0
		clearHighlights(currentData)
		selectedItemArr.forEach(item => {
			if (!item?.idx) {
				console.debug(`select grid row item does not contain idx:`, item)
				return
			} else {
				activatedRowIdxArr.push(item.idx)
				item.highlight = 'selected'
			}
		})
		activatedRowIdxArr = highlightUpperLower(selectedItemArr, activatedRowIdxArr)
		const filteredIdx = nc.grid.filteredRecordArr(state, areaName).map(row => row.idx)
		let targetArr
		for (const selItem of selectedItemArr) {
			if (selItem.scope != '') {
				selItem.scope_visible = 1
				if (selItem.scope.includes('↓')) {
					targetArr = lowerIdxArr[selItem.idx]
					if (!targetArr || targetArr.length == 0 || !targetArr.some(idx => filteredIdx.includes(idx))) {
						selItem.scope_visible = 0
					}
				}
				if (selItem.scope.includes('↑') && (selItem.scope_visible == 0 || !selItem.scope.includes('↓'))) {
					selItem.scope_visible = 0
					targetArr = upperIdxArr[selItem.idx]
					if (targetArr && targetArr.some(idx => filteredIdx.includes(idx))) {
						selItem.scope_visible = 2
					}
				}
			}
		}
		if (!rec.weekly_need || rec.chart_type === 'work_phase') {
			showWorkPhase(true)
		}
		updateAllowSave()
		if (scrollType !== 'scroll-first') {
			scrollType = 'no-scroll'
		}
		if (scrollType !== 'no-scroll' && activatedRowIdxArr.length > 0) {
			nc.grid.scrollToRow(state, areaName, activatedRowIdxArr[0], scrollType)
		}
		updateChartProduct(activatedRowIdxArr)
		if (areaName === 'product_need_weekly') {
			nc.grid.redrawRows(state, areaName)
		} else {
			nc.grid.refreshCells(state, areaName)
		}
	}
}

function clearHighlights(data) {
	if (!data) {
		return
	}
	for (let idx = 1; idx <= data.length; idx++) {
		const item = data[idx - 1]
		if (item.highlight === 'selected' || item.highlight === 'upper' || item.highlight === 'lower') {
			delete item.highlight
		}
		delete item.scope_visible
	}
}

function updateChart() {
	chartOption.value = null // trigger nc-chart option watch
	if (rec.chart_type === 'work_phase') {
		showWorkPhase()
	} else {
		setTimeout(() => {
			chartOption.value = chartPref[rec.chart_type]
		}, 0)
	}
}

// nc-svg-map
let mapSelected = ref([])

// eslint-disable-next-line no-unused-vars
let mapHover = ref([])

// eslint-disable-next-line no-unused-vars
function mapToggleSelected(param) {
	console.debug('map click', param.id, param.event)
	const i = mapSelected.value.indexOf(param.id)
	if (i === -1) {
		mapSelected.value.push(param.id)
	} else {
		mapSelected.value.splice(i, 1)
	}
}

// eslint-disable-next-line no-unused-vars
function mapDoubleClick(param) {
	console.debug('map double click', param.id, param.event)
}

// nc-gantt needs function mousemove
// eslint-disable-next-line no-unused-vars
function mousemove(id, e) {
	mouse.value.mousemove = `${id}: ${Math.floor(e.offsetX)}x${Math.floor(e.offsetY)}`
}
const mouse = ref({
	mouseover: null,
	mouseout: null,
	mousemove: null,
	click: null,
	drag: null,
	dragend: null
})

// nc-chart stock balance
let defaultDataset, defaultSeries
function updateChartProduct(selectedArray) {
	if (stockChart == null) {
		return
	}
	let chartMin = 0
	let chartMax = 0
	let item, pr
	stockChart.dataset.length = 2
	stockChart.series.length = 1
	let chartId = 0
	if (!defaultDataset) {
		defaultDataset = nc.clone(stockChart.dataset[0])
		defaultSeries = nc.clone(stockChart.series[0])
	}
	const chartSelected = []
	if (selectedArray.length === 0) {
		stockChart.dataset[chartId] = null
		stockChart.series[chartId] = null
	}
	const count = rec.chart_current ? 1 : selectedArray.length < chartPref.max_product_count ? selectedArray.length : chartPref.max_product_count
	for (let i = 0; i < count; i++) {
		item = currentDataByIdx[selectedArray[i]]
		if (item == null) {
			continue // include view?
		}
		pr = productIdx[item.product_id]
		if ((pr && pr.hasBalance) || i === 0) {
			chartSelected.push(item.product_id)
			if (pr) {
				if (pr.minBalance < chartMin) {
					chartMin = pr.minBalance
				}
				if (pr.maxBalance > chartMax) {
					chartMax = pr.maxBalance
				}
			}
			if (stockChart.dataset[chartId] == null) {
				stockChart.dataset[chartId] = nc.clone(defaultDataset)
			}
			stockChart.dataset[chartId].source = chartProductIdxArr[item.product_id] || []
			if (stockChart.series[chartId] == null) {
				stockChart.series[chartId] = nc.clone(defaultSeries)
			}
			stockChart.series[chartId].name = chartPref.show_product_name ? item.name || productIdx[item.product_id]?.name || item.product_id : item.product_id
			stockChart.series[chartId].datasetIndex = chartId
			chartId++
		}
	}
	if (chartSelected.length === prevChartSelected.length && nc.isEqual(chartSelected, prevChartSelected)) {
		return
	}
	prevChartSelected = chartSelected
	chartMin = Math.floor(chartMin)
	if (chartMin < 0) {
		chartMin = chartMin - 1
	}
	chartMax = Math.floor(chartMax) + 1
	stockChart.yAxis[0].min = chartMin
	stockChart.yAxis[1].min = chartMin
	stockChart.yAxis[0].max = chartMax
	stockChart.yAxis[1].max = chartMax
	updateChart()
}

function calculateScopeForData(data, isWeekly) {
	if (!data || data.length < 1) {
		return
	}
	if (data[0].scope != null) {
		return
	}
	let item, idx, fromArr, scope, visitedIdx, parentLevelCount, parentIdx, visibleUpperCount, childCount, childRow, sourceRow, visibleChildCount, parentArray
	for (let i = 0; i < data.length; i++) {
		item = data[i]
		upperIdxArr[item.idx] = []
		lowerIdxArr[item.idx] = []
	}
	for (let i = 0; i < data.length; i++) {
		item = data[i]
		idx = item.idx
		parentIdx = null
		if (isWeekly) {
			fromArr = needFromWeekIdxArr[idx]
			if (fromArr && fromArr.length > 0) {
				parentIdx = fromArr[0]
			}
		} else {
			parentIdx = item.from_row
		}
		if (parentIdx) {
			lowerIdxArr[parentIdx].push(idx)
			upperIdxArr[idx].push(parentIdx)
			if (isWeekly && item.from_row == null) {
				item.from_row = parentIdx
			}
		}
	}
	for (let i = 0; i < data.length; i++) {
		item = data[i]
		idx = item.idx
		parentLevelCount = 0
		parentIdx = idx
		visitedIdx = {}
		while (true) {
			parentArray = upperIdxArr[parentIdx]
			if (!parentArray || parentArray.length === 0) {
				break
			}
			parentLevelCount++
			parentIdx = parentArray[0]
			if (visitedIdx[parentIdx]) {
				break // prevent infinite loops
			}
			visitedIdx[parentIdx] = true
		}
		scope = []
		if (lowerIdxArr[idx].length > 0) {
			childCount = lowerIdxArr[idx].length
			visibleChildCount = 0
			for (let j = 0; j < childCount; j++) {
				childRow = currentDataByIdx[lowerIdxArr[idx][j]]
				sourceRow = isWeekly ? childRow?.calc : childRow
				if (sourceRow && itemFound(sourceRow)) {
					visibleChildCount++
				}
			}
			scope.push(visibleChildCount == childCount ? `↓${childCount}` : `↓${childCount}/${visibleChildCount}`)
		}
		if (parentLevelCount > 0) {
			visibleUpperCount = 0
			parentIdx = idx
			for (let j = 0; j < parentLevelCount; j++) {
				const parentRow = currentDataByIdx[upperIdxArr[parentIdx][0]]
				const sourceRow = isWeekly ? parentRow?.calc : parentRow
				if (sourceRow && itemFound(sourceRow)) {
					visibleUpperCount++
				}
				parentIdx = upperIdxArr[parentIdx][0]
			}
			scope.push(visibleUpperCount == parentLevelCount ? `↑${parentLevelCount}` : `↑${parentLevelCount}/${visibleUpperCount}`)
		}
		item.scope = scope.join(' ')
	}
}

function linkWeeklyToProductNeed() {
	if (!gridDataWeekly || !gridData) {
		return
	}
	let item, pr
	for (let idx = 1; idx <= gridDataWeekly.length; idx++) {
		item = gridDataWeekly[idx - 1]
		item.calc = gridData[gridDataRowLookup[item.calc_idx] - 1]
		item.calc.week_idx = idx
	}
	gridDataProductIdxArr = nc.createIndexArray(gridData, 'product_id')
	gridDataProductIdx = nc.createIndex(gridData, 'product_id', null, 'no-warning').index
	gridDataWeeklyProductIdx = nc.createIndex(gridDataWeekly, 'product_id', null, 'no-warning').index
	gridDataWeeklyProductIdxArr = nc.createIndexArray(gridDataWeekly, 'product_id')
	let arrCalc
	for (let idx = 1; idx <= gridDataWeekly.length; idx++) {
		item = gridDataWeekly[idx - 1]
		pr = productIdx[item.product_id]
		arrCalc = pr && pr.needToIdxArr
		if (arrCalc) {
			const newArr = []
			const newArrIdx = {}
			arrCalc.forEach(rowIdx => {
				const row = gridData[rowIdx - 1]
				if (!newArrIdx[row.product_id] && gridDataWeeklyProductIdx[row.product_id]) {
					newArrIdx[row.product_id] = true
					newArr.push(gridDataWeeklyProductIdx[row.product_id].idx)
				}
			})
			needToWeekIdxArr[idx] = newArr
		}
		if (pr && pr.needFromIdx) {
			const newArr = []
			const row = gridData[pr.needFromIdx - 1]
			if (gridDataWeeklyProductIdx[row.product_id]) {
				newArr.push(gridDataWeeklyProductIdx[row.product_id].idx)
			}
			needFromWeekIdxArr[idx] = newArr
		}
	}
}

function loadWeeklyData(weeklyArray) {
	state.grid.gridIdx['company-supplier-company_id'] = nc.createIndex(state.grid['company-supplier'].data, 'company_id').index
	const companyIdx = state.grid.gridIdx['company-supplier-company_id']
	let item, pr
	gridDataWeekly = weeklyArray
	if (gridDataWeekly) {
		let data = gridDataWeekly
		for (let idx = 1; idx <= data.length; idx++) {
			item = data[idx - 1]
			item['co-supplier'] = companyIdx[item.company_id]
			pr = productIdx[item.product_id]
			item.pr = pr
			if (pr && pr.error && pr.error !== item.error) {
				if (item.error) {
					item.error += '\n' + pr.error
				} else {
					item.error = pr.error
				}
			}
			if (item.error) {
				rec.error_count_week++
			}
			if (!hasNeedWeekProductIdx[item.product_id] && (item.amount > 0 || (pr && (pr.going_amount < 0 || pr.free_amount < 0)))) {
				hasNeedWeekProductIdx[item.product_id] = true
			}
			if (item.balance === 0 && pr && pr.json_data.coming_amount === 0 && pr.json_data.going_amount === 0 && pr.json_data.free_amount === 0) {
				item.is_empty = true
			}
			if (item.date && item.date >= 0 && item.date < chartStartDate) {
				item.before_today = true
			}
		}
		gridDataWeekly.sort(weeklyNeedSort)
		weeklyItemByIdx = {}
		for (let idx = 0; idx < data.length; idx++) {
			data[idx].idx = idx + 1
			weeklyItemByIdx[data[idx].idx] = data[idx]
		}
		state.grid.product_need_weekly.data = gridDataWeekly
	}
	rec.error_count = rec.weekly_need ? rec.error_count_week : rec.error_count_all
	updateAllowSave()
}

function loadProductNeed(productNeed) {
	flattenProductNeed(productNeed)
	if (!gridData) {
		return
	}
	state.grid.product_need.data = gridData
	let data = gridData
	let pr, item, prevItem, item2
	let chartEndDate = new Date()
	chartEndDate.setHours(0, 0, 0, 0)
	chartEndDate.setDate(chartEndDate.getDate() + chartPref?.chart_days || 60)
	chartEndDate = chartEndDate.getTime() / 1000
	for (let idx = 1; idx <= data.length; idx++) {
		item = data[idx - 1]
		if (item.first_row) {
			pr = productIdx[item.product_id]
			pr.minBalance = 0
			pr.maxBalance = 0
			if (item.last_row && item.balance === 0) {
				item.is_empty = true
			}
			if (pr.error && pr.error !== item.error) {
				if (item.error) {
					item.error += '\n' + pr.error
				} else {
					item.error = pr.error
				}
			}
		}
		if (item.error) {
			rec.error_count_all++
		}
		item.pr = pr
		if (pr && item.balance && item.balance !== 0) {
			pr.hasBalance = true
			if (item.balance < pr.minBalance) {
				pr.minBalance = item.balance
			}
			if (item.balance > pr.maxBalance) {
				pr.maxBalance = item.balance
			}
		}
		if (!hasNeedProductIdx[item.product_id] && (item.balance < 0 || (item.need_down && item.need_down > 0))) {
			hasNeedProductIdx[item.product_id] = true
		}
		if (item.from_row) {
			if (!needToIdxArr[item.from_row]) {
				needToIdxArr[item.from_row] = [item.idx]
			} else {
				needToIdxArr[item.from_row].push(item.idx)
			}
		}
		if (item.date && item.date >= 0) {
			if (item.date < chartStartDate) {
				item.before_today = true
			}
			if (stockChart) {
				if (item.first_row) {
					item.balance_date = chartStartDate * 1000
				} else if (item.date > chartStartDate && item.date < chartEndDate) {
					item.balance_date = item.date * 1000
				} else if (item.date >= chartEndDate) {
					item.balance_date = chartEndDate * 1000
				} else {
					item.balance_date = chartStartDate * 1000
				}
				if (item.first_row && item.last_row) {
					item.balance_date = chartStartDate * 1000
					chartData.push(item)
					item2 = { product_id: item.product_id, name: item.name || item.pr?.name, balance_date: chartEndDate * 1000, balance: item.balance }
					chartData.push(item2)
				} else if (!item.first_row) {
					if (prevItem.first_row && item.balance_date >= prevItem.balance_date && item.product_id === prevItem.product_id) {
						prevItem.balance_date = chartStartDate * 1000
						item2 = { product_id: item.product_id, name: prevItem.name || prevItem.pr?.name, balance_date: chartStartDate * 1000, balance: prevItem.balance }
						chartData.push(item2)
					}
					if (item.balance_date > prevItem.balance_date && prevItem.balance !== item.balance && item.product_id === prevItem.product_id) {
						item2 = { product_id: prevItem.product_id, name: prevItem.name || prevItem.pr?.name, balance_date: item.balance_date, balance: prevItem.balance }
						chartData.push(item2)
						chartData.push(item)
					} else {
						chartData.push(item)
					}
					if (item.last_row && prevItem.balance_date < chartEndDate * 1000 && item.balance !== prevItem.balance) {
						item2 = { product_id: item.product_id, name: item.name || item.pr?.name, balance_date: chartEndDate * 1000, balance: item.balance }
						chartData.push(item2)
					}
				}
				prevItem = item
			}
		}
	}
	gridData.forEach(item => {
		pr = productIdx[item.product_id]
		if (needToIdxArr[item.idx] && pr && (pr.needToIdxArr == null || pr.needToIdxArr.length < needToIdxArr[item.idx].length)) {
			pr.needToIdxArr = needToIdxArr[item.idx]
		}
		if (item.from_row && pr && pr.needFromIdx == null) {
			pr.needFromIdx = item.from_row
		}
	})
	linkWeeklyToProductNeed()
	chartProductIdxArr = nc.createIndexArray(chartData, 'product_id')
	if (stockChart) {
		currentData = gridData
		updateChartProduct([])
	}
}

function updateData(ret) {
	if (!state.grid) {
		message.setError(state, "state grid is not defined, can't update data")
		return
	}
	nc.grid.showLoading(state, 'product_need')
	nc.grid.showLoading(state, 'product_need_weekly')
	if (ret.grid?.gridIdx?.chart_option) {
		chartProductIdxArr = {}
		chartPref = ret.grid.gridIdx.chart_option
		stockChart = chartPref.stock_balance
		chartStartDate = new Date()
		chartStartDate.setHours(0, 0, 0, 0)
		chartStartDate = chartStartDate.getTime()
		stockChart.xAxis.min = chartStartDate
		const chartEndDate = new Date()
		chartEndDate.setHours(0, 0, 0, 0)
		chartEndDate.setDate(chartEndDate.getDate() + chartPref.chart_days || 60)
		stockChart.xAxis.max = chartEndDate.getTime()
		chartStartDate = chartStartDate / 1000
		chartInitOption.value = chartPref.init_option
		chartOption.value = stockChart
		chartData.length = 0
	}
	const data = ret.grid?.product?.data
	if (data) {
		productIdx = nc.createIndex(data, 'product_id')
		if (productIdx.warning) {
			message.setWarning(state, `duplicate id's when creating product id index: ${JSON.stringify(productIdx.warning)}`)
		}
		productIdx = productIdx.index
		hasNeedWeekProductIdx = {}
		hasNeedProductIdx = {}
		needToIdxArr = {}
		needToWeekIdxArr = {}
		rec.error_count_all = 0
		rec.error_count_week = 0
	}
	/* if (ret.grid?.['product_work-estimated']) {
		workEstimatedRecordIdx = nc.createIndex(ret.grid['product_work-estimated'].data, 'record_id').index
	} */
	loadUpdateData(ret)
}

function loadUpdateData(ret) {
	if (ret.grid?.product_need_weekly?.data) {
		loadWeeklyData(ret.grid.product_need_weekly.data)
	}
	if (ret.grid?.product_need?.data) {
		loadProductNeed(ret.grid.product_need.data)
	}
	rec.error_count = rec.weekly_need ? rec.error_count_week : rec.error_count_all
	if (state.grid?.product_need) {
		nc.grid.dataChanged(state.grid.product_need, state.grid.product_need.data)
	}
	if (state.grid?.product_need_weekly) {
		nc.grid.dataChanged(state.grid.product_need_weekly, state.grid.product_need_weekly.data)
	}
	needViewChanged()
}

function findAllUpper(upperProductIdx) {
	if (rec.select_upper !== true) {
		return
	}
	let foundUpper
	let arr, item2
	const alreadyFound = {}
	const dataProductIdx = rec.weekly_need ? gridDataWeeklyProductIdxArr : gridDataProductIdxArr
	do {
		foundUpper = false
		for (const productId in upperProductIdx) {
			if (!alreadyFound[productId]) {
				alreadyFound[productId] = true
				arr = dataProductIdx[productId]
				if (!arr) {
					continue
				}
				arr.forEach(item => {
					const fromRow = rec.weekly_need ? item.calc && item.calc.from_row : item.from_row
					if (fromRow) {
						item2 = gridItemByIdx[fromRow]
						if (item2 && !upperProductIdx[item2.product_id]) {
							foundUpper = true
							upperProductIdx[item2.product_id] = true
						}
					}
				})
			}
		}
	} while (foundUpper === true)
}

/*
function countLeafNode(node) {
	if (node == null) {
		return 0
	}
	if (!node.children || node.children.length === 0) {
		return 1
	}
	let leafCount = 0
	for (let i = 0; i < node.children.length; i++) {
		leafCount += countLeafNode(node.children[i])
	}
	return leafCount
}

const chartHeight = ref('15.2em')
// eslint-disable-next-line no-unused-vars
const gridHeight = computed(() => {
	const extra = parseFloat(chartHeight.value)
	console.log(`🚀 ~ extra:`, extra)
	return 'calc(100vh - ' + (37 + extra) + 'em)'
})
	const leafCount = countLeafNode(treeArr[0])
	const height = Math.max(15.2, leafCount * 2.4)
	chartHeight.value = height - 4 + 'em'
	console.log(`🚀 ~ setTreeChart ~ height:`, chartHeight.value)
*/

function getTreeDepth(node) {
	if (node == null) {
		return 0
	}
	let maxDepth = 0
	let depth
	if (node.children) {
		for (let i = 0; i < node.children.length; i++) {
			depth = getTreeDepth(node.children[i])
			if (depth > maxDepth) {
				maxDepth = depth
			}
		}
	}
	return maxDepth + 1
}

function setTreeChart(treeArr) {
	if (treeArr.length > 1) {
		let longestChildrenItem // find treeArr item with most children and use only it
		treeArr.forEach(item => {
			if (!longestChildrenItem) {
				longestChildrenItem = item
				if (!longestChildrenItem.children) {
					longestChildrenItem.children = []
				}
			} else if (item.children && item.children.length > longestChildrenItem.children.length) {
				longestChildrenItem = item
			}
		})
		treeArr = [longestChildrenItem]
	}
	const treeDepth = getTreeDepth(treeArr[0])
	const margin = Math.max(10, 52 - treeDepth * 8)
	chartPref.product_tree.series.length = 1
	chartPref.product_tree.series[0].left = margin + '%'
	chartPref.product_tree.series[0].right = margin + 8 + '%'
	chartPref.product_tree.series[0].data = treeArr
	updateChart()
}

function highlightUpperLower(selectedItems, arr) {
	const itemHighlight = {}
	if (rec.select_upper === true) {
		selectedItems.forEach(item => {
			let current = item.idx
			while (true) {
				const parentArray = upperIdxArr[current]
				if (!parentArray || parentArray.length === 0) {
					break
				}
				const parentIdx = parentArray[0]
				itemHighlight[parentIdx] = 'upper'
				if (arr.indexOf(parentIdx) === -1) {
					arr.push(parentIdx)
				}
				current = parentIdx
			}
		})
	}
	if (rec.select_lower === true) {
		const treeArr = []
		selectedItems.forEach(item => {
			const descendants = lowerIdxArr[item.idx] || []
			descendants.forEach(descendantIdx => {
				const row = currentDataByIdx[descendantIdx]
				if (row.highlight !== 'upper' && row.highlight !== 'selected') {
					itemHighlight[descendantIdx] = 'lower'
					if (arr.indexOf(descendantIdx) === -1) {
						arr.push(descendantIdx)
					}
				}
			})
			const tree = { children: [] }
			buildTreeChart(item.idx, tree)
			if (tree.children[0]) {
				treeArr.push(tree.children[0])
			}
		})
		setTreeChart(treeArr)
	}
	for (const idx in itemHighlight) {
		currentDataByIdx[idx].highlight = itemHighlight[idx]
	}
	return arr
}

function buildTreeChart(parentIdx, parentNode) {
	const row = currentDataByIdx[parentIdx]
	if (!row) {
		return
	}
	const node = {
		name: row.pr.name + ' (' + row.product_id + ')',
		value: nc.round(row.balance, 2),
		parent: row.product_id,
		children: []
	}
	parentNode.children.push(node)
	const children = rec.weekly_need === true ? needToWeekIdxArr[parentIdx] || [] : needToIdxArr[parentIdx] || []
	children.forEach(childIdx => {
		buildTreeChart(childIdx, node)
	})
}

// eslint-disable-next-line no-unused-vars
function doesExternalFilterPass(node) {
	return itemFound(node.data)
}

let findTextArr
function createFindTextArr(text) {
	findTextArr = text
		.toLowerCase()
		.split(',')
		.map(text => text.trim())
		.filter(text => text.length > 0)
}

function itemFound(item) {
	if (item == null) {
		return false
	}
	let allow = true
	if (rec.has_error && !item.error) {
		allow = false
	}
	if (allow && !rec.create_schedule && item.created) {
		return false
	}
	if (allow && rec.create_schedule && item.schedule && !item.created) {
		return false
	}
	if (allow && !rec.virtual_production_order && item.virtual) {
		return false
	}
	if (allow && rec.only_need) {
		if (rec.weekly_need) {
			allow = hasNeedWeekProductIdx[item.product_id]
		} else {
			allow = hasNeedProductIdx[item.product_id]
		}
	}
	if (allow && (rec.hide_empty || rec.only_need) && (item.is_empty || item.pr?.json_data?.need_calculation_in_use === 0)) {
		allow = false
	}
	if (allow && findProductIdx[item.product_id]) {
		return true
	}
	if (allow && findText) {
		if (!findTextArr) {
			createFindTextArr(findText)
		}
		for (let text of findTextArr) {
			allow =
				item.product_id.toLowerCase().includes(text) ||
				(item.name && item.name.toLowerCase().includes(text)) ||
				(item.calc?.name && item.calc.name.toLowerCase().includes(text)) ||
				(item.production_order_id && item.production_order_id.toLowerCase().includes(text)) ||
				(item.order_id && item.order_id.toLowerCase().includes(text)) ||
				(item.company_id && item.company_id.toLowerCase().includes(text)) ||
				false
			if (allow) {
				return true
			}
		}
	}
	return allow === true
}

let prevFindText = ''
let prevCheckBoxValue = 0
function updateFindTextFilter(checkBoxValue) {
	// console.time('* find products')
	findText = rec.find_text.toLowerCase()
	if (prevFindText !== findText) {
		createFindTextArr(findText)
	}
	let found = false
	const doFind = currentData && findText.length >= rec.find_min_characters
	const activatedRowIdxArr = []
	findProductIdx = {}
	if (doFind) {
		let item, sourceItem
		for (let idx = 1; idx <= currentData.length; idx++) {
			item = currentData[idx - 1]
			sourceItem = rec.weekly_need ? item.calc : item
			if (sourceItem && itemFound(sourceItem)) {
				findProductIdx[item.product_id] = true
				if (activatedRowIdxArr.length <= rec.max_activate_row_count + 1) {
					activatedRowIdxArr.push(idx)
				}
				if (sourceItem.level > 1) {
					found = true
				} else if (found === false) {
					found = 0
				}
			}
		}
	}
	if (found === true) {
		findAllUpper(findProductIdx)
	}
	const filterPromise = nc.grid.updateFilter(state, currentArea)
	prevFindText = rec.find_text
	if (checkBoxValue) {
		prevCheckBoxValue = checkBoxValue
	}
	return filterPromise
}

function recalculateScope() {
	if (currentData && currentData[0]) {
		currentData[0].scope = null
		calculateScopeForData(currentData, rec.weekly_need)
	}
}

function needViewChanged() {
	rec.weekly_need = rec.need_view == 'balance' ? true : false
	if (rec.weekly_need) {
		currentArea = 'product_need_weekly'
		currentData = gridDataWeekly
		currentDataByIdx = weeklyItemByIdx
	} else {
		currentArea = 'product_need'
		currentData = gridData
		currentDataByIdx = gridItemByIdx
	}
	recalculateScope()
	updateAllowSave()
	setTimeout(async () => {
		await updateFindTextFilter()
		nc.grid.redrawRows(state, currentArea)
		setTimeout(() => {
			nc.grid.hideLoading(state, currentArea)
		}, 50)
	}, 0)
}

async function checkBoxClicked() {
	nc.grid.showLoading(state, currentArea)
	needViewChanged()
	const checkBoxValue = rec.hide_empty + rec.only_need * 10 + rec.has_error * 100 + rec.weekly_need * 1000
	if (rec.weekly_need) {
		rec.error_count = rec.error_count_week
	} else {
		rec.error_count = rec.error_count_all
	}
	await updateFindTextFilter(checkBoxValue)
	if (selectedItemArr[0]) {
		const productIdArr = selectedItemArr.map(item => item.product_id)
		let idx
		if (rec.weekly_need) {
			idx = gridDataWeeklyProductIdx
		} else {
			idx = gridDataProductIdx
		}
		selectedItemArr.length = productIdArr.length
		for (let index = 0; index < productIdArr.length; index++) {
			selectedItemArr[index] = idx[productIdArr[index]]
		}
		selectGridRow(areaName, selectedItemArr, 'scroll-first') // we are selecting only product's first balance row, they never cause lower or upper level selection
	}
}

// eslint-disable-next-line no-unused-vars
function updateLower() {
	if (rec.weekly_need) {
		areaName = 'product_need_weekly'
	} else {
		areaName = 'product_need'
	}
	selectedItemArr = nc.grid.activatedRecordArr(state, areaName)
	if (selectedItemArr.length > 0) {
		if (rec.select_lower == false) {
			selectedItemArr.length = 1
		}
		selectGridRow(areaName, selectedItemArr)
	}
}

function rebuildAfterIncremental() {
	flattenProductNeed(state.grid.product_need.data)
	state.grid.product_need.data = gridData
	gridDataWeekly = state.grid.product_need_weekly.data
	if (!gridData) {
		return
	}
	if (state.grid.product.data) {
		productIdx = nc.createIndex(state.grid.product.data, 'product_id').index
	}
	let pr
	for (let idx = 1; idx <= gridData.length; idx++) {
		const item = gridData[idx - 1]
		if (item.pr) {
			pr = productIdx[item.product_id]
			if (pr) {
				item.pr = pr
			}
		}
	}
	if (gridDataWeekly) {
		let item, calcIdx
		for (let idx = 1; idx <= gridDataWeekly.length; idx++) {
			item = gridDataWeekly[idx - 1]
			calcIdx = gridDataRowLookup[item.calc_idx]
			if (calcIdx && gridData[calcIdx - 1]) {
				item.calc = gridData[calcIdx - 1]
			}
			pr = productIdx[item.product_id]
			if (pr) {
				item.pr = pr
			}
		}
	}
	gridDataProductIdxArr = nc.createIndexArray(gridData, 'product_id')
	gridDataProductIdx = nc.createIndex(gridData, 'product_id', null, 'no-warning').index
	if (gridDataWeekly) {
		gridDataWeeklyProductIdx = nc.createIndex(gridDataWeekly, 'product_id', null, 'no-warning').index
		gridDataWeeklyProductIdxArr = nc.createIndexArray(gridDataWeekly, 'product_id')
	}
	needViewChanged()
}

function buildReturnParam(gridNameArray, gridIdxArray) {
	return {
		grid: gridNameArray.map(item => (typeof item === 'string' ? { name: item } : item)),
		grid_idx: gridIdxArray
	}
}

function update(action, reload) {
	const prevReload = rec.reload_data
	const area = currentArea || (rec.weekly_need ? 'product_need_weekly' : 'product_need')
	currentArea = area
	nc.grid.showLoading(state, currentArea)
	const scrollPos = nc.grid.getScrollPosition(state, area)
	let topIdx
	if (scrollPos) {
		const filteredRow = nc.grid.filteredRecordArr(state, area)
		if (filteredRow[scrollPos.top]?.idx != null) {
			topIdx = filteredRow[scrollPos.top].idx
		}
	}
	let param = {
		reload_data: reload === 1 ? false : reload ? true : rec.reload_data,
		recalculate: reload === 1 ? false : true,
		create_schedule: rec.create_schedule,
		virtual_production_order: rec.virtual_production_order,
		estimate_order: rec.estimate_order,
		old_work_date: rec.old_work_date,
		max_need_down_loop_count: rec.max_need_down_loop_count,
		minimum_stock: rec.minimum_stock,
		action_limit: rec.action_limit,
		negative_start_balance: rec.negative_start_balance,
		negative_end_balance: rec.negative_end_balance,
		critical_date: rec.critical_date,
		only_sales_need: rec.only_sales_need,
		incremental_update: rec.incremental_update,
		weekly_need: rec.weekly_need,
		return: buildReturnParam(['product_need_weekly', 'product', 'company-supplier'], ['chart_option', 'capacityIdx', 'resourceIdx', 'capacityPreference']),
		not_in_layout: rec.weekly_need ? ['product_work-estimated', 'product_need'] : ['product_work-estimated'],
		cache_waning: false
	}
	nc.callServer(state, 'calc/product-need', { parameter: param }, ret => {
		const wasIncremental = nc.incrementalUpdate(state, ret)
		if (wasIncremental) {
			rebuildAfterIncremental()
		} else if (ret.grid?.product_need || ret.grid?.product_need_weekly) {
			param = {
				reload_data: false,
				recalculate: false,
				return: buildReturnParam(['product_need', 'product_work-estimated'], []),
				not_in_layout: rec.weekly_need ? ['product_work-estimated', 'product_need'] : ['product_work-estimated']
			}
			nc.callServer(state, 'calc/product-need', { parameter: param }, ret2 => {
				updateData(ret2)
				rec.reload_data = prevReload
			})
			updateData(ret)
		}
		rec.find_text = prevFindText
		setTimeout(async () => {
			await updateFindTextFilter(prevCheckBoxValue)
			setTimeout(() => {
				if (topIdx != null) {
					nc.grid.scrollToRow(state, area, topIdx, 'scroll-first')
				}
			}, 100)
		}, 0)
	})
}
