import { createLogger } from '@gongt/ts-stl-library/debug/create-logger';
import { LOG_LEVEL } from '@gongt/ts-stl-library/debug/levels';
import { GlobalVariable } from '@gongt/ts-stl-library/pattern/global-page-data';
import { applyMiddleware, compose, createStore, Dispatch, Middleware, Reducer, Store, Unsubscribe, } from 'redux';
import { ArrayOrSingle } from '../global';
import { IAction, IAData } from './action';
import { MyCombineReducers } from './combine-reducers';
import { IState } from './preload-state';
import { defaultMiddlewares } from './store.internal-middleware';
import { IVirtualStore, IVirtualStoreConstructor } from './virtual-store';

export type AppStore<StateInterface extends IState> = Store<StateInterface>;

export type ReduxStoreType = ReduxStore<any>;
export type LogicFunction<T> = (store: ReduxStore<T>) => void;
export type BorderLogicFunction<T> = (
	subscribe: (listener: (state: T) => void) => Unsubscribe,
	dispatch: Dispatch<T>,
) => void;

export type SimpleMiddleware<T, D extends IAData> = (store: AppStore<T>, action: IAction<D>)
	=> void|IAction<D>|Promise<void|IAction<D>>;

export const REDUX_PRELOAD_NAME = '__REDUX_PRELOAD_DATA__';

const debug = createLogger(LOG_LEVEL.SILLY, 'redux-store');

export interface MiddlewareCreator {
	(global: GlobalVariable): Middleware;
}

export interface IPlugin<State extends IState> {
	__redux_plugin(reduxStore: ReduxStore<State>): void;
}

/**
 * 根据逻辑函数（构造函数的参数）创建Store
 */
export class ReduxStore<StateInterface extends IState> {
	public middlewares: Middleware[];
	public processObject: BorderLogicFunction<StateInterface>[] = [];
	
	public subStore: {[id: string]: IVirtualStore<any>} = {};
	protected readonly composeEnhancers = compose;
	private finished = false;
	private middleware_callbacks: MiddlewareCreator[] = [];
	
	constructor(logicRegister?: ArrayOrSingle<LogicFunction<StateInterface>>) {
		this.middlewares = defaultMiddlewares.slice();
		if (logicRegister) {
			if (Array.isArray(logicRegister)) {
				logicRegister.forEach((fn) => {
					fn(this);
				});
			} else {
				logicRegister(this);
			}
		}
	}
	
	createStore(global: GlobalVariable): AppStore<StateInterface> {
		if (global.has(REDUX_PRELOAD_NAME)) {
			const preloadOrStoreObject = global.get(REDUX_PRELOAD_NAME);
			if (typeof preloadOrStoreObject.getState === 'function') {
				debug('  create store: re-call, return last object.');
				return preloadOrStoreObject;
			}
		}
		this.finished = true;
		const applicationLogic = this.combineReducers();
		const dynamicMiddleware = this.middleware_callbacks.map((cb) => {
			return cb(global);
		});
		const appliedMiddleware = applyMiddleware(...this.middlewares, ...dynamicMiddleware);
		
		const createStoreEnhancer = this.composeEnhancers(appliedMiddleware);
		const enhancedCreateStore = createStoreEnhancer<any>(createStore); // TODO: <- this any
		
		const preloadState = this.getPreloadState(global);
		
		Object.keys(this.subStore).forEach((key) => {
			const st = this.subStore[key];
			// console.log('%s: ', key, preloadState.hasOwnProperty(key), st.hasOwnProperty('defaultValue'), st);
			if (!preloadState.hasOwnProperty(key) && st.hasOwnProperty('defaultValue')) {
				preloadState[key] = st.defaultValue;
			}
		});
		
		if (debug.enabled) {
			debug('  preloadState=');
			for (let name of Object.keys(preloadState)) {
				const v = preloadState[name];
				debug('    %s -> %s', name, v.name || v.constructor.name || v);
			}
		}
		
		const store: Store<StateInterface> = enhancedCreateStore(applicationLogic, preloadState);
		store['toJSON'] = store.getState;
		
		this.processObject.forEach((fn) => {
			fn((f) => {
				f(store.getState());
				return store.subscribe(() => {
					f(store.getState());
				});
			}, (act: any) => {
				if (act && act['toJSON']) {
					act = act['toJSON']();
				}
				return store.dispatch(act);
			});
		});
		
		global.set(REDUX_PRELOAD_NAME, store);
		return store;
	}
	
	/** 添加预处理 */
	public logicDisplayBorder(middleware: BorderLogicFunction<StateInterface>) {
		if (this.finished) {
			throw new Error('ReduxStore: use middleware too late');
		}
		this.processObject.push(middleware);
	}
	
	public plugin(plugin: IPlugin<Partial<StateInterface>>) {
		plugin.__redux_plugin(this);
	}
	
	/** 添加子存储 */
	public register<T>(Constructor: IVirtualStoreConstructor<T>) {
		if (this.finished) {
			throw new Error('ReduxStore: register sub store too late');
		}
		const name = Constructor.name;
		// console.log('register sub store: ', name);
		if (this.subStore[name]) {
			throw new TypeError('duplicate store: ' + name);
		}
		this.subStore[name] = new Constructor();
	}
	
	/** 添加中间件 */
	public use(middleware: Middleware) {
		if (this.finished) {
			throw new Error('ReduxStore: use middleware too late');
		}
		this.middlewares.push(middleware);
	}
	
	public useDynamic(callback: MiddlewareCreator) {
		if (this.finished) {
			throw new Error('ReduxStore: use middleware too late');
		}
		this.middleware_callbacks.push(callback);
	}
	
	public useSimple<D extends IAData>(middleware: SimpleMiddleware<StateInterface, D>) {
		if (this.finished) {
			throw new Error('ReduxStore: use middleware too late');
		}
		const md: Middleware = <StateInterface>(state) => next => action => {
			const ret = middleware(state, action);
			if (!ret) {
				console.log('an action has been dropped: %s', action.type);
				return null;
			} else if (ret instanceof Promise) {
				ret.then((act) => {
					if (act) {
						next(act);
					} else {
						console.log('%can action has been dropped (after sync): %s', 'color:grey', action.type);
					}
				}, (e) => {
					console.error(e);
					throw new Error('simple middleware promise rejected.');
				});
				return null;
			} else {
				return next(action);
			}
		};
		this.middlewares.push(md);
	}
	
	protected combineReducers(): Reducer<StateInterface> {
		const pass = [];
		for (const key of Object.keys(this.subStore)) {
			const st = this.subStore[key];
			pass.push(...st.getReducers());
		}
		return MyCombineReducers(pass);
	}
	
	protected getPreloadState(global: GlobalVariable) {
		if (global.has(REDUX_PRELOAD_NAME)) {
			const pl = global.get(REDUX_PRELOAD_NAME);
			global.unset(REDUX_PRELOAD_NAME);
			return pl;
		}
		return {};
	}
}
