/**
 * Wraps newt.js (compiled from src/LSP.newt with some tweaks to `export`) with the
 * vscode LSP server module.
 */

import { LSP_checkFile, LSP_updateFile, LSP_hoverInfo, LSP_codeActionInfo, LSP_docSymbols } from './newt.js'

import {
  createConnection,
  TextDocuments,
  ProposedFeatures,
  Hover,
  InitializeParams,
  InitializeResult,
  TextDocumentSyncKind,
  Location,
  TextDocumentIdentifier,
} from "vscode-languageserver/node";
import fs from 'node:fs';
import path from 'node:path';
import { TextDocument } from "vscode-languageserver-textdocument";

const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);

// the last is the most important to the user, but we run FIFO
// to ensure dependencies are seen in causal order
let changes: (TextDocument|TextDocumentIdentifier)[] = []
let running = false
let lastChange = 0
function addChange(doc: TextDocument | TextDocumentIdentifier) {
  console.log('enqueue', doc.uri)
  // drop stale pending changes
  let before = changes.length
  changes = changes.filter(ch => ch.uri != doc.uri)
  console.log('DROPPED', before - changes.length);
  changes.push(doc)
  lastChange = +new Date()
  if (!running) runChange()
}

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

async function runChange() {
  try {
    running = true;
    while (changes.length) {
      console.log('LOOP TOP')
      // Wait until things stop changing
      let prev = lastChange
      while (1) {
        await sleep(100)
        if (prev == lastChange) break
        prev = lastChange
        console.log('DELAY')
      }
      let doc = changes.shift()
      if (!doc) {
        running = false
        return
      }
      const uri = doc.uri
      const start = +new Date()
      const diagnostics = LSP_checkFile(doc.uri)
      const end = +new Date()
      console.log('CHECK', doc.uri, 'in', end - start);
      await sleep(1);
      if (!changes.find(ch => ch.uri === uri)) {
        console.log('SEND', diagnostics.length, 'for', uri)
        connection.sendDiagnostics({ uri, diagnostics })
      } else {
        console.log('STALE result not sent for', uri)
      }
    }
  } catch (e) {
    console.error(e);
  } finally {
    running = false;
  }
}
documents.onDidChangeContent(async (change) => {
  console.log('DIDCHANGE', change.document.uri)
  const uri = change.document.uri;
  const text = change.document.getText();
  // update/invalidate happens now, check happens on quiesce.
  writeCache(path.basename(uri), text);
  LSP_updateFile(uri, text);
  addChange(change.document);
});

connection.onHover((params): Hover | null => {
  // wait until quiesced (REVIEW after query-based)
  if (running) return null

  const uri = params.textDocument.uri;
  const pos = params.position;
  console.log('HOVER', uri, pos)
  let res = LSP_hoverInfo(uri, pos.line, pos.character)
  if (!res) return null
  if (res == true) {
    addChange(params.textDocument)
    return null
  } else {
    console.log('HOVER is ', res)
    return { contents: { kind: "plaintext", value: res.info } };
  }
});

connection.onDefinition((params): Location | null => {
  const uri = params.textDocument.uri;
  const pos = params.position;
  let value = LSP_hoverInfo(uri, pos.line, pos.character)
  if (!value || value == true) return null;
  return value.location
})

connection.onCodeAction(({textDocument, range}) => {
  let actions = LSP_codeActionInfo(textDocument.uri, range.start.line, range.start.character);
  console.log('ACTIONS is ', JSON.stringify(actions,null,'  '))
  return actions
})

connection.onDocumentSymbol((params) => {
  try {

  const uri = params.textDocument.uri;
  let symbols = LSP_docSymbols(uri);
  console.log("docs got", symbols)
  return symbols;
  } catch (e) {
    console.error('ERROR in onDocumentSymbol', e);
  }
})

connection.onInitialize((_params: InitializeParams): InitializeResult => ({
  capabilities: {
    textDocumentSync: TextDocumentSyncKind.Incremental,
    hoverProvider: true,
    definitionProvider: true,
    codeActionProvider: true,
    documentSymbolProvider: true,
  },
}));

function writeCache(fn: string, content: string) {
  const home = process.env.HOME;
  if (!home) return;
  const dname = path.join(home, '.cache/newt-lsp');
  const fname = path.join(dname, fn);
  try {
    fs.mkdirSync(dname, {recursive: true});
  } catch (e) {
  }
  try {
    fs.writeFileSync(fname, content, 'utf8');
  } catch (e) {
    console.error(e);
  }
}
documents.listen(connection);
connection.listen();
console.log('STARTED')
