// src/transformers/customFunctionTransformer.ts
import * as path from 'path';
import { ITransformer, PipelineContext, PipelineStepConfig } from '../core/interfaces';
import { ComponentError, ConfigurationError } from '../core/errors';

interface CustomFunctionConfig extends PipelineStepConfig {
    type: 'custom';
    customFunctionPath: string; // Path relative to project root or absolute
    // Optionally pass other config from the step to the function
    functionConfig?: any;
}

type TransformFunction<TInput, TOutput> =
    (data: TInput, context: PipelineContext, config?: any) => Promise<TOutput> | TOutput;

export class CustomFunctionTransformer<TInput, TOutput> implements ITransformer<TInput, TOutput> {
    private transformFn: TransformFunction<TInput, TOutput> | null = null;
    private config: CustomFunctionConfig;
    private functionPath: string;

    constructor(config: CustomFunctionConfig) {
        if (!config.customFunctionPath) {
             throw new ConfigurationError(`CustomFunctionTransformer requires 'customFunctionPath' in step config.`);
        }
        this.config = config;
        // Resolve path - IMPORTANT: Consider security implications of loading arbitrary code.
        // This assumes path is relative to CWD or absolute. Might need better resolution logic.
        this.functionPath = path.resolve(process.cwd(), this.config.customFunctionPath);
    }

    private async loadFunction(context: PipelineContext): Promise<void> {
         if (this.transformFn) return; // Already loaded

         context.logger.info(`Loading custom transform function from: ${this.functionPath}`);
         try {
             // Use dynamic import()
             const module = await import(this.functionPath);
             if (typeof module.default !== 'function') {
                 throw new ConfigurationError(`Module ${this.functionPath} does not have a default export function.`);
             }
             this.transformFn = module.default;
             context.logger.debug(`Custom transform function loaded successfully.`);
         } catch (error: any) {
              context.logger.error({ err: error, path: this.functionPath }, `Failed to load custom transform function.`);
              throw new ComponentError(`Failed to load custom transform function from ${this.functionPath}`, 'CustomFunctionTransformer', error);
         }
    }

    async transform(data: TInput, context: PipelineContext): Promise<TOutput> {
        await this.loadFunction(context); // Load function on first call

        if (!this.transformFn) {
            // Should not happen if loadFunction succeeded or threw
             throw new ComponentError('Custom transform function not loaded.', 'CustomFunctionTransformer');
        }

        try {
            // Pass data, context, and optional step config to the function
            return await Promise.resolve(this.transformFn(data, context, this.config.functionConfig));
        } catch (error: any) {
             context.logger.error({ err: error, functionPath: this.functionPath, item: data }, `Error executing custom transform function`);
             throw new ComponentError(`Error during custom transformation from ${this.functionPath}`, 'CustomFunctionTransformer', error);
        }
    }
}