// form/nc/erp-sync/erp-sync.js
const recData = nc.recData
const recDataSet = nc.recDataSet
const loc = reactive({
	prevCall: '',
	selectedRecord: { local: null, external: null },
	tabIndex: 0, // { local: 0, external: 0 }. tabIndex is same for both, there is only one tab for both areas
	tabPrevLoadTime: 0,
	tabRefreshSeconds: 1.0,
	sort: {},
	sortState: {}
})

const doNotCloneFields = ['idx', 'link_rec']

function callServer(state, call, param, callback) {
	// TODO: this is very bad fix to set rec.tab to state
	// this should work in rec.json file: "tab": "{{conn.tab}}" or "tab": "form/nc/erp-sync/{{schema}}/rec.json/tab"
	// let connection = nc.searchParameter('connection')
	if (state.rec.conn?.query_parameter) {
		param.parameter = Object.assign(state.rec.conn.query_parameter, param.parameter)
		if (rec.conn.firm_id != null) {
			param.parameter.firm_id = rec.conn.firm_id
		}
	}
	if (param?.parameter) {
		if (rec.end_date) {
			rec.end_date2 = dt.addDays(rec.end_date, 1)
		}
		param.parameter.start_date = rec.start_date
		param.parameter.end_date = rec.end_date
		param.parameter.end_date2 = rec.end_date2
	}
	let tab = nc.searchParameter('table')
	if (tab) {
		state.rec.conn.tab = tab
	} else if (!state.rec.conn.tab) {
		state.rec.conn.tab = state.arr.tab[0]?.value
	}
	tab = state.rec.conn.tab
	nc.callServer(state, call, param, ret => {
		if (!state.rec.tab) {
			if (!nc.recordArrayRecord(state.arr.tab, 'value', tab)) {
				// console.error(`🚀 ~ callServer ~ state.rec.tab is missing`)
				// debugger
				// connection has changed, tab does not exist any more
				tab = state.rec.conn.tab || state.arr.tab[0]?.value
			}
			nc.setRec(state, 'tab', tab)
		}
		callback(ret)
	})
}

function showPreview(clickedArea, column, doubleClicked) {
	state.rec.show_modal = false
	if (!doubleClicked && !state.rec.show_preview) {
		return
	}
	let selectedRec = null
	const areaArr = {
		local: state.grid?.local ? selectedRecordArr('local') : [],
		external: state.grid?.external ? selectedRecordArr('external') : [],
		file_list_import: showFileList('import') && state.grid?.file_list_import ? selectedRecordArr('file_list_import') : [],
		file_list_export: showFileList('export') && state.grid?.file_list_export ? selectedRecordArr('file_list_export') : []
	}
	// check clicked area first
	if (clickedArea && areaArr[clickedArea]?.length > 0) {
		selectedRec = areaArr[clickedArea][0]
		state.rec.preview_area = clickedArea
	} else {
		// fallback to first available
		for (const area of ['file_list_import', 'file_list_export', 'external', 'local']) {
			if (areaArr[area]?.length > 0) {
				selectedRec = areaArr[area][0]
				state.rec.preview_area = area
				break
			}
		}
	}
	if (selectedRec == null) {
		state.rec.preview_content = ''
		state.rec.preview_type = ''
		state.rec.preview_area = ''
		return
	}
	if (column?.field === 'json_data.error_json' && selectedRec.json_data?.error_json != null) {
		state.rec.preview_content = JSON.stringify(selectedRec.json_data.error_json, null, 2)
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'json'
		state.rec.show_preview = true
		return
	} else if (column?.field === 'json_data.error' && selectedRec.json_data?.error != null) {
		state.rec.preview_content = selectedRec.json_data.error
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'log'
		state.rec.show_preview = true
		return
	} else if (column?.field === 'json_data.import_text' && selectedRec.json_data?.import_text != null) {
		state.rec.preview_content = nc.formatXml(selectedRec.json_data.import_text)
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'xml'
		state.rec.show_preview = true
		return
	} else if (column?.field === 'json_data.html_text' && selectedRec.json_data?.html_text != null) {
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'xml'
		state.rec.preview_content = selectedRec.json_data.html_text
		state.rec.preview_field = 'html_text'
		if (doubleClicked) {
			state.rec.show_modal = true
		} else {
			state.rec.show_preview = true
		}
		return
	} else if (column?.field === 'json_data.html_text_en' && selectedRec.json_data?.html_text_en != null) {
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'xml'
		state.rec.preview_content = selectedRec.json_data.html_text_en
		state.rec.preview_field = 'html_text_en'
		if (doubleClicked) {
			state.rec.show_modal = true
		} else {
			state.rec.show_preview = true
		}
		return
	}
	// show full record or error
	let previewData = selectedRec
	if (selectedRec.error) {
		previewData = { error: selectedRec.error }
	}
	if (previewData == null) {
		state.rec.preview_content = ''
		state.rec.preview_type = ''
		return
	}
	if (previewData.import_text) {
		state.rec.preview_content = nc.formatXml(previewData.import_text)
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'xml'
	} else if (previewData.error) {
		state.rec.preview_content = previewData.error
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'log'
	} else {
		const clonedData = nc.clone(previewData, doNotCloneFields)
		state.rec.preview_content = JSON.stringify(nc.sortObjectKeys(clonedData), null, 2)
		state.rec.preview_type = 'code'
		state.rec.preview_language = 'json'
	}
	state.rec.show_preview = true
}

function saveText(content, rec, row, fieldName = null, compareValue = null) {
	const nameField = rec.conn.export_file_name_field
	const table = tabTable()
	const filePrefix = typeof rec.conn.export_file_prefix === 'object' ? rec.conn.export_file_prefix[table] : rec.conn.export_file_prefix
	let nameArray = []
	if (filePrefix != null && filePrefix !== '') {
		nameArray.push(filePrefix)
	}
	if (row?.record_type != null && row.record_type !== '') {
		nameArray.push(row.record_type)
	}
	if (nameField != null && row) {
		if (Array.isArray(nameField)) {
			for (let i = 0; i < nameField.length; i++) {
				const fieldValue = recData(row, nameField[i])
				if (fieldValue != null && fieldValue !== '') {
					nameArray.push(fieldValue)
				}
			}
		} else {
			const fieldValue = recData(row, nameField)
			if (fieldValue != null && fieldValue !== '') {
				nameArray.push(fieldValue)
			}
		}
	}
	if (fieldName === 'html_text_en') {
		nameArray.push('en')
	}
	if (compareValue != null && compareValue !== '') {
		nameArray.push(compareValue)
	}
	let fileName = nameArray[0] || ''
	for (let i = 1; i < nameArray.length; i++) {
		const part = nameArray[i]
		if (fileName.charAt(fileName.length - 1) === '-') {
			fileName += part
		} else {
			fileName += '-' + part
		}
	}
	if (fileName === '') {
		fileName = 'erp-sync-file'
	}
	const isHtml = fieldName?.startsWith('html_text') || false
	const typeInfo = nc.file.getMimeType(isHtml ? 'html' : 'xml')
	const dotIdx = fileName.lastIndexOf('.')
	if (dotIdx > 0) {
		fileName = fileName.substring(0, dotIdx)
	}
	if (isHtml && content.startsWith('<?xml')) {
		content = content.substring(content.indexOf('>') + 1).trimStart()
	}
	nc.file.saveText(content, `${fileName}.${typeInfo.ext}`, typeInfo.mime)
}

// eslint-disable-next-line no-unused-vars
function closeHtmlPreview() {
	state.rec.preview_type = ''
	state.rec.preview_content = ''
}

// eslint-disable-next-line no-unused-vars
function downloadPreview() {
	if (!state.rec.preview_content) {
		message.setWarning(state, 'No preview content to download')
		return
	}
	const arr = selectedRecordArr(state.rec.preview_area || 'local')
	const row = arr.length > 0 ? arr[0] : null
	saveText(state.rec.preview_content, state.rec, row, state.rec.preview_field)
}

// eslint-disable-next-line no-unused-vars
function importFile(file, content) {
	if (file == null || content == null) {
		return
	}
	const fileName = file.name
	const fileExt = fileName.split('.').pop().toLowerCase()
	const base64Content = content.split(',')[1]
	const fileContent = Buffer.from(base64Content, 'base64').toString('utf-8')
	state.rec.preview_type = 'code'
	if (fileExt === 'xml') {
		state.rec.preview_content = nc.formatXml(fileContent)
		state.rec.preview_language = 'xml'
		// First, validate the XML using /validate endpoint
		const validateParam = {
			xml_input: fileContent,
			schema_path: state.rec.conn?.schema_path,
			no_out: true,
			quiet: true,
			no_net: true
		}
		nc.callServer(state, 'validate', validateParam, ret => {
			const validateRec = ret.rec || {}
			if (validateRec.validation_error_count && validateRec.validation_error_count > 0) {
				const errorText = validateRec.validation_errors ? validateRec.validation_errors.map(err => err.message || err).join('\n') : 'XML validation failed'
				message.setError(state, `Validation failed:\n${errorText}`)
				return
			}
			if (validateRec.validation_result) {
				message.setInfo(state, 'XML is valid')
			}
			// Then convert to HTML using /convert endpoint
			const convertParam = {
				convert_name: null, // Use inline conversion
				convert_input: fileContent,
				convert_type: 'xml_to_html',
				xslt: null // Use default identity transform or schema-based XSLT
			}
			nc.callServer(state, 'convert', convertParam, convertRet => {
				const convertRec = convertRet.rec || {}
				if (convertRec.convert_error) {
					message.setWarning(state, `HTML conversion: ${convertRec.convert_error}`)
				}
				if (convertRec.convert_output) {
					state.rec.preview_content = convertRec.convert_output
					state.rec.preview_type = 'html'
					state.rec.preview_language = 'html'
				} else {
					// No HTML output, show XML with validation info
					const header = `<!-- XML Valid\nSchema: ${validateParam.schema_path || 'none'}\n-->\n\n`
					state.rec.preview_content = header + fileContent
				}
			})
		})
	} else if (fileExt === 'json') {
		state.rec.preview_language = 'json'
	} else if (fileExt === 'lua') {
		state.rec.preview_language = 'lua'
	} else if (fileExt === 'js' || fileExt === 'javascript') {
		state.rec.preview_language = 'javascript'
	} else if (fileExt === 'html' || fileExt === 'htm') {
		state.rec.preview_language = 'html'
		state.rec.preview_type = 'html'
	} else if (fileExt === 'css') {
		state.rec.preview_language = 'css'
	} else if (fileExt === 'md' || fileExt === 'markdown') {
		state.rec.preview_language = 'markdown'
	} else if (fileExt === 'txt') {
		state.rec.preview_language = 'plaintext'
	} else {
		message.setWarning(state, `File type '${fileExt}' preview is limited. File: ${fileName}`)
		state.rec.preview_language = 'plaintext'
	}
}

function setSchema() {
	state.rec.schema = state.rec.connection
	const arrRec = nc.recordArrayRecord(arr.connection, 'value', state.rec.connection)
	if (arrRec?.schema) {
		state.rec.schema = arrRec.schema
	}
}

function tabChanged(value, idx) {
	// tabChanged(value, idx, event, tabArrValue)
	/* if (idx === loc.tabIndex) {
		let newTime = new Date()
		if (dt.secondsBetween(loc.tabPrevLoadTime, newTime) < loc.tabRefreshSeconds) {
			return
		}
	} */
	// check if tab has connection key, treat as connection change
	const tabRec = nc.recordArrayRecord(state.arr.tab, 'value', value)
	if (tabRec?.connection) {
		state.rec.connection = tabRec.connection
	}
	setSchema()
	loc.selectedRecord.local = null
	loc.selectedRecord.external = null
	// // clear data from screen
	if (state.grid?.local) {
		nc.grid.dataChanged(state.grid.local, [])
	}
	if (state.grid?.external) {
		nc.grid.dataChanged(state.grid.external, [])
	}
	if (state.grid?.file_list_import) {
		nc.grid.dataChanged(state.grid.file_list_import, [])
	}
	if (state.grid?.file_list_export) {
		nc.grid.dataChanged(state.grid.file_list_export, [])
	}
	if (!nc.isNil(idx)) {
		loc.tabIndex = idx
	}
	// loc.tabIndex.external = loc.tabIndex // external tab is not shown, keep tabs in sync
	if (value == 'connection') {
		nc.gotoUrl(state, {
			url: 'erp-sync',
			query: { connection: state.rec.connection } // + '?connection=' + state.rec.connection
		})
		loadRows('local', 0, 'created') // will call init
	} else {
		nc.gotoUrl(state, {
			url: 'erp-sync',
			query: { table: value, connection: state.rec.connection } // + '?connection=' + state.rec.connection
		})
		loadRows('all', 0)
	}
	loc.tabPrevLoadTime = new Date()
}

// eslint-disable-next-line no-unused-vars
function showRows() {
	nc.grid.updateFilter(state, 'local')
	nc.grid.updateFilter(state, 'external')
}

function tabValue() {
	return state.rec.tab || state.arr.tab[0]?.value
}

function tabTable() {
	let tab = tabValue()
	let table = nc.recordArrayField(state.arr.tab, 'value', tab, 'table')
	if (table) {
		return table
	}
	return tab
}

function getRowTablePrefix(table) {
	return state.rec.table_prefix[table] + 'r'
}

// eslint-disable-next-line no-unused-vars
function gridSortChanged(area, sortState) {
	const areaName = tabValue()
	loc.sortState[areaName] = sortState // TODO: how this is used?
}

function linkRecIndex(area, rec) {
	if (rec?.link_rec == null) {
		return -1
	}
	return rec.link_rec.idx || -1
}

// eslint-disable-next-line no-unused-vars
function gridRowDoubleClicked(area, rowIndex, event, column) {
	const htmlField = column?.field === 'json_data.html_text' || column?.field === 'json_data.html_text_en'
	if (htmlField) {
		showPreview(area, column, true)
	} else {
		state.rec.show_preview = !state.rec.show_preview
		if (state.rec.show_preview) {
			showPreview(area, column, true)
		}
	}
}

function gridRowClicked(area, rowIndex, event, selected, column) {
	let arr = selectedRecordArr(area)
	if (arr == null) {
		return
	}
	const area2 = otherArea(area)
	loc.selectedRecord[area] = null // force change, trigger buttonEnabled() call
	loc.selectedRecord[area2] = null
	if (event?.data?.link_rec) {
		const linkIdx = linkRecIndex(area2, event.data)
		if (linkIdx >= 0) {
			setTimeout(() => {
				nc.grid.activateRows(state, area2, linkIdx, 'scroll-first', 'unselectOther')
			}, 10)
		}
	}
	if (selected) {
		setSelectedRecord(area, arr[0]) // loc.selectedRecord[area] = arr[0]
		loadRecordRows(area, arr[0]) // needed??
		const linkIdx2 = linkRecIndex(area2, arr[0])
		if (linkIdx2 >= 0) {
			setTimeout(() => {
				nc.grid.activateRows(state, area2, linkIdx2, 'scroll-first', 'unselectOther')
			}, 10)
		}
	}
	showPreview(area, column)
}

function setMoloniCreditNoteRow(rec, data) {
	if (!rec.ordr) {
		message.setWarning(state, `no order row data for order '${rec.order_id}'`)
	} else {
		const moloniProductIdArr = nc.createIndexArray(data.ordr, 'product_id')
		function setRow(item, moloniRec) {
			if (item.json_data == null) {
				item.json_data = {}
			}
			item.json_data.transfer_id = moloniRec.json_data.document_product_id
		}
		rec.ordr.forEach((item, index) => {
			if (!data.ordr) {
				message.setWarning(state, `no Moloni order row data for product '${item.product_id}' invoice row ${index + 1}`)
			} else {
				const moloniRecArr = moloniProductIdArr[item.product_id]
				if (!moloniRecArr || moloniRecArr.length < 1) {
					message.setWarning(state, `no Moloni order row product found, product '${item.product_id}', invoice row ${index + 1}`)
				} else {
					moloniRecArr.sort(nc.dynamicSort(['delivery_amount', '>', 'unit_price', '>']))
					let used = false
					let amount = item.delivery_amount
					if (amount < 0) {
						amount = -amount
					}
					for (let moloniRec of moloniRecArr) {
						if (!used && !moloniRec.calc_used && amount === moloniRec.delivery_amount && item.unit_price <= moloniRec.unit_price) {
							used = true
							moloniRec.calc_used = true
							setRow(item, moloniRec)
							break
						}
					}
					if (!used) {
						for (let moloniRec of moloniRecArr) {
							if (!used && !moloniRec.calc_used && amount <= moloniRec.delivery_amount && item.unit_price <= moloniRec.unit_price) {
								used = true
								moloniRec.calc_used = true
								setRow(item, moloniRec)
								break
							}
						}
					}
					if (!used) {
						for (let moloniRec of moloniRecArr) {
							if (!used && amount <= moloniRec.delivery_amount && item.unit_price <= moloniRec.unit_price) {
								used = true
								moloniRec.calc_used = true
								setRow(item, moloniRec)
								break
							}
						}
					}
					if (!used) {
						message.setWarning(state, `could not find Moloni invoice row for, product '${item.product_id}', invoice row ${index + 1}`)
					}
				}
			}
		})
	}
}

function setSelectedRecord(area, rec) {
	loc.selectedRecord[area] = null
	setTimeout(() => {
		loc.selectedRecord[area] = rec
	}, 0)
}

function loadRecordRows(area, rec) {
	// console.debug('loadRecordRows', area, rec)
	let grid = state.grid[area]
	let table = tabTable()
	let rowTable = state.rec.conn.save_row_table?.[table]
	if (area === 'local' && grid && rowTable) {
		setSelectedRecord(area, rec)
		return
	}
	rowTable = state.rec.conn.load_row_table?.[table]
	if (area === 'local' && grid && rowTable) {
		const rowTablePrefix = getRowTablePrefix(table)
		const compareField = state.rec.conn.compare_field?.[table]
		const param = {
			name: `form/nc/erp-sync/${state.rec.connection}/query-${state.rec.main_connection}/${rowTable}.json`,
			parameter: {
				[compareField]: [recData(rec, compareField)],
				column: `form/nc/erp-sync/${state.rec.connection}/column/${rowTable}.json`
			}
		}
		callServer(state, 'query/data', param, ret => {
			if (ret.data) {
				rec[rowTablePrefix] = ret.data
				// console.debug('loadRecordRows callback rec, result: ', rec, ret)
				setSelectedRecord(area, rec)
			}
		})
		const externalRowTable = state.rec.conn.load_external_row_table?.[table]
		if (externalRowTable) {
			const param = {
				name: `form/nc/erp-sync/${state.rec.connection}/query/${externalRowTable.table}.json`,
				parameter: {
					[externalRowTable.parameter]: recData(rec, externalRowTable.field),
					column: `form/nc/erp-sync/${state.rec.connection}/column/${externalRowTable.table}.json`
				}
			}
			callServer(state, 'query/data', param, ret => {
				if (ret.data && ret.data.length > 0) {
					const data = ret.data[0]
					rec[rowTablePrefix + '-' + state.rec.connection] = data
					if (externalRowTable.set_field) {
						externalRowTable.set_field.forEach(item => {
							const val = recData(data, item.from)
							if (val != null) {
								recDataSet(rec, item.to, val)
							}
						})
						if ((state.rec.connection == 'moloni' && rowTable == 'order_row-credit_note') || table === 'order-credit_note') {
							setMoloniCreditNoteRow(rec, data)
						}
						nc.grid.redrawRows(state, area)
					}
					// console.debug(`loadRecordRows external callback rec, result: `, rec, ret)
					// setSelectedRecord(area, rec)
				}
			})
		}
	}
}

function showFileList(type) {
	const show = state.rec.conn?.show_file_list
	if (show == null) {
		return false
	}
	if (show === true) {
		return true
	}
	const tab = state.rec.tab
	const showImport = show === 'import' || show?.import?.includes(tab) || false
	const showExport = show === 'export' || show?.export?.includes(tab) || false
	if (type === 'import') {
		return showImport
	}
	if (type === 'export') {
		return showExport
	}
	if (type === 'both') {
		return showImport && showExport
	}
	// no type = any visible (import OR export)
	return showImport || showExport
}

function setOrganizationId() {
	let orgId, externalOrgId
	if (state.arr?.conn?.firm_id) {
		const firmRec = nc.recordArrayRecord(state.arr.conn.firm_id, 'value', state.rec.conn.firm_id)
		const tab = tabValue()
		const firmTab = firmRec.tab?.[tab]
		if (firmTab) {
			orgId = firmTab.local_organization_id
			externalOrgId = firmTab.external_organization_id
		}
	}
	return { orgId, externalOrgId }
}

function addFileListToQueryParam(call, area, tab) {
	const type = area === 'file_list_import' ? 'import' : 'export'
	const { orgId, externalOrgId } = setOrganizationId()
	if (showFileList(type)) {
		setImportPath(false)
		const gridInfo = state.grid?.[area]?.info || {}
		call.return.grid.push({
			name: area,
			tab: tab,
			file_path: area === 'file_list_import' ? rec.import_path : rec.export_path,
			organization_id: area === 'local' ? orgId : externalOrgId,
			info: {
				organization_id: gridInfo.organization_id,
				schema: gridInfo.schema,
				query_table: gridInfo.query_table
			},
			run_after: [
				{
					function: 'erpSyncReadFolder'
				}
			]
		})
	}
}

let prevConnection
function loadRows(area, rowIndex, event) {
	let table = tabTable()
	const { orgId, externalOrgId } = setOrganizationId()
	const conn = state.rec.conn.query_parameter ? nc.clone(state.rec.conn) : state.rec.conn
	delete conn.query_parameter
	const call = { parameter: {}, rec: { conn: conn } }
	const callback = data => {
		if (data) {
			if (prevConnection !== rec.connection) {
				prevConnection = rec.connection
				let tab = state.arr.tab[0]?.value
				nc.setRec(state, 'tab', tab)
			}
			changeConnection()
			if (data.grid) {
				setGridData(state.grid, data)
				if (state.rec.sort_order) {
					const compareField = state.rec.conn.compare_field?.[table]
					if (compareField && state.rec.conn.sort_allowed?.[table] != false) {
						for (let grid of Object.values(data.grid)) {
							if (grid?.data && grid.data[0] && recData(grid.data[0], compareField) != null && typeof nc?.grid?.sort === 'function') {
								nc.grid.sort(state, grid, compareField, state.rec.sort_order)
							}
						}
					}
				}
				if (state.grid) {
					table = tabTable()
					if (table && state.grid && state.grid.sort) {
						if (loc.sortState[table]) {
							state.grid.sort = loc.sortState[table] // todo: sortState?
						} else {
							loc.sortState[table] = state.grid.sort
						}
					}
					if (!nc.isNil(rowIndex)) {
						gridRowClicked(otherArea(area), rowIndex, event, true)
					}
				}
			}
		}
	}
	function queryTab(area) {
		call.name = 'form/nc/erp-sync/' + state.rec.schema + '/grid.json'
		const tab = tabValue()
		if (area === 'all') {
			call.return = {
				grid: [
					{
						name: 'local',
						tab: tab,
						organization_id: orgId,
						info: {
							organization_id: state.grid?.local?.info?.organization_id,
							schema: state.grid?.local?.info?.schema,
							query_table: state.grid?.local?.info?.query_table
						}
					},
					{
						name: 'external',
						tab: tab,
						organization_id: externalOrgId,
						info: {
							organization_id: state.grid?.external?.info?.organization_id,
							schema: state.grid?.external?.info?.schema,
							query_table: state.grid?.external?.info?.query_table
						}
					}
				]
			}
			if (showFileList('import')) {
				addFileListToQueryParam(call, 'file_list_import', tab)
			}
			if (showFileList('export')) {
				addFileListToQueryParam(call, 'file_list_export', tab)
			}
		} else if (area === 'local' || area === 'external') {
			call.return = {
				grid: [
					{
						name: area,
						tab: tab,
						organization_id: area === 'local' ? orgId : externalOrgId,
						info: {
							organization_id: state.grid?.[area]?.info?.organization_id,
							schema: state.grid?.[area]?.info?.schema,
							query_table: state.grid?.[area]?.info?.query_table
						}
					}
				]
			}
		} else if (area == 'file_list_import' || area == 'file_list_export') {
			const type = area === 'file_list_import' ? 'import' : 'export'
			if (!showFileList(type)) {
				return
			}
			call.return = {
				grid: []
			}
			addFileListToQueryParam(call, area, tab)
		} else {
			console.error(`loadRows invalid area '${area}'`)
		}
		if (call.return?.grid?.length > 1) {
			const gridArr = nc.clone(call.return.grid)
			for (let i = 0; i < gridArr.length; i++) {
				setTimeout(() => {
					const call2 = nc.clone(call)
					call2.return.grid = [gridArr[i]]
					callServer(state, 'query', call2, callback) // one call would mean we wait all areas to load before returning to web page
				}, 0)
			}
		} else {
			callServer(state, 'query', call, callback)
		}
	}
	if (event !== 'created' && table !== '') {
		queryTab(area)
	} else {
		// first call, set tab index on callback
		call.name = 'form/nc/erp-sync/init.json'
		let connection = state.rec.connection // nc.searchParameter('connection')
		table = nc.searchParameter('table')
		if (connection || table) {
			if (connection) {
				// todo: check connection validity
				call.parameter.schema = state.rec.schema
				call.parameter.connection = connection
				state.rec.connection = connection
			}
			if (table) {
				call.parameter.table = table // todo: check table validity
				call.parameter.tab = table
				state.rec.conn.tab = table
				state.rec.tab = table
			}
		}
		callServer(state, 'query', call, ret => {
			if (ret && ret.rec) {
				// for (let area in ret.rec.tab) {
				state.rec.tab = ret.rec.conn.tab
				if (area === 'local') {
					// only local tab is in use
					let idx = nc.recordArrayIndex(state.arr.tab, 'value', state.rec.conn.tab)
					if (!nc.isNil(idx) && idx !== loc.tabIndex) {
						if (idx < 0) {
							// ne need this for error, we may have changed connection and old table does not exist any more
							// message.setError(state, `tab index default value '${ret.rec.conn.tab}' was not found from tab array`)
							idx = 0
						}
						// console.debug('+++++ loc.tabIndex', idx, loc.tabIndex)
						loc.tabPrevLoadTime = new Date() // prevent double-load
						loc.tabIndex = idx
					}
					loadRows('all') // local load is much faster, better to draw it first
				}
				callback(ret)
				// }
			}
		})
	}
}

function selectedRecordArr(area) {
	return nc.grid.activatedRecordArr(state, area)
}

// eslint-disable-next-line no-unused-vars
function buttonEnabled(button, area) {
	if (!state.rec.conn.import_allowed) {
		return false // page has not been initialized yet
	}
	if (button === 'export') {
		let table = tabTable()
		let allowed = state.rec.conn.export_allowed
		allowed = allowed && allowed[table]
		if (loc.selectedRecord[area] && allowed) {
			const arr = selectedRecordArr(area)
			if (arr.length < 1) {
				return false
			}
			if (button === 'export' && area === 'external' && state.rec.conn?.export_file_allowed?.[table]) {
				const allowed = state.rec.conn.export_file_allowed[table]
				if (arr[0][allowed.field] != allowed.value) {
					return false
				}
			}
			if (state.rec.conn.save_row_table?.[table]) {
				return true
			}
			if (state.rec.conn.load_row_table?.[table]) {
				let rowPrefix = getRowTablePrefix(table)
				return arr[0][rowPrefix] != null
			}
			return true
		}
		return false
	} else if (button === 'import') {
		let table = tabTable()
		let allowed = state.rec.conn.import_allowed
		allowed = allowed && allowed[table]
		if (loc.selectedRecord[area] && allowed) {
			let arr = selectedRecordArr(area)
			if (arr.length > 0) {
				let updateAllowed = state.rec.conn.update_allowed?.[table]
				if (updateAllowed) {
					return arr.every(item => {
						if (item.product_id) {
							return item.product_id && (item.change_status === 'new' || item.change_status === 'changed')
						}
						return item.change_status === 'new' || item.change_status === 'changed' || item.change_status == null || state.rec.conn.overwrite_import?.[table]
					})
				}
				return arr.every(item => {
					return item.change_status === 'new' || item.change_status == null
				})
			}
		}
		return false
	} else if (button === 'download_attachment') {
		if (loc.selectedRecord[area]) {
			let arr = selectedRecordArr(area)
			return (arr && arr.length > 0) || false
		}
		return false
	} else if (button === 'load_detail') {
		if (loc.selectedRecord[area]) {
			let arr = selectedRecordArr(area)
			return (arr && arr.length > 0) || false
		}
		return false
	} /* else if (button === 'delete') {
		let area = 'external'
		let table = tabTable()
		let deleteAllowed = state.rec.conn.update_allowed?.[table]
		if (loc.selectedRecord[area] && deleteAllowed) {
			let arr = selectedRecordArr(area)
			if (arr.length < 1) {return false}
			if (arr[0][state.rec.conn.compare_field[table]] === '') {
				return false
			}
			return true
		}
		return false
	} */
	return false
}

// eslint-disable-next-line no-unused-vars
function isExternalFilterPresent() {
	if (state.rec.show_new !== true || state.rec.show_changed !== true || state.rec.show_equal !== true) {
		return true
	}
	return false
}

// eslint-disable-next-line no-unused-vars
function doesExternalFilterPass(node) {
	switch (node.data.change_status) {
		case 'new':
			return !(state.rec.show_new === false)
		case 'equal':
			return !(state.rec.show_equal === false)
		case 'changed':
			return !(state.rec.show_changed === false)
		default:
			return true
	}
}

function setGridData(grid) {
	let table = tabTable()
	let localData = grid && grid.local && grid.local.data
	let externalData = grid && grid.external && grid.external.data
	let otherIdx = -1
	let localCompareIdx = {}
	let tablePrefix = state.rec.table_prefix[table]
	let compareField = state.rec.conn.compare_field?.[table]
	const exportField = state.rec.conn.export_save_field?.[table]
	const exportSaveArr = []
	const roundDecimals = state.rec.round_decimals
	const fixVatPrice = state.rec.conn.fix_vat_price?.[table]
	let fixVatPriceError = false
	if (localData && compareField && localData.length > 0) {
		// init local calculated data
		let localCompareData
		for (let i = 0; i < localData.length; i++) {
			let rec = localData[i]
			localCompareData = recData(rec, compareField)
			if (!nc.isNil(localCompareData)) {
				localCompareData = localCompareData.toString().toLowerCase().trimEnd()
				localCompareIdx[localCompareData] = i
			}
			rec.link_rec = null
			rec.change_status = 'new'
			rec.change_field = null
			if (fixVatPrice) {
				const price = nc.round(rec.price_without_vat + rec.vat_price, roundDecimals)
				if (isNaN(price) == true) {
					if (!fixVatPriceError) {
						fixVatPriceError = true
						message.setWarning(
							state,
							`Vat price could not be calculated, check fields price_without_vat: ${rec.price_without_vat}, vat_price: ${rec.vat_price}, price_with_vat: ${rec.price_with_vat}`
						)
					}
				} else {
					rec.price_with_vat = price
					if (rec.price_with_vat < 0) {
						rec.price_with_vat = -rec.price_with_vat
					}
					if (rec.vat_price < 0) {
						rec.vat_price = -rec.vat_price
					}
					if (rec.price_without_vat < 0) {
						rec.price_without_vat = -rec.price_without_vat
					}
				}
			}
		}
	}
	if (externalData && externalData.length > 0) {
		// init external calculated data and set status on both data
		if (fixVatPrice && externalData[0].price_with_vat == null) {
			for (let i = 0; i < externalData.length; i++) {
				let rec = externalData[i]
				rec.price_with_vat = Number(rec.price_without_vat)
				rec.vat_price = Number(rec.vat_price)
				rec.price_without_vat = nc.round(rec.price_with_vat - rec.vat_price, roundDecimals)
			}
		}
		/* if (localData && localData.length > 0) {
			const setFromField = grid.external.column
				.filter(rec => rec.set_from_field != null)
				.reduce((acc, rec) => {
					acc.push({ from: peg.parseAfter(rec.set_from_field, tablePrefix + '.'), to: peg.parseAfter(rec.field, tablePrefix + '.') })
					return acc
				}, [])
			setFromField.forEach(item => {}) // copy data from local to external
		} */
		let dateField = grid.external.column
			.filter(rec => rec.type === 'date')
			.reduce((acc, rec) => {
				acc.push(peg.parseAfter(rec.field, tablePrefix + '.'))
				return acc
			}, [])
		// if data is not loaded then do not compare values
		grid.external.column.forEach(item => {
			if (item.organization_id?.[state.rec.connection]?.load === false || item.organization_id?.['']?.load === false || item.export === false || item.static_export != null) {
				item.change_compare = false
			} else if (item.change_compare == null) {
				item.change_compare = true
			}
		})
		let doNotCompareField = grid.external.column
			.filter(rec => rec.change_compare === false || rec.export === false)
			.reduce((acc, rec) => {
				let fld = rec.field || rec.variable
				fld = peg.parseAfter(fld, tablePrefix + '.')
				acc[fld] = true
				fld = rec.organization_id?.[state.rec.connection]?.field || rec.organization_id?.['']?.field
				if (fld) {
					fld = peg.parseAfter(fld, tablePrefix + '.')
					acc[fld] = true
				}
				return acc
			}, {})
		doNotCompareField.idx = true
		doNotCompareField.link_rec = true
		doNotCompareField.change_status = true
		doNotCompareField.change_field = true
		const compareAmount = state.rec.compare_amount
		window.recDataDifferent = function (rec, colDef, value) {
			// function for grid cell_class_rules compare
			let fldValue = recData(rec, colDef.field)
			if (fldValue == value) {
				return false
			}
			if (rec.link_rec) {
				// dates are formatted so slick param 3 value is not data value but formatted value
				if (recData(rec.link_rec, colDef.field) == fldValue) {
					return false
				}
			}
			if (compareAmount && (typeof fldValue === 'number' || typeof value === 'number')) {
				if (typeof fldValue === 'string') {
					fldValue = Number(fldValue.replace(',', '.'))
				}
				if (typeof value === 'string') {
					value = Number(value.replace(',', '.'))
				}
				if (Math.abs(fldValue - value) < colDef.compare_amount || compareAmount) {
					return false
				}
			}
			return true
		}
		const loadExternalTable = state.rec.conn?.load_external_table?.[table]
		let overwriteExportWarning = false
		const compareRecord = extRec => {
			if (compareField == null) {
				return
			}
			let compareData = recData(extRec, compareField)
			if (!extRec || nc.isNil(compareData)) {
				message.setWarning(state, `table '${table}' local data compare field '${compareField}' data is null`, { delay: true })
				return false
			}
			for (const key of dateField) {
				extRec[key] = peg.parseBefore(extRec[key], 'T') // does not work with object fields
			}
			if (localData && localData.length > 0 && localData[0] && externalData[0]) {
				compareData = compareData.toString().toLowerCase().trimEnd()
				otherIdx = localCompareIdx[compareData]
				if (otherIdx == null) {
					otherIdx = -1
				}
				// 1 and "1" must be true here. so do not use ===, use ==
				// compareData.toString().toLowerCase() == localCompareData.toString().toLowerCase()
			}
			if (loadExternalTable && loadExternalTable.set_field) {
				// loadExternalTable has been set to state.rec.conn.load_external_table[connection][table]
				loadExternalTable.set_field.forEach(item => {
					let localValue
					if (otherIdx >= 0) {
						localValue = recData(localData[otherIdx], item.from)
					}
					if (localValue != null) {
						recDataSet(extRec, item.to, localValue) // set local data to external
					} else if (item.default_value != null) {
						recDataSet(extRec, item.to, item.default_value) // set local data to external
					}
				})
			}
			if (otherIdx >= 0) {
				const localRec = localData[otherIdx]
				extRec.link_rec = localRec
				if (state.rec.conn.overwrite_export?.[table]) {
					if (!extRec.record_id) {
						if (!overwriteExportWarning) {
							message.setWarning(state, `external record_id is missing, table '${table}'`, { delay: true })
						}
						overwriteExportWarning = true
					} else {
						localRec.record_id = extRec.record_id
					}
				}
				// extRec.record_id = localRec.record_id
				if (extRec.note) {
					// only \n in local version, remove \r
					extRec.note = peg.replace(extRec.note, '\r', '')
				}
				const failField = {} // for debugging changes
				extRec.change_status = nc.isEqualLoose(extRec, localRec, doNotCompareField, state.rec.warning_char, failField, null, compareAmount) ? 'equal' : 'changed'
				localData[otherIdx].link_rec = extRec // link_rec will be circular reference
				localData[otherIdx].change_status = extRec.change_status
				if (extRec.change_status === 'changed') {
					extRec.change_field = failField.field
					localData[otherIdx].change_field = extRec.change_field
					// console.log(`🚀 ~ file: erp-sync.js ~ compareRecord ~ failField`, failField)
				} else {
					extRec.change_field = null
					localData[otherIdx].change_field = null
				}
				if (exportField) {
					const localId = recData(localRec, exportField)
					const extId = recData(extRec, exportField)
					if (localId != extId) {
						// != means allow num / string comparison
						const saveItem = {
							record_id: localRec.record_id,
							[exportField]: extId
						}
						exportSaveArr.push(saveItem)
					}
				}
			} else {
				// console.debug(state, `   local record was not found for compareData, tablePrefix '${tablePrefix}' compareField '${compareField}', table '${table}'`)
				extRec.link_rec = null
				extRec.change_status = 'new'
				extRec.change_field = null
				/*
				if (compareData != null) {
							recDataSet(extRec, compareField, '')
				} */
				// extRec.record_id = ''
			}
		}
		for (let i = 0; i < externalData.length; i++) {
			compareRecord(externalData[i])
		}
		if (exportField && exportSaveArr.length > 0) {
			if (loc.prevCall === 'area-local') {
				loc.prevCall = '' // exit save/load loop after local rows load
			} else {
				if (loc.prevCall === 'export-id') {
					loc.prevCall = 'area-local' // load local rows after save
					loadRows('local')
				} else {
					loc.prevCall = 'export-id'
					saveRows('local', 'export-id', [{ table: table, data: exportSaveArr }]) // save_preference: save/generic_save.json
				}
			}
		}
	}
	// update grids
	nc.grid.redrawRows(state, 'local')
	nc.grid.redrawRows(state, 'external')
}

function setImportPath(loadFileList) {
	if (state.rec) {
		state.rec.import_path = state.rec.conn?.import_path || ''
		state.rec.export_path = state.rec.conn?.export_path || ''
		if (state.rec.file_status !== '') {
			state.rec.import_path = state.rec.import_path + state.rec.file_status + '/'
			state.rec.export_path = state.rec.export_path + state.rec.file_status + '/'
		}
	}
	if (loadFileList) {
		loadRows('file_list_import')
		loadRows('file_list_export')
	}
}

function changeConnection(updateData) {
	if (updateData) {
		tabChanged('connection', 1)
	}
	if (state.rec) {
		if (state.rec.logo_path_external_template) {
			nc.setRec(state, 'logo_path_external', state.rec.logo_path_external_template.replace('_connection_', state.rec.connection))
		}
		setImportPath(false)
	}
}

function otherArea(area) {
	return area === 'external' ? 'local' : 'external'
}

function processOneRow(itemArray, processFunction, doneFunction) {
	let index = 0
	function next() {
		if (index >= itemArray.length) {
			if (doneFunction) {
				doneFunction()
			}
			return
		}
		processFunction(itemArray[index], index, () => {
			index++
			next()
		})
	}
	next()
}

// eslint-disable-next-line no-unused-vars
function openPath(type) {
	const path = type === 'import' ? rec.import_path : rec.export_path
	if (!path || path === '') {
		message.setWarning(state, `Path is empty for ${type}`)
		return
	}
	const param = { path: path }
	nc.callServer(state, 'path', param, ret => {
		if (ret.error) {
			message.setError(state, `Failed to open path: ${ret.error}`)
		}
	})
}

function saveRows(area, action, saveData) {
	let table = tabTable()
	let tab = tabValue()
	const { orgId, externalOrgId } = setOrganizationId()
	const tablePrefix = state.rec.table_prefix[table]
	if (!tablePrefix) {
		message.setWarning(state, `save table '${table}' is not valid or table prefix is missing`)
		return
	}
	const column = state.grid[area].column
	if (!column || column.length < 1) {
		message.setWarning(state, `save table '${table}' column do not exist`)
		return
	}
	let arr, changeStatus
	if (action !== 'export-id') {
		let arrOrig = selectedRecordArr(area)
		if (arrOrig.length < 1) {
			message.setWarning(state, `No rows selected for table '${table}'`)
			return
		}
		changeStatus = arrOrig[0].change_status
		if (changeStatus && changeStatus !== 'new' && changeStatus !== 'changed') {
			if (action === 'export' && !state.rec.conn.overwrite_export?.[table]) {
				message.setWarning(state, `You can ${action} only new or changed records, table '${table}'`)
				return
			}
			if (action === 'import' && !state.rec.conn.overwrite_import?.[table]) {
				message.setWarning(state, `You can ${action} only new or changed records, table '${table}'`)
				return
			}
		}
		let deleteKey
		if (action === 'import' || action === 'export') {
			let fld
			deleteKey = column
				.filter(item => item[action] === false)
				.reduce((acc, rec) => {
					fld = peg.parseAfter(rec.field, tablePrefix + '.')
					if (fld && fld !== 'record_id' && fld !== 'change_id') {
						acc.push()
					}
					return acc
				}, [])
		}
		arr = nc.clone(arrOrig, doNotCloneFields)
		for (const item of arr) {
			// this will delete fields from grid array, reload later to fix them
			if (item.change_status !== changeStatus) {
				message.setWarning(state, `Save can contain only one type at a same time, table '${table}', save type '${changeStatus}'`)
				return
			}
			if (deleteKey) {
				for (const key of deleteKey) {
					delete item[key]
				}
			}
			if (item.change_status === 'new') {
				delete item.record_id
				delete item.change_id
			}
			delete item.change_status
		}
	}
	const param = {}
	let preferenceName
	if (action === 'import') {
		preferenceName = 'form/nc/erp-sync/' + state.rec.main_connection + '/save/' + tab + '.json'
		if (area === 'file_list_import') {
			preferenceName = null
			if (typeof state.rec.conn.import_file_save_preference === 'object') {
				preferenceName = state.rec.conn.import_file_save_preference.name
			} else if (state.rec.conn.import_file_save_preference) {
				preferenceName = state.rec.conn.import_file_save_preference // string
			}
		}
		if (preferenceName == null || area !== 'file_list_import') {
			if (typeof state.rec.conn.import_save_preference === 'object') {
				preferenceName = state.rec.conn.import_save_preference.name
			} else if (state.rec.conn.import_save_preference) {
				preferenceName = state.rec.conn.import_save_preference // string
			}
		}
		const convertPreferenceName = 'form/nc/erp-sync/' + state.rec.schema + '/convert-local/' + tab + '.json'
		const gridInfo = state.grid?.[area]?.info || {}
		param.save = [
			{
				action: action,
				area: area,
				organization_id: area === 'file_list_import' ? externalOrgId : orgId, // file list import is to external, import from external to local
				table: table,
				data: arr,
				rec: state.rec,
				save_preference: preferenceName,
				convert_preference: convertPreferenceName,
				info: {
					organization_id: gridInfo.organization_id,
					schema: gridInfo.schema,
					query_table: gridInfo.query_table
				},
				run: [{ function: 'erpSyncSave' }]
			}
		]
	} else if (action === 'export-id') {
		param.save = saveData
	} else if (action === 'export') {
		preferenceName = null
		if (area === 'external') {
			// export from external to file_list_export
			if (typeof state.rec.conn.export_file_save_preference === 'object') {
				preferenceName = state.rec.conn.export_file_save_preference.name
			} else if (state.rec.conn.export_file_save_preference) {
				preferenceName = state.rec.conn.export_file_save_preference // string
			}
		}
		if (preferenceName == null) {
			preferenceName = state.rec.conn.export_save_preference || 'form/nc/erp-sync/' + state.rec.schema + '/save/' + tab + '.json'
		}
		const gridInfo = state.grid?.[area]?.info || {}
		param.save = [
			{
				action: action,
				area: area,
				organization_id: area === 'local' ? externalOrgId : undefined, // export is from local to external, or external to file list
				table: table,
				data: arr,
				rec: state.rec,
				save_preference: preferenceName,
				show_file_list: showFileList('export'),
				info: {
					organization_id: gridInfo.organization_id,
					schema: gridInfo.schema,
					query_table: gridInfo.query_table
				},
				run: [{ function: 'erpSyncSave' }]
			}
		]
	} else if (action === 'delete') {
		const compareField = state.rec.conn.compare_field?.[table]
		const doDelete = confirm(`Are you sure you want to delete ${table} id ${recData(arr[0], compareField)} ?`)
		if (doDelete === false) {
			return
		}
		const arr2 = arr.map(rec => {
			return rec.json_data.transfer_id
		})
		param.delete = [{ table: table, data: arr2 }]
	} else {
		message.setWarning(state, 'action is not export or delete')
		return
	}
	/* if (param.save && changeStatus === 'changed') {
		if (param.save[0].parameter == null) {
			param.save[0].parameter = { call_tag: 'call_update_url' }
		} else {
			param.save[0].parameter.call_tag = 'call_update_url'
		}
	} */
	let run = state.rec.conn.export_allowed?.[table]?.run
	if (typeof run === 'string' || typeof run === 'object') {
		param.run = run
	}
	const oneRowArea = state.rec.conn.one_row?.[table]
	if ((oneRowArea === area || oneRowArea === true) && arr && arr.length > 0) {
		processOneRow(
			arr,
			(item, itemIndex, done) => {
				param.save[0].data = [item]
				callServer(state, 'save', param, ret => {
					if (ret.result) {
						message.setWarning(state, ret.result)
					}
					done()
				})
			},
			() => {
				loadRows(otherArea(area), 0)
				if (action === 'export' && area === 'external') {
					loadRows('file_list_export', 0)
				} else if (action === 'export' && area === 'local' && state.rec.export_also_to_file) {
					loadRows('file_list_export', 0)
				}
			}
		)
		return
	}
	callServer(state, 'save', param, ret => {
		if (ret.status === 'ok') {
			loadRows(otherArea(area), 0)
			if (action === 'export' && area === 'external') {
				loadRows('file_list_export', 0)
			} else if (action === 'export' && area === 'local' && state.rec.export_also_to_file) {
				/* 	if (selectedRecordArr('external').length > 0) {
					saveRows('external', 'export') // this is bad, erp-sync backend will export file
				} else { */
				loadRows('file_list_export', 0)
				// }
			}
		}
		if (ret.result) {
			message.setWarning(state, ret.result)
		}
	})
}

function getAttachmentField() {
	const attachmentField = state.rec.conn?.attachment_field
	if (attachmentField == null) {
		return null
	}
	if (Array.isArray(attachmentField)) {
		return attachmentField.length > 0 ? attachmentField : null
	}
	const table = tabTable()
	const fieldForTable = attachmentField[table]
	if (Array.isArray(fieldForTable) && fieldForTable.length > 0) {
		return fieldForTable
	}
	return null
}

// eslint-disable-next-line no-unused-vars
function downloadAttachment(area) {
	const fieldArray = getAttachmentField()
	if (fieldArray == null) {
		message.setWarning(state, 'No attachment fields defined')
		return
	}
	const arr = selectedRecordArr(area)
	if (arr.length === 0) {
		message.setWarning(state, 'No rows selected')
		return
	}
	for (let i = 0; i < arr.length; i++) {
		const row = arr[i]
		let compareValue = null
		if (arr.length > 1) {
			const compareField = state.rec.conn.compare_field?.[tabTable()]
			compareValue = compareField ? recData(row, compareField) : i + 1
		}
		for (let j = 0; j < fieldArray.length; j++) {
			const fieldName = fieldArray[j]
			let content = row.json_data?.[fieldName]
			if (content == null) {
				continue
			}
			saveText(content, state.rec, row, fieldName, compareValue)
		}
	}
}

// eslint-disable-next-line no-unused-vars
function loadDetail() {
	const tab = tabValue()
	const config = state.rec.conn?.load_detail?.[tab]
	if (!config) {
		return
	}
	const arr = selectedRecordArr('external')
	if (!arr.length) {
		message.setWarning(state, 'No rows selected')
		return
	}
	const paramArray = config.parameter
	if (!paramArray || !paramArray.length) {
		message.setWarning(state, 'No parameter defined for load_detail')
		return
	}
	const param = { parameter: {} }
	const idArray = {}
	const idArrIdx = {}
	for (const paramField of paramArray) {
		idArray[paramField.name] = []
		idArrIdx[paramField.name] = []
		for (let i = 0; i < arr.length; i++) {
			const id = recData(arr[i], paramField.value)
			if (id != null) {
				idArray[paramField.name].push(id)
				idArrIdx[paramField.name].push(i)
			}
		}
		if (idArray[paramField.name].length) {
			param.parameter[paramField.name] = idArray[paramField.name]
		}
	}
	if (Object.keys(param.parameter).length === 0) {
		message.setWarning(state, 'No valid parameter values found in selected rows')
		return
	}
	for (const key in config) {
		if (key !== 'tab' && key !== 'query' && key !== 'parameter') {
			param[key] = config[key]
		}
	}
	const callback = (ret, arrIndex) => {
		const data = ret.grid?.external?.data || ret.data
		if (data) {
			let resultRec, arrRec
			for (let i = 0; i < data.length; i++) {
				resultRec = data[i]
				arrRec = arr[arrIndex != null ? arrIndex : i]
				if (arrRec && resultRec) {
					nc.copyDeepRec(resultRec, arrRec, config.copy_field || [], true) // only copy fields that are defined in copy_field, do not delete other fields in arrRec
				} else if (resultRec) {
					message.setWarning(state, `load detail result record was not found for row ${i}`)
				} else {
					message.setWarning(state, `load detail result row was not found for result row ${i}`)
				}
			}
			nc.grid.redrawRows(state, 'external')
		}
	}
	const { externalOrgId } = setOrganizationId()
	if (config.tab) {
		param.name = 'form/nc/erp-sync/' + state.rec.schema + '/grid.json'
		param.return = {
			grid: [
				{
					name: 'external',
					tab: config.tab,
					organization_id: externalOrgId,
					run_after: [{ function: 'erpSyncPageLoad' }]
				}
			]
		}
		callServer(state, 'query', param, callback)
	} else if (config.query) {
		param.name = config.query
		param.parameter.organization_id = externalOrgId
		param.parameter.rec = { conn: state.rec.conn, tab: tab }
		/* query json itself has run_after -tag
		param.parameter.run_after = [{function: 'erpSyncPageLoad'}] */
		if (config.one_row && paramArray?.length) {
			const taskArray = []
			for (const paramField of paramArray) {
				for (let i = 0; i < idArray[paramField.name].length; i++) {
					taskArray.push({ name: paramField.name, idIndex: i, arrIndex: idArrIdx[paramField.name][i] })
				}
			}
			processOneRow(taskArray, (task, taskIndex, done) => {
				param.parameter[task.name] = [idArray[task.name][task.idIndex]]
				callServer(state, 'query/data', param, ret => {
					callback(ret, task.arrIndex)
					done()
				})
			})
		} else {
			callServer(state, 'query/data', param, callback)
		}
	}
}

function init() {
	// console.debug('nc-erp-sync created')
	let connection = nc.searchParameter('connection')
	if (connection !== '' && connection !== state.rec.connection) {
		// console.error(`🚀 ~ init ~ state.rec.connection '${state.rec.connection}' is not same as url connection '${connection}'`)
		state.rec.connection = connection
	}
	setSchema()
	let tab = nc.searchParameter('table')
	if (tab !== '') {
		if (tab !== state.rec.conn.tab) {
			// in init we take tab from url without warning
			state.rec.conn.tab = tab
		}
	} else if (!state.rec.conn.tab) {
		state.rec.conn.tab = state.arr.tab[0]?.value
	}
	state.rec.tab = state.rec.conn.tab
	if (!state.grid?.local?.data?.length) {
		// lod if grid does not exist or length is 0
		loadRows('local', 0, 'created') // first call will be init, it will load both areas at the same call
	} else if (!state.grid?.external?.data) {
		loadRows('external', 0, 'created') //  local data is loaded on first load, but external is not
	}
}
init()
