"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnresolvedAction = exports.DefinitionList = exports.MatcherList = exports.ActionList = exports.Action = void 0;
const ajv_1 = __importDefault(require("ajv"));
const assert_1 = __importDefault(require("assert"));
const uuid_1 = require("uuid");
const ctx_js_1 = require("../ctx.js");
const migrations_js_1 = require("../migrations.js");
const retry_js_1 = require("../retry.js");
const index_js_1 = require("../services/index.js");
const util = __importStar(require("../util/index.js"));
const list_js_1 = require("./list.js");
const pipeline_js_1 = require("./pipeline.js");
const unit_js_1 = require("./unit.js");
const ajv = new ajv_1.default({
    messages: true,
});
class Action extends unit_js_1.Unit {
    constructor() {
        super(...arguments);
        this.id = (0, uuid_1.v4)();
        this.label = '';
        this.notes = '';
    }
    get $entityType() { return 'action'; }
    get type() { return this.$class.$type; }
    get $index() { return this.$owner.indexOf(this); }
    get $context() { return this.$owner.$context; }
    get $parent() { return this.$owner.$owner; }
    get $reporter() { return this.$engine.get(index_js_1.ReporterService); }
    get $action() { return this; }
    get $logger() {
        return this.$owner.$logger;
    }
    get $class() { return this.constructor; }
    get $key() { return `items/${this.$index}`; }
    init(spec) {
        const { id = (0, uuid_1.v4)(), label = '', notes = '', } = spec || {};
        this.id = id;
        this.label = label;
        this.notes = notes;
        this.children = new ActionList(this, 'children', spec.children || []);
        this.readParams(spec || {});
        this.reset();
    }
    hasChildren() {
        return false;
    }
    reset() {
        this.$runtime = {
            startedAt: null,
            finishedAt: null,
            error: null,
            bypassed: null,
        };
    }
    resetSubtree() {
        this.reset();
        this.resetDescendants();
    }
    resetDescendants() {
        for (const action of this.descendentActions()) {
            action.reset();
        }
    }
    getLabel() {
        return this.label;
    }
    isLabelEditable() {
        return !this.constructor.prototype.hasOwnProperty('getLabel');
    }
    validate(value) {
        const validator = ajv.compile(this.$class.$schema);
        const valid = validator(value);
        if (!valid) {
            throw util.createError({
                code: 'ActionValidationError',
                message: 'Value does not conform to action schema',
                details: {
                    messages: validator.errors?.map(_ => _.message),
                },
                retry: true,
            });
        }
        return value;
    }
    isRunning() {
        const { startedAt, finishedAt } = this.$runtime;
        return startedAt && !finishedAt;
    }
    isFinished() {
        const { startedAt, finishedAt } = this.$runtime;
        return startedAt && finishedAt;
    }
    getDuration() {
        const startedAt = this.$runtime.startedAt || 0;
        const finishedAt = this.$runtime.finishedAt || 0;
        return Math.max(0, Number(finishedAt - startedAt) || 0);
    }
    getStatus() {
        const { $runtime } = this;
        return !$runtime.startedAt ? 'idle' : !$runtime.finishedAt ? 'running' : !$runtime.error ? 'success' : 'fail';
    }
    createCtx() {
        return new ctx_js_1.RuntimeCtx(this);
    }
    async resolveScope() {
        return await this.$parent.resolveChildrenScope();
    }
    async resolveChildrenScope() {
        return await this.resolveScope();
    }
    async run() {
        this.$script.setStatus('running');
        this.$script.$events.emit('action.start', this);
        if (!this.$context.$runtime.startedAt) {
            this.$context.$runtime.startedAt = Date.now();
        }
        await Promise.resolve();
        this.resetDescendants();
        await this._trackRuntimeStats(() => this.exec());
        this.$script.setStatus('idle');
        this.$script.$events.emit('action.end', this);
        this.afterRun();
    }
    afterRun() {
        this.leave();
    }
    async _trackRuntimeStats(asyncFn) {
        this.$runtime = {
            startedAt: Date.now(),
            finishedAt: null,
            error: null,
            bypassed: null,
        };
        try {
            await asyncFn();
        }
        catch (err) {
            this.$runtime.error = err;
            throw err;
        }
        finally {
            this.$runtime.finishedAt = Date.now();
        }
    }
    enter() {
        (0, assert_1.default)(this.hasChildren(), 'enter() not allowed for leaf nodes');
        if (this.children.length) {
            this.$script.setPlayhead(this.children.first);
        }
        else {
            this.leave();
        }
    }
    leave() {
        this.skip();
    }
    skip() {
        const nextSibling = this.$owner.nextSibling(this);
        if (nextSibling) {
            this.$script.setPlayhead(nextSibling);
        }
        else {
            this.$parent.leave();
        }
    }
    async executeWithRetry(asyncFn, options = {}) {
        return this.retry(asyncFn, options);
    }
    async retry(asyncFn, options = {}) {
        return await (0, retry_js_1.retry)(this.$script, asyncFn, options);
    }
    async selectSingle(pipeline, optional, ctx = this.createCtx()) {
        const inputSet = await this.resolveScope();
        return optional ?
            await pipeline.selectOneOrNull(inputSet, ctx) :
            await pipeline.selectOne(inputSet, ctx);
    }
    async selectOne(pipeline, ctx = this.createCtx()) {
        const inputSet = await this.resolveScope();
        return await pipeline.selectOne(inputSet, ctx);
    }
    async selectAll(pipeline, ctx = this.createCtx()) {
        const inputSet = await this.resolveScope();
        return await pipeline.selectAll(inputSet, ctx);
    }
    getDepth() {
        return 1 + this.$parent.getDepth();
    }
    *descendentActions() {
        for (const child of this.children) {
            yield child;
            yield* child.descendentActions();
        }
    }
    *descendentPipes() {
        for (const value of Object.values(this)) {
            if (value instanceof pipeline_js_1.Pipeline) {
                yield* value.descendentPipes();
            }
        }
    }
    previousSiblings() {
        return this.$owner.previousSiblings(this);
    }
    nextSiblings() {
        return this.$owner.nextSiblings(this);
    }
    *collectInputKeys() {
        const { inputKey } = this;
        if (inputKey) {
            yield inputKey;
        }
        for (const pipe of this.descendentPipes()) {
            for (const param of pipe.getParams()) {
                if (param.type === 'string' && param.source === 'inputs') {
                    const val = pipe.getParamValue(param.name);
                    if (val) {
                        yield val;
                    }
                }
            }
        }
    }
    *collectOutputKeys() { }
    getUsedDefinitionIds() {
        const set = new Set();
        _collectUsedDefinitions(set, this);
        return Array.from(set);
    }
    *collectDefinitionIds() {
        for (const pipe of this.descendentPipes()) {
            for (const param of pipe.getParams()) {
                if (param.type === 'definition') {
                    const definitionId = pipe.getParamValue(param.name);
                    yield definitionId;
                }
            }
        }
    }
}
exports.Action = Action;
Action.$schema = {};
class ActionList extends list_js_1.EntityList {
    create(_spec) {
        const spec = (0, migrations_js_1.migrateActionSpec)(util.cloneWithoutIdsCollision(_spec || {}, this.$script.$ids));
        spec.type = this.coerceSpecType(spec.type);
        const ActionClass = this.$resolver.getActionClass(spec.type);
        const action = new ActionClass(this);
        action.init(spec);
        return action;
    }
    get $entityType() {
        return 'action-list';
    }
    get $script() { return this.$owner.$script; }
    get $context() { return this.$owner.$context; }
    get $logger() { return this.$owner.$logger; }
    get $idDatabase() { return this.$script; }
    get $resolver() { return this.$script.$engine.get(index_js_1.ResolverService); }
    coerceSpecType(type) {
        switch (type) {
            case 'matcher':
                return 'Flow.expect';
            case 'definition':
                return 'Flow.find';
            default:
                return type;
        }
    }
    resolveScope() {
        return this.$owner.resolveChildrenScope();
    }
}
exports.ActionList = ActionList;
class MatcherList extends ActionList {
    coerceSpecType(_type) {
        return 'matcher';
    }
}
exports.MatcherList = MatcherList;
class DefinitionList extends ActionList {
    coerceSpecType(_type) {
        return 'definition';
    }
}
exports.DefinitionList = DefinitionList;
class UnresolvedAction extends Action {
    init(spec = {}) {
        super.init(spec);
        this.$originalSpec = spec;
    }
    toJSON() {
        return this.$originalSpec;
    }
    async exec() {
        throw util.scriptError(`Cannot run unresolved action: ${this.$originalSpec.type}`);
    }
}
exports.UnresolvedAction = UnresolvedAction;
UnresolvedAction.$type = 'unresolved';
UnresolvedAction.$icon = 'fas fa-circle';
UnresolvedAction.$hidden = true;
function _collectUsedDefinitions(ids, action) {
    for (const definitionId of action.collectDefinitionIds()) {
        if (ids.has(definitionId)) {
            continue;
        }
        ids.add(definitionId);
        const def = action.$script.getDefinitionById(definitionId);
        if (def) {
            _collectUsedDefinitions(ids, def);
        }
    }
}
//# sourceMappingURL=action.js.map