// SPDX-License-Identifier: Apache-2.0
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var FileCommand_1;
import chalk from 'chalk';
import { BaseCommand } from './base.js';
import { SoloError } from '../core/errors/solo-error.js';
import { Flags as flags } from './flags.js';
import { Listr } from 'listr2';
import * as constants from '../core/constants.js';
import { FileCreateTransaction, FileUpdateTransaction, FileAppendTransaction, FileContentsQuery, FileInfoQuery, FileId, Status, PrivateKey, } from '@hiero-ledger/sdk';
import { inject, injectable } from 'tsyringe-neo';
import { InjectTokens } from '../core/dependency-injection/inject-tokens.js';
import { patchInject } from '../core/dependency-injection/container-helper.js';
import fs from 'node:fs';
import path from 'node:path';
let FileCommand = class FileCommand extends BaseCommand {
    static { FileCommand_1 = this; }
    accountManager;
    // Hiero's max content size per transaction
    static MAX_CHUNK_SIZE = 4096;
    constructor(accountManager) {
        super();
        this.accountManager = accountManager;
        this.accountManager = patchInject(accountManager, InjectTokens.AccountManager, this.constructor.name);
    }
    static CREATE_FLAGS_LIST = {
        required: [flags.deployment, flags.filePath],
        optional: [],
    };
    static UPDATE_FLAGS_LIST = {
        required: [flags.deployment, flags.fileId, flags.filePath],
        optional: [],
    };
    async close() {
        await this.accountManager.close();
    }
    /**
     * Helper method to prepare initial content and determine if append is needed
     * @param fileContent - The complete file content
     * @param operation - The operation being performed ('create' or 'update')
     * @returns Object with initialContent and needsAppend flag
     */
    prepareInitialContent(fileContent, operation) {
        const needsAppend = fileContent.length > FileCommand_1.MAX_CHUNK_SIZE;
        let initialContent;
        if (needsAppend) {
            initialContent = fileContent.slice(0, FileCommand_1.MAX_CHUNK_SIZE);
            this.logger.showUser(chalk.gray(`  ${operation === 'create' ? 'Creating' : 'Updating'} file with first ${initialContent.length} bytes (multi-part ${operation})...`));
        }
        else {
            initialContent = fileContent;
            this.logger.showUser(chalk.gray(`  ${operation === 'create' ? 'Creating' : 'Updating'} file with ${initialContent.length} bytes...`));
        }
        return { initialContent, needsAppend };
    }
    /**
     * Helper method to initialize configuration and read file content
     * @param argv - Command arguments
     * @param requireFileId - Whether file ID is required (true for update, false for create)
     * @returns Configuration context with file content
     */
    async initializeFileConfig(argv, requireFileId) {
        // Load configurations
        await this.localConfig.load();
        await this.remoteConfig.loadAndValidate(argv);
        this.configManager.update(argv);
        const filePath = argv[flags.filePath.name];
        const deployment = argv[flags.deployment.name];
        const namespace = this.remoteConfig.getNamespace();
        let fileId = '';
        if (requireFileId) {
            fileId = argv[flags.fileId.name];
            // Validate file ID format
            if (!/^\d+\.\d+\.\d+$/.test(fileId)) {
                throw new SoloError(`Invalid file ID format: ${fileId}. Expected format: 0.0.150`);
            }
        }
        // Validate file exists
        if (!fs.existsSync(filePath)) {
            throw new SoloError(`File not found: ${filePath}`);
        }
        // Read file content
        const fileContent = fs.readFileSync(filePath);
        const fileName = path.basename(filePath);
        this.logger.showUser(chalk.cyan(`File: ${fileName}`));
        this.logger.showUser(chalk.cyan(`Size: ${fileContent.length} bytes`));
        if (requireFileId) {
            this.logger.showUser(chalk.cyan(`File ID: ${fileId}`));
        }
        const config = {
            fileId,
            filePath,
            deployment,
            namespace,
        };
        return {
            config,
            fileContent,
            expectedSize: fileContent.length,
        };
    }
    /**
     * Helper method to load node client and treasury keys
     * @param namespace - The namespace
     * @param deployment - The deployment name
     * @param useGenesisKeyForSystemFile - Whether to use genesis key for system file operations
     * @returns The private key to use for transactions
     */
    async loadClientAndKeys(namespace, deployment, useGenesisKeyForSystemFile = false) {
        // Load node client
        await this.accountManager.loadNodeClient(namespace, this.remoteConfig.getClusterRefs(), deployment);
        // Use genesis key for system file operations if requested
        if (useGenesisKeyForSystemFile) {
            this.logger.showUser(chalk.cyan('Using genesis key for system file operations'));
            this.logger.showUser(chalk.gray('  Genesis key can be customized via GENESIS_KEY environment variable'));
            return PrivateKey.fromString(constants.GENESIS_KEY);
        }
        // Get treasury account keys
        const treasuryKeys = await this.accountManager.getTreasuryAccountKeys(namespace, deployment);
        return PrivateKey.fromString(treasuryKeys.privateKey);
    }
    /**
     * Helper method to verify uploaded file content
     * @param client - The Hiero client
     * @param fileId - The file ID to verify
     * @param expectedContent - The expected file content
     */
    async verifyFileUpload(client, fileId, expectedContent) {
        const fileIdObject = FileId.fromString(fileId);
        this.logger.showUser(chalk.cyan('Querying file contents to verify upload...'));
        const fileContentsQuery = new FileContentsQuery().setFileId(fileIdObject);
        const retrievedContents = await fileContentsQuery.execute(client);
        const uploadedSize = retrievedContents.length;
        const expectedSize = expectedContent.length;
        this.logger.showUser(chalk.gray(`  Expected size: ${expectedSize} bytes`));
        this.logger.showUser(chalk.gray(`  Retrieved size: ${uploadedSize} bytes`));
        if (uploadedSize !== expectedSize) {
            // Check if this is a system file (0.0.101-0.0.200 range)
            const fileIdParts = fileId.split('.');
            const fileNumber = Number.parseInt(fileIdParts[2]);
            const isSystemFile = fileNumber >= 101 && fileNumber <= 200;
            let errorMessage = `File size mismatch! Expected ${expectedSize} bytes but got ${uploadedSize} bytes`;
            if (isSystemFile && uploadedSize === 0) {
                errorMessage = `${errorMessage}

⚠️  System File Update Failed:
File ${fileId} is a system file that appears to be immutable or requires special authorization.

Possible reasons:
1. The genesis key may not have permission to update this specific system file
2. System file ${fileId} may require network-level authorization or freeze/unfreeze operations
3. The network may be using a different genesis key than expected

Troubleshooting:
• Verify the correct genesis key using: echo $GENESIS_KEY
• Set custom genesis key: export GENESIS_KEY=<your-genesis-key>
• Check if the file requires special permissions beyond genesis key
• Consider using FileUpdateTransaction with additional authorization in custom code`;
            }
            throw new SoloError(errorMessage);
        }
        // Also verify content matches
        const contentsMatch = Buffer.from(retrievedContents).equals(Buffer.from(expectedContent));
        if (!contentsMatch) {
            throw new SoloError('File content verification failed! Retrieved content does not match uploaded content');
        }
        this.logger.showUser(chalk.green('✓ File verification successful'));
        this.logger.showUser(chalk.green(`✓ Size: ${uploadedSize} bytes`));
        this.logger.showUser(chalk.green('✓ Content matches uploaded file'));
    }
    /**
     * Helper method to append remaining file chunks after initial create/update
     * @param task - The Listr task wrapper for updating progress
     * @param client - The Hiero client
     * @param fileId - The file ID to append to
     * @param fileContent - The complete file content
     * @param treasuryPrivateKey - The private key to sign transactions
     */
    async appendFileChunks(task, client, fileId, fileContent, treasuryPrivateKey) {
        const fileIdObject = FileId.fromString(fileId);
        let offset = FileCommand_1.MAX_CHUNK_SIZE;
        let chunkIndex = 1;
        // Calculate total chunks needed
        const totalChunks = Math.ceil((fileContent.length - FileCommand_1.MAX_CHUNK_SIZE) / FileCommand_1.MAX_CHUNK_SIZE);
        while (offset < fileContent.length) {
            const chunk = fileContent.slice(offset, offset + FileCommand_1.MAX_CHUNK_SIZE);
            const remaining = fileContent.length - offset;
            // Update task title to show progress
            task.title = `Append remaining file content (chunk ${chunkIndex}/${totalChunks})`;
            this.logger.showUser(chalk.gray(`  Appending chunk ${chunkIndex}/${totalChunks} (${chunk.length} bytes, ${remaining} bytes remaining)...`));
            const fileAppendTx = new FileAppendTransaction()
                .setFileId(fileIdObject)
                .setContents(chunk)
                .setMaxTransactionFee(100)
                .freezeWith(client);
            const signedAppendTx = await fileAppendTx.sign(treasuryPrivateKey);
            const appendResponse = await signedAppendTx.execute(client);
            const appendReceipt = await appendResponse.getReceipt(client);
            if (appendReceipt.status !== Status.Success) {
                throw new SoloError(`File append (chunk ${chunkIndex}) failed with status: ${appendReceipt.status.toString()}`);
            }
            offset += FileCommand_1.MAX_CHUNK_SIZE;
            chunkIndex++;
        }
        // Update final title
        task.title = `Append remaining file content (${totalChunks} chunks completed)`;
        this.logger.showUser(chalk.green(`✓ Appended ${totalChunks} chunks successfully`));
    }
    /**
     * Unified method to create or update a file on the Hiero network
     * @param argv - Command arguments
     * @param isCreate - True for create operation, false for update
     */
    async executeFileOperation(argv, isCreate) {
        const self = this;
        const tasks = new Listr([
            {
                title: 'Initialize configuration',
                task: async (context_) => {
                    const result = await self.initializeFileConfig(argv, !isCreate);
                    context_.config = result.config;
                    context_.fileContent = result.fileContent;
                    context_.expectedSize = result.expectedSize;
                    // Check if this is a system file (for update operations)
                    if (!isCreate && context_.config.fileId) {
                        const fileIdParts = context_.config.fileId.split('.');
                        const fileNumber = Number.parseInt(fileIdParts[2]);
                        context_.isSystemFile = fileNumber >= 101 && fileNumber <= 200;
                    }
                },
            },
            {
                title: 'Load node client and treasury keys',
                task: async (context_) => {
                    const useGenesisKey = context_.isSystemFile || false;
                    context_.treasuryPrivateKey = await self.loadClientAndKeys(context_.config.namespace, context_.config.deployment, useGenesisKey);
                },
            },
            {
                title: 'Check if file exists',
                skip: () => isCreate, // Skip for create operation
                task: async (context_) => {
                    const client = self.accountManager._nodeClient;
                    try {
                        const fileIdObject = FileId.fromString(context_.config.fileId);
                        const fileInfoQuery = new FileInfoQuery().setFileId(fileIdObject);
                        const fileInfo = await fileInfoQuery.execute(client);
                        context_.fileExists = true;
                        self.logger.showUser(chalk.green(`File ${context_.config.fileId} exists. Proceeding with update.`));
                        self.logger.showUser(chalk.gray(`  Current size: ${fileInfo.size.toString()} bytes`));
                        const keysCount = fileInfo.keys ? fileInfo.keys.toArray().length : 0;
                        self.logger.showUser(chalk.gray(`  Keys: ${keysCount}`));
                        // Check if file is a system file (no keys = immutable)
                        if (keysCount === 0) {
                            if (context_.isSystemFile) {
                                self.logger.showUser(chalk.cyan(`ℹ️  File ${context_.config.fileId} is a system file (no keys). Automatically using genesis key for update.`));
                            }
                            else {
                                self.logger.showUser(chalk.yellow(`⚠️  Warning: File ${context_.config.fileId} has no keys but is not in system file range (0.0.101-0.0.200).`));
                                self.logger.showUser(chalk.yellow('    Update may fail. Set GENESIS_KEY environment variable if this file requires genesis key authorization.'));
                            }
                        }
                    }
                    catch (error) {
                        const error_ = error.status === Status.FileDeleted || error.status === Status.InvalidFileId
                            ? new SoloError(`File ${context_.config.fileId} does not exist. Use 'ledger file create' to create a new file.`)
                            : new SoloError(`Failed to query file info: ${error.message}`, error);
                        throw error_;
                    }
                },
            },
            {
                title: isCreate ? 'Create file on Hiero network' : 'Update file on Hiero network',
                task: async (context_, task) => {
                    const client = self.accountManager._nodeClient;
                    const subTasks = [
                        {
                            title: isCreate ? 'Create new file' : 'Update existing file',
                            task: async (context__, task) => {
                                const { initialContent, needsAppend } = self.prepareInitialContent(context__.fileContent, isCreate ? 'create' : 'update');
                                if (isCreate) {
                                    // Create new file
                                    const fileCreateTx = new FileCreateTransaction()
                                        .setKeys([context__.treasuryPrivateKey.publicKey])
                                        .setContents(initialContent)
                                        .setMaxTransactionFee(100)
                                        .freezeWith(client);
                                    const signedTx = await fileCreateTx.sign(context__.treasuryPrivateKey);
                                    const txResponse = await signedTx.execute(client);
                                    const receipt = await txResponse.getReceipt(client);
                                    if (receipt.status !== Status.Success) {
                                        throw new SoloError(`File creation failed with status: ${receipt.status.toString()}`);
                                    }
                                    const createdFileId = receipt.fileId;
                                    context__.createdFileId = createdFileId?.toString();
                                    context__.config.fileId = context__.createdFileId; // Update config with actual file ID
                                    self.logger.showUser(chalk.green(`✓ File created with ID: ${context__.createdFileId}`));
                                }
                                else {
                                    // Update existing file
                                    const fileIdObject = FileId.fromString(context__.config.fileId);
                                    const fileUpdateTx = new FileUpdateTransaction()
                                        .setFileId(fileIdObject)
                                        .setContents(initialContent)
                                        .setMaxTransactionFee(100)
                                        .freezeWith(client);
                                    const signedUpdateTx = await fileUpdateTx.sign(context__.treasuryPrivateKey);
                                    const updateResponse = await signedUpdateTx.execute(client);
                                    const updateReceipt = await updateResponse.getReceipt(client);
                                    if (updateReceipt.status !== Status.Success) {
                                        throw new SoloError(`File update failed with status: ${updateReceipt.status.toString()}`);
                                    }
                                    self.logger.showUser(chalk.green('✓ File updated successfully'));
                                }
                                // Append remaining content if needed
                                if (needsAppend) {
                                    const appendSubtasks = [
                                        {
                                            title: 'Append remaining file content',
                                            task: async (context__, appendTask) => {
                                                await self.appendFileChunks(appendTask, client, context__.config.fileId, context__.fileContent, context__.treasuryPrivateKey);
                                            },
                                        },
                                    ];
                                    return task.newListr(appendSubtasks, {
                                        concurrent: false,
                                        rendererOptions: { collapseSubtasks: false },
                                    });
                                }
                            },
                        },
                    ];
                    // Create or update file
                    return task.newListr(subTasks, {
                        concurrent: false,
                        rendererOptions: {
                            collapseSubtasks: false,
                        },
                    });
                },
            },
            {
                title: 'Verify uploaded file',
                task: async (context_) => {
                    const client = self.accountManager._nodeClient;
                    await self.verifyFileUpload(client, context_.config.fileId, context_.fileContent);
                },
            },
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT);
        try {
            await tasks.run();
            const context = tasks.ctx;
            if (isCreate) {
                this.logger.showUser(chalk.green('\n✅ File created successfully!'));
                this.logger.showUser(chalk.cyan(`📄 File ID: ${context.createdFileId}`));
            }
            else {
                this.logger.showUser(chalk.green('\n✅ File updated successfully!'));
            }
        }
        catch (error) {
            const operation = isCreate ? 'creation' : 'update';
            throw new SoloError(`File ${operation} failed: ${error.message}`, error);
        }
        return true;
    }
    /**
     * Create a new file on the Hiero network
     */
    async create(argv) {
        return this.executeFileOperation(argv, true);
    }
    /**
     * Update an existing file on the Hiero network
     */
    async update(argv) {
        return this.executeFileOperation(argv, false);
    }
};
FileCommand = FileCommand_1 = __decorate([
    injectable(),
    __param(0, inject(InjectTokens.AccountManager)),
    __metadata("design:paramtypes", [Function])
], FileCommand);
export { FileCommand };
//# sourceMappingURL=file.js.map