import { alertController, AlertOptions, modalController, ToastButton, toastController, UseIonRouterResult, createAnimation } from '@ionic/vue';
import { AxiosRequestConfig } from 'axios';
import { VueI18n } from 'vue-i18n';
import Icon from './icon';
import apiService, { ApiService } from './api.service';
import { AppConfVo, ConsoleLogVo, ICommonService, ILogerService, IonColorType, LoadStatusType, ToolbarButtonVo, ConsoleLogType } from './common.interface';
import sharedService, { SharedService } from './shared.service';

/**
 * Common service
 */
export class CommonService implements ICommonService, ILogerService {
	/**
	 * Instance  of common service
	 */
	private static _instance: CommonService | null = null;

	/**
	 * Api  of common service
	 */
	private _api: ApiService = null;

	/**
	 * Shared  of common service
	 */
	private _shared: SharedService = null;

	/**
	 * Ion router of common service
	 */
	private _ionRouter: UseIonRouterResult;

	/**
	 * I18n  of common service
	 */
	private _i18n: VueI18n = null;

	/**
	 * Creates an instance of common service.
	 */
	constructor() {
		this._api = apiService;
		this._shared = sharedService;
		this._shared.setCommonService(this);
		this._api.setLogger(this);
		this._shared.setLogger(this);
	}

	/**
	 * Gets instance
	 *
	 * @returns instance
	 */
	public static getInstance(): CommonService {
		if (this._instance === null) {
			this._instance = new CommonService();
		}
		return this._instance;
	}

	/**
	 * Opens form invalid
	 *
	 * @param errorField
	 * @param [refs]
	 * @param [refName]
	 * @returns true if form invalid
	 */
	public openFormInvalid(errorField: string, refs: any = null, refName: string = null): boolean {
		this.openToast('MSG_CHECKMODEL', { field: errorField }, null, null, 'warning').then(() => {
			this.setFormFocus(refs, refName);
		});
		return false;
	}

	/**
	 * Gets ref
	 *
	 * @template T
	 * @param [refs]
	 * @param [refName]
	 * @param [methodName]
	 * @returns ref
	 */
	public getRef<T = any>(refs: any = null, refName: string = null, methodName?: string): T {
		if (refs && refs[refName] !== undefined && refs[refName] !== null) {
			const refObj = refs[refName];
			if (methodName === undefined || methodName === null || methodName === '') {
				return refObj as T;
			} else if (refObj[methodName] !== undefined && typeof refObj[methodName] === 'function') {
				return refObj;
			} else if (refObj.$el !== undefined && refObj.$el !== null) {
				return refObj.$el;
			}
		}
		return null;
	}

	/**
	 * Sets form focus
	 *
	 * @param [refs]
	 * @param [refName]
	 * @returns true if form focus
	 */
	public setFormFocus(refs: any = null, refName: string = null): boolean {
		const refEle = this.getRef(refs, refName, 'setFocus');
		if (refEle !== null && refEle !== undefined && refEle?.setFocus) {
			refEle.setFocus();
		}
		return false;
	}

	/**
	 * Gets ionic data
	 *
	 * @param target
	 * @param colunms
	 * @returns ionic data
	 */
	public getIonicData(target: { [key: string]: any }, colunms: { [key: string]: 'date' }): any {
		const dest = Object.assign({}, target);
		for (const key in colunms) {
			const keyType = colunms[key];
			switch (keyType) {
				case 'date':
					{
						const dataStr = dest[key];
						if (dataStr !== null && dataStr !== undefined && dataStr !== '' && dataStr.indexOf('-') === -1) {
							switch (dataStr.length) {
								case 8:
									dest[key] = dataStr.substring(0, 4) + '-' + dataStr.substring(4, 6) + '-' + dataStr.substring(6, 8) + 'T00:00';
									break;
								case 4:
									dest[key] = '20' + dataStr.substring(0, 2) + '-' + dataStr.substring(2, 4) + '-' + dataStr.substring(4, 6) + 'T00:00';
									break;
								default:
									dest[key] = null;
									break;
							}
						}
					}
					break;
			}
		}
		return dest;
	}

	/**
	 * Gets submit data
	 *
	 * @param target
	 * @param [colunms]
	 * @param [fixData]
	 * @returns submit data
	 */
	public getSubmitData(target: { [key: string]: any }, colunms?: { [key: string]: 'date' | 'codeall' }, fixData?: any): any {
		const dest = Object.assign(fixData || {}, target);
		if (colunms !== undefined && colunms !== null) {
			for (const key in colunms) {
				const keyType = colunms[key];
				switch (keyType) {
					case 'date':
						if (dest[key] !== null && dest[key] !== undefined && dest[key] !== '') {
							dest[key] = dest[key].split('T')[0].split('-').join('');
						}
						break;
					case 'codeall':
						if (dest[key] === null || dest[key] === undefined || dest[key] === '') {
							dest[key] = 'all';
						}
						break;
				}
			}
		}
		return dest;
	}

	/**
	 * Sets form disabled
	 *
	 * @param disabled
	 * @param [refs]
	 * @param [refName]
	 * @returns true if form disabled
	 */
	public setFormDisabled(disabled: boolean, refs: any = null, refName: string = null): boolean {
		const refEl: any = this.getRef(refs, refName, 'setDisabled');
		if (refEl !== null && refEl !== undefined) {
			if (refEl.setDisabled) {
				refEl.setDisabled(disabled);
			} else if (refEl.disabled !== undefined) {
				refEl.disabled = disabled;
			}
		}
		return false;
	}

	/**
	 * Gets translate
	 *
	 * @param message
	 * @param [named]
	 * @returns
	 */
	public getTranslate(message: string, named?: Record<string, unknown>) {
		if (message !== null && message !== undefined && (message.includes('MSG_') || message.includes('LBL_') || message.includes('TIT_') || message.includes('BTN_'))) {
			if (this._i18n !== null) {
				message = this._i18n.t(message, named);
			}
		}
		return message;
	}

	/**
	 * Opens toast
	 *
	 * 토스트 창 열기
	 * ```ts
	 * commonService.openToast('Message Message Message Message', null, 260000, 'message title', color, null, null, '/home')
	 * ```
	 * @param message 메세지
	 * @param [named] 메세지 인터폴레이션
	 * @param [duration] 열어둘 시간
	 * @param [header] 헤드
	 * @param [color] 색상
	 * @param [icon] 우측에 보여 줄 아이콘 미지정시 색상에 의해 결정됨
	 * @param [buttons] 강제 생성할 버튼
	 * @param [routerPath] 변경할 주소가 있다면 우측에 보기 버튼이 생기고 클릭시 이동함
	 * @returns
	 */
	public async openToast(
		message: string,
		named?: Record<string, any>,
		duration = 3000,
		header?: string,
		color?: IonColorType,
		icon?: string,
		buttons?: (ToastButton | string)[],
		routerPath?: string
	) {
		if (this._lastToastOpened !== null) {
			this._lastToastOpened.dismiss();
			this._lastToastOpened = null;
		}
		if (icon === null || icon === undefined) {
			switch (color) {
				case 'warning':
				case 'danger':
					icon = Icon.warningOutline;
					break;
				case 'success':
					icon = Icon.saveOutline;
					break;
				default:
					icon = Icon.helpOutline;
					break;
			}
		}
		if (routerPath !== null && routerPath !== undefined) {
			if (buttons === null || buttons === undefined) {
				buttons = [];
			}
			buttons.push({
				text: this.getTranslate('BTN_VIEW'),
				side: 'end',
				handler: () => {
					this.changePage(routerPath);
				},
			});
		}
		const toast = await toastController.create({
			header: this.getTranslate(header || 'TIT_NOTICE'),
			message: this.getTranslate(message, named),
			icon: icon,
			color: color || 'primary',
			duration: duration || 3000,
			translucent: true,
			animated: true,
			position: 'bottom',
			buttons: buttons,
			enterAnimation: (baseEl: any) => {
				const toastWrapper = baseEl.shadowRoot.querySelector('.toast-wrapper') as HTMLElement;
				toastWrapper.style.bottom = 'calc(20px + var(--ion-safe-area-bottom, 0px))';
				return createAnimation()
					.addElement(toastWrapper)
					.duration(Math.min((duration || 3000) * 0.3, 500))
					.fromTo('transform', 'translateY(100%)', 'translateY(0%)')
					.fromTo('opacity', '0', '1');
			},
			leaveAnimation: (baseEl: any) => {
				const toastWrapper = baseEl.shadowRoot.querySelector('.toast-wrapper') as HTMLElement;
				return createAnimation()
					.addElement(toastWrapper)
					.duration(Math.min((duration || 3000) * 0.3, 500))
					.fromTo('transform', 'translateY(0%)', 'translateY(100%)')
					.fromTo('opacity', '1', '0');
			},
		});
		await toast.present();
		toast.onDidDismiss().then(() => {
			if (this._lastToastOpened === toast) {
				this._lastToastOpened = null;
			}
		});
		this._lastToastOpened = toast;
		return toast.onWillDismiss();
	}

	private _lastToastOpened: HTMLIonToastElement = null;

	/**
	 * Opens alert
	 * ```ts
	 * commonService.openAlert('알려드립니다.').then(() => {
	 * 	alert('알림 닫힘');
	 * });
	 * ```
	 * @param message
	 * @param [named]
	 * @param [options]
	 * @returns alert
	 */
	public async openAlert(message: string, named?: Record<string, unknown>, options?: AlertOptions): Promise<boolean> {
		const alert = await alertController.create(
			Object.assign(options || {}, {
				header: options?.header ? this.getTranslate(options.header) : null,
				subHeader: options?.subHeader ? this.getTranslate(options.subHeader) : null,
				message: this.getTranslate(message, named),
				backdropDismiss: options?.backdropDismiss ? true : false,
				keyboardClose: options?.keyboardClose ? true : false,
				cssClass: 'custom-alert',
				buttons: [
					{
						text: this.getTranslate('BTN_CLOSE'),
						cssClass: 'alert-button-close',
						role: 'close',
					},
				],
			})
		);
		await alert.present();
		const { role } = await alert.onDidDismiss();
		return role == 'close' ? true : false;
	}

	/**
	 * Opens confirm
	 *
	 * 확인창을 보여줌
	 *
	 * ```ts
	 * commonService.openConfirm('Are U OK?').then(isOk => {
	 * 	if (isOk) {
	 * 		alert('You are OK');
	 * 	} else {
	 * 		alert('You are not OK');
	 *  }
	 * })
	 * ```
	 *
	 * @param message
	 * @param [named]
	 * @param [options]
	 * @returns confirm
	 */
	public async openConfirm(message: string, named?: Record<string, unknown>, options?: AlertOptions): Promise<boolean> {
		const alert = await alertController.create(
			Object.assign(options || {}, {
				header: options?.header ? this.getTranslate(options.header) : null,
				subHeader: options?.subHeader ? this.getTranslate(options.subHeader) : null,
				message: this.getTranslate(message, named),
				backdropDismiss: options?.backdropDismiss ? true : false,
				keyboardClose: options?.keyboardClose ? true : false,
				cssClass: 'custom-confirm',
				buttons: [
					{
						text: this.getTranslate('BTN_NO'),
						cssClass: 'confirm-button-cancel',
						role: 'no',
					},
					{
						text: this.getTranslate('BTN_YES'),
						cssClass: 'confirm-button-confirm',
						role: 'yes',
					},
				],
			})
		);
		await alert.present();
		const { role } = await alert.onDidDismiss();
		return role == 'yes' ? true : false;
	}

	/**
	 * Opens modal
	 *
	 * @param component
	 * @param [componentProps]
	 * @param [showBackdrop]
	 * @param [backdropDismiss]
	 * @returns
	 */
	public async openModal(component: any, componentProps?: any, showBackdrop?: boolean, backdropDismiss?: boolean) {
		const modal = await modalController.create({
			component: component,
			componentProps: componentProps,
			showBackdrop: showBackdrop,
			backdropDismiss: backdropDismiss,
		});
		modal.present();
		return modal.onWillDismiss();
	}

	/**
	 * Determines whether null is
	 *
	 * @param input
	 * @returns true if null
	 */
	public isNull(input: any): boolean {
		if (typeof input === 'undefined' || input === null || input === '0' || input === 0) {
			return true;
		}
		if (typeof input === 'string') {
			if (input.split(' ').join('') === '') {
				return true;
			}
		}
		if (typeof input === 'number') {
			if (input === 0) {
				return true;
			}
		}
		if (Array.isArray(input)) {
			if (input.length === 0) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Determines whether not null is
	 *
	 * @param input
	 * @returns true if not null
	 */
	public isNotNull(input: any): boolean {
		return !this.isNull(input);
	}

	/**
	 * Gets uuid
	 *
	 * @param prefix
	 * @returns uuid
	 */
	public getUuid(prefix: string): string {
		return prefix + '_' + Math.round(Math.random() * 899999 + 100000) + '_' + Math.round(Math.random() * 899999 + 100000);
	}

	/**
	 * Sets i18n
	 *
	 * @param i18n
	 */
	public setI18n(i18n: VueI18n) {
		this._i18n = i18n;
	}

	/**
	 * Sets router
	 *
	 * @param ionRouter
	 */
	public setRouter(ionRouter: UseIonRouterResult) {
		this._ionRouter = ionRouter;
	}

	/**
	 * Gets router
	 *
	 * @returns router
	 */
	public getRouter(): UseIonRouterResult {
		return this._ionRouter;
	}

	/**
	 * Changes page
	 *
	 * @param location
	 */
	public changePage(location: string) {
		if (this._ionRouter !== null) {
			this._ionRouter.navigate(location, 'root', 'replace');
		}
	}

	/**
	 * Gets app conf
	 *
	 * @returns app conf
	 */
	public getAppConf(): AppConfVo {
		return this._shared.getAppConf();
	}

	/**
	 * Gets shared service
	 *
	 * @returns shared service
	 */
	public getSharedService(): SharedService {
		return this._shared;
	}

	/**
	 * Checks login
	 *
	 * @returns login
	 */
	public async checkLogin(): Promise<boolean> {
		return this._shared.checkLogin();
	}

	/**
	 * Shows error
	 *
	 * @param error
	 */
	public showError(error: any) {
		if (error !== null && error !== undefined && (error.message || error.busiMessage)) {
			switch (error.message || error.busiMessage) {
				case 'exception.session.invalid':
					this._shared.checkLogout().then(() => {
						this.openToast('LOGIN.MSG_SESSION_INVALID', null, null, 'LOGIN.TIT_LOGOUT', 'warning');
						this.changePage('/login');
					});
					break;
				default:
					this.openToast(error?.message || error?.busiMessage || 'MSG_SERVER_ERROR', null, null, 'TIT_SERVER_ERROR', 'warning');
					break;
			}
		} else {
			this.openToast('MSG_SERVER_ERROR', null, null, 'TIT_SERVER_ERROR', 'warning');
		}
	}

	/**
	 * Gets list
	 *
	 * @template T
	 * @param url
	 * @param [params]
	 * @param [limit]
	 * @param [offset]
	 * @param [config]
	 * @returns list
	 */
	public getList<T>(url: string, params?: any, limit = 100, offset = 0, config?: AxiosRequestConfig): Promise<T[]> {
		params = params || {};
		params.limit = limit;
		params.offset = offset;
		return this._api.get(url, params, config).catch((error) => {
			this.showError(error);
		});
	}

	/**
	 * Gets view
	 *
	 * @template T
	 * @param url
	 * @param [params]
	 * @param [config]
	 * @returns view
	 */
	public getView<T>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
		return this._api.get(url, params, config).catch((error) => {
			this.showError(error);
		});
	}

	/**
	 * Gets create
	 *
	 * @template T
	 * @param url
	 * @param [params]
	 * @param [config]
	 * @returns create
	 */
	public getCreate<T>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
		return this.getPost(url, params, config);
	}

	/**
	 * Gets update
	 *
	 * @template T
	 * @param url
	 * @param [params]
	 * @param [config]
	 * @returns update
	 */
	public getUpdate<T>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
		return this.getPost(url, params, config);
	}

	/**
	 * Gets post
	 *
	 * @param url
	 * @param [params]
	 * @param [config]
	 * @returns post
	 */
	public getPost(url: string, params?: any, config?: AxiosRequestConfig): Promise<any> {
		return this._api.post(url, params, config).catch((error) => {
			this.showError(error);
		});
	}

	/**
	 * Gets delete
	 *
	 * @template T
	 * @param url
	 * @param [params]
	 * @param [config]
	 * @returns delete
	 */
	public getDelete<T>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
		return this._api.delete(url, params, config).catch((error) => {
			this.showError(error);
		});
	}

	/**
	 * Checks load status list
	 *
	 * @param component
	 * @param promise
	 * @param [event]
	 */
	public checkLoadStatusList(
		component: {
			$refs?: any;
			loadStatus?: LoadStatusType;
			dataList?: any[];
		},
		promise: Promise<any>,
		event?: any
	): void {
		component.loadStatus = 'loading';
		promise.finally(() => {
			component.loadStatus = this.isNotNull(component?.dataList) && component?.dataList?.length > 0 ? 'loaded' : 'nodata';
			if (event !== null && event !== undefined && event?.target?.complete) {
				event.target.complete();
			}
		});
	}

	/**
	 * Checks load status view
	 *
	 * @param component
	 * @param promise
	 * @param [event]
	 */
	public checkLoadStatusView(
		component: {
			loadStatus?: LoadStatusType;
			dataView?: any;
			writeModel?: any;
		},
		promise: Promise<any>,
		event?: any
	): void {
		component.loadStatus = 'loading';
		promise.finally(() => {
			component.loadStatus = this.isNotNull(component.dataView || component.writeModel) ? 'loaded' : 'error';
			if (event !== null && event !== undefined && event?.target?.complete) {
				event.target.complete();
			}
		});
	}

	/**
	 * Checks load status view
	 *
	 * @param component
	 * @param promise
	 * @param [event]
	 */
	public checkLoadStatusSubmit(
		component: {
			loadStatus?: LoadStatusType;
			toolbars?: ToolbarButtonVo[];
		},
		promise: Promise<any>,
		event?: any
	): void {
		component.loadStatus = 'submit';
		component.toolbars?.forEach((item) => {
			switch (item.role) {
				case 'submit':
					item.disabled = true;
					break;
			}
		});
		promise.finally(() => {
			component.loadStatus = 'loaded';
			component.toolbars?.forEach((item) => {
				switch (item.role) {
					case 'submit':
						item.disabled = false;
						break;
				}
			});
			if (event !== null && event !== undefined && event?.target?.complete) {
				event.target.complete();
			}
		});
	}

	/**
	 * Changes login data
	 *
	 * @param [siteCd]
	 * @param [pushUseYn]
	 * @param [pushKey]
	 */
	public changeLoginData(siteCd?: string, pushUseYn?: boolean, pushKey?: string) {
		if (this.getSharedService().changeLoginData(siteCd, pushUseYn, pushKey)) {
			this.openToast('MSG_SETTING_DONE');
		}
	}

	/**
	 * Logger  of common service
	 */
	private _logger: ILogerService = null;

	/**
	 * Sets logger
	 *
	 * @param logger
	 */
	public setLogger(logger: ILogerService) {
		if (logger !== null) {
			window.setTimeout(() => {
				this._lastLogs.forEach((log) => {
					logger.consoleLog(log.type, log.title, ...log.datas);
				});
				this._logger = logger;
				this._lastLogs = [];
			}, 2000);
		} else {
			this._logger = null;
		}
	}

	private _lastLogs: ConsoleLogVo[] = [];

	/**
	 * Consoles log
	 *
	 * @param title
	 * @param obj
	 */
	public consoleLog(type: ConsoleLogType, title: string, ...objs: any[]) {
		if (this._logger !== null) {
			this._logger.consoleLog(type, title, ...objs);
		} else {
			this._lastLogs.push({ type: type, title: title, datas: objs });
			if (this._lastLogs.length > 30) {
				this._lastLogs.shift();
			}
		}
	}
}

export default CommonService.getInstance();
