// ============================================================================
// Import
// ============================================================================

import fs from "fs/promises";
import path from "path";
import { Action } from "../../core/pipeline/Action";
import { ActionOptionsType } from "../../types/ActionOptionsType.js";
import packageConfig from "./package.config.js";

// ============================================================================
// Classes
// ============================================================================

/**
 * PackageManagerAction handles reading, validating, and creating `package.json`
 * files, supporting custom configurations and merging with selected fields.
 */
export class PackageManagerAction extends Action {
    /**
     * Executes the package management action.
     * Reads an existing `package.json`, extracts selected fields, and writes a new one.
     *
     * @param options - Options specifying input path, output directory, and fields to export.
     * @returns A Promise that resolves when the action is completed successfully.
     * @throws {Error} Throws an error if neither `packageJsonPath` nor `outputDir` is provided.
     */
    async execute(options: ActionOptionsType): Promise<void> {
        const {
            packageJsonPath,
            outputDir,
            fields = [], // Specify which fields to copy
            customConfig = {},
        } = options;

        if (!packageJsonPath) {
            throw new Error("The 'packageJsonPath' option is required.");
        }
        if (!outputDir) {
            throw new Error("The 'outputDir' option is required.");
        }

        const existingConfig = await this.readPackageJson(packageJsonPath);
        const filteredConfig = this.filterFields(existingConfig, fields);

        await this.createPackageJson(outputDir, filteredConfig, customConfig);
    }

    /**
     * Reads and parses an existing `package.json`.
     *
     * @param packageJsonPath - Path to the `package.json` file.
     * @returns Parsed JSON object.
     * @throws {Error} If the file does not exist or contains invalid JSON.
     */
    private async readPackageJson(
        packageJsonPath: string,
    ): Promise<Record<string, unknown>> {
        const fullPath = path.resolve(packageJsonPath);

        try {
            const fileContent = await fs.readFile(fullPath, "utf-8");
            const parsedContent = JSON.parse(fileContent);
            this.logInfo(`Successfully read package.json from ${fullPath}`);
            return parsedContent;
        } catch (error: any) {
            if (error.code === "ENOENT") {
                throw new Error(
                    `File not found at ${fullPath}. Please ensure the path is correct.`,
                );
            } else if (error.name === "SyntaxError") {
                throw new Error(
                    `Invalid JSON in ${fullPath}: ${error.message}`,
                );
            } else {
                throw new Error(
                    `Unexpected error while reading ${fullPath}: ${error.message}`,
                );
            }
        }
    }

    /**
     * Filters specified fields from a `package.json` object.
     *
     * @param config - The original package.json object.
     * @param fields - List of fields to extract.
     * @returns A new object containing only the selected fields.
     */
    private filterFields(
        config: Record<string, unknown>,
        fields: string[],
    ): Record<string, unknown> {
        if (!fields.length) {
            return config; // If no fields are specified, return the full config.
        }

        const filteredConfig: Record<string, unknown> = {};
        for (const field of fields) {
            if (config[field] !== undefined) {
                filteredConfig[field] = config[field];
            }
        }

        this.logInfo(
            `Filtered package.json fields: ${JSON.stringify(filteredConfig, null, 2)}`,
        );
        return filteredConfig;
    }

    /**
     * Creates a `package.json` file with selected fields and custom overrides.
     *
     * @param outputDir - Directory where the new `package.json` will be created.
     * @param filteredConfig - The filtered package.json fields.
     * @param customConfig - Custom overrides to apply.
     * @returns A Promise that resolves when the file has been successfully created.
     * @throws {Error} If the file cannot be written.
     */
    private async createPackageJson(
        outputDir: string,
        filteredConfig: Record<string, any>,
        customConfig: Record<string, any>,
    ): Promise<void> {
        const filePath = path.join(outputDir, "package.json");

        // Merge default settings with filtered config and custom overrides
        const finalConfig = {
            ...packageConfig,
            ...filteredConfig,
            ...customConfig,
        };
        const data = JSON.stringify(finalConfig, null, 2);

        try {
            await this.ensureDirectoryExists(outputDir);
            await fs.writeFile(filePath, data, "utf-8");
            this.logInfo(`package.json successfully created at ${filePath}`);
        } catch (error) {
            this.logError("Error creating package.json", error);
            throw error;
        }
    }

    /**
     * Ensures that the specified directory exists, creating it if it does not.
     *
     * @param dirPath - The path of the directory to verify or create.
     * @returns A Promise that resolves once the directory is verified or created.
     * @throws {Error} If the directory cannot be created.
     */
    private async ensureDirectoryExists(dirPath: string): Promise<void> {
        try {
            await fs.mkdir(dirPath, { recursive: true });
        } catch (error) {
            if ((error as NodeJS.ErrnoException).code !== "EEXIST") {
                throw error;
            }
        }
    }

    /**
     * Provides a description of the action.
     *
     * @returns A string description of the action.
     */
    describe(): string {
        return "Reads an existing package.json, extracts selected fields, and creates a new one.";
    }
}

// ============================================================================
// Export
// ============================================================================

// export default PackageManagerAction;
