import { nameFromFqn, parentFqn } from "@likec4/core";
import { DocumentState, DONE_RESULT, MultiMap, stream, StreamImpl } from "langium";
import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from "../ast.js";
import { logger, logWarnError } from "../logger.js";
import { ADisposable } from "../utils/index.js";
import { computeDocumentFqn } from "./fqn-computation.js";
export class FqnIndex extends ADisposable {
  constructor(services) {
    super();
    this.services = services;
    this.langiumDocuments = services.shared.workspace.LangiumDocuments;
    this.onDispose(
      services.shared.workspace.DocumentBuilder.onBuildPhase(
        DocumentState.IndexedContent,
        async (docs, _cancelToken) => {
          for (const doc of docs) {
            if (isLikeC4LangiumDocument(doc)) {
              delete doc.c4fqnIndex;
              delete doc.c4Elements;
              delete doc.c4Specification;
              delete doc.c4Relations;
              delete doc.c4Deployments;
              delete doc.c4DeploymentRelations;
              delete doc.c4Globals;
              delete doc.c4Views;
              try {
                computeDocumentFqn(doc, services);
              } catch (e) {
                logWarnError(e);
              }
            }
          }
          return await Promise.resolve();
        }
      )
    );
    logger.debug(`[FqnIndex] Created`);
  }
  langiumDocuments;
  get documents() {
    return this.langiumDocuments.all.filter(isFqnIndexedDocument);
  }
  entries(filterByFqn) {
    return this.documents.flatMap((doc) => {
      return doc.c4fqnIndex.keys().filter(filterByFqn).flatMap((fqn) => doc.c4fqnIndex.get(fqn));
    });
  }
  getFqn(el) {
    return ElementOps.readId(el) ?? null;
  }
  byFqn(fqn) {
    return this.documents.flatMap((doc) => {
      return doc.c4fqnIndex.get(fqn);
    });
  }
  directChildrenOf(parent) {
    return stream([parent]).flatMap((_parent) => {
      const children = this.entries((fqn) => parentFqn(fqn) === _parent).map((entry) => [entry.name, entry]).toArray();
      if (children.length === 0) {
        return [];
      }
      return new MultiMap(children).entriesGroupedByKey().flatMap(([_name, descrs]) => descrs.length === 1 ? descrs : []).iterator();
    });
  }
  /**
   * Returns descedant elements with unique names in the scope
   */
  uniqueDescedants(parent) {
    return new StreamImpl(
      () => {
        const prefix = `${parent}.`;
        const childrenNames = /* @__PURE__ */ new Set();
        const descedants = [];
        const nested = new MultiMap();
        this.entries((f) => f.startsWith(prefix)).forEach((e) => {
          const name = nameFromFqn(e.fqn);
          const entry = { ...e, name };
          if (parentFqn(e.fqn) === parent) {
            childrenNames.add(name);
            nested.add(name, entry);
          } else {
            descedants.push(entry);
          }
        });
        if (nested.size + descedants.length === 0) {
          return null;
        }
        for (const descedant of descedants) {
          if (!childrenNames.has(descedant.name)) {
            nested.add(descedant.name, descedant);
          }
        }
        return nested.entriesGroupedByKey().flatMap(([_name, descrs]) => descrs.length === 1 ? descrs : []).iterator();
      },
      (iterator) => {
        if (iterator) {
          return iterator.next();
        }
        return DONE_RESULT;
      }
    );
  }
}
