import * as vscode from 'vscode';
import * as path from 'path';
import { spawn, ChildProcess } from 'child_process';
import {
    LanguageClient,
    LanguageClientOptions,
    ServerOptions,
} from 'vscode-languageclient/node';

let client: LanguageClient | undefined;
let replTerminal: vscode.Terminal | undefined;

export function activate(context: vscode.ExtensionContext) {
    console.log('Lux extension activated');

    // Start LSP if enabled
    const config = vscode.workspace.getConfiguration('lux');
    if (config.get('lsp.enabled', true)) {
        startLspClient(context);
    }

    // Register commands
    context.subscriptions.push(
        vscode.commands.registerCommand('lux.run', runCurrentFile),
        vscode.commands.registerCommand('lux.format', formatDocument),
        vscode.commands.registerCommand('lux.check', checkFile),
        vscode.commands.registerCommand('lux.startRepl', startRepl),
        vscode.commands.registerCommand('lux.sendToRepl', sendToRepl),
        vscode.commands.registerCommand('lux.restartLsp', () => restartLspClient(context))
    );

    // Format on save if enabled
    if (config.get('format.onSave', false)) {
        context.subscriptions.push(
            vscode.workspace.onWillSaveTextDocument((event) => {
                if (event.document.languageId === 'lux') {
                    event.waitUntil(formatDocumentAsync(event.document));
                }
            })
        );
    }
}

export function deactivate(): Thenable<void> | undefined {
    if (replTerminal) {
        replTerminal.dispose();
    }
    if (client) {
        return client.stop();
    }
    return undefined;
}

function getLuxBinary(): string {
    const config = vscode.workspace.getConfiguration('lux');
    const customPath = config.get<string>('lspPath');

    if (customPath && customPath.trim() !== '') {
        return customPath;
    }

    // Try common locations
    const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
    const possiblePaths = [
        workspaceFolder ? path.join(workspaceFolder, 'result', 'bin', 'lux') : null,
        workspaceFolder ? path.join(workspaceFolder, 'target', 'release', 'lux') : null,
        workspaceFolder ? path.join(workspaceFolder, 'target', 'debug', 'lux') : null,
    ].filter(Boolean) as string[];

    // Check if any exist
    for (const p of possiblePaths) {
        try {
            require('fs').accessSync(p, require('fs').constants.X_OK);
            return p;
        } catch {
            // Continue to next
        }
    }

    // Fall back to PATH
    return 'lux';
}

function startLspClient(context: vscode.ExtensionContext) {
    const luxBinary = getLuxBinary();

    const serverOptions: ServerOptions = {
        command: luxBinary,
        args: ['lsp'],
    };

    const clientOptions: LanguageClientOptions = {
        documentSelector: [{ scheme: 'file', language: 'lux' }],
        synchronize: {
            fileEvents: vscode.workspace.createFileSystemWatcher('**/*.lux'),
        },
    };

    client = new LanguageClient(
        'luxLanguageServer',
        'Lux Language Server',
        serverOptions,
        clientOptions
    );

    client.start();
}

async function restartLspClient(context: vscode.ExtensionContext) {
    if (client) {
        await client.stop();
    }
    startLspClient(context);
    vscode.window.showInformationMessage('Lux language server restarted');
}

async function runCurrentFile() {
    const editor = vscode.window.activeTextEditor;
    if (!editor || editor.document.languageId !== 'lux') {
        vscode.window.showErrorMessage('No Lux file is active');
        return;
    }

    // Save the file first
    await editor.document.save();

    const filePath = editor.document.uri.fsPath;
    const luxBinary = getLuxBinary();

    // Create terminal and run
    const terminal = vscode.window.createTerminal('Lux Run');
    terminal.show();
    terminal.sendText(`${luxBinary} "${filePath}"`);
}

async function formatDocument() {
    const editor = vscode.window.activeTextEditor;
    if (!editor || editor.document.languageId !== 'lux') {
        vscode.window.showErrorMessage('No Lux file is active');
        return;
    }

    await editor.document.save();
    const formatted = await formatDocumentAsync(editor.document);

    if (formatted.length > 0) {
        const edit = new vscode.WorkspaceEdit();
        edit.set(editor.document.uri, formatted);
        await vscode.workspace.applyEdit(edit);
    }
}

async function formatDocumentAsync(document: vscode.TextDocument): Promise<vscode.TextEdit[]> {
    const luxBinary = getLuxBinary();
    const filePath = document.uri.fsPath;

    return new Promise((resolve) => {
        const process = spawn(luxBinary, ['fmt', filePath]);
        let stdout = '';
        let stderr = '';

        process.stdout.on('data', (data) => { stdout += data; });
        process.stderr.on('data', (data) => { stderr += data; });

        process.on('close', (code) => {
            if (code === 0) {
                // File was formatted, need to reload
                resolve([]);
            } else {
                vscode.window.showErrorMessage(`Format error: ${stderr}`);
                resolve([]);
            }
        });
    });
}

async function checkFile() {
    const editor = vscode.window.activeTextEditor;
    if (!editor || editor.document.languageId !== 'lux') {
        vscode.window.showErrorMessage('No Lux file is active');
        return;
    }

    await editor.document.save();
    const filePath = editor.document.uri.fsPath;
    const luxBinary = getLuxBinary();

    const process = spawn(luxBinary, ['check', filePath]);
    let stderr = '';

    process.stderr.on('data', (data) => { stderr += data; });

    process.on('close', (code) => {
        if (code === 0) {
            vscode.window.showInformationMessage('No type errors found');
        } else {
            vscode.window.showErrorMessage(`Type check failed:\n${stderr}`);
        }
    });
}

function startRepl() {
    if (replTerminal) {
        replTerminal.dispose();
    }

    const luxBinary = getLuxBinary();
    replTerminal = vscode.window.createTerminal({
        name: 'Lux REPL',
        shellPath: luxBinary,
    });
    replTerminal.show();
}

async function sendToRepl() {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        vscode.window.showErrorMessage('No active editor');
        return;
    }

    // Get selection or current line
    let text: string;
    if (editor.selection.isEmpty) {
        text = editor.document.lineAt(editor.selection.active.line).text;
    } else {
        text = editor.document.getText(editor.selection);
    }

    // Start REPL if not running
    if (!replTerminal) {
        startRepl();
        // Wait a bit for REPL to start
        await new Promise(resolve => setTimeout(resolve, 500));
    }

    if (replTerminal) {
        replTerminal.sendText(text);
        replTerminal.show();
    }
}
