import { Interval } from '@alexaegis/advent-of-code-lib';

export interface Card {
	seeds: number[];
	seedToSoilMap: Range[];
	soilToFertilizerMap: Range[];
	fertilizerToWaterMap: Range[];
	waterToLightMap: Range[];
	lightToTemperatureMap: Range[];
	temperatureToHumidityMap: Range[];
	humidityToLocationMap: Range[];
	maps: Range[][];
}

export interface Range {
	destinationRange: number;
	sourceRangeStart: number;
	rangeLength: number;
	from: Interval;
	to: Interval;
	slope: number;
}

enum ParseableData {
	SEEDS = 'seeds',
	SEED_TO_SOIL = 'seed-to-soil',
	SOIL_TO_FERTILIZER = 'soil-to-fertilizer',
	FERTILIZER_TO_WATER = 'fertilizer-to-water',
	WATER_TO_LIGHT = 'water-to-light',
	LIGHT_TO_TEMPERATURE = 'light-to-temperature',
	TEMPERATURE_TO_HUMIDITY = 'temperature-to-humidity',
	HUMIDITY_TO_LOCATION = 'humidity-to-location',
}

const allParseableData = Object.values(ParseableData);

export const toRange = (line: string): Range => {
	const [destinationRange, sourceRangeStart, rangeLength] = line.splitToInt();
	if (
		destinationRange === undefined ||
		sourceRangeStart === undefined ||
		rangeLength === undefined
	) {
		throw new Error('corrupt data');
	}
	return {
		destinationRange,
		sourceRangeStart,
		rangeLength,
		from: Interval.closed(sourceRangeStart, sourceRangeStart + rangeLength),
		to: Interval.closed(destinationRange, destinationRange + rangeLength),
		slope: destinationRange - sourceRangeStart,
	};
};

export const findRange = (seed: number, rangeMap: Range[]): number => {
	const range = rangeMap.find(
		(range) =>
			range.sourceRangeStart <= seed && seed < range.sourceRangeStart + range.rangeLength,
	);
	const delta = range ? range.destinationRange - range.sourceRangeStart : 0;
	return seed + delta;
};

export const parse = (input: string): Card => {
	let parsing: ParseableData = ParseableData.SEEDS;
	let seeds: number[] = [];
	const seedToSoilMap: Range[] = [];
	const soilToFertilizerMap: Range[] = [];
	const fertilizerToWaterMap: Range[] = [];
	const waterToLightMap: Range[] = [];
	const lightToTemperatureMap: Range[] = [];
	const temperatureToHumidityMap: Range[] = [];
	const humidityToLocationMap: Range[] = [];

	for (const line of input.lines(false)) {
		const parseableDataHeader = allParseableData.find((parseableData) =>
			line.startsWith(parseableData),
		);
		if (parseableDataHeader) {
			parsing = parseableDataHeader;
			if (parsing !== ParseableData.SEEDS) {
				continue;
			}
		}

		switch (parsing) {
			case ParseableData.SEEDS: {
				const [, values] = line.splitIntoStringPair(': ');
				seeds = values.split(' ').map((value) => Number.parseInt(value, 10));
				break;
			}
			case ParseableData.SEED_TO_SOIL: {
				seedToSoilMap.push(toRange(line));
				break;
			}
			case ParseableData.SOIL_TO_FERTILIZER: {
				soilToFertilizerMap.push(toRange(line));
				break;
			}
			case ParseableData.FERTILIZER_TO_WATER: {
				fertilizerToWaterMap.push(toRange(line));
				break;
			}
			case ParseableData.WATER_TO_LIGHT: {
				waterToLightMap.push(toRange(line));
				break;
			}
			case ParseableData.LIGHT_TO_TEMPERATURE: {
				lightToTemperatureMap.push(toRange(line));
				break;
			}
			case ParseableData.TEMPERATURE_TO_HUMIDITY: {
				temperatureToHumidityMap.push(toRange(line));
				break;
			}
			case ParseableData.HUMIDITY_TO_LOCATION: {
				humidityToLocationMap.push(toRange(line));
				break;
			}
		}
	}

	return {
		seeds,
		seedToSoilMap,
		soilToFertilizerMap,
		fertilizerToWaterMap,
		waterToLightMap,
		lightToTemperatureMap,
		temperatureToHumidityMap,
		humidityToLocationMap,
		maps: [
			seedToSoilMap,
			soilToFertilizerMap,
			fertilizerToWaterMap,
			waterToLightMap,
			lightToTemperatureMap,
			temperatureToHumidityMap,
			humidityToLocationMap,
		],
	};
};
