/**
 * 深度合併兩個 JavaScript 物件（可遞迴合併巢狀物件和陣列）
 * Deep (recursive) merge of two JavaScript objects
 *
 * @package deepmerge-plus
 * @version 3.0.2
 */
import isMergeableObject from 'is-mergeable-object';
import { ITSPickExtra, ITSWriteable, ITSToWriteableArray, ITSPropertyKey } from 'ts-type';

/**
 * 建立空目標物件 / Create empty target object
 *
 * 根據輸入值類型回傳空陣列或空物件
 * 用於深度合併時建立目標的空白副本
 *
 * Returns empty array or object based on input value type
 * Used to create a blank copy of target during deep merge
 *
 * @param val - 輸入值 / Input value (array or object)
 * @returns 空陣列（若輸入為陣列）或空物件（若輸入為物件）/ Empty array (if input is array) or empty object (if input is object)
 *
 * @example
 * emptyTarget([1, 2, 3]) // 回傳 []
 * emptyTarget({ a: 1 })  // 回傳 {}
 */
function emptyTarget(val)
{
	return Array.isArray(val) ? [] : {}
}

/**
 * 決定是否啟用深度複製功能
 * 預設為 true（啟用），除非選項明確設為 false
 *
 * Determine whether to enable deep cloning
 * Default is true (enabled), unless options explicitly set to false
 *
 * @note 注意事項 / Note:
 * 即使 `options.clone` 設為 `false`，在某些情況下仍可能會產生 clone。
 * 這是正常現象，因為深度合併需要確保輸出物件與輸入物件相互獨立，
 * 以避免意外修改原始資料。
 *
 * Even if `options.clone` is set to `false`, cloning may still occur
 * in certain situations. This is expected behavior because deep merge
 * needs to ensure the output object is independent from the input objects
 * to prevent accidental modification of original data.
 */
export function _shouldClone(optionsRuntime?: IOptions)
{
	// do not change or remove this code logic
	// const clone = !optionsRuntime || optionsRuntime.clone !== false;
	const clone = optionsRuntime?.clone !== false;
	return clone;
}

/**
 * 檢查目標物件中該 key 的值是否為 undefined
 * Check if target key value is undefined
 */
export function _defaultCheckShouldNotUpsertValue(value, optionsRuntime: IOptions, tmpRuntimeTarget: ICache, tmpRuntimeData: ITmpRuntimeData)
{
	const targetValue = tmpRuntimeTarget.target?.[tmpRuntimeTarget.key];
	const shouldNotUpsertValue = !_isUndefined(targetValue);
	return shouldNotUpsertValue;
}

/**
 * 除非另有指定，否則複製值 / Clone value unless otherwise specified
 *
 * 決定是否需要深度複製輸入值：
 * 1. 檢查選項中的 clone 設定
 * 2. 判斷值是否為可合併物件
 * 3. 若需要合併則遞迴複製
 *
 * Determines whether to deep clone the input value:
 * 1. Check clone option in optionsArgument
 * 2. Check if value is mergeable object
 * 3. Recursively clone if mergeable
 *
 * @param value - 要複製的值 / Value to clone
 * @param optionsRuntime - 合併選項 / Merge options
 * @param tmpRuntimeTarget - 內部快取資訊 / Internal cache information
 * @returns 複製後的值或原始值 / Cloned value or original value
 */
function cloneUnlessOtherwiseSpecified(value, optionsRuntime: IOptions, tmpRuntimeTarget: ICache, tmpRuntimeData: ITmpRuntimeData)
{
	/**
	 * 決定是否啟用深度複製功能
	 * 預設為 true（啟用），除非選項明確設為 false
	 *
	 * Determine whether to enable deep cloning
	 * Default is true (enabled), unless options explicitly set to false
	 *
	 * @note 即使 `options.clone` 設為 `false`，在某些情況下仍可能會產生 clone
	 * @note Even if `options.clone` is set to `false`, cloning may still occur
	 */
	const clone = _shouldClone(optionsRuntime);

	/**
	 * 判斷當前值是否需要進行深度複製
	 * 條件：clone 為 true 且值為可合併物件
	 * Determine if current value needs deep cloning
	 * Condition: clone is true AND value is a mergeable object
	 */
	const bool = clone && _isMergeableObject(value, optionsRuntime, tmpRuntimeTarget, tmpRuntimeData);

	/**
	 * 根據 bool 條件決定回傳值
	 * 若需要複製：建立空目標並遞迴合併（深度複製）
	 * 若不需要複製：直接回傳原始值
	 * Return value based on bool condition
	 * If needs cloning: create empty target and recursively merge (deep clone)
	 * If not needs cloning: return original value directly
	 */
	let ret = (bool)
		? deepmerge(emptyTarget(value), value, optionsRuntime, tmpRuntimeData)
		: value;

	/**
	 * keyValueOrMode 模式處理
	 * 當啟用此模式且值不需要合併時，優先使用目標中已存在的值
	 * 檢查順序：destination > target > source
	 * keyValueOrMode mode handling
	 * When enabled and value doesn't need merging, prefer existing values in target
	 *
	 * 由左至右的概念 / Left-to-right concept:
	 * merge(target, source, options) 中：
	 * - target 是「左側」（left）- 被合併的物件，代表現有值
	 * - source 是「右側」（right）- 要合併進來的物件，代表新值
	 * - 當 source 值為 falsy 時，使用「左側」的值作為回退
	 *
	 * In merge(target, source, options):
	 * - target is "left" - the object being merged INTO, represents existing values
	 * - source is "right" - the object being merged FROM, represents new values
	 * - When source value is falsy, use "left" value as fallback
	 *
	 * Check order: destination > target > source
	 */
	if (optionsRuntime?.keyValueOrMode && !bool && tmpRuntimeTarget && ('key' in tmpRuntimeTarget))
	{
		/** 檢查目的物件中是否有現有值 / Check if destination has existing value */
		if (tmpRuntimeTarget.destination)
		{
			//console.log('destination', tmpRuntimeTarget.destination[tmpRuntimeTarget.key], ret, tmpRuntimeTarget.key);
			ret = tmpRuntimeTarget.destination[tmpRuntimeTarget.key] || ret;
		}

		/** 檢查目標物件中是否有現有值 / Check if target has existing value */
		if (tmpRuntimeTarget.target)
		{
			//console.log('target', tmpRuntimeTarget.target[tmpRuntimeTarget.key], ret, tmpRuntimeTarget.key);
			ret = tmpRuntimeTarget.target[tmpRuntimeTarget.key] || ret;
		}

		/** 檢查來源物件中是否有現有值 / Check if source has existing value */
		if (tmpRuntimeTarget.source)
		{
			//console.log('source', tmpRuntimeTarget.source[tmpRuntimeTarget.key], ret, tmpRuntimeTarget.key);
			ret = tmpRuntimeTarget.source[tmpRuntimeTarget.key] || ret;
		}
	}

	// console.dir({
	// 	value,
	// 	ret,
	// 	optionsRuntime,
	// 	tmpRuntimeTarget,
	// 	tmpRuntimeData,
	// 	targetValue: tmpRuntimeTarget.target?.[tmpRuntimeTarget.key],
	// 	bool,
	// }, { depth: 2 });

	/**
	 * keyValueUpsertMode 模式處理
	 * 當啟用此模式時，根據條件決定是否應該使用目標中的現有值
	 * keyValueUpsertMode mode handling
	 * When enabled, decides whether to use existing value in target based on condition
	 *
	 * 由左至右的概念 / Left-to-right concept:
	 * merge(target, source, options) 中：
	 * - target 是「左側」（left）- 被合併的物件，代表現有值
	 * - source 是「右側」（right）- 要合併進來的物件，代表新值
	 * - 合併結果會傾向保留「左側」的值（除非條件允許使用「右側」的值）
	 *
	 * In merge(target, source, options):
	 * - target is "left" - the object being merged INTO, represents existing values
	 * - source is "right" - the object being merged FROM, represents new values
	 * - The merged result tends to preserve "left" values (unless conditions allow using "right" values)
	 *
	 * 為 true 時：只會在目標 key 的值為 undefined 時才使用目標的值
	 * 為 function 時：只會在函式回傳 true 時使用目標的值
	 *
	 * When true: Only use target's value when target key value is undefined
	 * When function: Only use target's value when function returns true
	 */
	if (optionsRuntime?.keyValueUpsertMode && !bool && tmpRuntimeTarget && ('key' in tmpRuntimeTarget))
	{
		/** 檢查是否應該使用目標的值 / Check if should use target's value */
		let shouldNotUpsertValue = false;

		if (typeof optionsRuntime.keyValueUpsertMode === 'function')
		{
			/** 使用自訂函式判斷 / Use custom function to determine */
			shouldNotUpsertValue = optionsRuntime.keyValueUpsertMode(value, optionsRuntime, tmpRuntimeTarget, tmpRuntimeData);
		}
		else if (optionsRuntime.keyValueUpsertMode === true)
		{
			/** 檢查目標物件中該 key 的值是否為 undefined / Check if target key value is undefined */
			const targetValue = tmpRuntimeTarget.target?.[tmpRuntimeTarget.key];
			shouldNotUpsertValue = !_isUndefined(targetValue);
		}

		/** 若符合條件，則使用目標中的現有值 / If condition met, use existing value in target */
		if (shouldNotUpsertValue)
		{
			let upsertValue: any;

			/** 檢查目的物件中是否有現有值 / Check if destination has existing value */
			if (tmpRuntimeTarget.destination)
			{
				upsertValue ??= tmpRuntimeTarget.destination[tmpRuntimeTarget.key];
			}

			/** 檢查目標物件中是否有現有值 / Check if target has existing value */
			if (tmpRuntimeTarget.target)
			{
				upsertValue ??= tmpRuntimeTarget.target[tmpRuntimeTarget.key];
			}

			ret = upsertValue;
		}

		// console.dir({
		// 	shouldNotUpsertValue,
		// 	key: tmpRuntimeTarget.key,
		// 	ret,
		// 	value,
		// });
	}

	return ret;
}

export function _isUndefined(value: unknown): value is undefined
{
	return typeof value === 'undefined'
}

export function _isNull(value: unknown): value is null
{
	return value === null
}

export function _isNullOrUndefined(value: unknown): value is null
{
	return value === null || typeof value === 'undefined'
}

/**
 * 檢查值是否為可合併物件 / Check if value is a mergeable object
 *
 * 判斷邏輯：
 * 1. 先檢查選項中的自訂 isMergeableObject 函式
 * 2. 若無自訂函式，檢查 SYMBOL_IS_MERGEABLE 符號
 * 3. 若無符號，使用預設的 isMergeableObject 函式
 *
 * Decision logic:
 * 1. First check custom isMergeableObject function in options
 * 2. If no custom function, check SYMBOL_IS_MERGEABLE symbol
 * 3. If no symbol, use default isMergeableObject function
 *
 * @param value - 要檢查的值 / Value to check
 * @param optionsArgument - 合併選項 / Merge options
 * @param tmpRuntimeTarget - 內部快取資訊 / Internal cache information
 * @returns 是否可合併 / Whether mergeable
 */
export function _isMergeableObject(value, optionsArgument: IOptions, tmpRuntimeTarget: ICache, tmpRuntimeData: ITmpRuntimeData): boolean
{
	/**
	 * 步驟 1：嘗試使用自訂 isMergeableObject 函式（若存在）
	 * Step 1: Try using custom isMergeableObject function (if exists)
	 */
	let ret = optionsArgument?.isMergeableObject?.(value, isMergeableObject, optionsArgument, tmpRuntimeTarget, tmpRuntimeData) as any;

	/**
	 * 步驟 2：若自訂函式未回傳明確結果（null 或 undefined）
	 * 檢查 SYMBOL_IS_MERGEABLE 符號
	 * Step 2: If custom function doesn't return definite result (null or undefined)
	 * Check SYMBOL_IS_MERGEABLE symbol
	 */
	if (ret === null || typeof ret === 'undefined')
	{
		/** 檢查值是否帶有可合併標記符號 / Check if value has mergeable marker symbol */
		if ((typeof value?.[SYMBOL_IS_MERGEABLE] == 'boolean'))
		{
			ret = value[SYMBOL_IS_MERGEABLE];
		}
		/** 若無符號，使用預設的 isMergeableObject 函式 / If no symbol, use default isMergeableObject function */
		else
		{
			ret = isMergeableObject(value);
		}
	}
	return ret
}

/**
 * 預設陣列合併函式 / Default array merge function
 *
 * 將來源陣列串接到目標陣列，並對每個元素進行深度複製
 * 這是 deepmerge 的預設陣列合併策略
 *
 * Concatenates source array to target array and deep clones each element
 * This is the default array merge strategy for deepmerge
 *
 * 處理邏輯 / Processing logic:
 * 1. 將來源陣列串接到目標陣列末尾
 * 2. 對每個元素進行條件性深度複製
 * 3. 確保合併後的陣列元素是獨立的副本
 *
 * @param target - 目標陣列（被合併的陣列）/ Target array (the array being merged into)
 * @param source - 來源陣列（要合併的陣列）/ Source array (the array to merge from)
 * @param optionsArgument - 合併選項 / Merge options
 * @returns 合併後的新陣列 / New merged array
 */
function defaultArrayMerge<T extends any[]>(target: T, source: any[], optionsArgument?: IOptions, tmpRuntimeData?: ITmpRuntimeData<T>): T[];
function defaultArrayMerge(target: any[], source: any[], optionsArgument?: IOptions, tmpRuntimeData?: ITmpRuntimeData): any[]
function defaultArrayMerge(target: any[], source: any[], optionsArgument?: IOptions, tmpRuntimeData?: ITmpRuntimeData): any[]
{
	// @ts-ignore
	tmpRuntimeData ??= _newTmpRuntimeData<any[]>([]);

	const level = tmpRuntimeData.level + 1;
	const root = tmpRuntimeData.root;
	const parent = [] as any[];

	(tmpRuntimeData.parent as any)[tmpRuntimeData.key] = parent;

	/**
	 * 1. 串接目標陣列和來源陣列
	 * 2. 對每個元素進行深度複製（確保元素是獨立的副本）
	 * 1. Concatenate target array and source array
	 * 2. Deep clone each element (ensuring elements are independent copies)
	 */
	return target.concat(source as any).reduce((parent, element, index) => {

		/**
		 * 對每個元素進行條件性複製
		 * 若為可合併物件則深度複製，否則直接複製
		 * Conditionally clone each element
		 * Deep clone if mergeable, otherwise copy directly
		 */
		element = cloneUnlessOtherwiseSpecified(element, optionsArgument, {
			key: index,
		}, {
			level,
			paths: [...tmpRuntimeData.paths, index],
			root,
			parent,
			key: index,
		});

		parent[index] = element;

		return parent;
	}, parent);
}

/**
 * 合併兩個物件 / Merge two objects
 *
 * 這是 deepmerge 處理物件合併的核心函式
 * 負責將來源物件的屬性合併到目標物件
 *
 * This is the core function for object merging in deepmerge
 * Responsible for merging source object properties into target object
 *
 * 由左至右的概念 / Left-to-right concept:
 * merge(target, source, options) 中：
 * - 第一個參數 target 是「左側」（left）- 被合併的物件，代表現有值
 * - 第二個參數 source 是「右側」（right）- 要合併進來的物件，代表新值
 * - 合併結果是將「右側」的值合併進「左側」
 *
 * In merge(target, source, options):
 * - First parameter target is "left" - the object being merged INTO, represents existing values
 * - Second parameter source is "right" - the object being merged FROM, represents new values
 * - The result merges "right" values INTO "left"
 *
 * 處理邏輯 / Processing logic:
 * 1. 建立目的物件（destination）用於存放合併結果
 * 2. 遍歷目標物件的所有鍵值：
 *    - 複製到目的物件，進行深度複製
 * 3. 遍歷來源物件的所有鍵值：
 *    - 若鍵不存在於目標 OR 來源值不可合併 → 直接複製
 *    - 若鍵存在於目標 AND 來源值可合併 → 遞迴呼叫 deepmerge 進行深度合併
 *
 * @param target - 目標物件（被合併的物件）/ Target object (the object being merged into)
 * @param source - 來源物件（要合併的物件）/ Source object (the object to merge from)
 * @param optionsArgument - 合併選項 / Merge options
 * @returns 合併後的新物件 / New merged object
 */
function mergeObject(target, source, optionsArgument: IOptions, tmpRuntimeData: ITmpRuntimeData)
{
	let destination = {};

	if (!tmpRuntimeData)
	{
		tmpRuntimeData = _newTmpRuntimeData(destination);
	}

	/**
	 * 處理階段 1：遍歷目標物件的所有鍵值
	 * 將目標物件的屬性複製到目的物件（進行深度複製）
	 * Processing phase 1: Iterate all keys of target object
	 * Copy target object properties to destination (deep clone)
	 */
	if (_isMergeableObject(target, optionsArgument, void 0, tmpRuntimeData))
	{
		Object.keys(target).forEach(function (key)
		{
			destination[key] = cloneUnlessOtherwiseSpecified(target[key], optionsArgument, {
				key,
				source,
				target,
				destination,
			}, {
				level: tmpRuntimeData.level + 1,
				paths: [...tmpRuntimeData.paths, key],
				root: tmpRuntimeData.root,
				parent: destination,
				key: key
			})
		})
	}
	/**
	 * 處理階段 2：遍歷來源物件的所有鍵值
	 * 根據是否存在於目標中以及是否可合併來決定合併策略
	 * Processing phase 2: Iterate all keys of source object
	 * Decide merge strategy based on whether key exists in target and if mergeable
	 */
	Object.keys(source).forEach(function (key)
	{
		/**
		 * 判斷邏輯：
		 * - 若來源值不可合併 OR 目標中沒有此鍵 → 直接複製
		 * - 若來源值可合併且目標中也有此鍵 → 遞迴合併
		 * Decision logic:
		 * - If source value not mergeable OR key doesn't exist in target → copy directly
		 * - If source value mergeable AND key exists in target → recursively merge
		 */
		if (!_isMergeableObject(source[key], optionsArgument, {
				key,
				source,
				target,
			}, tmpRuntimeData) || !target[key])
		{
			destination[key] = cloneUnlessOtherwiseSpecified(source[key], optionsArgument, {
				key,
				source,
				target,
			}, {
				level: tmpRuntimeData.level + 1,
				paths: [...tmpRuntimeData.paths, key],
				root: tmpRuntimeData.root,
				parent: destination,
				key: key
			})
		}
		else
		{
			destination[key] = deepmerge(target[key], source[key], optionsArgument, {
				level: tmpRuntimeData.level + 1,
				paths: [...tmpRuntimeData.paths, key],
				root: tmpRuntimeData.root,
				parent: destination,
				key: key
			})
		}
	});
	return destination
}

/**
 * 深度合併結果類型 / Deep merge result type
 *
 * 合併兩個物件後的類型，推斷規則如下：
 * 1. 取得 T2 中不在 T1 的鍵（新增的鍵）
 * 2. 與 T1 的鍵進行交集（保留 T1 的鍵）
 * 3. 確保結果為可寫入類型
 *
 * Type after merging two objects, inference rules:
 * 1. Get keys in T2 that are not in T1 (new keys)
 * 2. Intersect with keys in T1 (preserve T1's keys)
 * 3. Ensure result is writeable type
 *
 * @template T1 - 目標物件類型 / Target object type
 * @template T2 - 來源物件類型 / Source object type
 */
export type IDeepmergeResult<T1, T2> = ITSWriteable<ITSPickExtra<T2, Exclude<keyof T2, keyof T1>> & T1>;

/**
 * 初始化選項
 * 若未提供則使用預設設定（使用預設陣列合併函式）
 *
 * Initialize options
 * Use default settings if not provided (use default array merge function)
 */
export function _handleOptions(optionsArgument?: IOptions): IOptions
{
	const options: IOptions = optionsArgument || {};
	// @ts-ignore
	// options.arrayMerge ??= defaultArrayMerge;

	return options;
}

/**
 * 深度合併函式 / Deep merge function
 *
 * 主要的合併函式，處理物件和陣列的深度合併
 * Main merge function that handles deep merging of objects and arrays
 *
 * 處理邏輯 / Processing logic:
 * 1. 檢查來源和目標是否為陣列
 * 2. 檢查類型是否匹配（兩者都必須是陣列或都不是）
 * 3. 若類型不匹配，返回來源物件的克隆
 * 4. 若兩者都是陣列，使用 arrayMerge 函式合併
 * 5. 否則，使用 mergeObject 進行物件合併
 *
 * @param T1 - 目標物件類型 / Target object type
 * @param T2 - 來源物件類型 / Source object type
 * @param target - 目標物件 / Target object
 * @param source - 來源物件 / Source object
 * @param optionsArgument - 合併選項 / Merge options
 * @returns 合併後的物件 / Merged object
 */
export function deepmerge<T1 extends unknown[], T2 extends unknown[]>(x: T1, y: T2, options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): [...T1, ...T2]
export function deepmerge<T1, T2>(x: T1[], y: T2[], options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): (T2 | T1)[]
export function deepmerge<T1, T2>(x: T1, y: T2, options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): IDeepmergeResult<T1, T2>
export function deepmerge<T>(x: Partial<NoInfer<T>>, y: Partial<NoInfer<T>>, options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): ITSWriteable<T>
export function deepmerge(target, source, optionsArgument, tmpRuntimeData: ITmpRuntimeData)
{
	/**
	 * 步驟 1：類型檢測
	 * 判斷來源和目標是否為陣列
	 *
	 * Step 1: Type detection
	 * Check if source and target are arrays
	 */
	const sourceIsArray = Array.isArray(source);
	const targetIsArray = Array.isArray(target);

	/**
	 * 步驟 2：初始化選項
	 * 若未提供則使用預設設定（使用預設陣列合併函式）
	 *
	 * Step 2: Initialize options
	 * Use default settings if not provided (use default array merge function)
	 */
	const options = _handleOptions(optionsArgument);

	/**
	 * 步驟 3：類型匹配檢查
	 * 確保兩者都是陣列或都不是陣列
	 *
	 * Step 3: Type matching check
	 * Ensure both are arrays or both are not arrays
	 */
	const sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;

	/**
	 * 情境 1：類型不匹配（陣列 vs 物件）
	 * 回傳來源物件的克隆，不進行合併
	 *
	 * Scenario 1: Type mismatch (array vs object)
	 * Return clone of source object, no merging
	 *
	 * 範例 / Example:
	 * deepmerge([], {})    // 回傳 {} 的克隆 / returns clone of {}
	 * deepmerge({}, [])   // 回傳 [] 的克隆 / returns clone of []
	 */
	if (!sourceAndTargetTypesMatch)
	{
		return cloneUnlessOtherwiseSpecified(source, optionsArgument, {
			target,
			source,
		}, tmpRuntimeData);
	}
	/**
	 * 情境 2：兩者都是陣列
	 * 使用 arrayMerge 函式進行合併（預設為串接）
	 *
	 * Scenario 2: Both are arrays
	 * Use arrayMerge function to merge (default is concatenation)
	 *
	 * 範例 / Example:
	 * deepmerge([1, 2], [3, 4])
	 * // 回傳 / returns [1, 2, 3, 4]（預設行為）/ (default behavior)
	 */
	else if (sourceIsArray)
	{
		return (options?.arrayMerge ?? defaultArrayMerge)(target, source, optionsArgument, tmpRuntimeData);
	}
	/**
	 * 情境 3：兩者都是物件
	 * 使用 mergeObject 進行物件屬性合併
	 *
	 * Scenario 3: Both are objects
	 * Use mergeObject to merge object properties
	 *
	 * 範例 / Example:
	 * deepmerge({ a: 1 }, { b: 2 })
	 * // 回傳 / returns { a: 1, b: 2 }
	 */
	else
	{
		return mergeObject(target, source, optionsArgument, tmpRuntimeData);
	}
}

/**
 * 內部快取介面 / Internal cache interface
 *
 * 用於在合併過程中傳遞上下文資訊
 * 這些資訊在遞迴合併時用於維持物件間的引用關係
 *
 * 與 ITmpRuntimeData 的差異：
 * - ICache: 追蹤 source、target、destination 的引用關係
 * - ITmpRuntimeData: 追蹤遞迴路徑和深度資訊
 *
 * Used to pass context information during merge operations
 * This information is used during recursive merge to maintain object references
 *
 * Difference from ITmpRuntimeData:
 * - ICache: Tracks source, target, destination reference relationships
 * - ITmpRuntimeData: Tracks recursion path and depth information
 *
 * @example
 * {
 *   key: 'user',           // 目前處理的鍵名
 *   source: { ... },       // 來源物件
 *   target: { ... },       // 目標物件
 *   destination: { ... }   // 目的物件（目前合併結果）
 * }
 *
 * `tmpRuntimeTarget: ICache`
 */
export interface ICache
{
	/** 鍵名 / Key name */
	key?
	/** 來源物件 / Source object */
	source?
	/** 目標物件 / Target object */
	target?
	/** 目的物件 / Destination object */
	destination?
}

export type IAnyRecord = Record<ITSPropertyKey | number, any>;

/**
 * 執行時資料介面 / Runtime data interface
 *
 * 用於在 deepmerge 遞迴呼叫過程中傳遞上下文資訊
 * 追蹤目前在合併樹中的位置和路徑
 *
 * Used to pass context information during deepmerge recursive calls
 * Tracks current position and path in the merge tree
 *
 * 屬性說明 / Properties Description:
 * - level: 目前的遞迴深度（根為 0，每遞迴一次 +1）
 * - paths: 從根到目前位置的路徑陣列（可用於 _.get(root, paths)）
 * - root: 最頂層的 destination 物件（永遠不變）
 * - parent: 目前層級的父物件
 * - key: 目前正在處理的鍵名
 *
 * 遞迴流程範例 / Recursive Flow Example:
 * 合併 { a: { b: 1 } } 和 { a: { c: 2 } } 時:
 * - 根層級: level=0, paths=[], root={}, parent={}, key=undefined
 * - level1:  level=1, paths=['a'], root={}, parent={}, key='a'
 * - level2:  level=2, paths=['a','b'], root={}, parent={a:{}}, key='b'
 *
 * @template T - parent 物件的類型（通常與 root 相同或為其子物件）
 * @template R - root 物件的類型
 */
export interface ITmpRuntimeData<T extends IAnyRecord = IAnyRecord, R extends IAnyRecord = IAnyRecord>
{
	/** 遞迴深度計數（根為 0）/ Recursion depth count (root is 0) */
	readonly level: number;

	/** 從根到目前位置的路徑陣列 / Path array from root to current position */
	readonly paths: (keyof R | keyof T | ITSPropertyKey | number)[];

	/** 最頂層的 destination 物件（永遠不變）/ Top-level destination object (always unchanged) */
	readonly root: R;

	/** 目前層級的父物件 / Parent object at current level */
	readonly parent: T;

	/** 目前正在處理的鍵名 / Key currently being processed */
	readonly key: keyof T;
}

/**
 * 合併選項介面 / Merge options interface
 *
 * 定義 deepmerge 函式的可選配置參數
 * Allows customization of merge behavior
 *
 * Defines optional configuration parameters for deepmerge function
 */
export interface IOptions
{
	/**
	 * 決定是否啟用深度複製功能
	 * 預設為 true（啟用），除非選項明確設為 false
	 *
	 * Determine whether to enable deep cloning
	 * Default is true (enabled), unless options explicitly set to false
	 *
	 * @note 注意事項 / Note:
	 * - 即使設為 `false`，在部分狀況下仍可能會忽略此設定並產生 clone
	 * - 此行為為正常現象，因為深度合併需要確保輸出物件與輸入物件相互獨立
	 *
	 * - Even if set to `false`, cloning may still occur in certain situations
	 * - This is expected behavior because deep merge needs to ensure the output
	 *   object is independent from the input objects
	 *
	 * @default true
	 */
	clone?: boolean;

	/**
	 * 自訂陣列合併函式
	 * Custom array merge function
	 *
	 * 允許自訂陣列的合併行為
	 * 預設行為是串接兩個陣列
	 *
	 * Allows customization of array merge behavior
	 * Default behavior is to concatenate two arrays
	 *
	 * @param destination - 目標陣列 / Destination array
	 * @param source - 來源陣列 / Source array
	 * @param options - 合併選項 / Merge options
	 * @returns 合併後的陣列 / Merged array
	 */
	arrayMerge?<T extends any[]>(target: T, source: any[], options?: IOptions, tmpRuntimeData?: ITmpRuntimeData<T>): T[];
	arrayMerge?(target: any[], source: any[], options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): any[];

	/**
	 * 自訂可合併物件判斷函式
	 * Custom mergeable object check function
	 *
	 * 允許自訂哪些物件可以被合併
	 * 可用於支援自訂類型（如 Map、Set 等）
	 *
	 * Allows customizing which objects can be merged
	 * Can be used to support custom types (like Map, Set, etc.)
	 *
	 * @param value - 要檢查的值 / Value to check
	 * @param isMergeableObject - 預設的判斷函式 / Default check function
	 * @param optionsArgument - 合併選項 / Merge options
	 * @param tmpRuntimeData - 執行時資料 / Runtime data
	 */
	isMergeableObject?(value, isMergeableObject: typeof isMergeable, optionsArgument?: IOptions,
		tmpRuntimeTarget?: ICache, tmpRuntimeData?: ITmpRuntimeData): void;
	isMergeableObject?(value, isMergeableObject: typeof isMergeable, optionsArgument?: IOptions,
		tmpRuntimeTarget?: ICache,
		tmpRuntimeData?: ITmpRuntimeData): boolean;

	/**
	 * (val = old || new) 模式
	 * (val = old || new) mode
	 *
	 * 啟用時會保留目標中已存在的值
	 * 當來源物件和目標物件都有相同鍵時，優先使用目標的值
	 *
	 * When enabled, preserves values that already exist in target
	 * When both source and target have the same key, priority is given to target's value
	 *
	 * @deprecated 棄用，建議使用 `keyValueUpsertMode` 取代
	 * @deprecated Deprecated, use `keyValueUpsertMode` instead
	 *
	 * @note 注意事項 / Note:
	 * 此選項保留為相容性用途，不建議在新程式碼中使用。
	 * 若有需要自訂合併邏輯，建議使用 `keyValueUpsertMode` 選項。
	 *
	 * This option is kept for backward compatibility and is not recommended for new code.
	 * For custom merge logic, use the `keyValueUpsertMode` option instead.
	 *
	 * @example
	 * const target = { name: 'Alice', age: 25 };
	 * const source = { name: 'Bob', city: 'Taipei' };
	 * // keyValueOrMode: true 時 / when keyValueOrMode: true
	 * // 結果: { name: 'Alice', age: 25, city: 'Taipei' }
	 * // name 保留目標的值，age 來自目標，city 來自來源
	 */
	keyValueOrMode?: boolean,

	/**
	 * Upsert 模式（選擇性更新）
	 * Upsert mode (selective update)
	 *
	 * 控制何時應該更新目標中的值
	 * 為 true 時：保留原有值（不取代），只在目標 key 的值為 undefined 時才使用來源的值
	 * 為 function 時：只在函式回傳 true 時保留原有值（不取代）
	 *
	 * Control when to update values in target
	 * When true: Preserve existing values (don't replace), only use source's value when target key value is undefined
	 * When function: Only preserve existing values (don't replace) when function returns true
	 *
	 * @note 重要說明 / Important Note:
	 * 當 keyValueUpsertMode 為 true 時，表示「不取代」模式：
	 * - 如果目標值不是 undefined，則保留目標的原始值
	 * - 只有當目標值是 undefined 時，才會使用來源的值
	 * 這包括 null、0、false、空字串等 falsy 值，都會被保留
	 *
	 * When keyValueUpsertMode is true, it means "don't replace" mode:
	 * - If target value is not undefined, preserve the original target value
	 * - Only use source's value when target value is undefined
	 * This includes falsy values like null, 0, false, empty string, which are all preserved
	 *
	 * @note 升級說明 / Upgrade Note:
	 * 此選項為 `keyValueOrMode` 的升級版本，解決了以下問題：
	 * - 使用 `??` 取代 `||` 運算子，避免 falsy 值（如 `0`、`''`、`false`）被錯誤處理
	 * - 支援自訂函式，可根據條件靈活決定是否保留目標的值
	 * - 行為更直觀，true 時明確表示「保留原有值」
	 *
	 * This option is an upgraded version of `keyValueOrMode`, solving the following issues:
	 * - Uses `??` instead of `||` operator to avoid incorrect handling of falsy values (like `0`, `''`, `false`)
	 * - Supports custom functions for flexible conditional logic
	 * - More intuitive behavior, true clearly means "preserve existing values"
	 *
	 * @example
	 * const target = { name: 'Alice', age: undefined, count: 0, email: null };
	 * const source = { name: 'Bob', age: 30, count: 5, email: 'bob@example.com' };
	 * // keyValueUpsertMode: true 時 / when keyValueUpsertMode: true
	 * // 結果: { name: 'Alice', age: 30, count: 0, email: null }
	 * // name: 保留目標值 'Alice'（不是 undefined）
	 * // age: 使用來源值 30（目標值是 undefined）
	 * // count: 保留目標值 0（不是 undefined）
	 * // email: 保留目標值 null（不是 undefined）
	 *
	 * @example
	 * // 使用自訂函式 / Using custom function
	 * keyValueUpsertMode: (value, options, tmpRuntimeTarget, tmpRuntimeData) => {
	 *   return tmpRuntimeTarget.target?.[tmpRuntimeTarget.key] !== undefined;
	 * }
	 */
	keyValueUpsertMode?:
		| boolean
		| ((value: unknown, optionsRuntime?: IOptions, tmpRuntimeTarget?: ICache, tmpRuntimeData?: ITmpRuntimeData) => boolean);
}

/**
 * 檢查值是否為可合併物件 / Check if value is a mergeable object
 *
 * 這是一個便捷函式，包裝了 is-mergeable-object 模組
 * 用於快速判斷給定的值是否可以被 deepmerge 合併
 *
 * This is a convenience function that wraps the is-mergeable-object module
 * Used to quickly determine if a given value can be merged by deepmerge
 *
 * 可合併的類型通常包括：
 * - 普通物件（plain objects）
 * - 陣列（arrays）
 * - 不可合併的類型（如原始類型、函式、Date 等）會回傳 false
 *
 * Mergeable types typically include:
 * - Plain objects
 * - Arrays
 * - Non-mergeable types (such as primitives, functions, Date, etc.) return false
 *
 * @param value - 要檢查的值 / Value to check
 * @returns 是否可合併（true 表示可以進行深度合併）/ Whether mergeable (true means deep merge can be performed)
 *
 * @example
 * isMergeable({})           // 回傳 / returns true
 * isMergeable([])           // 回傳 / returns true
 * isMergeable('string')     // 回傳 / returns false
 * isMergeable(123)          // 回傳 / returns false
 * isMergeable(new Date())   // 回傳 / returns false
 */
export function isMergeable(value: any): boolean
{
	return isMergeableObject(value)
}

/**
 * 可合併物件標記符號 / Mergeable object marker symbol
 *
 * 全域符號，用於在物件上標記是否為可合併物件
 * 這是一個全域可共享的 Symbol，可跨模組使用
 *
 * Global symbol used to mark whether an object is mergeable
 * This is a globally shareable Symbol that can be used across modules
 *
 * 用法 / Usage:
 * ```typescript
 * const obj = { name: 'test' };
 * obj[SYMBOL_IS_MERGEABLE] = true;  // 標記為可合併 / Mark as mergeable
 * obj[SYMBOL_IS_MERGEABLE] = false; // 標記為不可合併 / Mark as not mergeable
 * ```
 *
 * 優勢 / Advantages:
 * - 可以自訂物件的可合併性
 * - 可以自訂物件的可合併性
 * - 避免與物件原有屬性衝突
 * - Avoid conflicts with object's original properties
 */
const SYMBOL_IS_MERGEABLE = Symbol.for('SYMBOL_IS_MERGEABLE');

export { SYMBOL_IS_MERGEABLE }

/**
 * 建立新的執行時資料 / Create new runtime data
 *
 * 在開始新的合併操作時呼叫此函式建立初始的 tmpRuntimeData
 * 初始狀態為根層級，level 為 0，paths 為空陣列
 *
 * Called when starting a new merge operation to create initial tmpRuntimeData
 * Initial state is root level, level is 0, paths is empty array
 *
 * @template R - root 物件的類型
 * @template T - parent 物件的類型（預設與 R 相同）
 * @param root - 初始的 destination 物件（將作為 root 傳遞）
 * @returns 初始化的 tmpRuntimeData
 *
 * @example
 * const data = _newTmpRuntimeData({});
 * // data.level === 0
 * // data.paths === []
 * // data.root === {}
 * // data.parent === {}
 * // data.key === undefined
 */
export function _newTmpRuntimeData<R extends ITmpRuntimeData["root"], T extends IAnyRecord = R>(root: R)
{
	const tmpRuntimeData = {
		level: 0,
		paths: [],
		root: root,
		parent: root,
		key: undefined,
	}
	return tmpRuntimeData as any as ITmpRuntimeData<T, R>;
}

/**
 * 合併多個物件 / Merge multiple objects
 *
 * 將陣列中的所有物件依序合併成單一物件
 * 這是 deepmerge 的陣列版本，適用於合併多個物件
 *
 * Merges all objects in the array into a single object sequentially
 * This is the array version of deepmerge, suitable for merging multiple objects
 *
 * 處理邏輯 / Processing logic:
 * 1. 驗證輸入參數是否為陣列
 * 2. 使用 reduce 依序合併所有物件
 * 3. 初始值為空物件，每次迭代都會與下一個物件合併
 *
 * @param array - 要合併的物件陣列 / Array of objects to merge
 * @param optionsArgument - 合併選項 / Merge options
 * @returns 合併後的物件 / Merged object
 * @throws 若第一個參數不是陣列，則拋出錯誤 / Throws error if first argument is not an array
 *
 * @example
 * deepmergeAll([{ a: 1 }, { b: 2 }, { c: 3 }])
 * // 回傳 / returns { a: 1, b: 2, c: 3 }
 */
export function deepmergeAll<T1, T2 = any>(array: [T1, ...T2[]], optionsArgument?: IOptions): IDeepmergeResult<T1, T2>
export function deepmergeAll<T1>(array: T1[], optionsArgument?: IOptions): T1
export function deepmergeAll<T>(array: any[], optionsArgument?: IOptions): T
{
	/**
	 * 驗證輸入參數是否為陣列
	 * 若不是陣列則拋出錯誤
	 * Validate input parameter is an array
	 * Throw error if not an array
	 */
	if (!Array.isArray(array))
	{
		throw new Error('first argument should be an array')
	}

	/**
	 * 使用 reduce 依序合併所有物件
	 * 初始值為空物件，每次迭代都會與下一個物件合併
	 * Use reduce to sequentially merge all objects
	 * Initial value is empty object, each iteration merges with next object
	 */
	// @ts-ignore
	return array.reduce(function (prev, next)
	{
		return deepmerge(prev, next, optionsArgument)
	}, {})
}

export { deepmergeAll as all }

export default deepmerge

/**
 * CommonJS 環境兼容性設定
 * CommonJS environment compatibility settings
 *
 * 為非 ESM 環境添加必要的屬性：
 * - __esModule: 標記為 ES 模組
 * - deepmerge: 指向 deepmerge 函式本身
 * - default: 指向預設匯出的 deepmerge
 * - isMergeable: 便捷函式
 * - SYMBOL_IS_MERGEABLE: 符號匯出
 * - deepmergeAll: 所有物件合併函式
 * - all: deepmergeAll 的別名
 * - _isMergeableObject: 內部可合併判斷函式
 *
 * Adds necessary properties for non-ESM environments:
 * - __esModule: Mark as ES module
 * - deepmerge: Point to deepmerge function itself
 * - default: Point to default exported deepmerge
 * - isMergeable: Convenience function
 * - SYMBOL_IS_MERGEABLE: Symbol export
 * - deepmergeAll: All objects merge function
 * - all: Alias for deepmergeAll
 * - _isMergeableObject: Internal mergeable check function
 */
// @ts-ignore
if (process.env.TSDX_FORMAT !== 'esm')
{
	/**
	 * CommonJS 環境兼容性處理
	 * 為模組添加必要的屬性以支援 CommonJS 匯入方式
	 * CommonJS environment compatibility handling
	 * Add necessary properties to support CommonJS import methods
	 *
	 * 例如：const deepmerge = require('deepmerge-plus')
	 * 或   ：import * as deepmerge from 'deepmerge-plus'
	 */

	/** 標記為 ES 模組 / Mark as ES module */
	Object.defineProperty(deepmerge, "__esModule", { value: true });

	/** 匯出 deepmerge 函式 / Export deepmerge function */
	Object.defineProperty(deepmerge, 'deepmerge', { value: deepmerge });

	/** 設定預設匯出 / Set default export */
	Object.defineProperty(deepmerge, 'default', { value: deepmerge });

	/** 匯出便捷函式 isMergeable / Export convenience function isMergeable */
	Object.defineProperty(deepmerge, 'isMergeable', { value: isMergeable });

	/** 匯出可合併物件標記符號 / Export mergeable object marker symbol */
	Object.defineProperty(deepmerge, 'SYMBOL_IS_MERGEABLE', { value: SYMBOL_IS_MERGEABLE });

	/** 匯出 deepmergeAll 函式 / Export deepmergeAll function */
	Object.defineProperty(deepmerge, 'deepmergeAll', { value: deepmergeAll });

	/** 匯出 all 別名 / Export all alias */
	Object.defineProperty(deepmerge, 'all', { value: deepmergeAll });

	/** 匯出內部函式 _isMergeableObject / Export internal function _isMergeableObject */
	Object.defineProperty(deepmerge, '_isMergeableObject', { value: _isMergeableObject });
}
