import Immutable from 'seamless-immutable';
import type {Immutable as ImmutableType} from 'seamless-immutable';
import {v4 as uuidv4} from 'uuid';
import {SESSION_ADD, SESSION_SET_ACTIVE} from '../../typings/constants/sessions';
import type {SessionAddAction} from '../../typings/constants/sessions';
import {TERM_GROUP_EXIT, TERM_GROUP_RESIZE} from '../../typings/constants/term-groups';
import type {ITermGroup, ITermState, ITermGroups, ITermGroupReducer, Mutable} from '../../typings/hyper';
import {decorateTermGroupsReducer} from '../utils/plugins';
import findBySession from '../utils/term-groups';
const MIN_SIZE = 0.05;
const initialState: ITermState = Immutable<Mutable<ITermState>>({
termGroups: {},
activeSessions: {},
activeRootGroup: null
});
function TermGroup(obj: Immutable.DeepPartial<Mutable<ITermGroup>>) {
const x: Mutable<ITermGroup> = {
uid: '',
sessionUid: null,
parentUid: null,
direction: null,
sizes: null,
children: []
};
return Immutable(x).merge(obj);
}
const findRootGroup = (termGroups: ITermGroups, uid: string): ITermGroup => {
const current = termGroups[uid];
if (!current.parentUid) {
return current;
}
return findRootGroup(termGroups, current.parentUid);
};
const setActiveGroup = (state: ITermState, action: {uid: string}) => {
if (!action.uid) {
return state.set('activeRootGroup', null);
}
const childGroup = findBySession(state, action.uid)!;
const rootGroup = findRootGroup(state.termGroups, childGroup.uid);
return state.set('activeRootGroup', rootGroup.uid).setIn(['activeSessions', rootGroup.uid], action.uid);
};
const insertRebalance = (oldSizes: ImmutableType<number[]>, index: number) => {
const newSize = 1 / (oldSizes.length + 1);
const balanced = oldSizes.map((size) => size - newSize * size);
return [...balanced.slice(0, index).asMutable(), newSize, ...balanced.slice(index).asMutable()];
};
const removalRebalance = (oldSizes: ImmutableType<number[]>, index: number) => {
const removedSize = oldSizes[index];
const increase = removedSize / (oldSizes.length - 1);
return Immutable(
oldSizes
.asMutable()
.filter((_size: number, i: number) => i !== index)
.map((size: number) => size + increase)
);
};
const splitGroup = (state: ITermState, action: SessionAddAction) => {
const {splitDirection, uid, activeUid} = action;
const activeGroup = findBySession(state, activeUid!)!;
let parentGroup = activeGroup.parentUid ? state.termGroups[activeGroup.parentUid] : activeGroup;
if (parentGroup.direction && parentGroup.direction !== splitDirection) {
parentGroup = activeGroup;
}
const newSession = TermGroup({
uid: uuidv4(),
sessionUid: uid,
parentUid: parentGroup.uid
});
state = state.setIn(['termGroups', newSession.uid], newSession);
if (parentGroup.sessionUid) {
const existingSession = TermGroup({
uid: uuidv4(),
sessionUid: parentGroup.sessionUid,
parentUid: parentGroup.uid
});
return state.setIn(['termGroups', existingSession.uid], existingSession).setIn(
['termGroups', parentGroup.uid],
parentGroup.merge({
sessionUid: '',
direction: splitDirection,
children: [existingSession.uid, newSession.uid]
})
);
}
const {children} = parentGroup;
const index = children.indexOf(activeGroup.uid) + 1;
const newChildren = [...children.slice(0, index).asMutable(), newSession.uid, ...children.slice(index).asMutable()];
state = state.setIn(
['termGroups', parentGroup.uid],
parentGroup.merge({
direction: splitDirection,
children: newChildren
})
);
if (parentGroup.sizes) {
const newSizes = insertRebalance(parentGroup.sizes, index);
state = state.setIn(['termGroups', parentGroup.uid, 'sizes'], newSizes);
}
return state;
};
const replaceParent = (state: ITermState, parent: ITermGroup, child: ITermGroup) => {
if (parent.parentUid) {
const parentParent = state.termGroups[parent.parentUid];
const newChildren = parentParent.children.map((uid: string) => (uid === parent.uid ? child.uid : uid));
state = state.setIn(['termGroups', parentParent.uid, 'children'], newChildren);
} else {
const newSessions = state.activeSessions.without(parent.uid).set(child.uid, state.activeSessions[parent.uid]);
state = state
.set('activeTermGroup', child.uid)
.set('activeRootGroup', child.uid)
.set('activeSessions', newSessions);
}
return state
.set('termGroups', state.termGroups.without(parent.uid))
.setIn(['termGroups', child.uid, 'parentUid'], parent.parentUid);
};
const removeGroup = (state: ITermState, uid: string) => {
const group = state.termGroups[uid];
if (group.parentUid && state.termGroups[group.parentUid]) {
const parent = state.termGroups[group.parentUid];
const newChildren = parent.children.filter((childUid) => childUid !== uid);
if (newChildren.length === 1) {
const child = state.termGroups[newChildren[0]];
state = replaceParent(state, parent, child);
} else {
state = state.setIn(['termGroups', group.parentUid, 'children'], newChildren);
if (parent.sizes) {
const childIndex = parent.children.indexOf(uid);
const newSizes = removalRebalance(parent.sizes, childIndex);
state = state.setIn(['termGroups', group.parentUid, 'sizes'], newSizes);
}
}
}
return state
.set('termGroups', state.termGroups.without(uid))
.set('activeSessions', state.activeSessions.without(uid));
};
const resizeGroup = (state: ITermState, uid: string, sizes: number[]) => {
if (sizes.find((size) => size < MIN_SIZE)) {
return state;
}
return state.setIn(['termGroups', uid, 'sizes'], sizes);
};
const reducer: ITermGroupReducer = (state = initialState, action) => {
switch (action.type) {
case SESSION_ADD: {
if (action.splitDirection) {
state = splitGroup(state, action);
return setActiveGroup(state, action);
}
const uid = uuidv4();
const termGroup = TermGroup({
uid,
sessionUid: action.uid
});
return state
.setIn(['termGroups', uid], termGroup)
.setIn(['activeSessions', uid], action.uid)
.set('activeRootGroup', uid);
}
case SESSION_SET_ACTIVE:
return setActiveGroup(state, action);
case TERM_GROUP_RESIZE:
return resizeGroup(state, action.uid, action.sizes);
case TERM_GROUP_EXIT:
return removeGroup(state, action.uid);
default:
return state;
}
};
export default decorateTermGroupsReducer(reducer);