
/**
 * tui Grid 도구
 * 	-참조사이트
 * 	 ->듀토리얼: https://github.com/nhn/tui.grid/tree/master/packages/toast-ui.grid/docs/ko
 *   ->예제: http://nhn.github.io/tui.grid/latest/tutorial-example01-basic
 *  @author 박민규
 *  @date 2020.05.25.
 */
var XitTuiGridConfig = function(){
	var _instance;

	var _gridId = 'grid';               //[필수]Grid를 출력할 Element Id
	var _data = {};                     //[선택]DataSource 정보(readData|createData|updateData|modifyData|deleteData 등)
	var _header = {};                   //[선택]헤더정보(헤더 명칭 및 매핑 field)
	var _columns = [];                  //[필수]컬럼정보(헤더 명칭 및 매핑 field)
	var _rowHeaders;                    //[선택]ROW 헤더 타입(rowNum: 순번, checkbox: 체크박스)
	var _bodyHeight;                    //[선택]Grid 높이 (number(단위: px)|'auto'|'fitToParent')
	var _minBodyHeight;                 //[선택]Grid 최소 높이 (단위: px)
	var _rowHeight = 25;                //[선택]Grid row 높이 (number(단위: px)|'auto' )     
	var _minRowHeight = 25;             //[선택]Grid row 최소 높이 (단위: px)     
	var _pageOptions = { perPage: 10 }; //[선택]한 페이지에 출력할 건수      
	var _columnOptions = {              //[선택]고정 컬럼
		frozenCount: 1 //고정컬럼 갯수
		, frozenBorderWidth: 2 //고정컬럼 보더(border) 두께
		, minWidth: 100 //최소 사이즈
	}
	var _summary;                       //[선택]하단합계
	var _treeColumnOptions = {};        //[선택]tree 구조 grid
	var _useClientSort = false;
	var _disableWhenFocusin = false; // 다른 요소로 포커스가 이동해도 편집 종료



	return {
		/** gridId 값을 설정 한다. */
		setOptGridId : function(val){
			this._gridId = val;
		},
		/** gridId 값을 반환 한다. */
		getOptGridId : function(){
			return this._gridId;
		},
		/** data 값을 설정 한다. */
		setOptDataSource : function(val){
			this._data = val;
		},
		/** data 값을 반환 한다. */
		getOptDataSource : function(){
			return this._data;
		},
		/** gridHeight 값을 설정 한다. */
		setOptGridHeight : function(val){
			this._bodyHeight = val;
		},
		/** gridHeight 값을 반환 한다. */
		getOptGridHeight : function(){
			return this._bodyHeight;
		},
		/** minGridHeight 값을 설정 한다. */
		setOptMinGridHeight : function(val){
			this._minBodyHeight = val;
		},
		/** minGridHeight 값을 반환 한다. */
		getOptMinGridHeight : function(){
			return this._minBodyHeight;
		},
		/** rowHeight 값을 설정 한다. */
		setOptRowHeight : function(val){
			this._rowHeight = val;
		},
		/** rowHeight 값을 반환 한다. */
		getOptRowHeight : function(){
			return this._rowHeight;
		},
		/** minRowHeight 값을 설정 한다. */
		setOptMinRowHeight : function(val){
			this._minRowHeight = val;
		},
		/** minRowHeight 값을 반환 한다. */
		getOptMinRowHeight : function(){
			return this._minRowHeight;
		},
		/** header 값을 설정 한다. */
		setOptHeader : function(val){
			this._header = val;
		},
		/** header 값을 반환 한다. */
		getOptHeader : function(){
			return this._header;
		},
		/** columns 값을 설정 한다. */
		setOptColumns : function(val){
			this._columns = val;
		},
		/** columns 값을 반환 한다. */
		getOptColumns : function(){
			return this._columns;
		},
		/** rowHeaderType 값을 설정 한다. */
		setOptRowHeaderType : function(val){
			this._rowHeaders = val;
		},
		/** rowHeaderType 값을 반환 한다. */
		getOptRowHeaderType : function(){
			return this._rowHeaders;
		},
		/** pageOptions 값을 설정 한다. */
		setOptPageOptions : function(val){
			this._pageOptions = val;
		},
		/** pageOptions 값을 반환 한다. */
		getOptPageOptions : function(){
			return this._pageOptions;
		},
		/** columnOptions 값을 설정 한다. */
		setOptColumnOptions : function(val){
			this._columnOptions = val;
		},
		/** columnOptions 값을 반환 한다. */
		getOptColumnOptions : function(){
			if(this._columnOptions==null)
				this._columnOptions = {};
			//컬럼 사이즈 조정 Default 설정
			if(this._columnOptions['resizable']==undefined||this._columnOptions['resizable']==null){	//컬럼 사이즈 조정(resizeable) 설정 값이 없으면..
				this._columnOptions['resizable'] = true;	//조정 가능(true) 으로 설정
			}
			return this._columnOptions;
		},
		/** summary 값을 설정 한다. */
		setOptSummary: function(val){
			this._summary = val;
		},
		/** summary 값을 반환 한다. */
		getOptSummary: function(){
			return this._summary;
		},
		/** treeColumnOptions 값을 설정 한다. */
		setOptTreeColumnOptions: function(val){
			this._treeColumnOptions = val;
		},
		/** treeColumnOptions 값을 반환 한다. */
		getOptTreeColumnOptions: function(){
			return this._treeColumnOptions;
		},
		/** treeColumnOptions 값을 반환 한다. */
		setOptUseClientSort: function(val){
			this._useClientSort = val;
		},
		/** treeColumnOptions 값을 반환 한다. */
		getOptUseClientSort: function(){
			return this._useClientSort;
		},
		setOptDisableWhenFocusin: function(val){
			this._disableWhenFocusin = val;
		},
		getOptDisableWhenFocusin: function(){
			return this._disableWhenFocusin;
		},
		/** instance 객체를 설정 한다. */
		setInstance : function(val){
			this._instance = val;
		},
		/** instance 객체를 반환 한다. */
		getInstance : function(){
			return this._instance;
		},
		/** tui Grid Instance */
		instance : function(tuiGrid){
			//Option Set
			var option = new Object();
			option.scrollX = true;
			option.scrollY = true;
			option.el = fnIsEmpty(this.getOptGridId())?document.getElementById(_gridId):document.getElementById(this.getOptGridId());
			if(!fnIsEmpty(this.getOptDataSource()))    option.data = this.getOptDataSource();
			if(!fnIsEmpty(this.getOptHeader()))        option.header = this.getOptHeader();
			if(!fnIsEmpty(this.getOptColumns()))       option.columns = this.getOptColumns();
			if(!fnIsEmpty(this.getOptRowHeaderType())) option.rowHeaders = [this.getOptRowHeaderType()];
			if(!fnIsEmpty(this.getOptGridHeight()))    option.bodyHeight = Number(this.getOptGridHeight());
			if(!fnIsEmpty(this.getOptMinGridHeight())) option.minBodyHeight = Number(this.getOptMinGridHeight());
			option.rowHeight    = fnIsEmpty(this.getOptRowHeight())   ?_rowHeight:Number(this.getOptRowHeight());
			option.minRowHeight = fnIsEmpty(this.getOptMinRowHeight())?_minRowHeight:Number(this.getOptMinRowHeight());
			if(!fnIsEmpty(this.getOptPageOptions()))   option.pageOptions = this.getOptPageOptions();
			if(!fnIsEmpty(this.getOptColumnOptions())) option.columnOptions = this.getOptColumnOptions();
			if(!fnIsEmpty(this.getOptSummary()))       option.summary = this.getOptSummary();
			if(!fnIsEmpty(this.getOptTreeColumnOptions())) option.treeColumnOptions = this.getOptTreeColumnOptions();
			// false 값도 유효한 값으로 처리하기 위해 undefined나 null이 아닌 경우에만 설정
			if(this.getOptUseClientSort() !== undefined && this.getOptUseClientSort() !== null) option.useClientSort = this.getOptUseClientSort();

			//2020.11.06 박민규 - 요청에 대한 Response 및 Exception 처리를 위한 "AJAX" 헤더 추가
			if(!fnIsEmpty(option.data)){
				var headers = { 'AJAX': true };
				option.data['headers'] = headers;
			}

			//Grid Instance
			var instance = new tuiGrid(option);

			//instance Grid Set
			this.setInstance(instance);

			//2020.11.06 박민규 - Api 호출결과 메시지 출력 이벤트 리스너 추가.
			//Api Call Result EventListener Set
			// 결과가 false인 경우 발생한 경우
			instance.on('successResponse', function(ev){
				var msg = JSON.parse(ev.xhr.response).message;	//tui-grid 기본 format 메시지
			});
			// 결과가 false인 경우 발생한 경우
			instance.on('failResponse', function(ev){
				console.log("failResponse");
				console.log(ev);
				try {
					var responseObj = JSON.parse(ev.xhr.response);
					var msg = responseObj.message;	//tui-grid 기본 format 메시지
					if(fnIsEmpty(msg) && responseObj.resp)
						msg = responseObj.resp.resultMsg;
					if(!fnIsEmpty(msg))
						alert(msg);
				} catch (e) {
					console.error("failResponse 처리 중 오류 발생:", e);
				}
			});
			// 오류가 발생한 경우
			instance.on('errorResponse', function(ev){
				console.log("errorResponse");
				console.log(ev);
				try {
					var responseObj = JSON.parse(ev.xhr.response);

					if(responseObj.errorCode === "SESSION_EXPIRED") {
                        handleSessionExpired();
					} else if(responseObj.errorCode === "MessageException") {
						alert(responseObj.message);
					} else if(responseObj.errorCode === "MESSAGE_EXCEPTION") {
						alert(responseObj.message);
					} else {
						alert("에러가 발생했습니다.\n\nERROR CODE : "+responseObj.errorCode+"\nMESSAGE : "+responseObj.message);
					}
				} catch (e) {
					console.error("errorResponse 처리 중 오류 발생:", e);
				}
			});



			/* 공백(empty) 여부 확인 */
			function fnIsEmpty(val){
				if(val==undefined||val==null||val==''||val==[]||val=={})
					return true;
				else
					return false;
			}

			return instance;
		},
		/** column 순서 변경 */
		fnColumnMove : function(curIdx, moveIdx){
			/*
			* 유효성 확인
			*/
			if(curIdx == moveIdx)
				return false;

			/*
			* 순서 변경
			*/
			var _columns = this.getOptColumns();
			var columnsRecordered = [];
			var isMoveRight = Number(curIdx) < Number(moveIdx);
			var isMoveLeft  = Number(curIdx) > Number(moveIdx);
			for(var i=0; i<_columns.length; i++){
				if(isMoveLeft){
					if(i == moveIdx)
						columnsRecordered.push(_columns[curIdx]);
				}

				if(i != curIdx)
					columnsRecordered.push(_columns[i]);

				if(isMoveRight){
					if(i == moveIdx)
						columnsRecordered.push(_columns[curIdx]);
				}
			}

			/*
			* 결과 반영
			*/
			this.getInstance().setColumns(columnsRecordered);
			this.setOptColumns(columnsRecordered);
		},
		/** Excel Export */
		exportExcel : function(fileName){
			/* 필수값 설정 */
			var _gridId = this.getOptGridId();
			var _instance = this.getInstance();
			var _frstColTyp = this.getOptRowHeaderType();
			var _arrHeader = [];
			var _arrName = [];
			var _mCustomRenderer = {};
			/* =================
			 * 2021.04.30. 박민규
			 *  컬럼 취득 방법 변경
			 *   AsIs: GridConfig 에 설정한 Columns 정보
			 *   ToBe: instance 에 설정된 Columns 정보
			 ================= */
			//2021.04.30. 주석처리
//			this.getOptColumns().forEach(function(opt, idx){
			_instance.getColumns().forEach(function(opt, idx){
				_arrHeader.push(opt.header);
				_arrName.push(opt.name);
				//2021.04.30. 주석처리
//				if(!fnIsEmpty(opt.renderer)){
				if(!(fnIsEmpty(opt.renderer)||'DefaultRenderer'==opt.renderer.type.name||fnIsEmpty(opt.renderer.type.name))){	//브라우저별 opt.renderer.type.name 값의 차이 => Chrome: "DefaultRenderer", IE: undefined
					_mCustomRenderer[opt.name] = opt.renderer;
				}
			});



			/* grid head Setting */
			var elementTHEAD = document.createElement('table');
			var $sltHeader = $('#'+_gridId+' .tui-grid-header-area > table.tui-grid-table').clone();
			//head 처리(table 고정셀 이용 시)
			if($sltHeader.length>1){
				//좌측 table의 head Selector
				var firstTableTh = $sltHeader.find('tr:last-child th');
				//우측 table row의 헤드(th)를 좌측 table row에 병합
				for(var i=1; i<$sltHeader.length; i++){
					$sltHeader.eq(i).find('tr').each(function(idx, row){
						var firstTableRow = $sltHeader.eq(0).find('tr:eq('+idx+')');
						if(firstTableRow.length == 0){
							//<tr> 신규생성
							var tr = document.createElement('tr');
							tr.innerHTML = row.innerHTML;
							$sltHeader.eq(0).append(tr);
							//firstTable <th>의 rowspan 변경
							for(var j=0; j<firstTableTh.length; j++){
								firstTableTh.eq(j).attr('rowspan', Number(firstTableTh.eq(j).attr('rowspan'))+1);
							}
						}
						else{
							firstTableRow.append(row.innerHTML);
						}
					});
				}
			}
			elementTHEAD.innerHTML = $sltHeader.eq(0).find('tbody').html();



			/* grid body Setting */
			var elementTBODY = document.createElement('tbody');
			_instance.getData().forEach(function(row, idxRow){
				var elementTR = document.createElement('tr');

				//타입별 <td> element 생성( 첫번째 컬럼타입 설정 시 )
				if(!fnIsEmpty(_frstColTyp))
					elementTR.appendChild(fnCreateTdByFrstCol(_frstColTyp, row));
				//<td> element 생성
				_arrName.forEach(function(columnName, idxColumn){
					var columnVal = '';
					var customRenderer = _mCustomRenderer[columnName];
					if(fnIsEmpty(customRenderer)){
						//column Value 설정
						columnVal = fnNvl(row[columnName]);
					}else{
						//prop 객체 생성
						var columnInfo = {};
						columnInfo['renderer'] = customRenderer;
						columnInfo['name'] = columnName;
						var props = {};
						props['columnInfo'] = columnInfo;
						props['rowKey'] = row.rowKey;
						props['grid'] = _instance;

						//렌더러 호출
						customRenderer.type(props);

						//column Value 설정
						columnVal = fnNvl(row[columnName]);
						if(row.rowKey<15){
//							console.log('[export]rowKey->'+row.rowKey+'/ column->'+columnName+' / value->'+row[columnName]);
//							console.log(_instance.getRow(row.rowKey));
						}
					}

					var elementTD = document.createElement('td');
					elementTD.innerHTML = columnVal;
					elementTR.appendChild(elementTD);
				});
				elementTBODY.appendChild(elementTR);
			});



			/* grid table Setting */
			var elementTABLE = document.createElement('table');
			elementTABLE.appendChild(elementTHEAD);
			elementTABLE.appendChild(elementTBODY);



			/* export Excel */
			var fileUtil = new XitFileExportUtil(fileName, 'table', elementTABLE);
			fileUtil.exportExcel();







			/* member Function Declare */
			//isEmpty Function
			function fnIsEmpty(val){
				if(val==undefined||val==null||val==''||val=={}||val==[])
					return true;
				return false;
			}
			//nvl Function
			function fnNvl(val, replaceVal){
				replaceVal = fnIsEmpty(replaceVal)?'':replaceVal;
				val = fnIsEmpty(val)?replaceVal:val;
				return val;
			}
			//<td> element 생성 Function
			function fnCreateTdByFrstCol(colType, row){
				var returnVal = '';

				/* column 타입별 value 설정 */
				switch (colType) {
					case 'rowNum':
						returnVal = row._attributes.rowNum;
						break;
					case 'checkbox':
						if(row._attributes.checked)
							returnVal = '☑';
						else
							returnVal = '□';
						break;
					default:
						return returnVal;
						break;
				}

				/* <td> element 생성 */
				var elementFrstTD = document.createElement('td');
				elementFrstTD.innerHTML = returnVal;


				return elementFrstTD;
			}
		}


	}
}








/**
 * Button 렌더러
 *  -설명: Grid의 cell에 Button을 생성 한다.
 *  	버튼명칭에 format 사용이 가능하며 사용방법은 아래와 같다.
 *  	ex) value: 'A is {0}. B is {1}. {0}!={1}',
 *  		listColumns: ['컬럼1', '컬럼2']
 *  @param value 버튼에 출력 할 명칭
 *  @param listColumns format에 매칭할 컬럼 목록
 *  @param callbackFnc 버튼 클릭 시 호출 할 함수명
 *  @author 박민규
 *  @date 2020.05.28.
 */
var XitButtonRenderer = function(props){
//	console.log('XitButtonRenderer Called!!-> '+props.columnInfo.name);
	//options get
	var opt = props.columnInfo.renderer.options;
	var value = opt.value;
	var callbackFnc = opt.callbackFnc;
	var args = opt.listColumns;

	//"value" Formatting 
	if(args != undefined){
		var row = props.grid.getRow(props.rowKey);
		for(var i=0; i<args.length; i++){
			var regEx = new RegExp('\\{'+i+'\\}', 'gi');
			value = value.replace(regEx, row[args[i]]);
		}
	}

	//Element Setting
	var el = document.createElement('input');
	el.type = 'button';
	el.value = value;

	//EventListener Setting
	el.addEventListener('click', function(){
		var callback = callbackFnc+'(props)';
		eval(callback);
	});

	//Element render
	this.el = el;
//	this.render(props);
	if(props.rowKey<15){
//		console.log('[Renderer]rowKey->'+props.rowKey+' column->'+props.columnInfo.name+' / el.value->'+el.value);
	}
	//DataSet Injection ( Dataset에 추가해야 "복사(ctrl+c)" 기능 사용 가능 )
	props.grid.setValue(props.rowKey, props.columnInfo.name, el.value, false);
}
XitButtonRenderer.prototype.getElement = function(){
	return this.el;
}
XitButtonRenderer.prototype.render = function(props){
	this.el.value = props.value;
}




/**
 * column 병합 렌더러
 *  -설명: 다수의 Column을 하나의 Column으로 병합하여 cell에 출력 한다.
 *  	필요 시 format 사용이 가능하며 사용방법은 아래와 같다. (※포맷 사용 시 "구분자(separator)"는 적용되지 않는다)
 *  	[단순 컬럼 병합]
 *  	ex) listColumns: ['컬럼1', '컬럼2'],
 *  		separator: '/'
 *  	[포맷사용 컬럼 병합]
 *  	ex) listColumns: ['컬럼1', '컬럼2'],
 *  		format: 'A is {0}. B is {1}. {0}!={1}'
 *  @param listColumns	병합할 컬럼 name 목록
 *  @param separator	컬럼 연결 구분자(default: 공백(' '))
 *  @param format       출력 포맷
 *  @author 박민규
 *  @date 2020.05.28.
 */
var XitColumnMergeRenderer = function(props){
//	console.log('XitColumnMergeRenderer Called!!-> '+props.columnInfo.name);
	//options get
	var opt = props.columnInfo.renderer.options;
	var args = opt.listColumns;
	var separator = opt.separator;
	if(fnIsEmpty(separator))
		separator = ' ';
	var value = opt.format;

	//Columns merge
	if(fnIsEmpty(value)){
		var arrStr = [];
		var row = props.grid.getRow(props.rowKey);
		args.forEach(function(column){
			arrStr.push(row[column]);
		});
		value = arrStr.join(separator);
	}else{
		var row = props.grid.getRow(props.rowKey);
		for(var i=0; i<args.length; i++){
			var regEx = new RegExp('\\{'+i+'\\}', 'gi');
			value = value.replace(regEx, fnNvl(row[args[i]]));
		}
	}
	function fnNvl(val){
		if(val==undefined||val==null)
			return '';
		return val;
	}
	function fnIsEmpty(val){
		if(val==undefined||val==null||val==''||val==[]||val=={})
			return true;
		return false;
	}

	//Element Setting
	var el = document.createElement('div');
	el.setAttribute('class', 'tui-grid-cell-content');
	el.innerHTML = value;

	//Element render
	this.el = el;
//	this.render(props);
	if(props.rowKey<15){
//		console.log('[Renderer]rowKey->'+props.rowKey+' column->'+props.columnInfo.name+' / el.value->'+el.innerHTML);
	}

	//DataSet Injection ( Dataset에 추가해야 "복사(ctrl+c)" 기능 사용 가능 )
	props.grid.setValue(props.rowKey, props.columnInfo.name, el.innerHTML, false);
}
XitColumnMergeRenderer.prototype.getElement = function(){
	return this.el;
}
XitColumnMergeRenderer.prototype.render = function(props){
	this.el.value = props.value;
}


/**
 * Button 렌더러
 *  @author 최유수
 *  @date 2020.07.15.
 *  formatter 		: 셀 안에 입력될 문자열.
 ,eventType		: 셀에 걸 이벤트.
 ,eventFunction 	: 이벤트에 부여될 함수.

 element 속성
 element : 생성 엘리먼트 속성
 type : 생성 엘리먼트의 타입
 value : 들어갈 텍스트
 */
/**
 * 요구사항
 * 셀 속성을 부여할때
 *
 * 1.셀에 개발자가 원하는 Element를 생성 할 수 있음
 * 2.1의 속성에 개발자가 원하는 이벤트를 부여 할 수 있음
 * 3. 2의 이벤트에 개발자가 원하는 함수를 부여 할 수 있음
 * 4. 개발자가 원하는만큼 유형을 추가 할 수 있음.(유형이 여러개일수도 아닐수도 있음)
 */
var CustomButtonRenderer = function(props){
	//options get
	var opt = props.columnInfo.renderer.options;
	//formatter를 구성할 인자값(해당 인자값은 함수형일때 배열일 수 있음.)
	var formatter = opt.formatter;
	//이벤트로 사용할 함수
	var eventFunction = opt.eventFunction;
	//이벤트 속성
	var eventType = opt.eventType;
	//객체 유형
	var element = opt.element;
	//객체 속성
	var type = opt.type;


	var formattObject = formatter;
	if(typeof formatter =="function"){
		//formatter가 함수에 의해 구현이됨, Return값은 Arr<Object>, Objcect임;
		//Object는 formatter : Text 와 Element로 구성됨.
		formattObject = formatter(props);
	}else{
		//formatter가 단순 텍스트임.
		formattObject = opt;
	}

	var ele = cellMaker(formattObject ,  eventFunction , eventType , element ,  type , props);
	this.el = ele;
}
CustomButtonRenderer.prototype.getElement = function(){
	return this.el;
}

//셀을 만듦.
var cellMaker = function(formattObject , eventFunction , eventType , element ,  type , props){
	var spanEle = document.createElement("span");
	var ele;

	//셀에 1개 이상의 데이터가 들어가는 경우
	if(formattObject instanceof Array && formattObject.length != 0){
		formattObject.forEach(function(elementObject , index , arrays){
			elementObject["eventFunction"] = eventFunction;
			elementObject["eventType"] = eventType;
			ele = elMaker(elementObject , props);
			$(spanEle).append(ele);
			//객체가 다수이면 띄어쓰기 함
			if(index < formattObject.length -1){
				$(spanEle).append(document.createTextNode('\u00a0  // \u00a0'));
			}
		});
	}

	//셀에 데이터가 1개이지만 함수로 Obj를 가져와서 만듦
	if(formattObject instanceof Object){
		formattObject["eventFunction"] = eventFunction;
		formattObject["eventType"] = eventType;
		ele = elMaker(formattObject , props);
		spanEle.appendChild(ele);
	}

	return spanEle;
}

var elMaker = function(elementObject,  props){
	var text = 				elementObject.formatter
	var eventType = 		elementObject.eventType
	var element = 			elementObject.element
	var eventFunction = 		elementObject.eventFunction
	var type =				elementObject.type

	var ele;
	if(element == "text"){
		ele = document.createElement("p");
		ele.style.whiteSpace = 'nowrap';
		ele.style.display = 'inline-block';
		ele.style.fontSize = '11px';
		ele.style.fontFamily = 'Nanum Barun Gothic';
		ele.appendChild(document.createTextNode(text));
	}else{
		ele = document.createElement(element);
		ele.type = type;
		ele.value = text;
	}
	//이벤트가 함수이면 이벤트 부여함.
	if(typeof eventFunction =="function"){
		ele.addEventListener(eventType, function(event){eventFunction(props, event)});
	}

	return ele;
}

var RowNumberRenderer = function(props){
	var el = document.createElement('span');
	el.innerHTML = props.rowKey + 1
	this.el = el;

	getElement = function(){
		return this.el;
	}

	render = function(props){
		this.el.innerHTML = props.rowKey + 1
	}
}

RowNumberRenderer.prototype.getElement = function(){
	return this.el;
}






/**
 * TUI Grid 드롭다운 메뉴 공통 컴포넌트
 * 그리드 행에서 사용할 수 있는 드롭다운 액션 메뉴를 제공합니다.
 *
 * 사용법:
 * 1. 그리드 컬럼 formatter에서 XitTuiGridDropdownMenu.createMenuHtml() 호출
 * 2. 그리드 이벤트에서 XitTuiGridDropdownMenu.bindEvents() 호출
 *
 * [기본 사용 예제]
 * // 1. 그리드 컬럼 정의 시 formatter에서 사용
 * {
 *     header: '작업',
 *     name: 'actions',
 *     width: 80,
 *     align: 'center',
 *     sortable: false,
 *     resizable: false,
 *     formatter: function(props) {
 *         return XitTuiGridDropdownMenu.createMenuHtml(props);
 *     }
 * }
 *
 * // 2. 그리드 이벤트 바인딩
 * var menuCallbacks = {
 *     view: function(row, rowKey) {
 *         console.log('보기:', row);
 *         // 상세보기 모달 열기 또는 페이지 이동
 *     },
 *     edit: function(row, rowKey) {
 *         console.log('수정:', row);
 *         // 수정 모달 열기 또는 페이지 이동
 *     },
 *     delete: function(row, rowKey) {
 *         if (confirm('삭제하시겠습니까?')) {
 *             // 삭제 Ajax 요청 처리
 *         }
 *     }
 * };
 * XitTuiGridDropdownMenu.bindEvents('#grid', menuCallbacks, gridInstance);
 *
 * [커스텀 메뉴 예제 - 방법 1: 배열로 직접 전달]
 * // 커스텀 메뉴 아이템 정의
 * var customMenuItems = [
 *     { text: '보기', action: 'view', class: 'js-row-view' },
 *     { text: '복사', action: 'copy', class: 'js-row-copy' },
 *     { text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' }
 * ];
 *
 * // formatter에서 커스텀 메뉴 사용
 * formatter: function(props) {
 *     return XitTuiGridDropdownMenu.createMenuHtml(props, customMenuItems);
 * }
 *
 * [커스텀 메뉴 예제 - 방법 2: config 객체로 설정]
 * // formatter에서 설정 객체를 통한 커스텀 메뉴 사용
 * formatter: function(props) {
 *     return XitTuiGridDropdownMenu.createMenuHtml(props, {
 *         menuItems: [
 *             { text: '보기', action: 'view', class: 'js-row-view' },
 *             { text: '복사', action: 'copy', class: 'js-row-copy' },
 *             { text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' }
 *         ]
 *     });
 * }
 *
 * [동적 메뉴 생성 예제]
 * // 행 데이터에 따라 동적으로 메뉴 생성
 * formatter: function(props) {
 *     var row = props.grid.getRow(props.rowKey);
 *     var dynamicMenuItems = [
 *         { text: '보기', action: 'view', class: 'js-row-view' }
 *     ];
 *
 *     // 조건에 따라 메뉴 추가
 *     if (row.status === 'ACTIVE') {
 *         dynamicMenuItems.push({ text: '수정', action: 'edit', class: 'js-row-edit' });
 *         dynamicMenuItems.push({ text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' });
 *     }
 *
 *     return XitTuiGridDropdownMenu.createMenuHtml(props, { menuItems: dynamicMenuItems });
 * }
 *
 * // 해당하는 콜백 함수들 정의
 * var customCallbacks = {
 *     view: function(row, rowKey) { ... },
 *     copy: function(row, rowKey) { ... },
 *     delete: function(row, rowKey) { ... }
 * };
 *
 * [스타일 커스터마이징 예제]
 * var customConfig = {
 *     btnIcon: '⚙', // 버튼 아이콘 변경
 *     btnStyle: 'background: #007bff; color: white; padding: 4px 8px;', // 버튼 스타일 변경
 *     menuStyle: 'min-width: 150px; background: #f8f9fa;' // 메뉴 스타일 변경
 * };
 *
 * // formatter에서 커스텀 설정 사용
 * formatter: function(props) {
 *     return XitTuiGridDropdownMenu.createMenuHtml(props, null, customConfig);
 * }
 *
 * @author XIT Framework Team
 * @since 2025-08-22
 */
/**
 * TUI Grid 라디오 버튼 렌더러
 * 그리드에서 단일 행 선택을 위한 라디오 버튼을 제공합니다.
 * 
 * 사용법:
 * 1. 그리드 컬럼 정의에서 renderer로 사용
 * {
 *     header: '선택',
 *     name: '_radio',
 *     align: 'center',
 *     width: 50,
 *     sortable: false,
 *     renderer: {
 *         type: XitRadioRenderer,
 *         options: {
 *             radioName: 'gridRowRadio',  // 라디오 버튼 name 속성 (선택적, 기본값: 'gridRowRadio')
 *             targetObject: 'MyModule',   // 선택된 행을 저장할 객체명 (선택적)
 *             selectedRowProperty: 'selectedRow',  // 선택된 행 속성명 (선택적, 기본값: 'selectedRow')
 *             onRowSelect: function(rowData, rowKey) {  // 행 선택 시 콜백 (선택적)
 *                 console.log('선택된 행:', rowData);
 *             }
 *         }
 *     }
 * }
 * 
 * 2. 그리드 이벤트에서 행 선택과 라디오 버튼 동기화
 * gridInstance.on('selection', function(ev) {
 *     if (ev.range && ev.range.row && ev.range.row.length > 0) {
 *         var rowKey = ev.range.row[0];
 *         XitRadioRenderer.syncRadioSelection(rowKey);
 *     }
 * });
 * 
 * @author XIT Framework Team
 * @since 2025-08-26
 */
var XitRadioRenderer = function(props) {
    // 옵션 설정
    var options = (props.columnInfo.renderer && props.columnInfo.renderer.options) ? props.columnInfo.renderer.options : {};
    var radioName = options.radioName || 'gridRowRadio';
    var targetObject = options.targetObject;
    var selectedRowProperty = options.selectedRowProperty || 'selectedRow';
    var onRowSelect = options.onRowSelect;
    
    // div 컨테이너 생성
    this.el = document.createElement('div');
    this.el.style.textAlign = 'center';
    this.el.style.paddingTop = '5px';
    
    // 라디오 버튼 생성
    this.radioEl = document.createElement('input');
    this.radioEl.type = 'radio';
    this.radioEl.name = radioName;
    this.radioEl.value = props.rowKey;
    
    // 라디오 버튼 클릭 이벤트
    this.radioEl.addEventListener('click', function(e) {
        e.stopPropagation();
        
        // 그리드에서 행 데이터 가져오기
        var grid = props.grid;
        if (grid) {
            var rowData = grid.getRow(props.rowKey);
            
            // 대상 객체에 선택된 행 저장
            if (targetObject && window[targetObject]) {
                window[targetObject][selectedRowProperty] = rowData;
            }
            
            // 다른 라디오 버튼들 해제
            document.querySelectorAll('input[name="' + radioName + '"]').forEach(function(radio) {
                if (radio !== this.radioEl) {
                    radio.checked = false;
                }
            }.bind(this));
            
            this.radioEl.checked = true;
            
            // 콜백 함수 호출
            if (typeof onRowSelect === 'function') {
                onRowSelect(rowData, props.rowKey);
            }
        }
    }.bind(this));
    
    this.el.appendChild(this.radioEl);
    this.render(props);
};

XitRadioRenderer.prototype.getElement = function() {
    return this.el;
};

XitRadioRenderer.prototype.render = function(props) {
    // 현재 선택된 행과 비교하여 라디오 버튼 상태 설정
    var options = (props.columnInfo.renderer && props.columnInfo.renderer.options) ? props.columnInfo.renderer.options : {};
    var targetObject = options.targetObject;
    var selectedRowProperty = options.selectedRowProperty || 'selectedRow';
    
    if (targetObject && window[targetObject] && window[targetObject][selectedRowProperty]) {
        var selectedRow = window[targetObject][selectedRowProperty];
        var currentRowData = props.grid.getRow(props.rowKey);
        
        // 기본적으로 rowKey로 비교하거나, 사용자 정의 비교 로직 적용
        var isSelected = false;
        if (selectedRow && currentRowData) {
            // rowKey로 비교
            isSelected = selectedRow.rowKey === currentRowData.rowKey;
            
            // 추가적으로 특정 키 필드들로 비교 (예: ID가 있는 경우)
            if (!isSelected && selectedRow.crdnYr && selectedRow.crdnNo && currentRowData.crdnYr && currentRowData.crdnNo) {
                isSelected = selectedRow.crdnYr === currentRowData.crdnYr && selectedRow.crdnNo === currentRowData.crdnNo;
            }
        }
        
        this.radioEl.checked = isSelected;
    }
};

/**
 * 라디오 버튼 선택 동기화 (그리드 외부에서 호출용)
 */
XitRadioRenderer.syncRadioSelection = function(rowKey, radioName) {
    radioName = radioName || 'gridRowRadio';
    document.querySelectorAll('input[name="' + radioName + '"]').forEach(function(radio) {
        radio.checked = (radio.value == rowKey);
    });
};


/**
 * TUI Grid 역순 행번호 렌더러
 * 그리드에서 전체 데이터 개수 기준으로 역순 번호를 표시합니다.
 * 
 * 사용법:
 * {
 *     header: '번호',
 *     name: '_rowNum',
 *     align: 'center',
 *     width: 60,
 *     sortable: false,
 *     formatter: function(e) {
 *         return XitReverseRowNumberRenderer.format(e);
 *     }
 * }
 * 
 * 또는 renderer로 사용:
 * {
 *     header: '번호',
 *     name: '_rowNum',
 *     align: 'center',
 *     width: 60,
 *     sortable: false,
 *     renderer: {
 *         type: XitReverseRowNumberRenderer
 *     }
 * }
 * 
 * @author XIT Framework Team
 * @since 2025-08-26
 */
var XitReverseRowNumberRenderer = function(props) {
    // Element 생성
    this.el = document.createElement('div');
    this.el.style.textAlign = 'center';
    this.el.innerHTML = XitReverseRowNumberRenderer.format({ 
        grid: props.grid, 
        rowKey: props.rowKey, 
        row: props.row 
    });
};

XitReverseRowNumberRenderer.prototype.getElement = function() {
    return this.el;
};

XitReverseRowNumberRenderer.prototype.render = function(props) {
    this.el.innerHTML = XitReverseRowNumberRenderer.format({ 
        grid: props.grid, 
        rowKey: props.rowKey, 
        row: props.row 
    });
};

/**
 * 역순 번호 포맷팅 함수 (formatter에서 직접 사용 가능)
 */
XitReverseRowNumberRenderer.format = function(gridDataLength, e) {
    var rowKey = e.row.rowKey;

    if (gridDataLength) {
        var totalCount = gridDataLength;
        return totalCount - rowKey;
    }
    return '';
};


var XitTuiGridDropdownMenu = {

    /**
     * 기본 설정
     */
    defaultConfig: {
        // 메뉴 버튼 스타일
        btnClass: 'xit-grid-menu-btn',
        btnStyle: 'background: #f8f9fa; border: 1px solid #dee2e6; padding: 2px 6px; font-size: 12px; line-height: 1; border-radius: 3px; cursor: pointer; color: #495057;',
        btnIcon: '&#8942;', // 세로 점 3개 아이콘

        // 메뉴 컨테이너 스타일
        containerClass: 'xit-grid-menu-container',
        containerStyle: 'position: relative;',

        // 메뉴 리스트 스타일
        menuClass: 'xit-grid-menu-list',
        menuStyle: 'display: none; position: fixed; background: white; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 9999; min-width: 120px;',

        // 메뉴 아이템 스타일
        itemClass: 'xit-grid-menu-item',
        itemStyle: 'display: block; padding: 8px 12px; text-decoration: none; color: #333; border-bottom: 1px solid #eee;',

        // 기본 메뉴 아이템들
        defaultMenuItems: [
            { text: '보기', action: 'view', class: 'js-row-view' },
            { text: '수정', action: 'edit', class: 'js-row-edit' },
            { text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' }
        ]
    },

    /**
     * 드롭다운 메뉴 HTML 생성
     * @param {Object} props - TUI Grid formatter props 객체
     * @param {Array|Object} menuItems - 메뉴 아이템 배열 또는 설정 객체 (선택적)
     * @param {Object} config - 설정 객체 (선택적, 기본값은 defaultConfig 사용)
     * @returns {String} 생성된 HTML 문자열
     */
    createMenuHtml: function(props, menuItems, config) {
        // 파라미터 유형에 따른 처리
        // 두 번째 파라미터가 객체이고 배열이 아닌 경우, config로 처리
        if (menuItems && typeof menuItems === 'object' && !Array.isArray(menuItems) && !config) {
            config = menuItems;
            menuItems = null;
        }

        // 설정 병합 (기본 설정 + 사용자 설정)
        config = $.extend({}, this.defaultConfig, config || {});

        // 메뉴 아이템 설정 우선순위:
        // 1. 직접 전달된 menuItems 배열
        // 2. config.menuItems
        // 3. config.defaultMenuItems (기본값)
        if (menuItems && Array.isArray(menuItems)) {
            // 직접 전달된 menuItems 사용
        } else if (config.menuItems && Array.isArray(config.menuItems)) {
            menuItems = config.menuItems;
        } else {
            menuItems = config.defaultMenuItems;
        }

        var rowKey = props.rowKey;
        var html = '';

        // 메뉴 컨테이너 시작
        html += '<div class="' + config.containerClass + '" style="' + config.containerStyle + '">';

        // 메뉴 버튼
        html += '  <button type="button" class="' + config.btnClass + '" data-row-key="' + rowKey + '" title="작업" ';
        html += '    style="' + config.btnStyle + '">';
        html += '    ' + config.btnIcon;
        html += '  </button>';

        // 메뉴 리스트
        html += '  <div class="' + config.menuClass + '" data-row-key="' + rowKey + '" style="' + config.menuStyle + '">';

        // 메뉴 아이템들 생성
        for (var i = 0; i < menuItems.length; i++) {
            var item = menuItems[i];
            var itemStyle = config.itemStyle;

            // 개별 아이템 스타일이 있으면 병합
            if (item.style) {
                itemStyle += item.style;
            }

            // 마지막 아이템은 하단 테두리 제거
            if (i === menuItems.length - 1) {
                itemStyle = itemStyle.replace('border-bottom: 1px solid #eee;', '');
            }

            html += '    <a class="' + config.itemClass + ' ' + (item.class || '') + '" ';
            html += '      href="javascript:void(0)" data-row-key="' + rowKey + '" data-action="' + item.action + '" ';
            html += '      style="' + itemStyle + '">' + item.text + '</a>';
        }

        html += '  </div>';
        html += '</div>';

        return html;
    },

    /**
     * 드롭다운 메뉴 이벤트 바인딩
     * @param {String} gridSelector - 그리드 컨테이너 선택자 (기본값: '#grid')
     * @param {Object} callbacks - 액션별 콜백 함수들 { view: function(row, rowKey) {}, edit: function(row, rowKey) {}, delete: function(row, rowKey) {} }
     * @param {Object} gridInstance - TUI Grid 인스턴스
     * @param {Object} config - 설정 객체 (선택적)
     */
    bindEvents: function(gridSelector, callbacks, gridInstance, config) {
        // 설정 병합
        config = $.extend({}, this.defaultConfig, config || {});

        // 기본값 설정
        gridSelector = gridSelector || '#grid';
        var $gridEl = $(gridSelector);

        // [중요] 드롭다운 영역 내 상호작용 시 그리드 행 선택/포커스가 되지 않도록 버블링 차단
        // - TUI Grid는 보통 mousedown 단계에서 셀 선택/포커스를 처리하므로 click 차단만으로는 불충분함
        // - 따라서 mousedown/pointerdown/touchstart 단계에서 전파 차단을 반드시 수행
        // - 또한 dblclick, focusin, keydown(방향키 등) 도 전파 차단하여 행 이동/선택을 근본 차단
        var dropdownSelectors = '.' + config.containerClass + ', .' + config.btnClass + ', .' + config.menuClass + ', .' + config.itemClass;
        $gridEl.off('mousedown.xitGridMenu pointerdown.xitGridMenu touchstart.xitGridMenu', dropdownSelectors)
            .on('mousedown.xitGridMenu pointerdown.xitGridMenu touchstart.xitGridMenu', dropdownSelectors, function(e) {
                e.stopPropagation();
            });
        $gridEl.off('dblclick.xitGridMenu', dropdownSelectors)
            .on('dblclick.xitGridMenu', dropdownSelectors, function(e) {
                e.stopPropagation();
            });
        $gridEl.off('focusin.xitGridMenu keydown.xitGridMenu', dropdownSelectors)
            .on('focusin.xitGridMenu keydown.xitGridMenu', dropdownSelectors, function(e) {
                e.stopPropagation();
            });

        // [캡처 단계] 드롭다운 영역 이벤트 전파(캡처/버블 모두) 근본 차단
        // - 그리드가 mousedown 단계에서 선택/포커스를 처리하는 경우를 대비하여 캡처 단계에서 우선 차단한다.
        // - gridSelector 범위 내의 드롭다운 요소들(btn/menu/item/container)에 대해서만 적용한다.
        if (!window.__xitGridMenuCaptureBound) { window.__xitGridMenuCaptureBound = {}; }
        if (!window.__xitGridMenuCaptureBound[gridSelector]) {
            var __xitCaptureHandler = function(e) {
                var target = e.target;
                var $t = $(target);
                var inGrid = $t.closest(gridSelector).length > 0;
                var inDropdown = $t.closest('.' + config.containerClass).length > 0
                    || $t.closest('.' + config.btnClass).length > 0
                    || $t.closest('.' + config.menuClass).length > 0
                    || $t.closest('.' + config.itemClass).length > 0;
                if (inGrid && inDropdown) {
                    // 기본 포커스 이동/텍스트 선택 방지 -> 그리드 포커스 훔침 방지
                    if (e.type === 'pointerdown' || e.type === 'touchstart' || e.type === 'mousedown') {
                        e.preventDefault();
                    }
                    // 전파 차단 (캡처/버블 모두 차단)
                    e.stopPropagation();
                }
            };
            // 캡처 단계 리스너 등록
            document.addEventListener('pointerdown', __xitCaptureHandler, true);
            document.addEventListener('mousedown', __xitCaptureHandler, true);
            document.addEventListener('touchstart', __xitCaptureHandler, true);
            // 중요: click까지 캡처 단계에서 차단하면 그리드 내부 위임 클릭 핸들러가 실행되지 않으므로 등록하지 않음
            window.__xitGridMenuCaptureBound[gridSelector] = true;
        }

        // 메뉴 아이템 클릭 이벤트 (이벤트 위임 방식)
        $gridEl.off('click', '.' + config.itemClass).on('click', '.' + config.itemClass, function(e) {
            e.preventDefault();
            e.stopPropagation();

            var $item = $(this);
            var rowKey = $item.data('rowKey');
            var action = $item.data('action');

            // 그리드에서 행 데이터 가져오기
            var row = gridInstance ? gridInstance.getRow(rowKey) : null;

            // 모든 메뉴 닫기
            $('.' + config.menuClass).hide();

            // 콜백 함수 호출
            if (callbacks && typeof callbacks[action] === 'function') {
                callbacks[action](row, rowKey);
            } else {
                console.warn('XitTuiGridDropdownMenu: ' + action + ' 액션에 대한 콜백 함수가 정의되지 않았습니다.');
            }
        });

        // 메뉴 버튼 클릭 이벤트 - 클릭 위치 기반 메뉴 토글 (스크롤 안전)
        $gridEl.off('click', '.' + config.btnClass).on('click', '.' + config.btnClass, function(e) {
            e.preventDefault();
            e.stopPropagation();

            var $btn = $(this);
            var $menu = $btn.siblings('.' + config.menuClass);
            var rowKey = $btn.data('rowKey');

            // 다른 모든 메뉴 닫기
            $('.' + config.menuClass).not($menu).hide();

            if ($menu.is(':visible')) {
                // 메뉴가 보이면 숨김
                $menu.hide();
            } else {
                // 메뉴가 숨겨져 있으면 표시 - 클릭 위치 기준 간단한 방식

                // 메뉴를 임시로 표시하여 크기 측정
                $menu.css({
                    'visibility': 'hidden',
                    'display': 'block'
                });

                var menuHeight = $menu.outerHeight();
                var menuWidth = $menu.outerWidth();

                // 다시 숨김
                $menu.css({
                    'visibility': 'visible',
                    'display': 'none'
                });

                // 클릭 위치 기반 계산 (스크롤에 안전)
                // clientX, clientY는 뷰포트 기준 좌표이므로 스크롤에 영향을 받지 않음
                var clickX = e.clientX || 0;
                var clickY = e.clientY || 0;

                // 뷰포트 크기
                var windowHeight = $(window).height();
                var windowWidth = $(window).width();

                var finalTop, finalLeft;

                // 간단한 위치 결정 로직
                // 세로 위치: 클릭 지점 아래에 충분한 공간이 있으면 아래로, 없으면 위로
                if (clickY + menuHeight + 10 <= windowHeight) {
                    // 아래쪽에 표시할 공간이 있음
                    finalTop = clickY + 5; // 클릭 지점 바로 아래
                } else {
                    // 위쪽에 표시
                    finalTop = Math.max(10, clickY - menuHeight - 5); // 클릭 지점 위쪽
                }

                // 가로 위치: 클릭 지점 기준으로 우측 정렬, 공간이 없으면 좌측 정렬
                if (clickX - menuWidth + 20 >= 0) {
                    // 우측 정렬 (메뉴가 버튼 오른쪽 끝에 맞춰짐)
                    finalLeft = clickX - menuWidth + 20;
                } else if (clickX + menuWidth + 10 <= windowWidth) {
                    // 좌측 정렬 (클릭 지점에서 시작)
                    finalLeft = clickX;
                } else {
                    // 화면 중앙 정렬
                    finalLeft = Math.max(10, Math.min(windowWidth - menuWidth - 10, clickX - menuWidth / 2));
                }

                // 최종 경계 검사 (뷰포트 내 강제 조정)
                finalTop = Math.max(10, Math.min(windowHeight - menuHeight - 10, finalTop));
                finalLeft = Math.max(10, Math.min(windowWidth - menuWidth - 10, finalLeft));

                // 클릭 위치 기반 메뉴 표시 (스크롤 상황에서도 정확)
                $menu.css({
                    'top': finalTop,
                    'left': finalLeft,
                    'position': 'fixed', // fixed 포지션으로 뷰포트 기준 위치 고정
                    'z-index': 9999      // 다른 요소들 위에 표시 보장
                }).show();
            }
        });

        // 메뉴 아이템 hover 효과
        $gridEl.off('mouseenter mouseleave', '.' + config.itemClass)
            .on('mouseenter', '.' + config.itemClass, function() {
                $(this).css('background-color', '#f8f9fa');
            })
            .on('mouseleave', '.' + config.itemClass, function() {
                $(this).css('background-color', 'white');
            });

        // 문서 클릭 시 모든 메뉴 닫기
        $(document).off('click.xitGridMenu').on('click.xitGridMenu', function(e) {
            if (!$(e.target).closest('.' + config.containerClass).length) {
                $('.' + config.menuClass).hide();
            }
        });
    },

    /**
     * 특정 그리드의 모든 메뉴 닫기
     * @param {String} gridSelector - 그리드 컨테이너 선택자 (선택적, 기본값: 전체)
     * @param {Object} config - 설정 객체 (선택적)
     */
    closeAllMenus: function(gridSelector, config) {
        config = $.extend({}, this.defaultConfig, config || {});

        if (gridSelector) {
            $(gridSelector).find('.' + config.menuClass).hide();
        } else {
            $('.' + config.menuClass).hide();
        }
    }
};
