const fs = require('fs');
const path = require('path');

/**
 * Generates documentation for the OpenVidu Meet WebComponent
 */
class WebComponentDocGenerator {
    constructor() {
        this.typingsPath = path.join(__dirname, '../meet-ce/typings/src/webcomponent');
        this.webComponentPath = path.join(__dirname, '../meet-ce/frontend/webcomponent/src/components/OpenViduMeet.ts');
    }

    /**
     * Reads and parses a TypeScript file to extract enum documentation
     */
    parseEnumFile(filePath) {
        const content = fs.readFileSync(filePath, 'utf8');
        const lines = content.split('\n');

        const enums = [];
        let currentEnum = null;
        let currentItem = null;
        let inEnum = false;
        let inComment = false;
        let commentLines = [];

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();

            // Detect start of enum
            if (line.startsWith('export enum')) {
                inEnum = true;
                currentEnum = {
                    name: line.match(/export enum (\w+)/)[1],
                    items: []
                };
                continue;
            }

            // Detect end of enum
            if (inEnum && line === '}') {
                if (currentItem) {
                    currentEnum.items.push(currentItem);
                }
                enums.push(currentEnum);
                inEnum = false;
                currentEnum = null;
                currentItem = null;
                continue;
            }

            if (!inEnum) continue;

            // Handle multi-line comments
            if (line.startsWith('/**')) {
                inComment = true;
                commentLines = [];
                continue;
            }

            if (inComment) {
                if (line.endsWith('*/')) {
                    inComment = false;
                    continue;
                }

                // Extract comment content
                const commentContent = line.replace(/^\*\s?/, '').trim();
                if (commentContent) {
                    commentLines.push(commentContent);
                }
                continue;
            }

            // Parse enum item
            if (line.includes('=') && !line.startsWith('//')) {
                // Save previous item if exists
                if (currentItem) {
                    currentEnum.items.push(currentItem);
                }

                const match = line.match(/(\w+)\s*=\s*'([^']+)'/);
                if (match) {
                    // Extract @required text if present
                    const requiredComment = commentLines.find(c => c.includes('@required'));
                    let requiredText = '';
                    if (requiredComment) {
                        const requiredMatch = requiredComment.match(/@required\s*(.*)/);
                        requiredText = requiredMatch ? requiredMatch[1].trim() : '';
                    }

                    currentItem = {
                        name: match[1],
                        value: match[2],
                        description: commentLines.filter(line => !line.includes('@')).join(' '),
                        isPrivate: commentLines.some(c => c.includes('@private')),
                        isModerator: commentLines.some(c => c.includes('@moderator')),
                        isRequired: commentLines.some(c => c.includes('@required')),
                        requiredText: requiredText
                    };
                    commentLines = [];
                }
            }
        }

        return enums;
    }

    /**
     * Extracts payload information from interface definitions
     */
    extractPayloads(filePath) {
        const content = fs.readFileSync(filePath, 'utf8');
        const payloads = {};

        // Find the payload interface
        const interfaceMatch = content.match(/export interface \w+Payloads\s*{([\s\S]*?)^}/m);
        if (!interfaceMatch) return payloads;

        const interfaceContent = interfaceMatch[1];
        const lines = interfaceContent.split('\n');

        let currentKey = null;
        let inComment = false;
        let commentLines = [];

        for (const line of lines) {
            const trimmed = line.trim();

            if (trimmed.startsWith('/**')) {
                inComment = true;
                commentLines = [];
                continue;
            }

            if (inComment) {
                if (trimmed.endsWith('*/')) {
                    inComment = false;
                    continue;
                }

                const commentContent = trimmed.replace(/^\*\s?/, '').trim();
                if (commentContent && !commentContent.includes('@')) {
                    commentLines.push(commentContent);
                }
                continue;
            }

            // Parse payload property - looking for patterns like [WebComponentEvent.JOIN]: {
            const propMatch = trimmed.match(/\[\w+\.(\w+)\]:\s*({[\s\S]*?}|[^,;]+)[,;]?/);
            if (propMatch) {
                const enumValue = propMatch[1];
                let type = propMatch[2].trim();

                // If it's a multi-line object, we need to collect the full definition
                if (type.startsWith('{') && !type.endsWith('}')) {
                    // Find the closing brace
                    let braceCount = 1;
                    let i = lines.indexOf(line) + 1;
                    while (i < lines.length && braceCount > 0) {
                        const nextLine = lines[i].trim();
                        type += '\n' + nextLine;
                        for (const char of nextLine) {
                            if (char === '{') braceCount++;
                            if (char === '}') braceCount--;
                        }
                        i++;
                    }
                }

                payloads[enumValue] = {
                    type: type.replace(/[,;]$/, ''), // Remove trailing comma or semicolon
                    description: commentLines.join(' ')
                };
                commentLines = [];
            }
        }

        return payloads;
    }

    /**
     * Extracts WebComponent attributes from the OpenViduMeet.ts file
     */
    extractWebComponentAttributes() {
        const content = fs.readFileSync(this.webComponentPath, 'utf8');
        const attributes = [];

        // Look for @attribute JSDoc comments
        const attributeMatches = content.match(/@attribute\s+([^\s]+)\s*-\s*([^\n]+)/g);
        if (attributeMatches) {
            attributeMatches.forEach(match => {
                const parts = match.match(/@attribute\s+([^\s]+)\s*-\s*(.+)/);
                if (parts) {
                    attributes.push({
                        name: parts[1],
                        description: parts[2].trim()
                    });
                }
            });
        }

        return attributes;
    }

    /**
     * Generates markdown table for events (only public events)
     */
    generateEventsTable() {
        const enums = this.parseEnumFile(path.join(this.typingsPath, 'event.model.ts'));
        const payloads = this.extractPayloads(path.join(this.typingsPath, 'event.model.ts'));

        const eventEnum = enums.find(e => e.name === 'WebComponentEvent');
        if (!eventEnum) return '';

        let markdown = '| Event | Description | Payload |\n';
        markdown += '|-------|-------------|------------|\n';

        for (const item of eventEnum.items) {
            // Skip private events
            if (item.isPrivate) continue;

            const payload = payloads[item.name];
            const payloadInfo = payload ? this.formatPayload(payload.type) : '-';

            markdown += `| \`${item.value}\` | ${item.description || 'No description available'} | ${payloadInfo} |\n`;
        }

        return markdown;
    }

    /**
     * Generates markdown table for commands/methods (only public methods)
     */
    generateCommandsTable() {
        const enums = this.parseEnumFile(path.join(this.typingsPath, 'command.model.ts'));
        const payloads = this.extractPayloads(path.join(this.typingsPath, 'command.model.ts'));

        const commandEnum = enums.find(e => e.name === 'WebComponentCommand');
        if (!commandEnum) return '';

        let markdown = '| Method | Command | Description | Parameters | Access Level |\n';
        markdown += '|--------|---------|-------------|------------|-------------|\n';

        for (const item of commandEnum.items) {
            // Skip private commands
            if (item.isPrivate) continue;

            const payload = payloads[item.name];

            // Generate method name from command name and payload
            const methodName = this.generateMethodName(item.name, item.value, payload);

            const params = payload ? this.formatMethodParameters(payload.type) : '-';

            // Determine access level based on @moderator annotation
            const accessLevel = this.getAccessLevel(item);

            markdown += `| \`${methodName}\` | \`${item.value}\` | ${item.description || 'No description available'} | ${params} | ${accessLevel} |\n`;
        }

        return markdown;
    }

    /**
     * Generates method name and signature from command enum
     */
    generateMethodName(commandName, commandValue, payload) {
        // Convert COMMAND_NAME to camelCase method name
        const methodName = commandName
            .toLowerCase()
            .split('_')
            .map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
            .join('');

        // If there's no payload or payload is void, no parameters needed
        if (!payload || payload.type === 'void') {
            return `${methodName}()`;
        }

        // Extract parameter names from payload type
        if (payload.type.includes('{') && payload.type.includes('}')) {
            // Remove comments (both single-line // and multi-line /* */)
            const cleanedType = payload.type
                .replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments
                .replace(/\/\/.*$/gm, ''); // Remove // comments

            const properties = cleanedType
                .replace(/[{}]/g, '')
                .split(';')
                .map(prop => prop.trim())
                .filter(prop => prop && !prop.startsWith('//') && !prop.startsWith('/*'))
                .map(prop => {
                    const [key] = prop.split(':').map(s => s.trim());
                    return key;
                })
                .filter(key => key); // Remove empty keys

            if (properties.length > 0) {
                return `${methodName}(${properties.join(', ')})`;
            }
        }

        // Fallback: no parameters
        return `${methodName}()`;
    }

    /**
     * Determines the access level of a command based on its @moderator annotation
     */
    getAccessLevel(item) {
        return item.isModerator ? 'Moderator' : 'All';
    }

    /**
     * Generates markdown table for attributes/properties
     */
    generateAttributesTable() {
        const propertyEnums = this.parseEnumFile(path.join(this.typingsPath, 'properties.model.ts'));
        const propertyEnum = propertyEnums.find(e => e.name === 'WebComponentProperty');

        let markdown = '| Attribute | Description | Required |\n';
        markdown += '|-----------|-------------|----------|\n';

        // Add attributes from the properties enum only
        if (propertyEnum) {
            for (const item of propertyEnum.items) {
                // Format required column with additional text if present
                let requiredColumn = 'No';
                if (item.isRequired) {
                    requiredColumn = item.requiredText ? `Yes (${item.requiredText})` : 'Yes';
                }

                // Use description from JSDoc comments, fallback to hardcoded if not available
                const description = item.description || this.getDescriptionForAttribute(item.value);

                markdown += `| \`${item.value}\` | ${description} | ${requiredColumn} |\n`;
            }
        }

        return markdown;
    }

    /**
     * Formats payload type information for display in events table
     */
    formatPayload(type) {
        if (type === 'void' || type === '{}') {
            return 'None';
        }

        // Handle object types
        if (type.includes('{') && type.includes('}')) {
            const properties = type
                .replace(/[{}]/g, '')
                .split(';')
                .map(prop => prop.trim())
                .filter(prop => prop)
                .map(prop => {
                    const [key, value] = prop.split(':').map(s => s.trim());
                    return `"${key}": "${value}"`;
                });

            if (properties.length > 0) {
                const tab = '&nbsp;&nbsp;&nbsp;&nbsp;';
                const jsonContent = '{ <br>' + tab + properties.join(',<br>' + tab) + '<br>}';
                return `<pre><code>${jsonContent}</code></pre>`;
            } else {
                return '<pre><code>{}</code></pre>';
            }
        }

        return `\`${type}\``;
    }

    /**
     * Formats method parameters for display
     */
    formatMethodParameters(type) {
        if (type === 'void') {
            return '-';
        }

        // Handle object types
        if (type.includes('{') && type.includes('}')) {
            // Remove comments (both single-line // and multi-line /* */)
            const cleanedType = type
                .replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments
                .replace(/\/\/.*$/gm, ''); // Remove // comments

            const properties = cleanedType
                .replace(/[{}]/g, '')
                .split(';')
                .map(prop => prop.trim())
                .filter(prop => prop && !prop.startsWith('//') && !prop.startsWith('/*'))
                .map(prop => {
                    const [key, value] = prop.split(':').map(s => s.trim());
                    return `• \`${key}\`: ${value}`;
                })
                .filter(param => param && !param.includes('undefined')); // Remove malformed parameters

            return properties.length > 0 ? properties.join('<br>') : 'object';
        }

        return type;
    }

    /**
     * Gets description for an attribute
     */
    getDescriptionForAttribute(attributeName) {
        const descriptions = {
            'room-id': 'Unique identifier for the meeting room',
            'participant-name': 'Display name for the local participant',
            'leave-redirect-url': 'URL to redirect to when leaving the meeting'
        };
        return descriptions[attributeName] || 'No description available';
    }

    /**
     * Generates separate documentation files
     */
    generateSeparateDocuments(outputDir = './docs') {
        // Ensure output directory exists
        if (!fs.existsSync(outputDir)) {
            fs.mkdirSync(outputDir, { recursive: true });
        }

        // Header comment for all generated files
        const headerComment = `<!-- This file is auto-generated. Do not edit manually. -->\n<!-- Generated by openvidu-meet/scripts/generate-webcomponent-docs.js -->\n\n`;

        const eventsTable = this.generateEventsTable();
        const commandsTable = this.generateCommandsTable();
        const attributesTable = this.generateAttributesTable();

        // Write separate files with header comments
        const eventsPath = path.join(outputDir, 'webcomponent-events.md');
        const commandsPath = path.join(outputDir, 'webcomponent-commands.md');
        const attributesPath = path.join(outputDir, 'webcomponent-attributes.md');

        fs.writeFileSync(eventsPath, headerComment + eventsTable, 'utf8');
        fs.writeFileSync(commandsPath, headerComment + commandsTable, 'utf8');
        fs.writeFileSync(attributesPath, headerComment + attributesTable, 'utf8');

        return {
            events: eventsPath,
            commands: commandsPath,
            attributes: attributesPath
        };
    }

    /**
     * Saves the generated documentation to separate files
     */
    saveDocumentation(outputDir = './docs') {
        const files = this.generateSeparateDocuments(outputDir);

        console.log('✅ Documentation generated successfully:');
        console.log(`📄 Events: ${files.events}`);
        console.log(`🔧 Commands: ${files.commands}`);
        console.log(`⚙️ Attributes: ${files.attributes}`);

        // Display summary
        console.log('\n📊 Documentation Summary:');
        console.log('- Only public/non-private elements included');
        console.log('- Three separate markdown files generated');
        console.log('- Tables only, no additional content');
    }
}

// Main execution
if (require.main === module) {
    const generator = new WebComponentDocGenerator();

    // Parse command line arguments
    const args = process.argv.slice(2);
    const outputDir = args[0] || './docs';

    try {
        generator.saveDocumentation(outputDir);
    } catch (error) {
        console.error('❌ Error generating documentation:', error.message);
        console.error('Stack trace:', error.stack);
        process.exit(1);
    }
}

module.exports = WebComponentDocGenerator;
