/** @C3-MONOREPO/YARN-PLUGIN-SEALED-DEPENDENCIES
 * This plugin allows marking dependencies as sealed in a workspace to prevent undesired changes.
 * When you use a yarn command to manage dependencies, the plugin will automatically check if the operation is permitted
 * and report a message otherwise.
 *
 * To configure a workspace as sealed, add the `sealed` property in package.json. Please see the `SealedOptions` type to
 * known more about the possible value of the `sealed` property.
 */

import type {Plugin, Workspace, Descriptor} from "@yarnpkg/core";
import {Operation, OperationReporter, OperationReporterOptions} from "./operation-reporters/operation-reporter";
import {ConfirmOperationReporter} from "./operation-reporters/confirm.operation-reporter";
import {WarnOperationReporter} from "./operation-reporters/warn.operation-reporter";
import {ErrorOperationReporter} from "./operation-reporters/error.operation-reporter";

const checkOperationIsPossible = (operation: Operation) => async (workspace: Workspace, target: string, descriptor: Descriptor, destinationDescriptor: Descriptor) => {
    const {manifest: {raw: {sealed: sealedEntry = false}}} = workspace;

    const operationReportersOptions: OperationReporterOptions = {
        operation,
        workspace,
        target,
        descriptor,
        destinationDescriptor
    };

    const operationReporters: Record<string, OperationReporter> = {
        'confirm': new ConfirmOperationReporter(operationReportersOptions),
        'warn': new WarnOperationReporter(operationReportersOptions),
        'error': new ErrorOperationReporter(operationReportersOptions),
    }

    if (!sealedEntry) {
        return;
    }

    const sealedOptions = resolveSealedOptions(sealedEntry);

    if (sealedOptions[target].includes(operation)) {
        await operationReporters[sealedOptions.level].reportOperation();
    }
}


////////////////////////////////////////////////////////////////////////////////
// SEALED OPTIONS
////////////////////////////////////////////////////////////////////////////////

type SealedOptions = boolean & {
    level?: 'error' | 'warn' | 'confirm',
    operations?: Array<Operation>,
    dependencies?: boolean | Array<Operation>,
    devDependencies?: boolean | Array<Operation>,
    peerDependencies?: boolean | Array<Operation>
};

const resolveSealedOptions = (sealedEntry: SealedOptions) => ({
    level: (sealedEntry === true && 'error') || (sealedEntry.level) || 'error',
    operations: (sealedEntry.operations instanceof Array && sealedEntry.operations) || ['add', 'remove', 'replace'],
    dependencies: (sealedEntry.dependencies instanceof Array && sealedEntry.dependencies) || (sealedEntry.dependencies === false && []) || sealedEntry.operations || ['add', 'remove', 'replace'],
    devDependencies: (sealedEntry.devDependencies instanceof Array && sealedEntry.devDependencies) || (sealedEntry.dependencies === false && []) || sealedEntry.operations || ['add', 'remove', 'replace'],
    peerDependencies: (sealedEntry.peerDependencies instanceof Array && sealedEntry.peerDependencies) || (sealedEntry.dependencies === false && []) || sealedEntry.operations || ['add', 'remove', 'replace'],
});

////////////////////////////////////////////////////////////////////////////////
// PLUGIN DECLARATION AND EXPORT
////////////////////////////////////////////////////////////////////////////////

const plugin: Plugin = {
    hooks: {
        afterWorkspaceDependencyAddition: checkOperationIsPossible('add'),
        afterWorkspaceDependencyRemoval: checkOperationIsPossible('remove'),
        afterWorkspaceDependencyReplacement: checkOperationIsPossible('replace'),
    },
};

export default plugin;

