import * as fs from 'fs';
import * as path from 'path';

function gcd(a: number, b: number): number {
    while (b !== 0) {
        const t = b;
        b = a % b;
        a = t;
    }
    return a;
}

function lcm(a: number, b: number): number {
    return (a * b) / gcd(a, b);
}

function parseFile(filePath: string): { steps: number[], rules: { [key: string]: { [key: string]: string } } } {
    console.log(`Parsing file: ${filePath}`);
    const data = fs.readFileSync(filePath, 'utf8').trim();
    const [stepsSection, rulesSection] = data.split('\n\n');
    const steps = stepsSection.split('').map((char: string) => char === 'L' ? 0 : 1);
    const rules = rulesSection.split('\n').reduce((acc: { [key: string]: { [key: string]: string } }, line: string) => {
        const [state, directions] = line.split('=').map((s: string) => s.trim());
        const [left, right] = directions.split(',').map((s: string) => s.trim());
        acc['L'][state] = left.slice(1);
        acc['R'][state] = right.slice(0, -1);
        return acc;
    }, { 'L': {}, 'R': {} });

    return { steps, rules };
}

function navigateNetworkSimultaneously(steps: number[], rules: { [key: string]: { [key: string]: string } }): number {
    console.log("Starting navigation of network.");
    const startNodes = Object.keys(rules['L']).filter((node: string) => node.endsWith('A'));
    const currentNodes = startNodes;
    let stepCount = 0;
    const timeToZ: { [key: number]: number } = {};

    while (Object.keys(timeToZ).length < startNodes.length) {
        currentNodes.forEach((node: string, index: number) => {
            const direction = steps[stepCount % steps.length] === 0 ? 'L' : 'R';
            const nextNode = rules[direction][node];
            currentNodes[index] = nextNode;
            if (nextNode.endsWith('Z') && !(index in timeToZ)) {
                timeToZ[index] = stepCount + 1;
            }
        });
        stepCount++;
        console.log(`Step ${stepCount}: Current nodes - ${currentNodes}`);
    }

    return Object.values(timeToZ).reduce((acc: number, val: number) => lcm(acc, val), 1);
}

function runTest(): void {
    console.log("Running test...");
    const testPath = path.join(__dirname, '../test.txt');
    const { steps, rules } = parseFile(testPath);
    const expected = 6;
    const result = navigateNetworkSimultaneously(steps, rules);

    if (result !== expected) {
        throw new Error(`Test failed: expected ${expected}, got ${result}`);
    }

    console.log(`Test passed with ${result} steps.`);
}

function main(): void {
    try {
        runTest();
        const inputPath = path.join(__dirname, '../input.txt');
        const { steps, rules } = parseFile(inputPath);
        const result = navigateNetworkSimultaneously(steps, rules);
        console.log(`All paths reached 'Z' nodes simultaneously in ${result} steps.`);
    } catch (error) {
        console.error(`Error: ${(error as Error).message}`);
    }
}

main();
