/* COPYRIGHT Esri - https://js.arcgis.com/5.1/LICENSE.txt */
import { Emitter as C, languages as u } from "monaco-editor";
import "monaco-editor/esm/vs/editor/standalone/browser/standaloneServices.js";
import "monaco-editor/esm/vs/editor/standalone/common/standaloneTheme.js";
import "monaco-editor/esm/vs/platform/hover/browser/hover.js";
import "monaco-editor/esm/vs/editor/common/languages/supports/tokenization.js";
import "monaco-editor/esm/vs/base/browser/dom.js";
import "monaco-editor/esm/vs/editor/browser/editorDom.js";
import "monaco-editor/esm/vs/editor/browser/controller/mouseTarget.js";
import { i as g } from "./monaco-importer.js";
import { debounce as x } from "@arcgis/toolkit/function";
import { rethrowError as l } from "@arcgis/toolkit/log";
let p = Promise.withResolvers();
class k {
  constructor(e) {
    this._defaults = e, this._worker = null, this._client = null, this._modelLifecycleListeners = [], this._isDisposed = !1, this._configChangeListener = this._defaults.onDidChange(() => this.stopWorker()), this._modelContextChangeListener = this._defaults.onModelContextDidChange(() => {
      this._stopWorkerIfUnused();
    }), this._registerModelLifecycleListeners();
  }
  dispose() {
    for (this._isDisposed = !0, this._configChangeListener.dispose(), this._modelContextChangeListener.dispose(); this._modelLifecycleListeners.length; )
      this._modelLifecycleListeners.pop()?.dispose();
    this.stopWorker();
  }
  stopWorker() {
    this._worker && (this._worker.dispose(), this._worker = null, p = Promise.withResolvers()), this._client = null;
  }
  static async waitForWorker() {
    return await p.promise;
  }
  // TODO: should also do this for arcade and other languages, as it ensures that workers are stopped when not in use.
  async _registerModelLifecycleListeners() {
    const e = await g();
    this._isDisposed || this._modelLifecycleListeners.push(
      e.onWillDisposeModel(() => {
        this._stopWorkerIfUnused();
      }),
      e.onDidChangeModelLanguage(() => {
        this._stopWorkerIfUnused();
      })
    );
  }
  async _stopWorkerIfUnused() {
    if (!this._worker)
      return;
    (await g()).getModels().some((i) => i.getLanguageId() === this._defaults.languageId) || this.stopWorker();
  }
  async _getClientProxy() {
    const e = await g();
    if (!this._client) {
      const { languageId: t } = this._defaults, i = window.MonacoEnvironment?.getWorker;
      if (!i)
        throw new Error(
          "MonacoEnvironment.getWorker is not configured. Call setupMonacoEnvironment(...) before creating SQL Layer workers."
        );
      this._worker = e.createWebWorker({
        worker: i("SqlLayerWorker", t),
        host: this._defaults.workerHost
      }), p.resolve(this._worker), this._client = this._worker.getProxy();
    }
    return await this._client;
  }
  async getLanguageServiceWorker(...e) {
    const t = await this._getClientProxy();
    return await this._worker?.withSyncedResources(e), t;
  }
}
const b = (o) => {
  const e = _(o.start), t = _(o.end);
  return {
    startRegex: new RegExp(e),
    contentRegex: new RegExp(`[^${t}]+`),
    escapedRegex: new RegExp(`${t}${t}`),
    endRegex: new RegExp(t)
  };
}, M = (o) => {
  const e = o.specialChars.identifierQuote;
  return {
    comments: {
      lineComment: "--",
      blockComment: ["/*", "*/"]
    },
    brackets: [
      ["{", "}"],
      ["[", "]"],
      ["(", ")"]
    ],
    autoClosingPairs: [
      { open: "{", close: "}" },
      { open: "[", close: "]" },
      { open: "(", close: ")" },
      { open: '"', close: '"' },
      { open: "'", close: "'" },
      { open: e.start, close: e.end }
    ],
    surroundingPairs: [
      { open: "{", close: "}" },
      { open: "[", close: "]" },
      { open: "(", close: ")" },
      { open: '"', close: '"' },
      { open: "'", close: "'" },
      { open: e.start, close: e.end }
    ]
  };
}, S = (o) => {
  const e = b(o.specialChars.identifierQuote);
  return {
    defaultToken: "",
    tokenPostfix: ".arcgis",
    ignoreCase: !0,
    brackets: [
      { open: "[", close: "]", token: "delimiter.square" },
      { open: "(", close: ")", token: "delimiter.parenthesis" }
    ],
    keywords: o.keywords,
    operators: o.operators,
    builtinFunctions: o.functions,
    builtinVariables: [],
    tokenizer: {
      root: [
        { include: "@comments" },
        { include: "@whitespace" },
        { include: "@numbers" },
        { include: "@strings" },
        { include: "@complexIdentifiers" },
        { include: "@scopes" },
        [/[;,.]/, "delimiter"],
        [/[()]/, "@brackets"],
        [
          /[\w@#$]+/,
          {
            cases: {
              "@operators": "operator",
              "@builtinVariables": "predefined",
              "@builtinFunctions": "predefined",
              "@keywords": "keyword",
              "@default": "identifier"
            }
          }
        ],
        [/[<>=!%&+\-*/|~^]/, "operator"]
      ],
      whitespace: [[/\s+/, "white"]],
      comments: [
        [/--+.*/, "comment"],
        [/\/\*/, { token: "comment.quote", next: "@comment" }]
      ],
      comment: [
        [/[^*/]+/, "comment"],
        [/\*\//, { token: "comment.quote", next: "@pop" }],
        [/./, "comment"]
      ],
      numbers: [
        [/0[xX][0-9a-fA-F]*/, "number"],
        [/[$][+-]*\d*(\.\d*)?/, "number"],
        [/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, "number"]
      ],
      strings: [
        [/N'/, { token: "string", next: "@string" }],
        [/'/, { token: "string", next: "@string" }]
      ],
      string: [
        [/[^']+/, "string"],
        [/''/, "string"],
        [/'/, { token: "string", next: "@pop" }]
      ],
      complexIdentifiers: [[e.startRegex, { token: "identifier.quote", next: "@quotedIdentifier" }]],
      quotedIdentifier: [
        [e.contentRegex, "identifier"],
        [e.escapedRegex, "identifier"],
        [e.endRegex, { token: "identifier.quote", next: "@pop" }]
      ],
      scopes: [
        [/(BEGIN|CASE)\b/i, { token: "keyword.block" }],
        [/END\b/i, { token: "keyword.block" }],
        [/WHEN\b/i, { token: "keyword.choice" }],
        [/THEN\b/i, { token: "keyword.choice" }]
      ]
    }
  };
}, _ = (o) => o.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
class I {
  constructor(e, t) {
    this._worker = e, this._defaults = t;
  }
  async provideCompletionItems(e, t) {
    try {
      const i = await this._worker(e.uri), a = this._defaults.getApiContextForModel(e.uri), r = e.getWordUntilPosition(t), c = {
        startLineNumber: t.lineNumber,
        endLineNumber: t.lineNumber,
        startColumn: r.startColumn,
        endColumn: r.endColumn
      };
      return await i.doComplete(e.uri.toString(), c, t, a);
    } catch (i) {
      return l("SqlLayerCompletionProvider")(i), { suggestions: [] };
    }
  }
}
class D {
  constructor(e, t, {
    defaults: i,
    diagnosticService: a
  }) {
    this._languageId = e, this._worker = t, this._disposables = [], this._modelListeners = /* @__PURE__ */ new Map(), this._defaults = i, this._diagnosticsService = a, g().then((r) => {
      const c = (s) => {
        const n = s.getLanguageId();
        if (n !== this._languageId)
          return;
        const d = x(() => {
          this._doValidate(s, n).catch((y) => {
            throw y;
          });
        }), f = s.onDidChangeContent(d), L = s.onDidChangeAttached(d);
        this._modelListeners.set(s.uri.toString(), [f, L]), this._doValidate(s, n).catch(l("SqlLayerDiagnosticsAdapter"));
      }, h = (s) => {
        const n = s.uri.toString();
        r.setModelMarkers(s, this._languageId, []);
        const d = this._modelListeners.get(n);
        if (d) {
          for (; d.length; )
            d.pop()?.dispose();
          this._modelListeners.delete(n);
        }
      };
      this._disposables.push(r.onDidCreateModel(c)), this._disposables.push(r.onWillDisposeModel((s) => h(s))), this._disposables.push(
        r.onDidChangeModelLanguage((s) => {
          h(s.model), c(s.model);
        })
      ), this._disposables.push(
        i.onDidChange(() => {
          r.getModels().forEach((s) => {
            s.getLanguageId() === this._languageId && (h(s), c(s));
          });
        })
      ), this._disposables.push(
        i.onModelContextDidChange((s) => {
          r.getModels().forEach((n) => {
            n.getLanguageId() === this._languageId && n.uri.toString() === s && this._doValidate(n, this._languageId).catch(l("SqlLayerDiagnosticsAdapter"));
          });
        })
      ), this._disposables.push({
        dispose: () => {
          this._modelListeners.forEach((s) => s.forEach((n) => n.dispose())), this._modelListeners.clear();
        }
      }), r.getModels().forEach(c);
    });
  }
  async _doValidate(e, t) {
    const i = await g();
    if (e.isAttachedToEditor())
      try {
        const a = await this._worker(e.uri), r = this._defaults.getApiContextForModel(e.uri), c = await a.doValidation(e.uri.toString(), r);
        this._diagnosticsService.fireDiagnosticsChange(e.uri, c), i.setModelMarkers(e, t, c);
      } catch (a) {
        l("SqlLayerDiagnosticsAdapter")(a);
      }
  }
}
let w;
async function U(...o) {
  return await k.waitForWorker(), await new Promise((e, t) => {
    if (!w) {
      t(new Error("SQL layer worker not registered!"));
      return;
    }
    e(w(...o));
  });
}
class v {
  constructor() {
    this._onDiagnosticsChange = new C();
  }
  get onDiagnosticsChange() {
    return this._onDiagnosticsChange.event;
  }
  fireDiagnosticsChange(e, t) {
    this._onDiagnosticsChange.fire({ uri: e, diagnostics: t });
  }
}
const m = new v();
function T() {
  return m;
}
async function H(o) {
  const e = new k(o), t = async (...a) => await e.getLanguageServiceWorker(...a);
  w = t;
  const i = await o.getDialectSpec();
  u.setMonarchTokensProvider(o.languageId, S(i)), u.setLanguageConfiguration(o.languageId, M(i)), u.registerCompletionItemProvider(
    o.languageId,
    new I(t, o)
  ), new D(o.languageId, t, {
    defaults: o,
    diagnosticService: m
  });
}
export {
  T as getSqlLayerDiagnosticService,
  U as getSqlLayerWorker,
  H as setupMode
};
