const input = await Deno.readTextFile("input.txt");

const SourceKindTable = {
    "%": "flipflop",
    "&": "conjunction",
};
function getSourceKind(prefix: string): "flipflop" | "conjunction" {
    if (prefix in SourceKindTable) {
        return SourceKindTable[prefix as keyof typeof SourceKindTable] as "flipflop" | "conjunction";
    }
    throw new Error(`unknown source kind ${prefix}`);
}

function parseInput(input: string) {
    const lines = input.split("\n").map(x => x.trim()).filter(x => x.length > 0);
    return lines.map(line => {
        const [source, targetsLine] = line.split("->").map(x => x.trim())
        const targets = targetsLine.split(",").map(x => x.trim());
        if (source === "broadcaster") {
            return {
                sourceKind: source,
                sourceName: "broadcaster",
                targets
            }
        }
        const sourceKind = getSourceKind(source[0]);
        const sourceName = source.slice(1);

        return {
            sourceKind: sourceKind,
            sourceName: sourceName,
            targets
        }
    });
}

const s = parseInput(input);
console.log(s);

type Signal = boolean;
type ModuleMap = Record<string, Module>;

type PropagateResult = {
    signal: Signal,
};

interface Module {
    sourceName: string,
    targets: string[],
    propagate(sourceName: string, signal: Signal): null | PropagateResult;
}

class FlipFlop implements Module {
    public state = false;
    constructor(
        public sourceName: string,
        public targets: string[],
    ) {
    }

    propagate(_sourceName: string, signal: Signal) {
        if (!signal) {
            this.state = !this.state;
            return {
                signal: this.state
            };
        }
        return null;
    }
}

class Conjunction implements Module {
    public state: Map<string, Signal>;
    constructor(
        public sourceName: string,
        public targets: string[],
        public inputs: string[],
    ) {
        this.state = new Map<string, Signal>();
        for (const input of inputs) {
            this.state.set(input, false);
        }
    }

    propagate(sourceName: string, signal: Signal) {
        this.state.set(sourceName, signal);
        if ([...this.state.values()].every(x => x)) {
            return {
                signal: false,
            };
        }
        return {
            signal: true,
        };
    }
}

class Broadcaster implements Module {
    constructor(
        public sourceName: string,
        public targets: string[],
    ) {
    }
    propagate(_sourceName: string, signal: boolean): PropagateResult | null {
        return {
            signal
        };
    }
}

const state = {} as ModuleMap;
for (const line of s) {
    if (line.sourceKind === "broadcaster") {
        state[line.sourceName] = new Broadcaster(line.sourceName, line.targets);
    } else if (line.sourceKind === "flipflop") {
        state[line.sourceName] = new FlipFlop(line.sourceName, line.targets);
    }
    else if (line.sourceKind === "conjunction") {
        const sourceName = line.sourceName;
        const inputs = s.filter((x) => {
            return x.targets.includes(sourceName)
        }).map(x=> x.sourceName);
        state[line.sourceName] = new Conjunction(line.sourceName, line.targets, inputs);
    }
}

function cycle(state: ModuleMap) {
    let lowSignals = 1;
    let highSignals = 0;
    const queue = [{
        from: "button",
        target: "broadcaster",
        signal: false,
    }];
    while (queue.length > 0) {
        const { target, signal, from } = queue.shift()!;
        const targetModule = state[target];
        if (targetModule === undefined) {
            // ignore
            continue;    
        }
        const result = targetModule.propagate(from, signal);
        if (result !== null) {
            const { signal } = result;
            targetModule.targets.forEach(x => {
                if (signal) {
                    highSignals++;
                }
                else {
                    lowSignals++;
                }
                // console.log(`${target} -${signal ? "high" : "low"}-> ${x}`);
                queue.push({ from: target, target: x, signal });
            });
        }
    }
    // console.log(`low signals: ${lowSignals}, high signals: ${highSignals}`);
    return { lowSignals, highSignals };
}

let totalLowSignals = 0, totalHighSignals = 0;
for (let i = 0; i < 1000; i++) {
    console.log("i", i);
    const { lowSignals, highSignals } = cycle(state);
    console.log(`low signals: ${lowSignals}, high signals: ${highSignals}`);
    totalLowSignals += lowSignals;
    totalHighSignals += highSignals;
}
console.log(`total high signals: ${totalHighSignals}, total low signals: ${totalLowSignals}`);
console.log(`mul: ${totalHighSignals * totalLowSignals}`)
