/**
 * LSP 客户端
 * 负责与语言服务器通信
 */

import { spawn, ChildProcess } from 'child_process';
import {
  createMessageConnection,
  StreamMessageReader,
  StreamMessageWriter,
  MessageConnection,
} from 'vscode-jsonrpc/node.js';
import {
  InitializeParams,
  Diagnostic,
  DiagnosticSeverity,
  PublishDiagnosticsParams,
} from 'vscode-languageserver-protocol';
import * as fs from 'fs/promises';
import * as path from 'path';
import { getLanguageId, type LanguageId } from './language.js';
import { getServerConfig, type ServerConfig } from './server.js';

// 诊断信息接口
export interface FileDiagnostic {
  file: string;
  line: number;
  column: number;
  endLine?: number;
  endColumn?: number;
  severity: 'error' | 'warning' | 'info' | 'hint';
  message: string;
  source?: string;
  code?: string | number;
}

// 客户端状态
interface ClientState {
  process: ChildProcess;
  connection: MessageConnection;
  languageId: LanguageId;
  config: ServerConfig;
  initialized: boolean;
  openDocuments: Set<string>;
  documentVersions: Map<string, number>;
  diagnostics: Map<string, Diagnostic[]>;
  rootUri: string;
}

/**
 * LSP 客户端管理器
 * 管理多个语言服务器的生命周期
 */
export class LSPClientManager {
  private clients: Map<LanguageId, ClientState> = new Map();
  private rootPath: string;

  constructor(rootPath?: string) {
    this.rootPath = rootPath || process.cwd();
  }

  /**
   * 设置工作区根目录
   */
  setRootPath(rootPath: string): void {
    this.rootPath = rootPath;
  }

  /**
   * 获取或启动语言服务器
   */
  async getClient(languageId: LanguageId): Promise<ClientState | undefined> {
    // 如果已有客户端，直接返回
    if (this.clients.has(languageId)) {
      return this.clients.get(languageId);
    }

    // 获取服务器配置
    const config = getServerConfig(languageId);
    if (!config) {
      return undefined;
    }

    // 启动语言服务器
    try {
      const client = await this.startServer(languageId, config);
      this.clients.set(languageId, client);
      return client;
    } catch {
      // 静默忽略启动失败（如语言服务器未安装）
      return undefined;
    }
  }

  /**
   * 启动语言服务器
   */
  private async startServer(languageId: LanguageId, config: ServerConfig): Promise<ClientState> {
    // 检查命令是否可用
    const commandExists = await this.checkCommand(config.command);
    if (!commandExists) {
      throw new Error(`语言服务器命令不存在: ${config.command}`);
    }

    // 启动进程
    const serverProcess = spawn(config.command, config.args, {
      env: { ...process.env, ...config.env },
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    if (!serverProcess.stdin || !serverProcess.stdout) {
      throw new Error('无法创建进程管道');
    }

    // 创建 JSON-RPC 连接
    const connection = createMessageConnection(
      new StreamMessageReader(serverProcess.stdout),
      new StreamMessageWriter(serverProcess.stdin)
    );

    const rootUri = `file://${this.rootPath}`;

    const state: ClientState = {
      process: serverProcess,
      connection,
      languageId,
      config,
      initialized: false,
      openDocuments: new Set(),
      documentVersions: new Map(),
      diagnostics: new Map(),
      rootUri,
    };

    // 监听诊断通知（使用字符串方法名）
    connection.onNotification('textDocument/publishDiagnostics', (params: PublishDiagnosticsParams) => {
      const filePath = params.uri.replace('file://', '');
      state.diagnostics.set(filePath, params.diagnostics);
    });

    // 监听进程错误
    serverProcess.on('error', (error) => {
      console.error(`语言服务器错误 (${languageId}):`, error);
    });

    serverProcess.on('exit', () => {
      this.clients.delete(languageId);
    });

    // 启动连接
    connection.listen();

    // 初始化服务器
    await this.initializeServer(state, config);

    return state;
  }

  /**
   * 初始化语言服务器（带超时）
   */
  private async initializeServer(state: ClientState, config: ServerConfig): Promise<void> {
    const initParams: InitializeParams = {
      processId: process.pid,
      rootUri: state.rootUri,
      capabilities: {
        textDocument: {
          synchronization: {
            dynamicRegistration: false,
            willSave: false,
            willSaveWaitUntil: false,
            didSave: true,
          },
          publishDiagnostics: {
            relatedInformation: true,
            tagSupport: { valueSet: [1, 2] },
          },
        },
        workspace: {
          workspaceFolders: true,
        },
      },
      workspaceFolders: [
        {
          uri: state.rootUri,
          name: path.basename(this.rootPath),
        },
      ],
      initializationOptions: config.initializationOptions,
    };

    // 使用字符串方法名发送请求，添加超时保护
    const initPromise = state.connection.sendRequest('initialize', initParams);
    const timeoutPromise = new Promise<never>((_, reject) => {
      setTimeout(() => reject(new Error('LSP 初始化超时（10秒）')), 10000);
    });

    await Promise.race([initPromise, timeoutPromise]);
    await state.connection.sendNotification('initialized', {});
    state.initialized = true;
  }

  /**
   * 通知文件变更（打开或更新）
   * @returns 是否是首次启动服务器（需要更长等待时间）
   */
  async touchFile(filePath: string, isNew: boolean = false): Promise<boolean> {
    const absolutePath = path.isAbsolute(filePath)
      ? filePath
      : path.join(this.rootPath, filePath);

    const languageId = getLanguageId(absolutePath);
    if (!languageId) {
      return false;
    }

    // 检查是否是首次启动
    const wasRunning = this.clients.has(languageId);

    const client = await this.getClient(languageId);
    if (!client || !client.initialized) {
      return false;
    }

    const isFirstStart = !wasRunning;

    const uri = `file://${absolutePath}`;
    const content = await fs.readFile(absolutePath, 'utf-8');

    if (!client.openDocuments.has(absolutePath)) {
      // 打开文档（使用字符串方法名）
      await client.connection.sendNotification('textDocument/didOpen', {
        textDocument: {
          uri,
          languageId,
          version: 1,
          text: content,
        },
      });
      client.openDocuments.add(absolutePath);
      client.documentVersions.set(absolutePath, 1);
    } else {
      // 更新文档
      const version = (client.documentVersions.get(absolutePath) || 0) + 1;
      client.documentVersions.set(absolutePath, version);

      await client.connection.sendNotification('textDocument/didChange', {
        textDocument: {
          uri,
          version,
        },
        contentChanges: [{ text: content }],
      });
    }

    // 等待诊断结果（给服务器一些时间处理）
    await this.waitForDiagnostics(100);

    return isFirstStart;
  }

  /**
   * 关闭文档
   */
  async closeFile(filePath: string): Promise<void> {
    const absolutePath = path.isAbsolute(filePath)
      ? filePath
      : path.join(this.rootPath, filePath);

    const languageId = getLanguageId(absolutePath);
    if (!languageId) {
      return;
    }

    const client = this.clients.get(languageId);
    if (!client || !client.openDocuments.has(absolutePath)) {
      return;
    }

    const uri = `file://${absolutePath}`;
    await client.connection.sendNotification('textDocument/didClose', {
      textDocument: { uri },
    });

    client.openDocuments.delete(absolutePath);
    client.documentVersions.delete(absolutePath);
    client.diagnostics.delete(absolutePath);
  }

  /**
   * 获取文件诊断信息
   */
  getDiagnostics(filePath?: string): Map<string, FileDiagnostic[]> {
    const result = new Map<string, FileDiagnostic[]>();

    for (const client of this.clients.values()) {
      for (const [file, diagnostics] of client.diagnostics) {
        if (filePath && file !== filePath) {
          continue;
        }

        const fileDiagnostics = diagnostics.map((d) => this.convertDiagnostic(file, d));
        if (fileDiagnostics.length > 0) {
          const existing = result.get(file) || [];
          result.set(file, [...existing, ...fileDiagnostics]);
        }
      }
    }

    return result;
  }

  /**
   * 获取单个文件的诊断信息
   */
  getFileDiagnostics(filePath: string): FileDiagnostic[] {
    const absolutePath = path.isAbsolute(filePath)
      ? filePath
      : path.join(this.rootPath, filePath);

    for (const client of this.clients.values()) {
      const diagnostics = client.diagnostics.get(absolutePath);
      if (diagnostics) {
        return diagnostics.map((d) => this.convertDiagnostic(absolutePath, d));
      }
    }

    return [];
  }

  /**
   * 转换诊断信息格式
   */
  private convertDiagnostic(file: string, diagnostic: Diagnostic): FileDiagnostic {
    return {
      file,
      line: diagnostic.range.start.line + 1,
      column: diagnostic.range.start.character + 1,
      endLine: diagnostic.range.end.line + 1,
      endColumn: diagnostic.range.end.character + 1,
      severity: this.convertSeverity(diagnostic.severity),
      message: diagnostic.message,
      source: diagnostic.source,
      code: diagnostic.code as string | number | undefined,
    };
  }

  /**
   * 转换严重性
   */
  private convertSeverity(severity?: DiagnosticSeverity): FileDiagnostic['severity'] {
    switch (severity) {
      case DiagnosticSeverity.Error:
        return 'error';
      case DiagnosticSeverity.Warning:
        return 'warning';
      case DiagnosticSeverity.Information:
        return 'info';
      case DiagnosticSeverity.Hint:
        return 'hint';
      default:
        return 'error';
    }
  }

  /**
   * 检查命令是否存在
   */
  private async checkCommand(command: string): Promise<boolean> {
    try {
      const { execSync } = await import('child_process');
      execSync(`which ${command}`, { stdio: 'ignore' });
      return true;
    } catch {
      return false;
    }
  }

  /**
   * 等待诊断结果
   */
  private waitForDiagnostics(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /**
   * 关闭所有客户端
   */
  async shutdown(): Promise<void> {
    for (const [languageId, client] of this.clients) {
      try {
        client.connection.dispose();
        client.process.kill();
      } catch (error) {
        console.error(`关闭语言服务器失败 (${languageId}):`, error);
      }
    }
    this.clients.clear();
  }

  /**
   * 检查服务器是否运行中
   */
  isServerRunning(languageId: LanguageId): boolean {
    return this.clients.has(languageId);
  }

  /**
   * 获取运行中的服务器列表
   */
  getRunningServers(): LanguageId[] {
    return Array.from(this.clients.keys());
  }
}
