import {
	add,
	countBy,
	descend,
	difference,
	equals,
	flatten,
	head,
	identity,
	keys,
	last,
	length,
	map,
	mergeWith,
	negate,
	pair,
	pipe,
	reduce,
	sortBy,
	sortWith,
	takeWhile,
	toPairs,
	zipWith,
} from 'ramda'
import { useContext, useMemo } from 'react'
import { useSelector } from 'react-redux'
import { Simulation, SimulationConfig } from 'Reducers/rootReducer'
import { DottedName } from 'modele-social'
import {
	answeredQuestionsSelector,
	configSelector,
	currentQuestionSelector,
	objectifsSelector,
	situationSelector,
} from 'Selectors/simulationSelectors'
import { EngineContext } from './EngineContext'
import Engine from 'publicodes'

type MissingVariables = Partial<Record<DottedName, number>>
export function getNextSteps(
	missingVariables: Array<MissingVariables>
): Array<DottedName> {
	const byCount = ([, [count]]: [unknown, [number]]) => count
	const byScore = ([, [, score]]: [unknown, [unknown, number]]) => score

	const missingByTotalScore = reduce<MissingVariables, MissingVariables>(
		mergeWith(add),
		{},
		missingVariables
	)

	const innerKeys = flatten(map(keys, missingVariables)),
		missingByTargetsAdvanced = Object.fromEntries(
			Object.entries(countBy(identity, innerKeys)).map(
				// Give higher score to top level questions
				([name, score]) => [
					name,
					score + Math.max(0, 4 - name.split('.').length),
				]
			)
		)

	const missingByCompound = mergeWith(
			pair,
			missingByTargetsAdvanced,
			missingByTotalScore
		),
		pairs = toPairs<number>(missingByCompound),
		sortedPairs = sortWith([descend(byCount), descend(byScore) as any], pairs)
	return map(head, sortedPairs) as any
}

// Max : 1
// Min -> 0
const questionDifference = (rule1 = '', rule2 = '') =>
	1 /
	(1 +
		pipe(
			zipWith(equals),
			takeWhile(Boolean),
			length
		)(rule1.split(' . '), rule2.split(' . ')))

export function getNextQuestions(
	missingVariables: Array<MissingVariables>,
	questionConfig: SimulationConfig['questions'] = {},
	answeredQuestions: Array<DottedName> = [],
	situation: Simulation['situation'] = {},
	engine: Engine
): Array<DottedName> {
	const {
		'non prioritaires': notPriority = [],
		liste: whitelist = [],
		'liste noire': blacklist = [],
	} = questionConfig

	let nextSteps = difference(getNextSteps(missingVariables), answeredQuestions)
	nextSteps = nextSteps.filter(
		(step) =>
			(!whitelist.length || whitelist.some((name) => step.startsWith(name))) &&
			(!blacklist.length || !blacklist.some((name) => step.startsWith(name)))
	)

	const nextQuestions = nextSteps.filter((name) => {
		const rule = engine.getRule(name)
		return rule.rawNode.question != null
	})
	const lastStep = last(answeredQuestions)
	// L'ajout de la réponse permet de traiter les questions dont la réponse est
	// "une possibilité", exemple "contrat salarié . cdd"
	const lastStepWithAnswer =
		lastStep && situation[lastStep]
			? ([lastStep, situation[lastStep]]
					.join(' . ')
					.replace(/'/g, '')
					.trim() as DottedName)
			: lastStep
	return sortBy((question) => {
		const indexList =
			whitelist.findIndex((name) => question.startsWith(name)) + 1
		const indexNotPriority =
			notPriority.findIndex((name) => question.startsWith(name)) + 1
		const differenceCoeff = questionDifference(question, lastStepWithAnswer)
		return indexList + indexNotPriority + differenceCoeff
	}, nextQuestions)
}

export const useNextQuestions = function (): Array<DottedName> {
	const objectifs = useSelector(objectifsSelector)
	const answeredQuestions = useSelector(answeredQuestionsSelector)
	const currentQuestion = useSelector(currentQuestionSelector)
	const questionsConfig = useSelector(configSelector).questions ?? {}
	const situation = useSelector(situationSelector)
	const engine = useContext(EngineContext)
	const missingVariables = objectifs.map(
		(node) => engine.evaluate(node).missingVariables ?? {}
	)
	const nextQuestions = useMemo(() => {
		return getNextQuestions(
			missingVariables,
			questionsConfig,
			answeredQuestions,
			situation,
			engine
		)
	}, [missingVariables, questionsConfig, answeredQuestions, situation])
	if (currentQuestion && currentQuestion !== nextQuestions[0]) {
		return [currentQuestion, ...nextQuestions]
	}
	return nextQuestions
}

export function useSimulationProgress(): number {
	const objectifs = useSelector(objectifsSelector)
	const numberQuestionAnswered = useSelector(answeredQuestionsSelector).length
	const numberQuestionLeft = useNextQuestions().length

	return (
		numberQuestionAnswered / (numberQuestionAnswered + numberQuestionLeft) || 0
	)
}