/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */
import { Range as _, languages as l, MarkerSeverity as p, Emitter as M } 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 { ArcadeKeywords as L } from "@arcgis/arcade-languageservice";
import { i as w } from "./monaco-importer.js";
import { InsertTextFormat as y, CompletionItemKind as i, DiagnosticSeverity as m } from "vscode-languageserver-types";
import { debounce as A } from "@arcgis/toolkit/function";
import { rethrowError as f } from "@arcgis/toolkit/log";
const F = [
  "<=",
  ">=",
  "==",
  "!=",
  "+",
  "-",
  "*",
  "/",
  "%",
  "++",
  "--",
  "<<",
  ">>",
  ">>>",
  "&",
  "|",
  "^",
  "!",
  "~",
  "&&",
  "||",
  "=",
  "+=",
  "-=",
  "*=",
  "**=",
  "/=",
  "%="
], S = {
  // the default separators except `@$`
  wordPattern: /(-?\d*\.\d\w*)|([^`~!#%\^&\*\(\)\-=\+\[\{\]\}\\\|;:'",\.<>\/\?\s]+)/gu,
  comments: {
    lineComment: "//",
    blockComment: ["/*", "*/"]
  },
  brackets: [
    ["{", "}"],
    ["[", "]"],
    ["(", ")"]
  ],
  autoClosingPairs: [
    { open: "{", close: "}" },
    { open: "[", close: "]" },
    { open: "(", close: ")" },
    { open: '"', close: '"', notIn: ["string"] },
    { open: "'", close: "'", notIn: ["string", "comment"] },
    { open: "`", close: "`", notIn: ["string", "comment"] }
  ],
  autoCloseBefore: ";:.,=}])` \n	",
  folding: {
    markers: {
      start: /^\s*\/\/\s*#?region\b/u,
      end: /^\s*\/\/\s*#?endregion\b/u
    }
  },
  indentationRules: {
    // ^(.*\*/)?\s*\}.*$
    decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/u,
    // ^.*\{[^}"']*$
    increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/u
  }
}, T = {
  // Set defaultToken to invalid to see what you do not tokenize yet
  defaultToken: "invalid",
  tokenPostfix: ".arcgis",
  // Arcade is case insensitive
  ignoreCase: !0,
  // builtinFunctions: [...arcadeService.FunctionNames],
  // Arcade keywords. 'from' is a special case as we want to treat it as a keyword in
  // import statement but as an identifier in var statement.
  keywords: L,
  operators: F,
  constants: ["true", "false", "null"],
  // we include these common regular expressions
  symbols: /[=><!~?:&|+\-*\/\^%]+/u,
  escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/u,
  digits: /\d+(_+\d+)*/u,
  octaldigits: /[0-7]+(_+[0-7]+)*/u,
  binarydigits: /[0-1]+(_+[0-1]+)*/u,
  hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/u,
  regexpctl: /[(){}\[\]\$\^|\-*+?\.]/u,
  regexpesc: /\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/u,
  // The main tokenizer for our languages
  tokenizer: {
    root: [[/[{}]/u, "delimiter.bracket"], { include: "common" }],
    common: [
      // import statement. import followed by a white space
      [/import(?=\s)/u, { token: "keyword", next: "@import" }],
      // Special handling for `for` keyword
      [/for(?=\s)/u, { token: "keyword", next: "@forLoopInit" }],
      // identifiers and keywords
      [
        /[a-z_$][\w$]*/u,
        {
          cases: {
            "@constants": "constant",
            "@keywords": "keyword",
            "@default": "identifier"
          }
        }
      ],
      // whitespace
      { include: "@whitespace" },
      // regular expression: ensure it is terminated before beginning (otherwise it is an operator)
      [
        /\/(?=([^\\\/]|\\.)+\/([gimsuy]*)(\s*)(\.|;|\/|,|\)|\]|\}|$))/u,
        { token: "regexp", bracket: "@open", next: "@regexp" }
      ],
      // delimiters and operators
      [/[()\[\]]/u, "@brackets"],
      [/[<>](?!@symbols)/u, "@brackets"],
      [
        /@symbols/u,
        {
          cases: {
            "@operators": "delimiter",
            "@default": ""
          }
        }
      ],
      // numbers
      [/(@digits)[eE]([\-+]?(@digits))?/u, "number"],
      [/(@digits)\.(@digits)([eE][\-+]?(@digits))?/u, "number"],
      [/0[xX](@hexdigits)/u, "number"],
      [/0[oO]?(@octaldigits)/u, "number"],
      [/0[bB](@binarydigits)/u, "number"],
      [/(@digits)/u, "number"],
      // delimiter: after number because of .\d floats
      [/[;,.]/u, "delimiter"],
      // strings
      [/"([^"\\]|\\.)*$/u, "string.invalid"],
      // non-terminated string
      [/'([^'\\]|\\.)*$/u, "string.invalid"],
      // non-terminated string
      [/"/u, "string", "@string_double"],
      [/'/u, "string", "@string_single"],
      [/`/u, "string", "@string_backtick"]
    ],
    import: [
      // import keyword
      [/import(?=\s)/u, { token: "keyword" }],
      // whitespace
      { include: "@whitespace" },
      // identifier
      [/[a-z_$][\w$]*/u, "identifier"],
      // whitespace
      { include: "@whitespace" },
      // from keyword
      [/from/u, { token: "keyword", next: "@popall" }]
    ],
    // State after `for` keyword: expects '('
    forLoopInit: [
      { include: "@whitespace" },
      [/\(/u, { token: "@brackets", next: "@forLoopVar" }],
      // If we don't get an '(', just pop back
      [/.*/u, "", "@pop"]
    ],
    // In a for loop after '(' we expect either var/let/const or directly an identifier
    forLoopVar: [
      { include: "@whitespace" },
      [/var(?=\s)/u, "keyword", "@forLoopVarName"],
      [/[a-z_$][\w$]*/u, "identifier", "@forLoopCheckInOf"],
      [/\)/u, { token: "@brackets", next: "@popall" }]
      // close the for(...)
    ],
    forLoopVarName: [
      { include: "@whitespace" },
      // The next thing after var/let/const should be an identifier
      [/[a-z_$][\w$]*/u, "identifier", "@forLoopCheckInOf"],
      [/\)/u, { token: "@brackets", next: "@popall" }]
    ],
    // After we have a variable name, we check if we have `in` or `of`
    forLoopCheckInOf: [
      { include: "@whitespace" },
      [/in(?=\s)/u, "keyword", "@popall"],
      // for-in
      [/of(?=\s)/u, "keyword", "@popall"],
      // for-of is highlighted here
      // If something else occurs, just highlight normally
      [/[a-z_$][\w$]*/u, "identifier"],
      [/\)/u, { token: "@brackets", next: "@popall" }]
    ],
    whitespace: [
      [/[ \t\r\n]+/u, ""],
      [/\/\*/u, "comment", "@comment"],
      [/\/\/.*$/u, "comment"]
    ],
    comment: [
      [/[^\/*]+/u, "comment"],
      [/\*\//u, "comment", "@pop"],
      [/[\/*]/u, "comment"]
    ],
    // We match regular expression quite precisely
    regexp: [
      [/(\{)(\d+(?:,\d*)?)(\})/u, ["regexp.escape.control", "regexp.escape.control", "regexp.escape.control"]],
      [
        /(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/u,
        ["regexp.escape.control", { token: "regexp.escape.control", next: "@regexrange" }]
      ],
      [/(\()(\?:|\?=|\?!)/u, ["regexp.escape.control", "regexp.escape.control"]],
      [/[()]/u, "regexp.escape.control"],
      [/@regexpctl/u, "regexp.escape.control"],
      [/[^\\\/]/u, "regexp"],
      [/@regexpesc/u, "regexp.escape"],
      [/\\\./u, "regexp.invalid"],
      [/(\/)([gimsuy]*)/u, [{ token: "regexp", bracket: "@close", next: "@pop" }, "keyword.other"]]
    ],
    regexrange: [
      [/-/u, "regexp.escape.control"],
      [/\^/u, "regexp.invalid"],
      [/@regexpesc/u, "regexp.escape"],
      [/[^\]]/u, "regexp"],
      [/\]/u, { token: "regexp.escape.control", next: "@pop", bracket: "@close" }]
    ],
    string_double: [
      [/[^\\"]+/u, "string"],
      [/@escapes/u, "string.escape"],
      [/\\./u, "string.escape.invalid"],
      [/"/u, "string", "@pop"]
    ],
    string_single: [
      [/[^\\']+/u, "string"],
      [/@escapes/u, "string.escape"],
      [/\\./u, "string.escape.invalid"],
      [/'/u, "string", "@pop"]
    ],
    string_backtick: [
      [/\$\{/u, { token: "delimiter.bracket", next: "@bracketCounting" }],
      [/[^\\`$]+/u, "string"],
      [/@escapes/u, "string.escape"],
      [/\\./u, "string.escape.invalid"],
      [/`/u, "string", "@pop"]
    ],
    bracketCounting: [
      [/\{/u, "delimiter.bracket", "@bracketCounting"],
      [/\}/u, "delimiter.bracket", "@pop"],
      { include: "common" }
    ]
  }
};
class P {
  constructor(e, n, {
    defaults: s,
    diagnosticsService: g
  }) {
    this._languageId = e, this._worker = n, this._disposables = [], this._modelListeners = /* @__PURE__ */ new Map(), this._diagnosticsService = g, this._defaults = s, w().then((a) => {
      const u = (o) => {
        const r = o.getLanguageId();
        if (r !== this._languageId)
          return;
        const c = A(() => {
          this._doValidate(o, r).catch((E) => {
            throw E;
          });
        }), I = o.onDidChangeContent(c), v = o.onDidChangeAttached(c);
        this._modelListeners.set(o.uri.toString(), [I, v]), this._doValidate(o, r).catch(f("DiagnosticsAdapter"));
      }, d = (o) => {
        const r = o.uri.toString();
        a.setModelMarkers(o, this._languageId, []);
        const c = this._modelListeners.get(r);
        if (c) {
          for (; c.length; )
            c.pop()?.dispose();
          this._modelListeners.delete(r);
        }
      };
      this._disposables.push(a.onDidCreateModel(u)), this._disposables.push(
        a.onWillDisposeModel((o) => {
          d(o);
        })
      ), this._disposables.push(
        a.onDidChangeModelLanguage((o) => {
          d(o.model), u(o.model);
        })
      ), this._disposables.push(
        s.onDidChange(() => {
          a.getModels().forEach((o) => {
            o.getLanguageId() === this._languageId && (d(o), u(o));
          });
        })
      ), this._disposables.push(
        s.onModelContextDidChange((o) => {
          a.getModels().forEach((r) => {
            r.getLanguageId() === this._languageId && r.uri.toString() === o && this._doValidate(r, this._languageId).catch(f("DiagnosticsAdapter"));
          });
        })
      ), this._disposables.push({
        dispose: () => {
          this._modelListeners.forEach((o) => o.forEach((r) => r.dispose())), this._modelListeners.clear();
        }
      }), a.getModels().forEach(u);
    });
  }
  dispose() {
    this._disposables.forEach((e) => e.dispose()), this._disposables = [];
  }
  async _doValidate(e, n) {
    if (e.isAttachedToEditor())
      try {
        const s = await w(), g = await this._worker(e.uri), a = this._defaults.getApiContextForModel(e.uri), u = await g.doValidation(e.uri.toString(), a), d = u.map((o) => $(e.uri, o));
        this._diagnosticsService.fireDiagnosticsChange(e.uri, u), s.setModelMarkers(e, n, d);
      } catch (s) {
        f("DiagnosticsAdapter")(s);
      }
  }
}
function W(t) {
  switch (t) {
    case m.Error:
      return p.Error;
    case m.Warning:
      return p.Warning;
    case m.Information:
      return p.Info;
    case m.Hint:
      return p.Hint;
    default:
      return p.Info;
  }
}
function $(t, e) {
  return {
    severity: W(e.severity),
    startLineNumber: e.range.start.line + 1,
    startColumn: e.range.start.character + 1,
    endLineNumber: e.range.end.line + 1,
    endColumn: e.range.end.character + 1,
    message: e.message
  };
}
function D(t) {
  return { character: t.column - 1, line: t.lineNumber - 1 };
}
function h(t) {
  return new _(t.start.line + 1, t.start.character + 1, t.end.line + 1, t.end.character + 1);
}
function R(t) {
  return {
    range: h(t.range),
    text: t.newText
  };
}
function V(t) {
  return typeof t.insert < "u" && typeof t.replace < "u";
}
function K(t) {
  const e = l.CompletionItemKind;
  switch (t) {
    case i.Text:
      return e.Text;
    case i.Method:
      return e.Method;
    case i.Function:
      return e.Function;
    case i.Constructor:
      return e.Constructor;
    case i.Field:
      return e.Field;
    case i.Variable:
      return e.Variable;
    case i.Class:
      return e.Class;
    case i.Interface:
      return e.Interface;
    case i.Module:
      return e.Module;
    case i.Property:
      return e.Property;
    case i.Unit:
      return e.Unit;
    case i.Value:
      return e.Value;
    case i.Enum:
      return e.Enum;
    case i.Keyword:
      return e.Keyword;
    case i.Snippet:
      return e.Snippet;
    case i.Color:
      return e.Color;
    case i.File:
      return e.File;
    case i.Reference:
      return e.Reference;
    case i.Folder:
      return e.Folder;
    case i.EnumMember:
      return e.EnumMember;
    case i.Constant:
      return e.Constant;
    case i.Struct:
      return e.Struct;
    case i.Event:
      return e.Event;
    case i.Operator:
      return e.Operator;
    case i.TypeParameter:
      return e.TypeParameter;
    default:
      return e.Property;
  }
}
class N {
  constructor(e, n) {
    this._worker = e, this._defaults = n, this.triggerCharacters = [".", "("];
  }
  async provideCompletionItems(e, n) {
    const s = await this._worker(e.uri), g = this._defaults.getApiContextForModel(e.uri), a = await s.doComplete(e.uri.toString(), D(n), g), u = e.getWordUntilPosition(n), d = new _(n.lineNumber, u.startColumn, n.lineNumber, u.endColumn), o = a.items.map((r) => {
      const c = {
        label: r.label,
        insertText: r.insertText || r.label,
        sortText: r.sortText,
        filterText: r.filterText,
        detail: r.detail,
        range: d,
        kind: K(r.kind)
      };
      return r.textEdit && (V(r.textEdit) ? c.range = {
        insert: h(r.textEdit.insert),
        replace: h(r.textEdit.replace)
      } : c.range = h(r.textEdit.range), c.insertText = r.textEdit.newText), r.additionalTextEdits && (c.additionalTextEdits = r.additionalTextEdits.map(R)), r.insertTextFormat === y.Snippet && (c.insertTextRules = l.CompletionItemInsertTextRule.InsertAsSnippet), r.documentation && (typeof r.documentation == "string" ? c.documentation = r.documentation : c.documentation = {
        supportThemeIcons: !1,
        value: r.documentation.value,
        supportHtml: !0
      }), c;
    });
    return {
      incomplete: a.isIncomplete,
      suggestions: o
    };
  }
}
class z {
  constructor(e, n) {
    this._worker = e, this._defaults = n;
  }
  async provideDocumentFormattingEdits(e) {
    const n = await this._worker(e.uri), s = this._defaults.getApiContextForModel(e.uri.toString());
    return (await n.doFormat(e.uri.toString(), s)).map((a) => ({ range: h(a.range), text: a.newText }));
  }
}
let k = Promise.withResolvers();
class b {
  constructor(e) {
    this._defaults = e, this._worker = null, this._client = null, this._configChangeListener = this._defaults.onDidChange(() => this.stopWorker());
  }
  dispose() {
    this._configChangeListener.dispose(), this.stopWorker();
  }
  stopWorker() {
    this._worker && (this._worker.dispose(), this._worker = null, k = Promise.withResolvers()), this._client = null;
  }
  /**
   * Wait for the worker to be ready.
   * @returns A promise that resolves when the worker is ready.
   */
  static async waitForWorker() {
    return await k.promise;
  }
  async _getClientProxy() {
    const e = await w();
    if (!this._client) {
      const { languageId: n } = this._defaults, s = window.MonacoEnvironment?.getWorker;
      if (!s)
        throw new Error(
          "MonacoEnvironment.getWorker is not configured. Call setupMonacoEnvironment(...) before creating Arcade workers."
        );
      this._worker = e.createWebWorker({
        worker: s("ArcadeWorker", n),
        host: this._defaults.workerHost
      }), k.resolve(this._worker), this._client = this._worker.getProxy();
    }
    return await this._client;
  }
  async getLanguageServiceWorker(...e) {
    const n = await this._getClientProxy();
    return await this._worker?.withSyncedResources(e), n;
  }
}
let x;
async function re(...t) {
  return await b.waitForWorker(), await new Promise((e, n) => {
    if (!x) {
      n(new Error("Arcade not registered!"));
      return;
    }
    e(x(...t));
  });
}
class O {
  constructor() {
    this._onDiagnosticsChange = new M();
  }
  /**
   * An event to signal changes to the diagnostics.
   * The event value is the uri string and the diagnostics.
   */
  get onDiagnosticsChange() {
    return this._onDiagnosticsChange.event;
  }
  /**
   * Fires the diagnostics change event.
   * @param uri The uri of the model for which the diagnostics changed.
   * @param diagnostics The diagnostics for the model.
   */
  fireDiagnosticsChange(e, n) {
    this._onDiagnosticsChange.fire({ uri: e, diagnostics: n });
  }
}
const C = new O();
function ne() {
  return C;
}
function oe(t) {
  const e = new b(t), n = async (...s) => await e.getLanguageServiceWorker(...s);
  x = n, l.setMonarchTokensProvider(t.languageId, T), l.setLanguageConfiguration(t.languageId, S), l.registerCompletionItemProvider(
    t.languageId,
    new N(n, t)
  ), l.registerDocumentFormattingEditProvider(
    t.languageId,
    new z(n, t)
  ), new P(t.languageId, n, {
    defaults: t,
    diagnosticsService: C
  });
}
export {
  ne as getArcadeDiagnosticService,
  re as getArcadeWorker,
  oe as setupMode
};
