/**
 * Synaptic AI Pro - Token SuperSave Mode
 *
 * Experimental: Only 6 meta-tools for 99% context reduction.
 * Compatible with all MCP clients without dynamic tool loading support.
 *
 * Tools:
 * 1. list_categories() - Show available tool categories
 * 2. list_tools(category) - Show tools in a category with their schemas
 * 3. execute(tool_name, params) - Run any tool by name
 * 4. inspect(target, ...) - Dynamically inspect objects/components
 * 5. modify(gameObject, component, properties) - Dynamically modify properties
 * 6. create(type, ...) - Create objects, prefabs, components
 */

const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const cors = require('cors');
const { z } = require('zod');
const { createServer } = require('./mcp-server');
const fs = require('fs');
const path = require('path');
const os = require('os');

const app = express();
app.use(cors());
app.use(express.json());

const server = http.createServer(app);
let wss = null;

let unityWebSocket = null;
let mcpServer = null;

// =====================================================
// Load Tool Registry from JSON file
// =====================================================
let TOOL_REGISTRY_RAW = {};
let CATEGORIES = {};
let ALL_TOOLS = {};

function loadToolRegistry() {
    try {
        const registryPath = path.join(__dirname, 'tool-registry.json');
        const data = fs.readFileSync(registryPath, 'utf8');
        TOOL_REGISTRY_RAW = JSON.parse(data);

        // Build categories from registry
        CATEGORIES = {};
        ALL_TOOLS = {};

        for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
            const category = (toolData.category || 'Other').toLowerCase();

            if (!CATEGORIES[category]) {
                CATEGORIES[category] = {
                    description: `${toolData.category} tools`,
                    tools: {}
                };
            }

            // Store tool info (use clean name without unity_ prefix as primary key)
            const cleanName = toolName.replace(/^unity_/, '');
            CATEGORIES[category].tools[cleanName] = {
                fullName: toolName,
                title: toolData.title,
                description: toolData.description
            };

            // Only store clean name to avoid duplication
            ALL_TOOLS[cleanName] = {
                fullName: toolName,
                category: category,
                title: toolData.title,
                description: toolData.description
            };
        }

        console.error(`[SuperSave] Loaded ${Object.keys(TOOL_REGISTRY_RAW).length} tools from tool-registry.json`);
    } catch (err) {
        console.error('[SuperSave] Failed to load tool-registry.json:', err.message);
    }
}

// Load on startup
loadToolRegistry();

// =====================================================
// WebSocket Setup (same as index.js)
// =====================================================
function setupWebSocketHandlers() {
    if (!wss) return;

    // Surface server-level errors (ECONNRESET etc.) that would otherwise
    // disappear into the void.
    wss.on('error', (err) => {
        console.error('[SuperSave] WSS error:', err && err.message ? err.message : err);
    });

    wss.on('connection', (ws, req) => {
        const isUnity = req.headers['x-client-type'] === 'unity' || req.url === '/unity';

        // Per-socket error handler — without this, send() failures (peer gone,
        // backpressure, etc.) crash silently and the caller just hits the 60s
        // timeout with no clue why.
        ws.on('error', (err) => {
            console.error('[SuperSave] WS socket error:', err && err.message ? err.message : err);
        });

        if (isUnity || !req.url.includes('mcp')) {
            if (unityWebSocket) {
                unityWebSocket.close();
            }
            unityWebSocket = ws;

            ws.on('message', async (message) => {
                try {
                    const data = JSON.parse(message);

                    // Handle shutdown request from new process trying to take over
                    if (data.type === 'shutdown') {
                        console.error('[SuperSave] Received shutdown request from new process, shutting down...');
                        shutdownServer();
                        return;
                    }

                    const responseId = data.id || data.operationId;
                    if ((data.type === 'operation_result' || data.type === 'operation_response') && responseId) {
                        const numericId = typeof responseId === 'string' ? parseInt(responseId) : responseId;
                        if (pendingRequests.has(numericId)) {
                            const { resolve, reject, timeout } = pendingRequests.get(numericId);
                            clearTimeout(timeout);
                            pendingRequests.delete(numericId);
                            if (data.success !== false) {
                                resolve(data.content || data.result);
                            } else {
                                reject(new Error(data.content || data.error || 'Unity command failed'));
                            }
                        }
                    }
                } catch (e) {}
            });

            ws.on('close', () => {
                unityWebSocket = null;
            });
        }
    });
}

// Unity command helper
const pendingRequests = new Map();
let requestId = 0;
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function sendUnityCommandOnce(command, params, id) {
    return new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
            pendingRequests.delete(id);
            reject(new Error('timeout'));
        }, 60000);

        pendingRequests.set(id, { resolve, reject, timeout });

        const message = JSON.stringify({
            type: 'unity_operation',
            command: command,
            parameters: {
                ...params,
                operationId: id.toString()
            }
        });

        // Confirm the socket is open before pushing — without this we have
        // raced an in-flight close() and the message disappears.
        if (!unityWebSocket || unityWebSocket.readyState !== 1 /* OPEN */) {
            clearTimeout(timeout);
            pendingRequests.delete(id);
            const state = unityWebSocket ? unityWebSocket.readyState : 'null';
            return reject(new Error(`unityWebSocket not open (readyState=${state})`));
        }

        // send() with callback so write failures surface immediately instead
        // of bleeding into the 60s timeout. ESC-0102 root-cause hunt.
        unityWebSocket.send(message, (err) => {
            if (err) {
                clearTimeout(timeout);
                pendingRequests.delete(id);
                console.error('[SuperSave] send failed:', err.message);
                reject(err);
            }
        });
    });
}

async function sendUnityCommand(command, params = {}) {
    const MAX_RETRIES = 30;
    const RETRY_DELAY = 10000;

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
        if (!unityWebSocket || unityWebSocket.readyState !== WebSocket.OPEN) {
            if (attempt < MAX_RETRIES) {
                console.error(`[SuperSave] Unity not connected (attempt ${attempt}/${MAX_RETRIES}). Waiting...`);
                await sleep(RETRY_DELAY);
                continue;
            }
            throw new Error('Unity not connected');
        }

        const id = ++requestId;
        try {
            const result = await sendUnityCommandOnce(command, params, id);
            return result;
        } catch (error) {
            if (attempt < MAX_RETRIES) {
                console.error(`[SuperSave] Command failed (attempt ${attempt}): ${error.message}`);
                await sleep(RETRY_DELAY);
            }
        }
    }
    throw new Error('Unity not connected after retries');
}

// =====================================================
// MCP Server with 3 Meta-Tools
// =====================================================
async function setupMCPServer() {
    mcpServer = createServer();

    // ===== META-TOOL 1: list_categories =====
    mcpServer.registerTool('list_categories', {
        title: 'List Tool Categories',
        description: 'List all available tool categories. Use this first to discover what tools are available.',
        inputSchema: z.object({})
    }, async () => {
        const categories = Object.entries(CATEGORIES).map(([name, data]) => ({
            name,
            description: data.description,
            toolCount: Object.keys(data.tools).length
        }));

        const totalTools = Object.keys(TOOL_REGISTRY_RAW).length;

        return {
            content: [{
                type: 'text',
                text: `Available Categories (${categories.length} categories, ${totalTools} total tools):\n\n` +
                    categories.map(c => `• ${c.name} (${c.toolCount} tools)\n  ${c.description}`).join('\n\n') +
                    '\n\nUse list_tools(category) to see tools in a specific category.'
            }]
        };
    });

    // ===== META-TOOL 2: list_tools =====
    mcpServer.registerTool('list_tools', {
        title: 'List Tools in Category',
        description: 'List all tools in a specific category with their parameters. Use this to learn how to use specific tools.',
        inputSchema: z.object({
            category: z.string().describe('Category name (e.g., "gameobject", "material", "lighting")')
        })
    }, async (params) => {
        const category = params.category.toLowerCase();

        if (!CATEGORIES[category]) {
            const availableCategories = Object.keys(CATEGORIES).join(', ');
            return {
                content: [{
                    type: 'text',
                    text: `Unknown category: "${category}"\n\nAvailable categories: ${availableCategories}`
                }]
            };
        }

        const categoryData = CATEGORIES[category];
        const tools = Object.entries(categoryData.tools).map(([name, data]) => {
            return `• ${name} (${data.fullName})\n  ${data.title}\n  ${data.description}`;
        });

        return {
            content: [{
                type: 'text',
                text: `Category: ${category}\n${categoryData.description}\n\nTools (${tools.length}):\n\n${tools.join('\n\n')}\n\nUse execute(tool_name, params) to run a tool.`
            }]
        };
    });

    // ===== META-TOOL 2.5: search_tools =====
    mcpServer.registerTool('search_tools', {
        title: 'Search Tools',
        description: 'Search for tools by keyword in name, title, or description. Use this when you don\'t know the exact category.',
        inputSchema: z.object({
            query: z.string().describe('Search keyword (e.g., "material", "camera", "physics")'),
            category: z.string().optional().describe('Optional: filter by category'),
            limit: z.number().optional().default(20).describe('Max results (default: 20)')
        })
    }, async (params) => {
        const query = (params.query || '').toLowerCase();
        const categoryFilter = params.category?.toLowerCase();
        const limit = params.limit || 20;

        const results = [];

        for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
            const title = toolData.title || toolName;
            const description = toolData.description || '';
            const category = (toolData.category || 'Other').toLowerCase();

            // Category filter
            if (categoryFilter && category !== categoryFilter) continue;

            // If no query, return all (with limit)
            if (!query) {
                results.push({ name: toolName, title, description, category: toolData.category, score: 0 });
                if (results.length >= limit) break;
                continue;
            }

            // Calculate score
            let score = 0;
            if (toolName.toLowerCase().includes(query)) score += 100;
            if (title.toLowerCase().includes(query)) score += 80;
            if (description.toLowerCase().includes(query)) score += 40;

            if (score > 0) {
                results.push({ name: toolName, title, description, category: toolData.category, score });
            }
        }

        // Sort by score and limit
        const sorted = results
            .sort((a, b) => b.score - a.score)
            .slice(0, limit);

        if (sorted.length === 0) {
            return {
                content: [{
                    type: 'text',
                    text: `No tools found for query: "${params.query}"\n\nTry different keywords or use list_categories to see available categories.`
                }]
            };
        }

        const toolList = sorted.map(t => `• ${t.name}\n  ${t.title} [${t.category}]\n  ${t.description.slice(0, 100)}${t.description.length > 100 ? '...' : ''}`);

        return {
            content: [{
                type: 'text',
                text: `Found ${sorted.length} tools for "${params.query}":\n\n${toolList.join('\n\n')}\n\nUse execute(tool_name, params) to run a tool.`
            }]
        };
    });

    // ===== META-TOOL 2.6: get_tools_reference =====
    mcpServer.registerTool('get_tools_reference', {
        title: 'Get Full Tools Reference',
        description: 'Get complete reference of ALL tools in Markdown format. Use this once at the start of a session to have full tool knowledge without repeated search_tools calls. Saves tokens by eliminating multiple tool discovery calls.',
        inputSchema: z.object({
            lang: z.enum(['en', 'jp']).optional().default('en').describe('Language for descriptions (en/jp)'),
            category: z.string().optional().describe('Optional: filter by specific category'),
            format: z.enum(['markdown', 'compact']).optional().default('markdown').describe('Output format: markdown (detailed) or compact (name + description only)')
        })
    }, async (params) => {
        const lang = params.lang || 'en';
        const categoryFilter = params.category?.toLowerCase();
        const format = params.format || 'markdown';

        // Group tools by category
        const byCategory = {};
        for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
            const category = (toolData.category || 'Other');
            const categoryLower = category.toLowerCase();

            if (categoryFilter && categoryLower !== categoryFilter) continue;

            if (!byCategory[category]) {
                byCategory[category] = [];
            }
            byCategory[category].push({ name: toolName, ...toolData });
        }

        const totalTools = Object.values(byCategory).reduce((sum, tools) => sum + tools.length, 0);
        let output = '';

        if (format === 'markdown') {
            output = `# Synaptic AI Pro - Tools Reference\n`;
            output += `Total: ${totalTools} tools in ${Object.keys(byCategory).length} categories\n\n`;

            for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
                output += `## ${category} (${tools.length} tools)\n\n`;

                for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
                    output += `### ${tool.name}\n`;
                    output += `${tool.description || tool.title || ''}\n`;

                    // Add inputSchema info if available
                    if (tool.inputSchema?.properties) {
                        const props = Object.entries(tool.inputSchema.properties);
                        if (props.length > 0) {
                            output += `**Parameters:**\n`;
                            for (const [propName, propData] of props) {
                                const required = tool.inputSchema.required?.includes(propName) ? ' (required)' : '';
                                const type = propData.type || 'any';
                                const desc = propData.description || '';
                                output += `- \`${propName}\`: ${type}${required} - ${desc}\n`;
                            }
                        }
                    }
                    output += '\n';
                }
            }
        } else {
            // Compact format
            output = `# Tools Reference (${totalTools} tools)\n\n`;

            for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
                output += `## ${category}\n`;
                for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
                    output += `- ${tool.name}: ${(tool.description || tool.title || '').slice(0, 80)}\n`;
                }
                output += '\n';
            }
        }

        output += `\n---\nUse execute(tool_name, params) to run any tool.`;

        return {
            content: [{
                type: 'text',
                text: output
            }]
        };
    });

    // ===== MCP RESOURCES: Tools Reference =====
    // Generate tools reference markdown from tool-registry.json
    function generateToolsReference(format = 'compact') {
        const byCategory = {};
        for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
            const category = (toolData.category || 'Other');
            if (!byCategory[category]) {
                byCategory[category] = [];
            }
            byCategory[category].push({ name: toolName, ...toolData });
        }

        const totalTools = Object.values(byCategory).reduce((sum, tools) => sum + tools.length, 0);
        let output = '';

        if (format === 'markdown') {
            output = `# Synaptic AI Pro - Tools Reference\n`;
            output += `Total: ${totalTools} tools in ${Object.keys(byCategory).length} categories\n\n`;

            for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
                output += `## ${category} (${tools.length} tools)\n\n`;
                for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
                    output += `### ${tool.name}\n`;
                    output += `${tool.description || tool.title || ''}\n`;
                    if (tool.inputSchema?.properties) {
                        const props = Object.entries(tool.inputSchema.properties);
                        if (props.length > 0) {
                            output += `**Parameters:**\n`;
                            for (const [propName, propData] of props) {
                                const required = tool.inputSchema.required?.includes(propName) ? ' (required)' : '';
                                const type = propData.type || 'any';
                                const desc = propData.description || '';
                                output += `- \`${propName}\`: ${type}${required} - ${desc}\n`;
                            }
                        }
                    }
                    output += '\n';
                }
            }
        } else {
            // Compact format
            output = `# Tools Reference (${totalTools} tools)\n\n`;
            for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
                output += `## ${category}\n`;
                for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
                    output += `- ${tool.name}: ${(tool.description || tool.title || '').slice(0, 80)}\n`;
                }
                output += '\n';
            }
        }

        output += `\n---\nUse execute(tool_name, params) to run any tool.`;
        return output;
    }

    // Register MCP Resources for tools reference (for prompt caching)
    mcpServer.registerResource('synaptic://tools/reference', {
        title: 'Tools Reference (Compact)',
        description: 'Complete list of all Unity tools in compact format. Add to context for efficient tool discovery.',
        mimeType: 'text/markdown'
    }, async () => {
        return {
            contents: [{
                uri: 'synaptic://tools/reference',
                mimeType: 'text/markdown',
                text: generateToolsReference('compact')
            }]
        };
    });

    mcpServer.registerResource('synaptic://tools/reference/full', {
        title: 'Tools Reference (Full)',
        description: 'Complete list of all Unity tools with full parameter details.',
        mimeType: 'text/markdown'
    }, async () => {
        return {
            contents: [{
                uri: 'synaptic://tools/reference/full',
                mimeType: 'text/markdown',
                text: generateToolsReference('markdown')
            }]
        };
    });

    // ===== META-TOOL 3: execute =====
    mcpServer.registerTool('execute', {
        title: 'Execute Tool',
        description: 'Execute any Unity tool by name. Use list_tools(category) first to see available tools and their parameters.',
        inputSchema: z.object({
            tool: z.string().describe('Tool name to execute (e.g., "create_gameobject", "set_transform")'),
            params: z.any().optional().describe('Parameters as JSON object {"name":"value"}')
        })
    }, async (params) => {
        const toolName = params.tool;
        let toolParams = params.params || {};

        // Handle case where params is passed as string (e.g., '{"name":"x"}')
        if (typeof toolParams === 'string') {
            try {
                toolParams = JSON.parse(toolParams);
            } catch (e) {
                // Try to parse key=value format (e.g., 'name=MyCube')
                const keyValueMatch = toolParams.match(/^(\w+)=(.+)$/);
                if (keyValueMatch) {
                    toolParams = { [keyValueMatch[1]]: keyValueMatch[2] };
                } else {
                    // Treat as single value if it's just a plain string
                    toolParams = {};
                }
            }
        }

        // Normalize tool name - strip unity_ prefix if present
        const strippedName = toolName.startsWith('unity_') ? toolName.substring(6) : toolName;
        const fullName = `unity_${strippedName}`;

        // Check if tool exists in registry
        const toolInfo = ALL_TOOLS[strippedName];
        if (!toolInfo) {
            // Find similar tool names for helpful error message
            const allToolNames = Object.keys(ALL_TOOLS);
            const similar = allToolNames.filter(t =>
                t.includes(strippedName) || strippedName.includes(t) ||
                t.split('_').some(part => strippedName.includes(part))
            ).slice(0, 5);

            let errorMsg = `Unknown tool: "${toolName}"`;
            if (similar.length > 0) {
                errorMsg += `\n\nDid you mean: ${similar.join(', ')}?`;
            }
            errorMsg += `\n\nUse list_categories() to see available categories, then list_tools(category) to see tools.`;

            return {
                content: [{
                    type: 'text',
                    text: errorMsg
                }]
            };
        }

        // Get the command name (without unity_ prefix) for Unity
        // Use lowercase - Unity's ConvertCommandToOperationType expects lowercase
        const commandName = strippedName.toLowerCase();

        try {
            const result = await sendUnityCommand(commandName, toolParams);
            return {
                content: [{
                    type: 'text',
                    text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
                }]
            };
        } catch (error) {
            // Detailed error message
            let errorDetail = `Error executing "${strippedName}":\n`;
            errorDetail += `  Message: ${error.message}\n`;

            if (error.message.includes('not connected')) {
                errorDetail += `\nTroubleshooting:\n`;
                errorDetail += `  1. Check Unity Editor is running\n`;
                errorDetail += `  2. Verify Synaptic AI Pro is connected (check Console)\n`;
                errorDetail += `  3. Try restarting the MCP server\n`;
            } else if (error.message.includes('timeout')) {
                errorDetail += `\nThe command timed out. Unity may be:\n`;
                errorDetail += `  - Compiling scripts\n`;
                errorDetail += `  - Processing a heavy operation\n`;
                errorDetail += `  - Not responding\n`;
            }

            errorDetail += `\nTool info: ${toolInfo.title} (${toolInfo.category})`;

            return {
                content: [{
                    type: 'text',
                    text: errorDetail
                }]
            };
        }
    });

    // ===== META-TOOL 3.5: run_csharp =====
    // Arbitrary C# execution escape-hatch (equivalent of Blender's run_python).
    // Promoted to a top-level meta-tool so small local LLMs don't have to go
    // through the execute({tool, params}) two-level nest.
    mcpServer.registerTool('run_csharp', {
        title: 'Run C# Code',
        description: 'Execute arbitrary C# code against the running Unity Editor (equivalent of Blender run_python). UnityEngine / UnityEditor / System.Linq / Newtonsoft.Json are pre-imported. Use this when no dedicated tool covers the operation. Does NOT trigger AssemblyReload so the connection stays alive.\n\nReturn value: end the snippet with `return X;` to capture X into the `result` field (prefix statements like `var x = ...;` execute first). A bare expression without trailing `;` is also accepted. Side-effect-only snippets return `result: null`. Debug.Log / LogWarning / LogError are captured into `output`.\n\nWORKS: GameObject / Transform / Component manipulation, AssetDatabase, Selection, EditorApplication, scene/asset queries via `FindObjectsByType<T>()` and other generic METHODS, generic method extension calls (`GetComponent<T>()`), arrays + foreach + LINQ-like loops, string interpolation, math, multi-statement bodies.\n\nKNOWN LIMITATION: Unity Mono.CSharp interactive parser cannot instantiate generic TYPES — `new List<int>()`, `new Dictionary<K,V>()`, `new HashSet<T>()` etc. silently return `result: null`. Workarounds: use plain arrays (`new int[] {1,2,3}`), `System.Collections.ArrayList`, or invoke generic helper methods that already exist (e.g. `FindObjectsByType<GameObject>(...)`). Generic method calls themselves are fine; only `new T<U>()` is blocked. LINQ chains that infer `IEnumerable<T>` may also fail — use `foreach` instead.',
        inputSchema: z.object({
            code: z.string().describe('C# code. End with `return X;` to capture X into `result`. A bare expression (no trailing `;`) also returns its value. Pre-imported: System, System.Linq, System.Collections.Generic, UnityEngine, UnityEditor, Newtonsoft.Json. AVOID `new List<int>()` / `new Dictionary<K,V>()` style generic instantiation (Mono parser limitation) — use arrays or ArrayList instead.')
        })
    }, async (params) => {
        try {
            const result = await sendUnityCommand('run_csharp', params);
            return {
                content: [{
                    type: 'text',
                    text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
                }]
            };
        } catch (error) {
            return {
                content: [{
                    type: 'text',
                    text: `run_csharp failed: ${error.message}`
                }]
            };
        }
    });

    // ===== META-TOOL 4: inspect =====
    mcpServer.registerTool('inspect', {
        title: 'Inspect Unity Objects',
        description: 'Dynamically inspect any Unity object, component, scene, or project assets. Use this to discover what properties are available before modifying them.',
        inputSchema: z.object({
            target: z.enum(['gameobject', 'component', 'scene', 'project', 'prefabs', 'hierarchy'])
                .describe('What to inspect: gameobject (properties/components), component (all serialized fields), scene (current scene info), project (project structure), prefabs (search prefabs), hierarchy (scene hierarchy)'),
            name: z.string().optional().describe('GameObject name (for gameobject/component targets)'),
            component: z.string().optional().describe('Component type to inspect (e.g., "Camera", "CinemachineVirtualCamera")'),
            path: z.string().optional().describe('Asset path filter for project/prefabs (e.g., "Assets/Prefabs/*")'),
            depth: z.number().optional().default(2).describe('Hierarchy depth for nested inspection (default: 2)')
        })
    }, async (params) => {
        try {
            const result = await sendUnityCommand('dynamic_inspect', params);
            return {
                content: [{
                    type: 'text',
                    text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
                }]
            };
        } catch (error) {
            return {
                content: [{
                    type: 'text',
                    text: `Inspect failed: ${error.message}\n\nTips:\n- For gameobject: provide "name"\n- For component: provide "name" and "component"\n- For prefabs: provide "path" with wildcards (e.g., "Assets/**/*.prefab")`
                }]
            };
        }
    });

    // ===== META-TOOL 5: modify =====
    mcpServer.registerTool('modify', {
        title: 'Modify Unity Objects',
        description: 'Dynamically modify any property of a Unity component using property paths. Use inspect() first to discover available properties.',
        inputSchema: z.object({
            gameObject: z.string().describe('GameObject name'),
            component: z.string().describe('Component type (e.g., "Transform", "Camera", "CinemachineVirtualCamera")'),
            properties: z.record(z.any()).describe('Property paths and values as key-value pairs (e.g., {"m_Lens.FieldOfView": 60, "m_Priority": 10})'),
            createIfMissing: z.boolean().optional().default(false).describe('Add component if it does not exist')
        })
    }, async (params) => {
        try {
            const result = await sendUnityCommand('dynamic_modify', params);
            return {
                content: [{
                    type: 'text',
                    text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
                }]
            };
        } catch (error) {
            return {
                content: [{
                    type: 'text',
                    text: `Modify failed: ${error.message}\n\nTips:\n- Use inspect(target:"component") first to see available property paths\n- Nested properties use dot notation: "m_Lens.FieldOfView"\n- Array elements: "m_Materials.Array.data[0]"`
                }]
            };
        }
    });

    // ===== META-TOOL 6: create =====
    mcpServer.registerTool('create', {
        title: 'Create Unity Objects',
        description: 'Create GameObjects, instantiate prefabs, load scenes, or add components. Universal creation tool.',
        inputSchema: z.object({
            type: z.enum(['gameobject', 'prefab', 'scene', 'component'])
                .describe('What to create: gameobject (empty or primitive), prefab (instantiate from asset), scene (load scene), component (add to existing object)'),
            // For gameobject
            name: z.string().optional().describe('Name for new GameObject'),
            primitive: z.enum(['empty', 'cube', 'sphere', 'cylinder', 'plane', 'capsule', 'quad']).optional().describe('Primitive type (for gameobject)'),
            // For prefab
            asset: z.string().optional().describe('Asset path for prefab (e.g., "Assets/Prefabs/Enemy.prefab")'),
            // For scene
            scene: z.string().optional().describe('Scene name or path to load'),
            additive: z.boolean().optional().default(false).describe('Load scene additively (for scene type)'),
            // For component
            gameObject: z.string().optional().describe('Target GameObject (for component type)'),
            component: z.string().optional().describe('Component type to add (for component type)'),
            // Common
            parent: z.string().optional().describe('Parent GameObject name'),
            position: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('World position'),
            rotation: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('Euler rotation'),
            scale: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('Local scale')
        })
    }, async (params) => {
        try {
            const result = await sendUnityCommand('dynamic_create', params);
            return {
                content: [{
                    type: 'text',
                    text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
                }]
            };
        } catch (error) {
            return {
                content: [{
                    type: 'text',
                    text: `Create failed: ${error.message}\n\nExamples:\n- GameObject: {type:"gameobject", name:"Player", primitive:"capsule"}\n- Prefab: {type:"prefab", asset:"Assets/Prefabs/Enemy.prefab", position:{x:0,y:0,z:0}}\n- Scene: {type:"scene", scene:"Level2", additive:true}\n- Component: {type:"component", gameObject:"Player", component:"Rigidbody"}`
                }]
            };
        }
    });

    // Start MCP server
    await mcpServer.start();
}

// =====================================================
// Main Entry Point
// =====================================================

// Send shutdown request to prior process on same port
async function requestShutdownFromPriorProcess(port) {
    return new Promise((resolve) => {
        try {
            const ws = new WebSocket(`ws://localhost:${port}`);
            const timeout = setTimeout(() => {
                ws.close();
                resolve(false);
            }, 2000);

            ws.on('open', () => {
                ws.send(JSON.stringify({ type: 'shutdown' }));
                clearTimeout(timeout);
                setTimeout(() => {
                    ws.close();
                    resolve(true);
                }, 500);
            });

            ws.on('error', () => {
                clearTimeout(timeout);
                resolve(false);
            });
        } catch (e) {
            resolve(false);
        }
    });
}

// Try to start server with retry logic
function startServerWithRetry(port, maxRetries = 5, retryDelay = 1000) {
    return new Promise((resolve, reject) => {
        let attempt = 0;

        const tryListen = () => {
            attempt++;
            console.error(`[SuperSave] Attempting to listen on port ${port} (attempt ${attempt}/${maxRetries})`);

            const onError = async (err) => {
                server.removeListener('error', onError);

                if (err.code === 'EADDRINUSE') {
                    console.error(`[SuperSave] Port ${port} in use`);

                    // Check if existing server is healthy (another Claude session may be using it)
                    try {
                        const http = require('http');
                        const healthCheck = await new Promise((res, rej) => {
                            const req = http.get(`http://localhost:${port}/health`, { timeout: 2000 }, (resp) => {
                                let data = '';
                                resp.on('data', chunk => data += chunk);
                                resp.on('end', () => res(data));
                            });
                            req.on('error', rej);
                            req.on('timeout', () => { req.destroy(); rej(new Error('timeout')); });
                        });
                        // Existing server is healthy - don't kill it, just skip server binding
                        console.error(`[SuperSave] Existing healthy server found on port ${port} - sharing connection`);
                        resolve();
                        return;
                    } catch (e) {
                        // Existing server is dead - safe to take over
                    }

                    if (attempt === 1) {
                        // First retry: try to shutdown prior process
                        console.error(`[SuperSave] Sending shutdown request to prior process...`);
                        await requestShutdownFromPriorProcess(port);
                    }

                    if (attempt < maxRetries) {
                        console.error(`[SuperSave] Retrying in ${retryDelay}ms...`);
                        setTimeout(tryListen, retryDelay);
                    } else {
                        reject(new Error(`Failed to bind to port ${port} after ${maxRetries} attempts`));
                    }
                } else {
                    reject(err);
                }
            };

            server.once('error', onError);

            server.listen(port, () => {
                server.removeListener('error', onError);
                console.error(`[SuperSave] Token SuperSave Mode started on port ${port}`);
                console.error(`[SuperSave] Only 6 meta-tools loaded (99% context reduction)`);
                resolve();
            });
        };

        tryListen();
    });
}

async function main() {
    const PORT = process.env.PORT || 8090;

    // Start WebSocket server
    wss = new WebSocket.Server({ server });
    setupWebSocketHandlers();

    // Setup and start MCP
    await setupMCPServer();

    // Start HTTP server with retry logic for EADDRINUSE
    await startServerWithRetry(PORT);
}

// Shutdown handler
function shutdownServer() {
    if (unityWebSocket && unityWebSocket.readyState === WebSocket.OPEN) {
        unityWebSocket.close();
    }
    if (wss) {
        wss.close();
    }
    if (server && server.listening) {
        server.close(() => {
            process.exit(0);
        });
    } else {
        process.exit(0);
    }
    setTimeout(() => {
        process.exit(1);
    }, 5000);
}

process.on('SIGINT', shutdownServer);
process.on('SIGTERM', shutdownServer);
process.stdin.on('close', () => {
    shutdownServer();
});

process.on('uncaughtException', (error) => {
    // Log EADDRINUSE and other critical errors
    if (error.code === 'EADDRINUSE') {
        console.error(`[SuperSave] uncaughtException: EADDRINUSE - port already in use`);
    } else {
        console.error(`[SuperSave] uncaughtException: ${error.message}`);
    }
});

process.on('unhandledRejection', (reason, promise) => {
    console.error(`[SuperSave] unhandledRejection: ${reason}`);
});

main().catch(err => {
    console.error('[SuperSave] Fatal error:', err);
    process.exit(1);
});
