import { FnStmtKind } from "../ast.ts";
import { Reporter } from "../info.ts";
import {
    Block,
    Fn,
    LocalId,
    Mir,
    RValue,
    visitBlockDsts,
    visitBlockSrcs,
} from "./mir.ts";

export function eliminateUnusedLocals(
    mir: Mir,
    reporter: Reporter,
    isPassOne: boolean,
) {
    for (const fn of mir.fns) {
        new EliminateUnusedLocalsFnPass(fn, reporter, isPassOne).pass();
    }
}

class EliminateUnusedLocalsFnPass {
    private locals: LocalId[];

    public constructor(
        private fn: Fn,
        private reporter: Reporter,
        private isPassOne: boolean,
    ) {
        this.locals = this.fn.locals
            .slice(1 + (fn.stmt.kind as FnStmtKind).params.length)
            .map((local) => local.id);
    }

    public pass() {
        for (const block of this.fn.blocks) {
            this.markLocalsInBlock(block);
        }
        for (const local of this.locals) {
            for (const block of this.fn.blocks) {
                this.eliminateLocalInBlock(block, local);
            }
        }
        for (const id of this.locals) {
            const local = this.fn.locals.find((local) => local.id === id)!;
            if (local.sym?.type === "let" && this.isPassOne) {
                this.reporter.reportWarning({
                    reporter: "analysis mf'er",
                    msg: `unused let symbol '${local.sym.ident}'`,
                    pos: local.sym.pos,
                });
            }
        }
        this.fn.locals = this.fn.locals
            .filter((local) => !this.locals.includes(local.id));
    }

    private eliminateLocalInBlock(block: Block, local: LocalId) {
        const elimIndices: number[] = [];
        visitBlockDsts(block, (dst, i) => {
            if (dst === local) {
                elimIndices.push(i);
            }
        });
        for (const i of elimIndices.toReversed()) {
            block.ops.splice(i, 1);
        }
    }

    private markLocalsInBlock(block: Block) {
        visitBlockSrcs(block, (src) => this.markUsed(src));
    }

    private markUsed(local: RValue) {
        if (local.type !== "local") {
            return;
        }
        this.locals = this.locals.filter((lid) => lid !== local.id);
    }
}
