import { Action } from 'Actions/actions'
import { Analysis } from 'Engine/traverse'
import { areUnitConvertible, convertUnit, parseUnit, Unit } from 'Engine/units'
import { defaultTo, identity, omit, without } from 'ramda'
import reduceReducers from 'reduce-reducers'
import { combineReducers, Reducer } from 'redux'
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
import { SavedSimulation } from 'Selectors/storageSelectors'
import { DottedName, Rule } from 'Types/rule'
import i18n, { AvailableLangs } from '../i18n'
import inFranceAppReducer, { Company } from './inFranceAppReducer'
import storageRootReducer from './storageReducer'

function explainedVariable(state: DottedName | null = null, action: Action) {
	switch (action.type) {
		case 'EXPLAIN_VARIABLE':
			return action.variableName
		case 'STEP_ACTION':
			return null
		default:
			return state
	}
}

type Example = null | {
	name: string
	situation: object
	dottedName: DottedName
	defaultUnits?: Array<Unit>
}

function currentExample(state: Example = null, action: Action): Example {
	switch (action.type) {
		case 'SET_EXAMPLE':
			return action.name != null ? action : null
		default:
			return state
	}
}

function situationBranch(state: number | null = null, action: Action) {
	switch (action.type) {
		case 'SET_SITUATION_BRANCH':
			return action.id
		default:
			return state
	}
}

function activeTargetInput(state: DottedName | null = null, action: Action) {
	switch (action.type) {
		case 'SET_ACTIVE_TARGET_INPUT':
			return action.name
		case 'RESET_SIMULATION':
			return null
		default:
			return state
	}
}

function lang(
	state = i18n.language as AvailableLangs,
	{ type, lang }
): AvailableLangs {
	switch (type) {
		case 'SWITCH_LANG':
			return lang
		default:
			return state
	}
}

function goalsFromAnalysis(analysis) {
	return (
		analysis &&
		(Array.isArray(analysis) ? analysis[0] : analysis).targets
			.map(target => target.explanation || target)
			.filter(target => !!target.formule == !!target.question)
			.map(({ dottedName }) => dottedName)
	)
}

function updateSituation(
	situation,
	{
		fieldName,
		value,
		analysis
	}: {
		fieldName: DottedName
		value: any
		analysis: Analysis | Array<Analysis> | null
	}
) {
	const goals = goalsFromAnalysis(analysis)
	const removePreviousTarget = goals?.includes(fieldName)
		? omit(goals)
		: identity
	return { ...removePreviousTarget(situation), [fieldName]: value }
}

function updateDefaultUnit(situation, { toUnit, analysis }) {
	const unit = parseUnit(toUnit)
	const goals = goalsFromAnalysis(analysis)
	const convertedSituation = Object.keys(situation)
		.map(
			dottedName =>
				analysis.targets.find(target => target.dottedName === dottedName) ||
				analysis.cache[dottedName]
		)
		.filter(
			rule =>
				rule.dottedName === 'entreprise . charges' || // HACK en attendant de revoir le fonctionnement des unités
				(goals?.includes(rule.dottedName) &&
					(rule.unit || rule.defaultUnit) &&
					!rule.unité &&
					areUnitConvertible(rule.unit || rule.defaultUnit, unit))
		)
		.reduce(
			(convertedSituation, rule) => ({
				...convertedSituation,
				[rule.dottedName]: convertUnit(
					rule.unit || rule.defaultUnit,
					unit,
					situation[rule.dottedName]
				)
			}),
			situation
		)
	return convertedSituation
}

type QuestionsKind =
	| "à l'affiche"
	| 'non prioritaires'
	| 'uniquement'
	| 'liste noire'

export type SimulationConfig = Partial<{
	objectifs:
		| Array<DottedName>
		| Array<{ icône: string; nom: string; objectifs: Array<DottedName> }>
	questions: Partial<Record<QuestionsKind, Array<DottedName>>>
	bloquant: Array<DottedName>
	situation: Simulation['situation']
	branches: Array<{ nom: string; situation: SimulationConfig['situation'] }>
	'unités par défaut': [string]
}>

type Situation = Partial<Record<DottedName, any>>
export type Simulation = {
	config: SimulationConfig
	url: string
	hiddenControls: Array<string>
	situation: Situation
	initialSituation: Situation
	defaultUnits: [string]
	foldedSteps: Array<DottedName>
	unfoldedStep?: DottedName | null
}
function getCompanySituation(company: Company): Situation {
	return {
		...(company?.localisation && {
			'établissement . localisation': JSON.stringify(company.localisation)
		}),
		...(company?.dateDeCréation && {
			'entreprise . date de création': company.dateDeCréation.replace(
				/(.*)-(.*)-(.*)/,
				'$3/$2/$1'
			)
		})
	}
}

function simulation(
	state: Simulation | null = null,
	action: Action,
	analysis: Analysis | Array<Analysis> | null,
	existingCompany: Company
): Simulation | null {
	if (action.type === 'SET_SIMULATION') {
		const companySituation = action.useCompanyDetails
			? getCompanySituation(existingCompany)
			: {}
		const { config, url } = action
		if (state && state.config === config) {
			return state
		}
		return {
			config,
			url,
			hiddenControls: [],
			situation: companySituation,
			initialSituation: companySituation,
			defaultUnits: config['unités par défaut'] || ['€/mois'],
			foldedSteps: Object.keys(companySituation) as Array<DottedName>,
			unfoldedStep: null
		}
	}
	if (state === null) {
		return state
	}

	switch (action.type) {
		case 'HIDE_CONTROL':
			return { ...state, hiddenControls: [...state.hiddenControls, action.id] }
		case 'RESET_SIMULATION':
			return {
				...state,
				hiddenControls: [],
				situation: state.initialSituation,
				foldedSteps: [],
				unfoldedStep: null
			}
		case 'UPDATE_SITUATION':
			return {
				...state,
				situation: updateSituation(state.situation, {
					fieldName: action.fieldName,
					value: action.value,
					analysis
				})
			}
		case 'STEP_ACTION':
			const { name, step } = action
			if (name === 'fold')
				return {
					...state,
					foldedSteps: [...state.foldedSteps, step],
					unfoldedStep: null
				}
			if (name === 'unfold') {
				return {
					...state,
					foldedSteps: without([step], state.foldedSteps),
					unfoldedStep: step
				}
			}
			return state
		case 'UPDATE_DEFAULT_UNIT':
			return {
				...state,
				defaultUnits: [action.defaultUnit],
				situation: updateDefaultUnit(state.situation, {
					toUnit: action.defaultUnit,
					analysis
				})
			}
	}
	return state
}
const existingCompanyReducer = (state, action: Action) => {
	if (action.type.startsWith('EXISTING_COMPANY::') && state.simulation) {
		return {
			...state,
			simulation: {
				...state.simulation,
				situation: {
					...state.simulation.situation,
					...getCompanySituation(state.inFranceApp.existingCompany)
				}
			}
		}
	}
	return state
}
const mainReducer = (state, action: Action) =>
	combineReducers({
		lang,
		rules: defaultTo(null) as Reducer<Array<Rule>>,
		explainedVariable,
		// We need to access the `rules` in the simulation reducer
		simulation: (a: Simulation | null = null, b: Action): Simulation | null =>
			simulation(
				a,
				b,
				a && analysisWithDefaultsSelector(state),
				state.inFranceApp?.existingCompany
			),
		previousSimulation: defaultTo(null) as Reducer<SavedSimulation | null>,
		currentExample,
		situationBranch,
		activeTargetInput,
		inFranceApp: inFranceAppReducer
	})(state, action)

export default reduceReducers<RootState>(
	mainReducer as any,
	existingCompanyReducer as any,
	storageRootReducer as any
) as Reducer<RootState>

export type RootState = ReturnType<typeof mainReducer>