import { generateBytecodeMap, OpcodesCommon, } from '../vm/instruction-sets/instruction-sets';
import { CompilerDefaults } from './compiler-defaults';
import { compilerOperationsCommon } from './compiler-operations';
import { compileScript } from './language/compile';
import { generateScenarioCommon } from './scenarios';
/**
 * Create a `Compiler` from the provided compilation environment. This method
 * requires a full `CompilationEnvironment` and does not instantiate any new
 * crypto or VM implementations.
 *
 * @param compilationEnvironment - the environment from which to create the
 * compiler
 */
export const createCompiler = (compilationEnvironment) => ({
    environment: compilationEnvironment,
    generateBytecode: (scriptId, data, debug = false) => {
        const result = compileScript(scriptId, data, compilationEnvironment);
        return (debug
            ? result
            : result.success
                ? { bytecode: result.bytecode, success: true }
                : {
                    errorType: result.errorType,
                    errors: result.errors,
                    success: false,
                });
    },
    generateScenario: ({ unlockingScriptId, scenarioId }) => generateScenarioCommon({
        environment: compilationEnvironment,
        scenarioId,
        unlockingScriptId,
    }),
});
const nullHashLength = 32;
/**
 * A common `createAuthenticationProgram` implementation for most compilers.
 *
 * Accepts the compiled contents of an evaluation and produces a
 * `AuthenticationProgramCommon` which can be evaluated to produce the resulting
 * program state.
 *
 * The precise shape of the authentication program produced by this method is
 * critical to the determinism of BTL evaluations for the compiler in which it
 * is used, it therefore must be standardized between compiler implementations.
 *
 * @param evaluationBytecode - the compiled bytecode to incorporate in the
 * created authentication program
 */
export const createAuthenticationProgramEvaluationCommon = (evaluationBytecode) => ({
    inputIndex: 0,
    sourceOutput: {
        lockingBytecode: evaluationBytecode,
        satoshis: Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0]),
    },
    spendingTransaction: {
        inputs: [
            {
                outpointIndex: 0,
                outpointTransactionHash: new Uint8Array(nullHashLength),
                sequenceNumber: 0,
                unlockingBytecode: Uint8Array.of(),
            },
        ],
        locktime: 0,
        outputs: [
            {
                lockingBytecode: Uint8Array.of(),
                satoshis: Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0]),
            },
        ],
        version: 0,
    },
});
/**
 * Synchronously create a compiler using the default common environment. Because
 * this compiler has no access to Secp256k1, Sha256, or a VM, it cannot compile
 * evaluations or operations which require key derivation or hashing.
 *
 * @param scriptsAndOverrides - a compilation environment from which properties
 * will be used to override properties of the default common compilation
 * environment – must include the `scripts` property
 */
export const createCompilerCommonSynchronous = (scriptsAndOverrides) => {
    return createCompiler({
        ...{
            createAuthenticationProgram: createAuthenticationProgramEvaluationCommon,
            opcodes: generateBytecodeMap(OpcodesCommon),
            operations: compilerOperationsCommon,
        },
        ...scriptsAndOverrides,
    });
};
/**
 * Create a partial `CompilationEnvironment` from an `AuthenticationTemplate` by
 * extracting and formatting the `scripts` and `variables` properties.
 *
 * Note, if this `AuthenticationTemplate` might be malformed, first validate it
 * with `validateAuthenticationTemplate`.
 *
 * @param template - the `AuthenticationTemplate` from which to extract the
 * compilation environment
 */
export const authenticationTemplateToCompilationEnvironment = (template) => {
    const scripts = Object.entries(template.scripts).reduce((all, [id, def]) => ({ ...all, [id]: def.script }), {});
    const variables = Object.values(template.entities).reduce((all, entity) => ({ ...all, ...entity.variables }), {});
    const entityOwnership = Object.entries(template.entities).reduce((all, [entityId, entity]) => ({
        ...all,
        ...Object.keys(entity.variables ?? {}).reduce((entityVariables, variableId) => ({
            ...entityVariables,
            [variableId]: entityId,
        }), {}),
    }), {});
    const unlockingScripts = Object.entries(template.scripts).reduce((all, [id, def]) => 'unlocks' in def && def.unlocks !== undefined
        ? { ...all, [id]: def.unlocks }
        : all, {});
    const unlockingScriptTimeLockTypes = Object.entries(template.scripts).reduce((all, [id, def]) => 'timeLockType' in def && def.timeLockType !== undefined
        ? { ...all, [id]: def.timeLockType }
        : all, {});
    const lockingScriptTypes = Object.entries(template.scripts).reduce((all, [id, def]) => 'lockingType' in def &&
        def.lockingType !== undefined
        ? { ...all, [id]: def.lockingType }
        : all, {});
    const scenarios = template.scenarios === undefined
        ? undefined
        : Object.entries(template.scenarios).reduce((all, [id, def]) => ({ ...all, [id]: def }), {});
    return {
        entityOwnership,
        lockingScriptTypes,
        ...(scenarios === undefined ? {} : { scenarios }),
        scripts,
        unlockingScriptTimeLockTypes,
        unlockingScripts,
        variables,
    };
};
/**
 * Create a partial `CompilationEnvironment` from an `AuthenticationTemplate`,
 * virtualizing all script tests as unlocking and locking script pairs.
 *
 * @param template - the authentication template from which to extract the
 * compilation environment
 */
export const authenticationTemplateToCompilationEnvironmentVirtualizedTests = (template) => {
    const virtualizedScripts = Object.entries(template.scripts).reduce((all, [scriptId, script]) => {
        if ('tests' in script) {
            return {
                ...all,
                ...script.tests.reduce((tests, test, index) => {
                    const pushTestedScript = script.pushed === true;
                    const checkScriptId = `${CompilerDefaults.virtualizedTestCheckScriptPrefix}${scriptId}_${index}`;
                    const virtualizedLockingScriptId = `${CompilerDefaults.virtualizedTestLockingScriptPrefix}${scriptId}_${index}`;
                    const virtualizedUnlockingScriptId = `${CompilerDefaults.virtualizedTestUnlockingScriptPrefix}${scriptId}_${index}`;
                    return {
                        ...tests,
                        [checkScriptId]: { script: test.check },
                        [virtualizedLockingScriptId]: {
                            script: pushTestedScript
                                ? `<${scriptId}> ${checkScriptId}`
                                : `${scriptId} ${checkScriptId}`,
                        },
                        [virtualizedUnlockingScriptId]: {
                            script: test.setup ?? '',
                            unlocks: virtualizedLockingScriptId,
                        },
                    };
                }, {}),
            };
        }
        return all;
    }, {});
    const templateWithVirtualizedTests = {
        ...template,
        scripts: {
            ...template.scripts,
            ...virtualizedScripts,
        },
    };
    return authenticationTemplateToCompilationEnvironment(templateWithVirtualizedTests);
};
//# sourceMappingURL=compiler.js.map