import { FqnRef, isNonEmptyArray, nameFromFqn, nonexhaustive, nonNullable } from "@likec4/core";
import { filter, first, isTruthy, map, mapToObj, pipe } from "remeda";
import {
  ast,
  streamDeploymentModel,
  toElementStyle,
  toRelationshipStyleExcludeDefaults
} from "../../ast.js";
import { logWarnError } from "../../logger.js";
import { elementRef } from "../../utils/elementRef.js";
import { stringHash } from "../../utils/stringHash.js";
import { removeIndent, toSingleLine } from "./Base.js";
export function DeploymentModelParser(B) {
  return class DeploymentModelParser extends B {
    parseDeployment() {
      const doc = this.doc;
      for (const el of streamDeploymentModel(doc)) {
        try {
          switch (true) {
            case ast.isDeploymentRelation(el): {
              if (this.isValid(el)) {
                doc.c4DeploymentRelations.push(this.parseDeploymentRelation(el));
              }
              break;
            }
            case ast.isDeployedInstance(el):
              doc.c4Deployments.push(this.parseDeployedInstance(el));
              break;
            case ast.isDeploymentNode(el): {
              doc.c4Deployments.push(this.parseDeploymentNode(el));
              break;
            }
            default:
              nonexhaustive(el);
          }
        } catch (e) {
          logWarnError(e);
        }
      }
    }
    parseDeploymentNode(astNode) {
      const isValid = this.isValid;
      const id = this.resolveFqn(astNode);
      const kind = nonNullable(astNode.kind.ref, "DeploymentKind not resolved").name;
      const tags = this.convertTags(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 bodyProps = pipe(
        astNode.body?.props ?? [],
        filter(isValid),
        filter(ast.isElementStringProperty),
        mapToObj((p) => [p.key, p.value || void 0])
      );
      const title = removeIndent(astNode.title ?? bodyProps.title);
      const description = removeIndent(bodyProps.description);
      const technology = toSingleLine(bodyProps.technology);
      const links = this.convertLinks(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,
        title: title ?? nameFromFqn(id),
        ...metadata && { metadata },
        ...tags && { tags },
        ...links && isNonEmptyArray(links) && { links },
        ...isTruthy(technology) && { technology },
        ...isTruthy(description) && { description },
        style
      };
    }
    parseDeployedInstance(astNode) {
      const isValid = this.isValid;
      const id = this.resolveFqn(astNode);
      const element = this.resolveFqn(nonNullable(elementRef(astNode.element), "DeployedInstance element not found"));
      const tags = this.convertTags(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 bodyProps = pipe(
        astNode.body?.props ?? [],
        filter(isValid),
        filter(ast.isElementStringProperty),
        mapToObj((p) => [p.key, p.value || void 0])
      );
      const title = removeIndent(astNode.title ?? bodyProps.title);
      const description = removeIndent(bodyProps.description);
      const technology = toSingleLine(bodyProps.technology);
      const links = this.convertLinks(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,
        element,
        ...metadata && { metadata },
        ...title && { title },
        ...tags && { tags },
        ...links && isNonEmptyArray(links) && { links },
        ...isTruthy(technology) && { technology },
        ...isTruthy(description) && { description },
        style
      };
    }
    parseDeploymentRelation(astNode) {
      const isValid = this.isValid;
      const astPath = this.getAstNodePath(astNode);
      const source = FqnRef.toDeploymentRef(this.parseFqnRef(astNode.source));
      const target = FqnRef.toDeploymentRef(this.parseFqnRef(astNode.target));
      const tags = this.convertTags(astNode) ?? this.convertTags(astNode.body);
      const links = this.convertLinks(astNode.body);
      const kind = astNode.kind?.ref?.name;
      const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty));
      const bodyProps = mapToObj(
        astNode.body?.props.filter(ast.isRelationStringProperty) ?? [],
        (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(
        "deployment",
        astPath,
        source.id,
        target.id
      );
      return {
        id,
        source,
        target,
        ...title && { title },
        ...metadata && { metadata },
        ...isTruthy(technology) && { technology },
        ...isTruthy(description) && { description },
        ...kind && { kind },
        ...tags && { tags },
        ...isNonEmptyArray(links) && { links },
        ...toRelationshipStyleExcludeDefaults(styleProp?.props, isValid),
        ...navigateTo && { navigateTo },
        astPath
      };
    }
  };
}
