"use strict";
// -*- mode: typescript; indent-tabs-mode: nil; js-basic-offset: 4 -*-
//
// This file is part of Genie
//
// Copyright 2020 The Board of Trustees of the Leland Stanford Junior University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Giovanni Campagna <gcampagn@cs.stanford.edu>
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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.repeatCommandReply = exports.recommendationLearnMoreReply = exports.recommendationCancelReply = exports.negativeRecommendationReply = exports.positiveRecommendationReply = exports.makeDisplayResultReply = exports.makeRecommendationReply = exports.combineDisplayResult = exports.makeDisplayResult = exports.checkActionForRecommendation = exports.checkRecommendation = exports.makeAnswerStyleRecommendation = exports.makeThingpediaRecommendation = exports.makeRecommendation = exports.makeArgMinMaxRecommendation = exports.makeActionRecommendation = exports.recommendationSetLearnMore = exports.recommendationKeyFn = void 0;
const assert_1 = __importDefault(require("assert"));
const thingtalk_1 = require("thingtalk");
const C = __importStar(require("../ast_manip"));
const slot_bag_1 = require("../slot_bag");
const state_manip_1 = require("../state_manip");
const common_1 = require("./common");
const refinement_helpers_1 = require("./refinement-helpers");
const results_1 = require("./results");
function recommendationKeyFn(rec) {
    return {
        functionName: rec.ctx.currentFunction.qualifiedName
    };
}
exports.recommendationKeyFn = recommendationKeyFn;
function makeActionRecommendation(ctx, action) {
    assert_1.default(action instanceof thingtalk_1.Ast.Invocation);
    const results = ctx.results;
    assert_1.default(results && results.length > 0);
    const currentStmt = ctx.current.stmt;
    const currentTable = currentStmt.expression;
    const last = currentTable.last;
    if (last instanceof thingtalk_1.Ast.SliceExpression && last.limit.toJS() !== 1)
        return null;
    const topResult = results[0];
    const id = topResult.value.id;
    if (!id)
        return null;
    if (action.in_params.length !== 1)
        return null;
    for (const param of action.in_params) {
        if (param.value.equals(id))
            return { ctx, topResult, info: null, action, hasLearnMore: false, hasAnythingElse: false };
    }
    return null;
}
exports.makeActionRecommendation = makeActionRecommendation;
function makeArgMinMaxRecommendation(ctx, name, base, param, direction) {
    const resultInfo = ctx.resultInfo;
    if (!resultInfo.argMinMaxField)
        return null;
    if (!C.isSameFunction(base.schema, ctx.currentFunction))
        return null;
    if (!C.isSameFunction(param.schema, ctx.currentFunction))
        return null;
    if (direction !== resultInfo.argMinMaxField[1] ||
        param.name !== resultInfo.argMinMaxField[0])
        return null;
    return makeRecommendation(ctx, name);
}
exports.makeArgMinMaxRecommendation = makeArgMinMaxRecommendation;
function makeRecommendation(ctx, name) {
    const results = ctx.results;
    assert_1.default(results && results.length > 0);
    const currentStmt = ctx.current.stmt;
    const currentTable = currentStmt.expression;
    const last = currentTable.last;
    if (last instanceof thingtalk_1.Ast.SliceExpression && last.limit.toJS() !== 1)
        return null;
    const topResult = results[0];
    const id = topResult.value.id;
    if (!id || !id.equals(name))
        return null;
    return {
        ctx, topResult,
        info: null,
        action: ctx.nextInfo && ctx.nextInfo.isAction ? C.getInvocation(ctx.next) : null,
        hasLearnMore: false,
        hasAnythingElse: false
    };
}
exports.makeRecommendation = makeRecommendation;
function makeThingpediaRecommendation(ctx, info) {
    const results = ctx.results;
    assert_1.default(results && results.length > 0);
    const currentStmt = ctx.current.stmt;
    const currentTable = currentStmt.expression;
    const last = currentTable.last;
    if (last instanceof thingtalk_1.Ast.SliceExpression && last.limit.toJS() !== 1)
        return null;
    const topResult = results[0];
    if (!common_1.isInfoPhraseCompatibleWithResult(topResult, info))
        return null;
    return {
        ctx, topResult,
        info,
        action: ctx.nextInfo && ctx.nextInfo.isAction ? C.getInvocation(ctx.next) : null,
        hasLearnMore: false,
        hasAnythingElse: false
    };
}
exports.makeThingpediaRecommendation = makeThingpediaRecommendation;
function checkRecommendation(rec, info) {
    if (!common_1.isInfoPhraseCompatibleWithResult(rec.topResult, info))
        return null;
    // check that the filter uses the right set of parameters
    const resultInfo = rec.ctx.resultInfo;
    if (resultInfo.projection !== null) {
        // check that all projected names are present
        for (const name of resultInfo.projection) {
            if (!info.has(name))
                return null;
        }
    }
    return {
        ctx: rec.ctx, topResult: rec.topResult,
        info,
        action: rec.action,
        hasLearnMore: rec.hasLearnMore,
        hasAnythingElse: rec.hasAnythingElse
    };
}
exports.checkRecommendation = checkRecommendation;
function checkActionForRecommendation(rec, action) {
    if (!rec.topResult.value.id)
        return null;
    const resultType = rec.topResult.value.id.getType();
    if (rec.action !== null) {
        if (!C.isSameFunction(rec.action.schema, action.schema))
            return null;
    }
    if (!C.hasArgumentOfType(action, resultType))
        return null;
    return {
        ctx: rec.ctx, topResult: rec.topResult,
        info: rec.info,
        action,
        hasLearnMore: rec.hasLearnMore,
        hasAnythingElse: rec.hasAnythingElse
    };
}
exports.checkActionForRecommendation = checkActionForRecommendation;
// make a recommendation that looks like an answer, that is, "so and so is a ..."
function makeAnswerStyleRecommendation(rec, filter) {
    assert_1.default(C.isSameFunction(rec.ctx.currentFunction, filter.schema));
    const added = results_1.addSlotToBag(new slot_bag_1.SlotBag(rec.ctx.currentFunction), filter);
    if (!added)
        return null;
    return checkRecommendation(rec, added[0]);
}
exports.makeAnswerStyleRecommendation = makeAnswerStyleRecommendation;
function recommendationSetLearnMore(rec) {
    return {
        ctx: rec.ctx, topResult: rec.topResult,
        info: rec.info,
        // reset the action to null if the agent explicitly asks to "learn more"
        action: null,
        hasLearnMore: true,
        hasAnythingElse: rec.hasAnythingElse
    };
}
exports.recommendationSetLearnMore = recommendationSetLearnMore;
function makeDisplayResult(ctx, info) {
    const results = ctx.results;
    assert_1.default(results && results.length > 0);
    const topResult = results[0];
    if (ctx.currentFunction.is_list)
        return null;
    if (!C.isSameFunction(ctx.currentFunction, info.schema))
        return null;
    if (!common_1.isInfoPhraseCompatibleWithResult(topResult, info))
        return null;
    return {
        ctx, topResult,
        info,
        action: ctx.nextInfo && ctx.nextInfo.isAction ? C.getInvocation(ctx.next) : null,
        hasLearnMore: false,
        hasAnythingElse: false
    };
}
exports.makeDisplayResult = makeDisplayResult;
function combineDisplayResult(proposal, newInfo) {
    const { ctx, info: oldInfo } = proposal;
    if (!ctx)
        return null;
    const results = ctx.results;
    assert_1.default(results && results.length > 0);
    const topResult = results[0];
    assert_1.default(common_1.isInfoPhraseCompatibleWithResult(topResult, newInfo));
    const maybeNewInfo = oldInfo ? slot_bag_1.SlotBag.merge(oldInfo, newInfo) : newInfo;
    if (maybeNewInfo === null)
        return null;
    const newProposal = {
        ctx: proposal.ctx,
        topResult: proposal.topResult,
        info: maybeNewInfo,
        action: proposal.action,
        hasLearnMore: false,
        hasAnythingElse: proposal.hasAnythingElse,
    };
    return newProposal;
}
exports.combineDisplayResult = combineDisplayResult;
function makeRecommendationReply(ctx, proposal) {
    const { topResult, action, hasLearnMore } = proposal;
    const options = {
        numResults: 1
    };
    if (action || hasLearnMore)
        options.end = false;
    if (action === null) {
        return state_manip_1.makeAgentReply(ctx, state_manip_1.makeSimpleState(ctx, 'sys_recommend_one', null), proposal, null, options);
    }
    else {
        const chainParam = common_1.findChainParam(topResult, action);
        if (!chainParam)
            return null;
        return state_manip_1.makeAgentReply(ctx, state_manip_1.addActionParam(ctx, 'sys_recommend_one', action, chainParam, topResult.value.id, 'proposed'), proposal, null, options);
    }
}
exports.makeRecommendationReply = makeRecommendationReply;
function makeDisplayResultReply(ctx, proposal) {
    const { action, hasAnythingElse } = proposal;
    const options = {
        numResults: 1
    };
    if (action || hasAnythingElse)
        options.end = false;
    return state_manip_1.makeAgentReply(ctx, state_manip_1.makeSimpleState(ctx, 'sys_display_result', null), proposal, null, options);
}
exports.makeDisplayResultReply = makeDisplayResultReply;
function negativeRecommendationReply(ctx, [preamble, request]) {
    if (!((preamble === null || preamble instanceof thingtalk_1.Ast.FilterExpression) &&
        (request === null || request instanceof thingtalk_1.Ast.FilterExpression)))
        return null;
    const proposal = ctx.aux;
    const { topResult, info, } = proposal;
    const proposalType = topResult.value.id ? topResult.value.id.getType() : null;
    request = refinement_helpers_1.combinePreambleAndRequest(preamble, request, info, proposalType);
    if (request === null)
        return null;
    return refinement_helpers_1.proposalReply(ctx, request, refinement_helpers_1.refineFilterToAnswerQuestionOrChangeFilter);
}
exports.negativeRecommendationReply = negativeRecommendationReply;
function positiveRecommendationReply(loader, ctx, acceptedAction, name) {
    const proposal = ctx.aux;
    const { topResult, action: actionProposal } = proposal;
    // FIXME this should be allowed when we can parameter-pass by non-ID
    if (!topResult.value.id)
        return null;
    if (acceptedAction === null) {
        // if the user did not give an action earlier, and no action
        // was proposed by the agent right now, the flow is roughly
        //
        // U: hello i am looking for a restaurant
        // A: how about the ... ?
        // U: sure I like that
        //
        // this doesn't make much sense, so we don't want this flow
        if (actionProposal === null)
            return null;
        acceptedAction = actionProposal;
    }
    assert_1.default(acceptedAction);
    if (actionProposal !== null && !C.isSameFunction(actionProposal.schema, acceptedAction.schema))
        return null;
    if (name !== null && !topResult.value.id.equals(name))
        return null;
    // do not consider a phrase of the form "play X" to be "accepting the action by name"
    // if the action auto-confirms, because the user is likely playing something else
    if (name) {
        const confirm = loader.ttUtils.normalizeConfirmAnnotation(acceptedAction.schema);
        if (confirm === 'auto')
            return null;
    }
    const chainParam = common_1.findChainParam(topResult, acceptedAction);
    if (!chainParam)
        return null;
    return state_manip_1.addActionParam(ctx, 'execute', acceptedAction, chainParam, topResult.value.id, 'accepted');
}
exports.positiveRecommendationReply = positiveRecommendationReply;
function recommendationCancelReply(ctx, valid) {
    // see dialogue.genie for the meaning of this boolean
    if (!valid)
        return null;
    // "thank you" closes the dialogue
    // we cannot close the dialogue if we have pending actions
    if (ctx.next)
        return null;
    return state_manip_1.makeSimpleState(ctx, 'cancel', null);
}
exports.recommendationCancelReply = recommendationCancelReply;
function recommendationLearnMoreReply(ctx, name) {
    const proposal = ctx.aux;
    const { topResult, } = proposal;
    if (name !== null && (!topResult.value.id || !topResult.value.id.equals(name)))
        return null;
    return state_manip_1.makeSimpleState(ctx, 'learn_more', null);
}
exports.recommendationLearnMoreReply = recommendationLearnMoreReply;
function repeatCommandReply(ctx) {
    if (ctx.next)
        return null;
    if (ctx.currentFunction.is_monitorable)
        return null;
    const clone = ctx.current.clone();
    clone.results = null;
    clone.confirm = 'accepted';
    return state_manip_1.addNewItem(ctx, 'execute', null, 'accepted', clone);
}
exports.repeatCommandReply = repeatCommandReply;
//# sourceMappingURL=recommendation.js.map