"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DirExistsRule = exports.FileExistsRule = exports.SourceFileUpdatedRule = exports.OptionalFileUpdatedRule = exports.FileUpdatedRule = void 0;
exports.pathParseAndUpdate = pathParseAndUpdate;
exports.File = File;
exports.ImplicitFileRules = ImplicitFileRules;
const tslib_1 = require("tslib");
const fs = tslib_1.__importStar(require("fs-extra"));
const path = tslib_1.__importStar(require("path"));
const interface_1 = require("../core/interface");
const host_1 = require("../file-hasher/host");
const parse_path_1 = tslib_1.__importDefault(require("../match/parse-path"));
const posixify_path_1 = tslib_1.__importDefault(require("../match/posixify-path"));
const matchers_1 = require("./matchers");
const oracle_1 = require("./oracle");
const rule_base_1 = require("./rule-base");
const util_1 = require("./util");
class FileStatInfoImpl extends parse_path_1.default {
    constructor(s, updated, hash) {
        super(s);
        this.present = false;
        this.updated = "?";
        this.hash = "";
        if (updated && hash) {
            this.present = true;
            this.updated = updated.toISOString();
            this.hash = hash;
        }
    }
    notChanged(that) {
        if (!that)
            return false;
        if (!this.present)
            return false;
        if (!that.present)
            return false;
        if (this.hash === "?" || that.hash === "?")
            return false;
        return this.hash === that.hash;
    }
    optionalFileNotChanged(that) {
        if (!that)
            return false;
        if (this.present !== that.present)
            return false;
        if (this.hash === "?" || that.hash === "?")
            return false;
        return this.hash === that.hash;
    }
}
async function pathParseAndUpdate(s) {
    if (!(await fs.pathExists(s))) {
        return new FileStatInfoImpl(s);
    }
    else {
        const stats = await fs.stat(s);
        const updated = stats.mtime;
        const hash = !stats.isFile()
            ? "?"
            : stats.size < 0x1000000
                ? await (0, host_1.hashSmallFile)(s)
                : await (0, host_1.hashFile)(s);
        return new FileStatInfoImpl(s, updated, hash);
    }
}
async function getDirContents(recursive, target, $1, gfu, gf) {
    target.is.volatile();
    if (!(await fs.pathExists($1))) {
        throw new Error("Dependent directory not found: " + $1);
    }
    await target.need(gfu `${$1}`);
    const tracking = {};
    let subDirKeys = [];
    let subDirTargets = [];
    const contents = (await fs.readdir($1)).sort();
    for (const file of contents) {
        if (/^\./.test(file))
            continue;
        const subName = `${$1}/${file}`;
        const subStat = await fs.stat(subName);
        if (subStat.isDirectory()) {
            if (recursive) {
                subDirKeys.push(file + "/");
                subDirTargets.push(gf `${subName}`);
            }
        }
        else {
            tracking[file] = subName;
        }
    }
    const values = await target.need(...subDirTargets);
    for (let j = 0; j < subDirKeys.length; j++)
        tracking[subDirKeys[j]] = values[j];
    return tracking;
}
function getRegularPath(cwd, p) {
    const rp = path.relative(cwd, path.resolve(cwd, p));
    return (0, posixify_path_1.default)(rp);
}
class FileRule extends rule_base_1.RuleBase {
    constructor(matcher, FRecipe) {
        super(matcher);
        this.FRecipe = FRecipe;
        this.kindTag = "Builtin::FileRule";
    }
    async build(t, path, ...args) {
        await this.FRecipe(t, path, ...args);
        t.is.modified();
        const u = await pathParseAndUpdate(path.full);
        return u;
    }
    async preBuild(t, path) {
        if (t.isVolatile)
            return interface_1.PreBuildResult.YES;
        if (await t.dependencyModified())
            return interface_1.PreBuildResult.YES;
        const u = await pathParseAndUpdate(path.full);
        return !u || !u.notChanged(t.lastResult) ? interface_1.PreBuildResult.YES : interface_1.PreBuildResult.TIME;
    }
}
class FileUpdatedRule extends rule_base_1.RuleBase {
    constructor(matcher) {
        super(matcher);
        this.kindTag = "Builtin::FileUpdateRule";
    }
    async build(target, $1) {
        const u = await pathParseAndUpdate($1.full);
        if (!u.notChanged(target.lastResult)) {
            target.is.modified();
        }
        else {
            target.is.not.modified();
        }
        return u;
    }
    async preBuild(target, $1) {
        const changed = target.isVolatile || !(await fs.pathExists($1.full)) || (await target.cutoffEarly());
        return changed ? interface_1.PreBuildResult.YES : interface_1.PreBuildResult.TIME;
    }
}
exports.FileUpdatedRule = FileUpdatedRule;
class OptionalFileUpdatedRule extends rule_base_1.RuleBase {
    constructor(matcher) {
        super(matcher);
        this.kindTag = "Builtin::OptionalFileUpdateRule";
    }
    async build(target, $1) {
        const u = await pathParseAndUpdate($1.full);
        if (!u.optionalFileNotChanged(target.lastResult)) {
            target.is.modified();
        }
        else {
            target.is.not.modified();
        }
        return u;
    }
    async preBuild(target, $1) {
        const changed = target.isVolatile || (await target.cutoffEarly());
        return changed ? interface_1.PreBuildResult.YES : interface_1.PreBuildResult.TIME;
    }
}
exports.OptionalFileUpdatedRule = OptionalFileUpdatedRule;
class SourceFileUpdatedRule extends rule_base_1.RuleBase {
    constructor(matcher) {
        super(matcher);
        this.kindTag = "Builtin::SourceFileUpdatedRule";
    }
    async build(target, $1) {
        const u = await pathParseAndUpdate($1.full);
        if (!u.present) {
            throw new Error(`File ${$1.full} is required but missing.`);
        }
        if (!u.notChanged(target.lastResult)) {
            target.is.modified();
        }
        else {
            target.is.not.modified();
        }
        return u;
    }
    async preBuild(target, $1) {
        const changed = target.isVolatile || !(await fs.pathExists($1.full)) || (await target.cutoffEarly());
        return changed ? interface_1.PreBuildResult.YES : interface_1.PreBuildResult.TIME;
    }
}
exports.SourceFileUpdatedRule = SourceFileUpdatedRule;
class FileExistsRule extends rule_base_1.RuleBase {
    constructor(matcher) {
        super(matcher);
        this.kindTag = "Builtin::FileExistsRule";
    }
    matchString(id) {
        return null;
    }
    async build(target, $1) {
        const u = await pathParseAndUpdate($1.full);
        if (!u.present)
            throw new Error("Dependent file not found: " + $1.full);
        target.is.not.modified();
        return u;
    }
    async preBuild(target, $1) {
        return target.isVolatile || !(await fs.pathExists($1.full))
            ? interface_1.PreBuildResult.YES
            : interface_1.PreBuildResult.NO;
    }
}
exports.FileExistsRule = FileExistsRule;
class DirExistsRule extends rule_base_1.RuleBase {
    constructor(matcher) {
        super(matcher);
        this.kindTag = "Builtin::DirExistsRule";
    }
    matchString(id) {
        return null;
    }
    async build(target, $1) {
        if (!(await fs.pathExists($1.full))) {
            await fs.ensureDir($1.full);
            target.is.modified();
        }
        else {
            target.is.not.modified();
        }
        const u = await pathParseAndUpdate($1.full);
        return u;
    }
    async preBuild(target, $1) {
        return target.isVolatile || !(await fs.pathExists($1.full))
            ? interface_1.PreBuildResult.YES
            : interface_1.PreBuildResult.NO;
    }
}
exports.DirExistsRule = DirExistsRule;
function File(cfg, dir) {
    const PREFIX = "Builtin::File::";
    const _file = (0, util_1.SinglePlural_F)(PREFIX, dir, cfg, (matcher, FRecipe) => new FileRule(matcher, FRecipe));
    return {
        file: Object.assign(_file.exact, {
            glob: _file.glob,
            make: _file.make,
            getPathOf(goal) {
                return new parse_path_1.default(goal.id.slice(PREFIX.length));
            },
        }),
    };
}
function FileUpdated(cfg) {
    const prefix = "Builtin::FileUpdated::";
    const matcher = new matchers_1.NoStringMatcherT(new matchers_1.KindMatcherT(prefix, new matchers_1.FilePathMatcherT(cfg, new matchers_1.AlwaysMatcher())));
    const rule = new FileUpdatedRule(matcher);
    return { gb: (0, util_1.GoalBuilder)(matcher, rule), rule };
}
function OptionalFileUpdated(cfg) {
    const prefix = "Builtin::OptionalFileUpdated::";
    const matcher = new matchers_1.NoStringMatcherT(new matchers_1.KindMatcherT(prefix, new matchers_1.FilePathMatcherT(cfg, new matchers_1.AlwaysMatcher())));
    const rule = new OptionalFileUpdatedRule(matcher);
    return { gb: (0, util_1.GoalBuilder)(matcher, rule), rule };
}
function SourceFileUpdated(cfg) {
    const prefix = "Builtin::SourceFileUpdated::";
    const matcher = new matchers_1.NoStringMatcherT(new matchers_1.KindMatcherT(prefix, new matchers_1.FilePathMatcherT(cfg, new matchers_1.AlwaysMatcher())));
    const rule = new SourceFileUpdatedRule(matcher);
    return { gb: (0, util_1.GoalBuilder)(matcher, rule), rule };
}
function FileExists(cfg) {
    const prefix = "Builtin::FileExists::";
    const matcher = new matchers_1.NoStringMatcherT(new matchers_1.KindMatcherT(prefix, new matchers_1.FilePathMatcherT(cfg, new matchers_1.AlwaysMatcher())));
    const rule = new FileExistsRule(matcher);
    return { gb: (0, util_1.GoalBuilder)(matcher, rule), rule };
}
function DirExists(cfg) {
    const prefix = "Builtin::DirExists::";
    const matcher = new matchers_1.NoStringMatcherT(new matchers_1.KindMatcherT(prefix, new matchers_1.FilePathMatcherT(cfg, new matchers_1.AlwaysMatcher())));
    const rule = new DirExistsRule(matcher);
    return { gb: (0, util_1.GoalBuilder)(matcher, rule), rule };
}
function DirContent(cfg, gfu) {
    const prefix = "Builtin::DirContent::";
    let gfDC;
    const matcher = new matchers_1.NoStringMatcherT(new matchers_1.KindMatcherT(prefix, new matchers_1.AlwaysMatcher()));
    const rule = new oracle_1.OracleRule(true, matcher, (t, path) => getDirContents(false, t, getRegularPath(cfg.cwd, path), gfu, gfDC));
    gfDC = (0, util_1.GoalBuilder)(matcher, rule);
    return { gb: gfDC, rule };
}
function DirStructure(cfg, gfu) {
    const prefix = "Builtin::DirStructure::";
    let gfDC;
    const matcher = new matchers_1.NoStringMatcherT(new matchers_1.KindMatcherT(prefix, new matchers_1.AlwaysMatcher()));
    const rule = new oracle_1.OracleRule(true, matcher, (t, path) => getDirContents(true, t, getRegularPath(cfg.cwd, path), gfu, gfDC));
    gfDC = (0, util_1.GoalBuilder)(matcher, rule);
    return { gb: gfDC, rule };
}
function ImplicitFileRules(cfg, dir) {
    const fu = FileUpdated(cfg);
    const sfu = SourceFileUpdated(cfg);
    const ofu = OptionalFileUpdated(cfg);
    const fe = FileExists(cfg);
    const de = DirExists(cfg);
    const dc = DirContent(cfg, fu.gb);
    const ds = DirStructure(cfg, fu.gb);
    fe.rule.ignoreStringMatch = true;
    fe.rule.isUser = false;
    dir.addRule(fe.rule);
    de.rule.ignoreStringMatch = true;
    de.rule.isUser = false;
    dir.addRule(de.rule);
    dc.rule.ignoreStringMatch = true;
    dc.rule.isUser = false;
    dir.addRule(dc.rule);
    ds.rule.ignoreStringMatch = true;
    ds.rule.isUser = false;
    dir.addRule(ds.rule);
    fu.rule.ignoreStringMatch = true;
    fu.rule.isUser = false;
    dir.addRule(fu.rule);
    sfu.rule.ignoreStringMatch = true;
    sfu.rule.isUser = false;
    dir.addRule(sfu.rule);
    ofu.rule.ignoreStringMatch = true;
    ofu.rule.isUser = false;
    dir.addRule(ofu.rule);
    return {
        F: fu.gb,
        S: sfu.gb,
        D: de.gb,
        fu: fu.gb,
        fileUpdated: fu.gb,
        sfu: sfu.gb,
        sourceFileUpdated: sfu.gb,
        ofu: ofu.gb,
        optionalFileUpdated: ofu.gb,
        fe: fe.gb,
        fileExists: fe.gb,
        de: de.gb,
        dirExists: de.gb,
        dc: dc.gb,
        dirContent: dc.gb,
        ds: ds.gb,
        dirStructure: ds.gb,
    };
}
