import { isNonEmptyArray, nonexhaustive, nonNullable } from "@likec4/core";
import { filter, first, isNonNullish, isTruthy, map, mapToObj, pipe } from "remeda";
import {
  ast,
  resolveRelationPoints,
  streamModel,
  toElementStyle,
  toRelationshipStyleExcludeDefaults
} from "../../ast.js";
import { logWarnError } from "../../logger.js";
import { stringHash } from "../../utils/stringHash.js";
import { removeIndent, toSingleLine } from "./Base.js";
export function ModelParser(B) {
  return class ModelParser extends B {
    parseModel() {
      const doc = this.doc;
      for (const el of streamModel(doc)) {
        try {
          if (ast.isElement(el)) {
            doc.c4Elements.push(this.parseElement(el));
            continue;
          }
          if (ast.isRelation(el)) {
            if (this.isValid(el)) {
              doc.c4Relations.push(this.parseRelation(el));
            }
            continue;
          }
          nonexhaustive(el);
        } catch (e) {
          logWarnError(e);
        }
      }
    }
    parseElement(astNode) {
      const isValid = this.isValid;
      const id = this.resolveFqn(astNode);
      const kind = nonNullable(astNode.kind.ref, "Element kind is not resolved").name;
      const tags = this.parseTags(astNode.body);
      const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props;
      const style = toElementStyle(stylePropsAst, isValid);
      const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty));
      const astPath = this.getAstNodePath(astNode);
      let [title, description, technology] = astNode.props ?? [];
      const bodyProps = pipe(
        astNode.body?.props ?? [],
        filter(isValid),
        filter(ast.isElementStringProperty),
        mapToObj((p) => [p.key, p.value || void 0])
      );
      title = removeIndent(title ?? bodyProps.title);
      description = removeIndent(bodyProps.description ?? description);
      technology = toSingleLine(bodyProps.technology ?? technology);
      const links = this.parseLinks(astNode.body);
      const iconProp = astNode.body?.props.find(ast.isIconProperty);
      if (iconProp && isValid(iconProp)) {
        const value = iconProp.libicon?.ref?.name ?? iconProp.value;
        if (isTruthy(value)) {
          style.icon = value;
        }
      }
      return {
        id,
        kind,
        astPath,
        title: title ?? astNode.name,
        ...metadata && { metadata },
        ...tags && { tags },
        ...links && isNonEmptyArray(links) && { links },
        ...isTruthy(technology) && { technology },
        ...isTruthy(description) && { description },
        style
      };
    }
    parseRelation(astNode) {
      const isValid = this.isValid;
      const coupling = resolveRelationPoints(astNode);
      const target = this.resolveFqn(coupling.target);
      const source = this.resolveFqn(coupling.source);
      const tags = this.parseTags(astNode) ?? this.parseTags(astNode.body);
      const links = this.parseLinks(astNode.body);
      const kind = astNode.kind?.ref?.name;
      const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty));
      const astPath = this.getAstNodePath(astNode);
      const bodyProps = mapToObj(
        astNode.body?.props.filter(ast.isRelationStringProperty).filter((p) => isNonNullish(p.value)) ?? [],
        (p) => [p.key, p.value]
      );
      const navigateTo = pipe(
        astNode.body?.props ?? [],
        filter(ast.isRelationNavigateToProperty),
        map((p) => p.value.view.ref?.name),
        filter(isTruthy),
        first()
      );
      const title = removeIndent(astNode.title ?? bodyProps.title) ?? "";
      const description = removeIndent(bodyProps.description);
      const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology);
      const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty);
      const id = stringHash(
        astPath,
        source,
        target
      );
      return {
        id,
        astPath,
        source,
        target,
        title,
        ...metadata && { metadata },
        ...isTruthy(technology) && { technology },
        ...isTruthy(description) && { description },
        ...kind && { kind },
        ...tags && { tags },
        ...isNonEmptyArray(links) && { links },
        ...toRelationshipStyleExcludeDefaults(styleProp?.props, isValid),
        ...navigateTo && { navigateTo }
      };
    }
  };
}
