import {
    createConnection,
    TextDocuments,
    Diagnostic,
    DiagnosticSeverity,
    ProposedFeatures,
    InitializeParams,
    InitializeResult,
    TextDocumentSyncKind,
    Location,
    Range,
    Position,
    DefinitionParams
} from 'vscode-languageserver/node';

import { TextDocument } from 'vscode-languageserver-textdocument';
import { EftlTokenizer, TokenType } from './tokenizer';
import { EftlParser } from './parser';

// Create a connection for the server
const connection = createConnection(ProposedFeatures.all);

// Create a document manager
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);

// Store variable definitions per document
interface VariableDefinition {
    name: string;
    line: number;
    column: number;
    length: number;
}

const documentVariables: Map<string, VariableDefinition[]> = new Map();

connection.onInitialize((params: InitializeParams): InitializeResult => {
    return {
        capabilities: {
            textDocumentSync: TextDocumentSyncKind.Incremental,
            definitionProvider: true
        }
    };
});

// Handle "Go to Definition" requests
connection.onDefinition((params: DefinitionParams): Location | null => {
    const document = documents.get(params.textDocument.uri);
    if (!document) {
        return null;
    }

    const text = document.getText();
    const offset = document.offsetAt(params.position);

    // Find the word at the current position
    const wordRange = getWordRangeAtPosition(text, offset);
    if (!wordRange) {
        return null;
    }

    const word = text.substring(wordRange.start, wordRange.end);

    // Look for variable definition
    const variables = documentVariables.get(params.textDocument.uri) || [];
    const variable = variables.find(v => v.name === word);

    if (variable) {
        return {
            uri: params.textDocument.uri,
            range: {
                start: { line: variable.line - 1, character: variable.column - 1 },
                end: { line: variable.line - 1, character: variable.column - 1 + variable.length }
            }
        };
    }

    return null;
});

function getWordRangeAtPosition(text: string, offset: number): { start: number; end: number } | null {
    // Find word boundaries (alphanumeric and underscore)
    let start = offset;
    let end = offset;

    // Move start backwards
    while (start > 0 && /[a-zA-Z0-9_]/.test(text[start - 1])) {
        start--;
    }

    // Move end forwards
    while (end < text.length && /[a-zA-Z0-9_]/.test(text[end])) {
        end++;
    }

    if (start === end) {
        return null;
    }

    return { start, end };
}

// Validate documents when they change
documents.onDidChangeContent(change => {
    validateDocument(change.document);
});

async function validateDocument(textDocument: TextDocument): Promise<void> {
    const text = textDocument.getText();
    const diagnostics: Diagnostic[] = [];

    // Tokenize
    const tokenizer = new EftlTokenizer(text);
    const { tokens, errors: tokenErrors } = tokenizer.tokenize();

    // Valid types for VAR
    const validTypes = ['string', 'boolean', 'number', 'date', 'object', 'iterable'];

    // Extract variable definitions and validate types
    const variables: VariableDefinition[] = [];
    for (const token of tokens) {
        if (token.type === TokenType.VAR_OPEN) {
            // Parse name="varName" from the token value
            const nameMatch = token.value.match(/name="([^"]+)"/);
            if (nameMatch) {
                variables.push({
                    name: nameMatch[1],
                    line: token.line,
                    column: token.column,
                    length: token.length
                });
            }

            // Validate type attribute
            const typeMatch = token.value.match(/type="([^"]*)"/);
            if (typeMatch) {
                const typeValue = typeMatch[1];
                if (!validTypes.includes(typeValue)) {
                    // Find position of the type value in the token
                    const typeIndex = token.value.indexOf(`type="${typeValue}"`);
                    const typeValueStart = typeIndex + 6; // 'type="'.length
                    diagnostics.push({
                        severity: DiagnosticSeverity.Error,
                        range: {
                            start: { line: token.line - 1, character: token.column - 1 + typeValueStart },
                            end: { line: token.line - 1, character: token.column - 1 + typeValueStart + typeValue.length }
                        },
                        message: `Invalid type "${typeValue}". Must be one of: ${validTypes.join(', ')}`,
                        source: 'eftl'
                    });
                }
            }
        }
    }
    documentVariables.set(textDocument.uri, variables);

    // Add tokenizer errors
    for (const error of tokenErrors) {
        diagnostics.push({
            severity: DiagnosticSeverity.Error,
            range: {
                start: { line: error.line - 1, character: error.column - 1 },
                end: { line: error.line - 1, character: error.column - 1 + error.length }
            },
            message: error.message,
            source: 'eftl'
        });
    }

    // Parse
    const parser = new EftlParser(tokens);
    const { errors: parseErrors } = parser.parse();

    // Add parser errors
    for (const error of parseErrors) {
        diagnostics.push({
            severity: DiagnosticSeverity.Error,
            range: {
                start: { line: error.line - 1, character: error.column - 1 },
                end: { line: error.line - 1, character: error.column - 1 + error.length }
            },
            message: error.message,
            source: 'eftl'
        });
    }

    // Send diagnostics to VS Code
    connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}

// Clean up when documents are closed
documents.onDidClose(e => {
    documentVariables.delete(e.document.uri);
});

// Make the text document manager listen on the connection
documents.listen(connection);

// Listen on the connection
connection.listen();
