import { AstUtils, DocumentState, MultiMap } from "langium";
import { forEachObj, groupBy, isTruthy, pipe, prop } from "remeda";
import {
  ast,
  ElementOps,
  isLikeC4LangiumDocument
} from "../ast.js";
import { logWarnError } from "../logger.js";
export class DeploymentsIndex {
  constructor(services) {
    this.services = services;
    this.Names = services.references.NameProvider;
    this.langiumDocuments = services.shared.workspace.LangiumDocuments;
    this.documentCache = services.DocumentCache;
  }
  Names;
  langiumDocuments;
  documentCache;
  documents() {
    return this.langiumDocuments.all.filter(
      (d) => isLikeC4LangiumDocument(d) && d.state >= DocumentState.IndexedContent
    );
  }
  get(document) {
    if (document.state < DocumentState.IndexedContent) {
      logWarnError(`Document ${document.uri.path} is not indexed`);
    }
    return this.documentCache.get(document.uri, "DeploymentsIndex", () => this.createDocumentIndex(document));
  }
  /**
   * Nested elements (nodes/artifacts) of the node
   * @param nodeName Name of the deployment node
   * @returns Stream of artifacts
   */
  nested(node) {
    const fqnName = this.getFqn(node);
    return this.documents().flatMap((doc) => this.get(doc).nested(fqnName));
  }
  byFqn(fqnName) {
    return this.documents().flatMap((doc) => this.get(doc).byFqn(fqnName));
  }
  getFqn(node) {
    let id = ElementOps.readId(node);
    if (isTruthy(id)) {
      return id;
    }
    const fqn = [
      this.Names.getNameStrict(node)
    ];
    let _node = node;
    let parentNode;
    while (parentNode = AstUtils.getContainerOfType(_node.$container, ast.isDeploymentNode)) {
      fqn.push(this.Names.getNameStrict(parentNode));
      _node = parentNode;
    }
    id = fqn.reduceRight((acc, cur) => `${acc}.${cur}`);
    ElementOps.writeId(node, id);
    return id;
  }
  createDocumentIndex(document) {
    const rootNodes = document.parseResult.value.deployments.flatMap((m) => m.elements);
    if (rootNodes.length === 0) {
      return DocumentDeploymentsIndex.EMPTY;
    }
    const _root = new Array();
    const _nested = new MultiMap();
    const _byfqn = new MultiMap();
    const Names = this.Names;
    const Descriptions = this.services.workspace.AstNodeDescriptionProvider;
    const createAndSaveDescription = (props) => {
      const desc = {
        ...Descriptions.createDescription(props.node, props.name, document),
        fqn: props.fqn
      };
      ElementOps.writeId(props.node, props.fqn);
      _byfqn.add(props.fqn, desc);
      return desc;
    };
    const traverseNode = (container, parentFqn) => {
      const _descedants = [];
      const children = container.body?.elements;
      if (!children || children.length === 0) {
        return [];
      }
      const directChildren = /* @__PURE__ */ new Set();
      for (const node of children) {
        if (ast.isDeploymentRelation(node)) {
          continue;
        }
        try {
          const name = Names.getName(node);
          if (isTruthy(name)) {
            const fqn = `${parentFqn}.${name}`;
            const desc = createAndSaveDescription({ node, name, fqn });
            _nested.add(parentFqn, desc);
            directChildren.add(desc.name);
            if (ast.isDeploymentNode(node) && node.body) {
              _descedants.push(...traverseNode(node, fqn));
            }
          }
        } catch (e) {
          logWarnError(e);
        }
      }
      if (_descedants.length > 0) {
        pipe(
          _descedants,
          groupBy(prop("name")),
          forEachObj((descs, key) => {
            if (descs.length > 1 || directChildren.has(key)) {
              return;
            }
            _nested.add(parentFqn, descs[0]);
          })
        );
      }
      return _nested.get(parentFqn);
    };
    for (const node of rootNodes) {
      try {
        if (ast.isDeploymentRelation(node)) {
          continue;
        }
        const name = Names.getName(node);
        if (isTruthy(name)) {
          _root.push(createAndSaveDescription({ node, name, fqn: name }));
          traverseNode(node, name);
        }
      } catch (e) {
        logWarnError(e);
      }
    }
    return new DocumentDeploymentsIndex(_root, _nested, _byfqn);
  }
}
export class DocumentDeploymentsIndex {
  constructor(_rootNodes, _nested, _byfqn) {
    this._rootNodes = _rootNodes;
    this._nested = _nested;
    this._byfqn = _byfqn;
  }
  static EMPTY = new DocumentDeploymentsIndex([], new MultiMap(), new MultiMap());
  rootNodes() {
    return this._rootNodes;
  }
  byFqn(fqnName) {
    return this._byfqn.get(fqnName);
  }
  /**
   * Returns artifacts of a deployment node
   * @param nodeName Name of the deployment node
   * @returns Stream of artifacts
   */
  nested(nodeName) {
    return this._nested.get(nodeName);
  }
  /**
   * Returns all deployment elements in the document,
   * with unique combination "type and name"
   */
  unique() {
    const result = [];
    pipe(
      [...this._byfqn.values()],
      groupBy(prop("name")),
      forEachObj((descs) => {
        if (descs.length === 1) {
          result.push(descs[0]);
        }
      })
    );
    return result;
  }
}
