/**
 * File Error Boundary Component
 *
 * Specialized error boundary for catching and handling file system errors.
 * Provides detailed error information, recovery actions for common file system issues,
 * and permission handling.
 */
import React, { Component } from 'react';
import { Box, Text } from 'ink';
import * as fs from 'fs';
import * as path from 'path';
import { logger } from '../../../utils/logger.js';
/**
 * File system error types
 */
export var FileErrorType;
(function (FileErrorType) {
    FileErrorType["NOT_FOUND"] = "NOT_FOUND";
    FileErrorType["PERMISSION_DENIED"] = "PERMISSION_DENIED";
    FileErrorType["ALREADY_EXISTS"] = "ALREADY_EXISTS";
    FileErrorType["IS_DIRECTORY"] = "IS_DIRECTORY";
    FileErrorType["NOT_DIRECTORY"] = "NOT_DIRECTORY";
    FileErrorType["DISK_FULL"] = "DISK_FULL";
    FileErrorType["READ_ONLY"] = "READ_ONLY";
    FileErrorType["INVALID_PATH"] = "INVALID_PATH";
    FileErrorType["SYMLINK_LOOP"] = "SYMLINK_LOOP";
    FileErrorType["TOO_MANY_OPEN"] = "TOO_MANY_OPEN";
    FileErrorType["UNKNOWN"] = "UNKNOWN";
})(FileErrorType || (FileErrorType = {}));
/**
 * Detects the type of file system error based on error code and message
 */
function detectFileErrorType(error) {
    const errorCode = error.code;
    const message = error.message.toLowerCase();
    // Check error codes (Node.js file system error codes)
    switch (errorCode) {
        case 'ENOENT':
            return FileErrorType.NOT_FOUND;
        case 'EACCES':
        case 'EPERM':
            return FileErrorType.PERMISSION_DENIED;
        case 'EEXIST':
            return FileErrorType.ALREADY_EXISTS;
        case 'EISDIR':
            return FileErrorType.IS_DIRECTORY;
        case 'ENOTDIR':
            return FileErrorType.NOT_DIRECTORY;
        case 'ENOSPC':
            return FileErrorType.DISK_FULL;
        case 'EROFS':
            return FileErrorType.READ_ONLY;
        case 'EINVAL':
            return FileErrorType.INVALID_PATH;
        case 'ELOOP':
            return FileErrorType.SYMLINK_LOOP;
        case 'EMFILE':
        case 'ENFILE':
            return FileErrorType.TOO_MANY_OPEN;
    }
    // Check message patterns as fallback
    if (message.includes('not found') || message.includes('no such file')) {
        return FileErrorType.NOT_FOUND;
    }
    if (message.includes('permission') || message.includes('access denied')) {
        return FileErrorType.PERMISSION_DENIED;
    }
    if (message.includes('already exists') || message.includes('file exists')) {
        return FileErrorType.ALREADY_EXISTS;
    }
    if (message.includes('is a directory')) {
        return FileErrorType.IS_DIRECTORY;
    }
    if (message.includes('not a directory')) {
        return FileErrorType.NOT_DIRECTORY;
    }
    if (message.includes('disk full') || message.includes('no space')) {
        return FileErrorType.DISK_FULL;
    }
    if (message.includes('read-only')) {
        return FileErrorType.READ_ONLY;
    }
    return FileErrorType.UNKNOWN;
}
/**
 * Extracts file path from error message or props
 */
function extractFilePath(error, propsPath) {
    if (propsPath) {
        return propsPath;
    }
    const pathMatch = error.message.match(/'([^']+)'/);
    if (pathMatch && pathMatch[1]) {
        return pathMatch[1];
    }
    return null;
}
/**
 * Suggests recovery action based on error type
 */
function getSuggestedAction(errorType, filePath) {
    switch (errorType) {
        case FileErrorType.NOT_FOUND:
            if (filePath) {
                const dir = path.dirname(filePath);
                return `Create missing directory: ${dir}`;
            }
            return 'Check if the file path is correct';
        case FileErrorType.PERMISSION_DENIED:
            return 'Check file permissions or run with appropriate privileges';
        case FileErrorType.ALREADY_EXISTS:
            return 'Use a different file name or remove the existing file';
        case FileErrorType.IS_DIRECTORY:
            return 'The path points to a directory, not a file';
        case FileErrorType.NOT_DIRECTORY:
            return 'The path component is not a directory';
        case FileErrorType.DISK_FULL:
            return 'Free up disk space and try again';
        case FileErrorType.READ_ONLY:
            return 'The file system is read-only';
        case FileErrorType.INVALID_PATH:
            return 'The file path contains invalid characters';
        case FileErrorType.SYMLINK_LOOP:
            return 'The path contains a symbolic link loop';
        case FileErrorType.TOO_MANY_OPEN:
            return 'Too many files are open. Close some files and try again';
        default:
            return null;
    }
}
/**
 * File Error Boundary for handling file system errors
 */
export class FileErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = {
            hasError: false,
            error: null,
            errorInfo: null,
            fileErrorType: FileErrorType.UNKNOWN,
            suggestedAction: null,
            isRecovering: false,
        };
    }
    static getDerivedStateFromError(error) {
        const fileErrorType = detectFileErrorType(error);
        return {
            hasError: true,
            error,
            fileErrorType,
        };
    }
    componentDidCatch(error, errorInfo) {
        const fileErrorType = detectFileErrorType(error);
        const filePath = extractFilePath(error, this.props.filePath);
        const suggestedAction = getSuggestedAction(fileErrorType, filePath);
        this.setState({ errorInfo, suggestedAction });
        // Log error details
        logger.error('[FileErrorBoundary] File system error', error, {
            filePath: filePath || 'Unknown',
            errorType: fileErrorType,
            errorCode: error.code || 'N/A'
        });
        // Call custom error handler if provided
        if (this.props.onError) {
            this.props.onError(error, errorInfo);
        }
        // Auto-recover for certain error types
        if (this.props.autoCreateDirectories &&
            fileErrorType === FileErrorType.NOT_FOUND &&
            filePath) {
            this.handleAutoCreateDirectory(filePath);
        }
    }
    handleAutoCreateDirectory = async (filePath) => {
        const dir = path.dirname(filePath);
        try {
            this.setState({ isRecovering: true });
            // Check if directory already exists
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true });
                logger.info(`[FileErrorBoundary] Created directory: ${dir}`);
                // Notify callback
                if (this.props.onCreateDirectory) {
                    this.props.onCreateDirectory(dir);
                }
                // Reset error state to retry
                setTimeout(() => {
                    this.setState({
                        hasError: false,
                        error: null,
                        errorInfo: null,
                        isRecovering: false,
                    });
                    if (this.props.onRetry) {
                        this.props.onRetry();
                    }
                }, 1000);
            }
        }
        catch (recoveryError) {
            logger.error('[FileErrorBoundary] Failed to create directory', recoveryError);
            this.setState({ isRecovering: false });
        }
    };
    handleManualRecovery = () => {
        const filePath = extractFilePath(this.state.error, this.props.filePath);
        if (this.state.fileErrorType === FileErrorType.NOT_FOUND &&
            filePath &&
            this.props.onCreateDirectory) {
            this.handleAutoCreateDirectory(filePath);
        }
    };
    getErrorMessage() {
        if (!this.state.error) {
            return 'An unexpected file system error occurred';
        }
        const filePath = extractFilePath(this.state.error, this.props.filePath);
        const fileDisplay = filePath ? ` '${filePath}'` : '';
        switch (this.state.fileErrorType) {
            case FileErrorType.NOT_FOUND:
                return `File or directory${fileDisplay} not found`;
            case FileErrorType.PERMISSION_DENIED:
                return `Permission denied accessing${fileDisplay}`;
            case FileErrorType.ALREADY_EXISTS:
                return `File${fileDisplay} already exists`;
            case FileErrorType.IS_DIRECTORY:
                return `Expected a file but found a directory${fileDisplay}`;
            case FileErrorType.NOT_DIRECTORY:
                return `Expected a directory but found a file${fileDisplay}`;
            case FileErrorType.DISK_FULL:
                return 'Disk is full - no space available';
            case FileErrorType.READ_ONLY:
                return `File system is read-only for${fileDisplay}`;
            case FileErrorType.INVALID_PATH:
                return `Invalid file path${fileDisplay}`;
            case FileErrorType.SYMLINK_LOOP:
                return `Symbolic link loop detected in${fileDisplay}`;
            case FileErrorType.TOO_MANY_OPEN:
                return 'Too many files are open';
            default:
                return `File system error: ${this.state.error.message}`;
        }
    }
    getErrorIcon() {
        switch (this.state.fileErrorType) {
            case FileErrorType.NOT_FOUND:
                return '📁';
            case FileErrorType.PERMISSION_DENIED:
                return '🔒';
            case FileErrorType.ALREADY_EXISTS:
                return '⚠️';
            case FileErrorType.DISK_FULL:
                return '💾';
            case FileErrorType.READ_ONLY:
                return '🔐';
            default:
                return '❌';
        }
    }
    canRecover() {
        return (this.state.fileErrorType === FileErrorType.NOT_FOUND &&
            this.props.onCreateDirectory !== undefined);
    }
    render() {
        if (this.state.hasError && this.state.error) {
            const filePath = extractFilePath(this.state.error, this.props.filePath);
            const canRecover = this.canRecover();
            return (React.createElement(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "red" },
                React.createElement(Box, { marginBottom: 1 },
                    React.createElement(Text, { color: "red", bold: true },
                        this.getErrorIcon(),
                        " File System Error")),
                filePath && (React.createElement(Box, { marginBottom: 1 },
                    React.createElement(Text, { color: "cyan" }, "Path: "),
                    React.createElement(Text, { color: "white" }, filePath))),
                React.createElement(Box, { marginBottom: 1 },
                    React.createElement(Text, { color: "yellow" }, this.getErrorMessage())),
                this.state.suggestedAction && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
                    React.createElement(Text, { color: "green" }, "Suggested action:"),
                    React.createElement(Text, { color: "white" },
                        "  ",
                        this.state.suggestedAction))),
                this.props.showDetails && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
                    React.createElement(Text, { color: "gray", dimColor: true },
                        "Error Type: ",
                        this.state.fileErrorType),
                    React.createElement(Text, { color: "gray", dimColor: true },
                        "Error Code: ",
                        this.state.error.code || 'N/A'),
                    this.state.error.stack && (React.createElement(Text, { color: "gray", dimColor: true },
                        this.state.error.stack.slice(0, 200),
                        "...")))),
                this.state.isRecovering && (React.createElement(Box, { marginTop: 1 },
                    React.createElement(Text, { color: "cyan" }, "Attempting to recover..."))),
                canRecover && !this.state.isRecovering && (React.createElement(Box, { marginTop: 1 },
                    React.createElement(Text, { color: "green" }, "Recovery available - Press C to create missing directories"))),
                this.props.onRetry && !this.state.isRecovering && (React.createElement(Box, { marginTop: 1 },
                    React.createElement(Text, { color: "cyan" }, "Press R to retry"))),
                React.createElement(Box, { marginTop: 1 },
                    React.createElement(Text, { color: "gray", dimColor: true }, "Press Ctrl+C to exit"))));
        }
        return this.props.children;
    }
}
export function withFileErrorBoundary(WrappedComponent, options = {}) {
    const WithFileErrorBoundaryComponent = (props) => (React.createElement(FileErrorBoundary, { filePath: options.filePath, onRetry: options.onRetry, onCreateDirectory: options.onCreateDirectory, onError: options.onError, showDetails: options.showDetails, autoCreateDirectories: options.autoCreateDirectories },
        React.createElement(WrappedComponent, { ...props })));
    WithFileErrorBoundaryComponent.displayName = `WithFileErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
    return WithFileErrorBoundaryComponent;
}
export default FileErrorBoundary;
//# sourceMappingURL=file-error-boundary.js.map