// 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: {}
})

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 (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)
	})
}

// eslint-disable-next-line no-unused-vars
function showPDF() {
	return
}

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
		}
	} */
	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 (!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('local', 0)
		loadRows('external', 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 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 gridRowClicked(area, rowIndex, event, selected) {
	// loc.selectedRecord[area] must be used in buttonEnabled() to trigger calls to buttonEnabled()
	let arr = selectedRecordArr(area)
	if (arr == null) {
		return
	}
	// debugger
	const area2 = otherArea(area)
	loc.selectedRecord[area] = null // force change, trigger buttonEnabled() call
	loc.selectedRecord[area2] = null
	if (event?.data?.link_rec_arr_idx >= 0) {
		setTimeout(() => {
			nc.grid.activateRows(state, area2, event.data.link_rec_arr_idx + 1, 'scroll-first', 'unselectOther')
		}, 10)
	}
	if (selected) {
		const table = tabValue()
		if (state.rec.conn.one_row_select?.[table]) {
			let arr2 = selectedRecordArr(area)
			if (arr2.length !== 1) {
				nc.grid.activateRows(state, area, rowIndex, 'scroll-first', 'unselectOther')
				arr2 = selectedRecordArr(area)
				if (arr2.length > 1) {
					message.setError(state, `only one row can be selected, ${arr2.length} rows selected`)
				}
			}
		}
		setSelectedRecord(area, arr[0]) // loc.selectedRecord[area] = arr[0]
		loadRecordRows(area, arr[0]) // needed??
		if (arr[0]?.link_rec_arr_idx >= 0) {
			setTimeout(() => {
				nc.grid.activateRows(state, area2, arr[0].link_rec_arr_idx + 1, 'scroll-first', 'unselectOther')
			}, 10)
		}
	}
}

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 = tabValue()
	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]: [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 addFileListToQueryParam(call, area, tab) {
	if (state.rec.conn.show_file_list) {
		// extra check to be sure is ok here
		setImportPath(false)
		call.return.grid.push({
			name: area,
			tab: tab,
			file_path: area === 'file_list_import' ? rec.import_path : rec.export_path,
			organization_id: state.rec.connection,
			run_after: [
				{
					function: 'erpSyncReadFolder',
					parameter: state.rec.conn.import_file_parameter // [('xml', 'form/nc/erp-sync/cxml/save/order-sales-xml-convert.json')]
				}
			]
		})
	}
}

let prevConnection
function loadRows(area, rowIndex, event) {
	let table = tabValue()
	const call = { parameter: {} }
	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) {
				let arr = data.grid
				if (state.rec.sort_order && arr && arr[area]?.data && state.rec.conn.sort_allowed?.[table] != false) {
					arr = arr[area].data
					let compareField = state.rec.conn.compare_field?.[table]
					if (compareField && arr[0] && recData(arr[0], compareField)) {
						arr.sort(nc.dynamicSort([compareField, state.rec.sort_order]))
					}
				}
				setGridData(state.grid, data)
				if (state.grid) {
					table = tabValue()
					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
					},
					{
						name: 'external',
						tab: tab,
						organization_id: state.rec.connection
					}
				]
			}
			if (state.rec.conn.show_file_list) {
				addFileListToQueryParam(call, 'file_list_import', tab)
				addFileListToQueryParam(call, 'file_list_export', tab)
			}
		} else if (area === 'local' || area === 'external') {
			call.return = {
				grid: [
					{
						name: area,
						tab: tab,
						organization_id: area == 'external' ? state.rec.connection : undefined
					}
				]
			}
		} else if (area == 'file_list_import' || area == 'file_list_export') {
			if (!state.rec.conn.show_file_list) {
				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)
			call.return.grid = [gridArr[0]]
			callServer(state, 'query', call, callback)
			for (let i = 1; i < gridArr.length; i++) {
				// one call would mean we wait all areas to load before returning to web page
				setTimeout(() => {
					const call2 = nc.clone(call)
					call2.return.grid = [gridArr[i]]
					callServer(state, 'query', call2, callback)
				}, 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 = tabValue()
		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 = tabValue()
		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
					})
				}
				return arr.every(item => {
					return item.change_status === 'new' || item.change_status == null
				})
			}
		}
		return false
	} /* else if (button === 'delete') {
		let area = 'external'
		let table = tabValue()
		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 = tabValue()
	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_arr_idx = -1
			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)
			.reduce((acc, rec) => {
				let fld = rec.field || rec.variable
				fld = peg.parseAfter(fld, tablePrefix + '.')
				acc[fld] = true
				return acc
			}, {})
		doNotCompareField.idx = true
		doNotCompareField.link_rec_arr_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, i) => {
			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) {
				extRec.link_rec_arr_idx = otherIdx
				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_arr_idx = i
				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.link_rec_arr_idx = -1
				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], 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 saveRows(area, action, saveData) {
	let table = tabValue()
	const tablePrefix = state.rec.table_prefix[table]
	if (!tablePrefix) {
		alert(`save table '${table}' is not valid or table prefix is missing`)
		return
	}
	const column = state.grid[area].column
	if (!column || column.length < 1) {
		alert(`save table '${table}' column do not exist`)
		return
	}
	let arr, changeStatus
	if (action !== 'export-id') {
		let arrOrig = selectedRecordArr(area)
		changeStatus = arrOrig[0].change_status
		if (changeStatus && changeStatus !== 'new' && changeStatus !== 'changed') {
			if (!state.rec.conn.overwrite_export?.[table]) {
				alert(`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
				}, [])
		}
		const doNotCloneFields = ['idx', 'link_rec_arr_idx', 'link_rec'] // , 'record_id'
		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) {
				alert(`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/' + table + '.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/' + table + '.json'
		param.save = [
			{
				action: action,
				area: area,
				table: table,
				data: arr,
				rec: state.rec,
				save_preference: preferenceName,
				convert_preference: convertPreferenceName,
				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/' + table + '.json'
		}
		param.save = [
			{
				action: action,
				area: area,
				organization_id: state.rec.connection,
				table: table,
				data: arr,
				rec: state.rec,
				save_preference: preferenceName,
				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') {
		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
		// param.save = [{ table: table, data: arr, save_preference: preferenceName }]
	}
	callServer(state, 'save', param, ret => {
		if (ret.status === 'ok' && !ret.warning && !ret.error) {
			loadRows(otherArea(area), 0)
			if (action === 'export' && area === 'external') {
				loadRows('file_list_export', 0)
			}
		}
		if (ret.result) {
			alert(ret.result)
		}
	})
}

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()
