/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var registry_exports = {};
__export(registry_exports, {
  inferSelectsFromUsage: () => inferSelectsFromUsage,
  variables: () => variables
});
module.exports = __toCommonJS(registry_exports);
var import_lodash = __toESM(require("lodash"));
var import_contexts = require("../template/contexts");
var import_utils = require("@nocobase/utils");
var import_selects = require("./selects");
var import_records = require("./records");
class VariableRegistry {
  vars = /* @__PURE__ */ new Map();
  register(def) {
    this.vars.set(def.name, def);
  }
  get(name) {
    return this.vars.get(name);
  }
  list() {
    return Array.from(this.vars.values());
  }
  extractUsage(template) {
    return (0, import_utils.extractUsedVariablePaths)(template);
  }
  validate(template, contextParams) {
    var _a;
    const usage = this.extractUsage(template);
    const missing = [];
    for (const varName of Object.keys(usage)) {
      const def = this.get(varName);
      if (!((_a = def == null ? void 0 : def.requiredParams) == null ? void 0 : _a.length)) continue;
      const params = import_lodash.default.get(contextParams, varName);
      for (const spec of def.requiredParams) {
        if (!spec.required) continue;
        if (params && typeof params[spec.name] !== "undefined") continue;
        missing.push(`contextParams.${varName}.${spec.name}`);
      }
    }
    return { ok: missing.length === 0, missing: missing.length ? missing : void 0 };
  }
  async attachUsedVariables(ctx, koaCtx, template, contextParams) {
    const usage = this.extractUsage(template);
    for (const varName of Object.keys(usage)) {
      const def = this.get(varName);
      const params = import_lodash.default.get(contextParams, varName);
      if (def) {
        await def.attach(ctx, koaCtx, params, { [varName]: usage[varName] });
      }
    }
    attachGenericRecordVariables(ctx, koaCtx, usage, contextParams);
  }
}
const GLOBAL_KEY = "__ncbVarRegistry__";
const g = typeof globalThis !== "undefined" ? globalThis : global;
if (g[GLOBAL_KEY] && typeof g[GLOBAL_KEY] === "object") {
  Object.setPrototypeOf(g[GLOBAL_KEY], VariableRegistry.prototype);
}
if (!g[GLOBAL_KEY]) {
  g[GLOBAL_KEY] = new VariableRegistry();
}
const variables = g[GLOBAL_KEY];
function inferSelectsFromUsage(paths = [], _params) {
  if (!Array.isArray(paths) || paths.length === 0) {
    return { generatedAppends: void 0, generatedFields: void 0 };
  }
  const appendSet = /* @__PURE__ */ new Set();
  const fieldSet = /* @__PURE__ */ new Set();
  const normalizePath = (raw) => {
    if (!raw) return "";
    let s = String(raw);
    s = s.replace(/\[(?:\d+)\]/g, "");
    s = s.replace(/\[(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)')\]/g, (_m, g1, g2) => `.${g1 || g2}`);
    s = s.replace(/\.\.+/g, ".");
    s = s.replace(/^\./, "").replace(/\.$/, "");
    return s;
  };
  for (let path of paths) {
    if (!path) continue;
    while (/^\[(\d+)\](\.|$)/.test(path)) {
      path = path.replace(/^\[(\d+)\]\.?/, "");
    }
    const norm = normalizePath(path);
    if (!norm) continue;
    const segments = norm.split(".").filter(Boolean);
    if (segments.length === 0) continue;
    if (segments.length === 1) {
      fieldSet.add(segments[0]);
      continue;
    }
    for (let i = 0; i < segments.length - 1; i++) {
      appendSet.add(segments.slice(0, i + 1).join("."));
    }
    fieldSet.add(segments.join("."));
  }
  const generatedAppends = appendSet.size ? Array.from(appendSet) : void 0;
  const generatedFields = fieldSet.size ? Array.from(fieldSet) : void 0;
  return { generatedAppends, generatedFields };
}
async function fetchRecordWithRequestCache(koaCtx, dataSourceKey, collection, filterByTk, fields, appends, strictSelects, preferFullRecord, associationName, sourceId) {
  var _a, _b, _c, _d, _e;
  try {
    const log = (_b = (_a = koaCtx.app) == null ? void 0 : _a.logger) == null ? void 0 : _b.child({
      module: "plugin-flow-engine",
      submodule: "variables.resolve",
      method: "fetchRecordWithRequestCache"
    });
    const kctx = koaCtx;
    if (!kctx.state) kctx.state = {};
    if (!kctx.state["__varResolveBatchCache"]) {
      kctx.state["__varResolveBatchCache"] = /* @__PURE__ */ new Map();
    }
    const cache = kctx.state.__varResolveBatchCache || null;
    const ds = koaCtx.app.dataSourceManager.get(dataSourceKey || "main");
    const cm = ds.collectionManager;
    if (!(cm == null ? void 0 : cm.db)) return void 0;
    const repo = associationName && typeof sourceId !== "undefined" ? cm.db.getRepository(associationName, sourceId) : cm.db.getRepository(collection);
    const modelInfo = (_c = repo.collection) == null ? void 0 : _c.model;
    const pkAttr = modelInfo == null ? void 0 : modelInfo.primaryKeyAttribute;
    const pkIsValid = pkAttr && (modelInfo == null ? void 0 : modelInfo.rawAttributes) && Object.prototype.hasOwnProperty.call(modelInfo.rawAttributes, pkAttr);
    const collectionInfo = repo == null ? void 0 : repo.collection;
    const filterTargetKey = collectionInfo == null ? void 0 : collectionInfo.filterTargetKey;
    const extraKeys = (0, import_records.getExtraKeyFieldsForSelect)(filterByTk, {
      filterTargetKey,
      pkAttr,
      pkIsValid,
      rawAttributes: (modelInfo == null ? void 0 : modelInfo.rawAttributes) || void 0
    });
    const effectiveExtras = strictSelects && Array.isArray(extraKeys) && extraKeys.length ? extraKeys.filter((k) => k === pkAttr) : extraKeys;
    const fieldsWithExtras = (0, import_records.mergeFieldsWithExtras)(fields, effectiveExtras);
    const cacheKeyFields = preferFullRecord && pkIsValid ? void 0 : Array.isArray(fieldsWithExtras) ? [...fieldsWithExtras].sort() : void 0;
    const cacheKeyAppends = preferFullRecord ? void 0 : Array.isArray(appends) ? [...appends].sort() : void 0;
    const keyObj = {
      ds: dataSourceKey || "main",
      c: collection,
      tk: filterByTk,
      f: cacheKeyFields,
      a: cacheKeyAppends,
      full: preferFullRecord ? true : void 0,
      assoc: associationName,
      sid: typeof sourceId === "undefined" ? void 0 : sourceId
    };
    const key = JSON.stringify(keyObj);
    if (cache) {
      if (cache.has(key)) {
        return cache.get(key);
      }
      if (!strictSelects) {
        const needFields = !preferFullRecord && Array.isArray(fieldsWithExtras) ? [...new Set(fieldsWithExtras)] : void 0;
        const needAppends = !preferFullRecord && Array.isArray(appends) ? new Set(appends) : void 0;
        for (const [cacheKey, cacheVal] of cache.entries()) {
          const parsed = JSON.parse(cacheKey);
          if (!parsed || parsed.ds !== keyObj.ds || parsed.c !== keyObj.c || !import_lodash.default.isEqual(parsed.tk, keyObj.tk) || parsed.assoc !== keyObj.assoc || !import_lodash.default.isEqual(parsed.sid, keyObj.sid))
            continue;
          const cachedFields = new Set(parsed.f || []);
          const cachedAppends = new Set(parsed.a || []);
          const fieldCoveredByAppends = (fieldPath) => {
            const p = String(fieldPath || "");
            for (const a of cachedAppends) {
              if (!a) continue;
              if (p === a || p.startsWith(a + ".")) return true;
            }
            return false;
          };
          const fieldsOk = needFields ? needFields.every((f) => cachedFields.has(f) || fieldCoveredByAppends(f)) : parsed.f === void 0;
          const appendsOk = !needAppends || [...needAppends].every((a) => cachedAppends.has(a));
          const fullOk = preferFullRecord ? parsed.full === true : true;
          if (fieldsOk && appendsOk && fullOk) {
            return cacheVal;
          }
        }
      }
    }
    const json = await (0, import_records.fetchRecordOrRecordsJson)(repo, {
      filterByTk,
      preferFullRecord,
      fields: fieldsWithExtras,
      appends,
      filterTargetKey,
      pkAttr,
      pkIsValid
    });
    if (cache) cache.set(key, json);
    return json;
  } catch (e) {
    const log = (_e = (_d = koaCtx.app) == null ? void 0 : _d.logger) == null ? void 0 : _e.child({
      module: "plugin-flow-engine",
      submodule: "variables.resolve",
      method: "fetchRecordWithRequestCache"
    });
    const errMsg = e instanceof Error ? e.message : String(e);
    log == null ? void 0 : log.warn("[variables.resolve] fetchRecordWithRequestCache error", {
      ds: dataSourceKey,
      collection,
      tk: filterByTk,
      fields,
      appends,
      error: errMsg
    });
    return void 0;
  }
}
function isRecordParams(val) {
  return val && typeof val === "object" && "collection" in val && "filterByTk" in val;
}
function attachGenericRecordVariables(flowCtx, koaCtx, usage, contextParams) {
  const parseIndexSegment = (segment) => {
    const m = segment.match(/^\[(\d+)\]$/);
    return m ? m[1] : void 0;
  };
  for (const varName of Object.keys(usage)) {
    const usedPaths = usage[varName] || [];
    const topParams = import_lodash.default.get(contextParams, varName);
    const deepRecordMap = /* @__PURE__ */ new Map();
    const cp = contextParams;
    if (cp && typeof cp === "object") {
      const cpRec = cp;
      for (const key of Object.keys(cpRec)) {
        if (!key || key !== varName && !key.startsWith(`${varName}.`)) continue;
        if (key === varName) continue;
        const val = cpRec[key];
        if (!isRecordParams(val)) continue;
        const relative = key.slice(varName.length + 1);
        if (!relative) continue;
        deepRecordMap.set(relative, val);
      }
    }
    if (isRecordParams(topParams)) {
      const usedPathsForBase = deepRecordMap.size ? (usedPaths || []).filter((p) => {
        if (!p) return true;
        for (const relative of deepRecordMap.keys()) {
          if (!relative) continue;
          if (p === relative || p.startsWith(relative + ".") || p.startsWith(relative + "[")) return false;
        }
        return true;
      }) : usedPaths || [];
      const hasDirectRefTop = usedPathsForBase.some((p) => p === "");
      flowCtx.defineProperty(varName, {
        get: async () => {
          const dataSourceKey = (topParams == null ? void 0 : topParams.dataSourceKey) || "main";
          const strictSelects = Array.isArray(topParams == null ? void 0 : topParams.fields) || Array.isArray(topParams == null ? void 0 : topParams.appends);
          let { generatedAppends, generatedFields } = inferSelectsFromUsage(usedPathsForBase, topParams);
          if (Array.isArray(topParams == null ? void 0 : topParams.fields)) generatedFields = topParams.fields;
          if (Array.isArray(topParams == null ? void 0 : topParams.appends)) generatedAppends = topParams.appends;
          const fixed = (0, import_selects.adjustSelectsForCollection)(
            koaCtx,
            dataSourceKey,
            topParams.collection,
            generatedFields,
            generatedAppends
          );
          const base = await fetchRecordWithRequestCache(
            koaCtx,
            dataSourceKey,
            topParams.collection,
            topParams.filterByTk,
            fixed.fields,
            fixed.appends,
            strictSelects,
            hasDirectRefTop,
            topParams.associationName,
            topParams.sourceId
          );
          if (!deepRecordMap.size) return base;
          const merged = base && typeof base === "object" && !Array.isArray(base) ? { ...base } : {};
          const setClonedPath = (obj, path, value) => {
            const segs = String(path || "").split(".").filter(Boolean);
            if (!segs.length) return;
            if (segs.length === 1) {
              obj[segs[0]] = value;
              return;
            }
            let cur = obj;
            for (let i = 0; i < segs.length - 1; i++) {
              const seg = segs[i];
              const prev = cur[seg];
              const next = prev && typeof prev === "object" && !Array.isArray(prev) ? { ...prev } : {};
              cur[seg] = next;
              cur = next;
            }
            cur[segs[segs.length - 1]] = value;
          };
          const buildNestedPromise = (recordParams, relative) => {
            const subPaths = (usedPaths || []).map((p) => p === relative ? "" : p.startsWith(relative + ".") ? p.slice(relative.length + 1) : "").filter((x) => x !== "");
            const hasDirectRef = (usedPaths || []).some((p) => p === relative);
            const dataSourceKey2 = (recordParams == null ? void 0 : recordParams.dataSourceKey) || "main";
            const strictSelects2 = Array.isArray(recordParams == null ? void 0 : recordParams.fields) || Array.isArray(recordParams == null ? void 0 : recordParams.appends);
            let { generatedAppends: generatedAppends2, generatedFields: generatedFields2 } = inferSelectsFromUsage(subPaths, recordParams);
            if (Array.isArray(recordParams == null ? void 0 : recordParams.fields)) generatedFields2 = recordParams.fields;
            if (Array.isArray(recordParams == null ? void 0 : recordParams.appends)) generatedAppends2 = recordParams.appends;
            const fixed2 = (0, import_selects.adjustSelectsForCollection)(
              koaCtx,
              dataSourceKey2,
              recordParams.collection,
              generatedFields2,
              generatedAppends2
            );
            return fetchRecordWithRequestCache(
              koaCtx,
              dataSourceKey2,
              recordParams.collection,
              recordParams.filterByTk,
              fixed2.fields,
              fixed2.appends,
              strictSelects2,
              hasDirectRef,
              recordParams.associationName,
              recordParams.sourceId
            );
          };
          for (const [relative, recordParams] of deepRecordMap.entries()) {
            setClonedPath(merged, relative, buildNestedPromise(recordParams, relative));
          }
          return merged;
        },
        cache: true
      });
      continue;
    }
    const segmentMap = /* @__PURE__ */ new Map();
    const splitHead = (path) => {
      if (!path) return { seg: "", remainder: "" };
      const mIdx = path.match(/^\[(\d+)\](?:\.(.*))?$/);
      if (mIdx) {
        return { seg: `[${mIdx[1]}]`, remainder: mIdx[2] || "" };
      }
      const m = path.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(\[(?:\d+|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\])?(?:\.(.*))?$/);
      if (m) {
        const seg2 = m[1];
        const idxPart = m[2] || "";
        const tail = m[3] || "";
        const remainder = (idxPart ? `${idxPart}${tail ? `.${tail}` : ""}` : tail) || "";
        return { seg: seg2, remainder };
      }
      const [seg, ...rest] = path.split(".");
      return { seg, remainder: rest.join(".") };
    };
    for (const p of usedPaths) {
      if (!p) continue;
      const { seg, remainder } = splitHead(p);
      if (!seg) continue;
      const arr = segmentMap.get(seg) || [];
      arr.push(remainder);
      segmentMap.set(seg, arr);
    }
    const segEntries = Array.from(segmentMap.entries());
    const oneLevelRecordChildren = segEntries.filter(([seg]) => {
      const idx = parseIndexSegment(seg);
      const nestedObj = import_lodash.default.get(contextParams, [varName, seg]) ?? (idx ? import_lodash.default.get(contextParams, [varName, idx]) : void 0);
      const dotted = (contextParams || {})[`${varName}.${seg}`] ?? (idx ? (contextParams || {})[`${varName}.${idx}`] : void 0);
      return isRecordParams(nestedObj) || isRecordParams(dotted);
    });
    if (!oneLevelRecordChildren.length && deepRecordMap.size === 0) continue;
    flowCtx.defineProperty(varName, {
      get: () => {
        const root = new import_contexts.ServerBaseContext();
        const definedFirstLevel = /* @__PURE__ */ new Set();
        const defineRecordGetter = (container, key, recordParams, subPaths = [], preferFull) => {
          const strictSelects = Array.isArray(recordParams == null ? void 0 : recordParams.fields) || Array.isArray(recordParams == null ? void 0 : recordParams.appends);
          let { generatedAppends, generatedFields } = inferSelectsFromUsage(subPaths, recordParams);
          if (Array.isArray(recordParams == null ? void 0 : recordParams.fields)) generatedFields = recordParams.fields;
          if (Array.isArray(recordParams == null ? void 0 : recordParams.appends)) generatedAppends = recordParams.appends;
          container.defineProperty(key, {
            get: async () => {
              const dataSourceKey = (recordParams == null ? void 0 : recordParams.dataSourceKey) || "main";
              const fixed = (0, import_selects.adjustSelectsForCollection)(
                koaCtx,
                dataSourceKey,
                recordParams.collection,
                generatedFields,
                generatedAppends
              );
              return await fetchRecordWithRequestCache(
                koaCtx,
                dataSourceKey,
                recordParams.collection,
                recordParams.filterByTk,
                fixed.fields,
                fixed.appends,
                strictSelects,
                preferFull || ((subPaths == null ? void 0 : subPaths.length) ?? 0) === 0,
                recordParams.associationName,
                recordParams.sourceId
              );
            },
            cache: true
          });
        };
        const subContainers = /* @__PURE__ */ new Map();
        const ensureSubContainer = (parent, key) => {
          let map = subContainers.get(parent);
          if (!map) {
            map = /* @__PURE__ */ new Map();
            subContainers.set(parent, map);
          }
          let child = map.get(key);
          if (!child) {
            const inst = new import_contexts.ServerBaseContext();
            parent.defineProperty(key, { get: () => inst.createProxy(), cache: true });
            map.set(key, inst);
            child = inst;
          }
          return child;
        };
        for (const [seg, remainders] of oneLevelRecordChildren) {
          const idx = parseIndexSegment(seg);
          const recordParams = import_lodash.default.get(contextParams, [varName, seg]) ?? (idx ? import_lodash.default.get(contextParams, [varName, idx]) : void 0) ?? (contextParams || {})[`${varName}.${seg}`] ?? (idx ? (contextParams || {})[`${varName}.${idx}`] : void 0);
          let effRemainders = (remainders || []).filter((r) => !!r);
          if (!effRemainders.length) {
            const all = usedPaths.map(
              (p) => p.startsWith(`${seg}.`) ? p.slice(seg.length + 1) : p.startsWith(`${seg}[`) ? p.slice(seg.length) : ""
            ).filter((x) => !!x);
            if (all.length) effRemainders = all;
          }
          const hasDirectRefOne = (usedPaths || []).some((p) => p === seg || !!idx && p === `[${idx}]`);
          defineRecordGetter(root, idx ?? seg, recordParams, effRemainders, hasDirectRefOne);
          definedFirstLevel.add(idx ?? seg);
        }
        for (const [relative, recordParams] of deepRecordMap.entries()) {
          const segs = String(relative).split(".").filter(Boolean);
          if (segs.length === 0) continue;
          const first = segs[0];
          let container;
          if (definedFirstLevel.has(first)) {
            continue;
          } else {
            container = root;
            for (let i = 0; i < segs.length - 1; i++) {
              container = ensureSubContainer(container, segs[i]);
            }
          }
          const leaf = segs[segs.length - 1];
          const subPaths = (usedPaths || []).map((p) => p === relative ? "" : p.startsWith(relative + ".") ? p.slice(relative.length + 1) : "").filter((x) => x !== "");
          const hasDirectRef = (usedPaths || []).some((p) => p === relative);
          defineRecordGetter(container, leaf, recordParams, subPaths, hasDirectRef);
        }
        return root.createProxy();
      },
      cache: true
    });
  }
}
function registerBuiltInVariables(reg) {
  reg.register({
    name: "user",
    scope: "request",
    // no requiredParams: frontend will not pass context params for user
    attach: (flowCtx, koaCtx, _params, usage) => {
      const paths = (usage == null ? void 0 : usage["user"]) || [];
      const { generatedAppends, generatedFields } = inferSelectsFromUsage(paths);
      flowCtx.defineProperty("user", {
        get: async () => {
          var _a;
          const authObj = koaCtx.auth;
          const uid = (_a = authObj == null ? void 0 : authObj.user) == null ? void 0 : _a.id;
          if (typeof uid === "undefined" || uid === null) return void 0;
          return await fetchRecordWithRequestCache(
            koaCtx,
            "main",
            "users",
            uid,
            generatedFields,
            generatedAppends,
            false,
            void 0,
            void 0,
            void 0
          );
        },
        cache: true
      });
    }
  });
}
registerBuiltInVariables(variables);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  inferSelectsFromUsage,
  variables
});
