import { DefaultArrowType, DefaultLineStyle, DefaultRelationshipColor, LinkedList, nonexhaustive } from "@likec4/core";
import { DocumentState } from "langium";
import { clamp, isBoolean, isDefined, isNullish, isTruthy } from "remeda";
import * as ast from "./generated/ast.js";
import { LikeC4LanguageMetaData } from "./generated/module.js";
import { elementRef } from "./utils/elementRef.js";
export { ast };
const idattr = Symbol.for("idattr");
export const ViewOps = {
  writeId(node, id) {
    node[idattr] = id;
    return node;
  },
  readId(node) {
    return node[idattr];
  }
};
export const ElementOps = {
  writeId(node, id) {
    if (isNullish(id)) {
      node[idattr] = void 0;
    } else {
      node[idattr] = id;
    }
    return node;
  },
  readId(node) {
    return node[idattr];
  }
};
export function isLikeC4LangiumDocument(doc) {
  return doc.textDocument.languageId === LikeC4LanguageMetaData.languageId;
}
export function isFqnIndexedDocument(doc) {
  return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !!doc.c4fqnIndex;
}
export function isParsedLikeC4LangiumDocument(doc) {
  return isLikeC4LangiumDocument(doc) && doc.state == DocumentState.Validated && !!doc.c4Specification && !!doc.c4Elements && !!doc.c4Relations && !!doc.c4Views && !!doc.c4fqnIndex && !!doc.c4Deployments && !!doc.c4DeploymentRelations;
}
export function* streamModel(doc) {
  const traverseStack = LinkedList.from(doc.parseResult.value.models.flatMap((m) => m.elements));
  const relations = [];
  let el;
  while (el = traverseStack.shift()) {
    if (ast.isRelation(el)) {
      relations.push(el);
      continue;
    }
    if (ast.isExtendElement(el)) {
      if (el.body && el.body.elements.length > 0) {
        for (const child of el.body.elements) {
          traverseStack.push(child);
        }
      }
      continue;
    }
    if (el.body && el.body.elements.length > 0) {
      for (const child of el.body.elements) {
        traverseStack.push(child);
      }
    }
    yield el;
  }
  yield* relations;
  return;
}
export function* streamDeploymentModel(doc) {
  const traverseStack = LinkedList.from(
    doc.parseResult.value.deployments.flatMap((m) => m.elements)
  );
  const relations = [];
  let el;
  while (el = traverseStack.shift()) {
    if (ast.isDeploymentRelation(el)) {
      relations.push(el);
      continue;
    }
    if (ast.isDeploymentNode(el) && el.body && el.body.elements.length > 0) {
      for (const child of el.body.elements) {
        traverseStack.push(child);
      }
    }
    yield el;
  }
  yield* relations;
  return;
}
export function resolveRelationPoints(node) {
  const target = elementRef(node.target);
  if (!target) {
    throw new Error("RelationRefError: Invalid reference to target");
  }
  if (isDefined(node.source)) {
    const source = elementRef(node.source);
    if (!source) {
      throw new Error("RelationRefError: Invalid reference to source");
    }
    return {
      source,
      target
    };
  }
  if (!ast.isElementBody(node.$container)) {
    throw new Error("RelationRefError: Invalid container for sourceless relation");
  }
  return {
    source: node.$container.$container,
    target
  };
}
export function parseAstOpacityProperty({ value }) {
  const opacity = parseFloat(value);
  return isNaN(opacity) ? 100 : clamp(opacity, { min: 0, max: 100 });
}
export function toElementStyle(props, isValid) {
  const result = {};
  if (!props || props.length === 0) {
    return result;
  }
  for (const prop of props) {
    if (!isValid(prop)) {
      continue;
    }
    switch (true) {
      case ast.isBorderProperty(prop): {
        if (isTruthy(prop.value)) {
          result.border = prop.value;
        }
        break;
      }
      case ast.isColorProperty(prop): {
        const color = toColor(prop);
        if (isTruthy(color)) {
          result.color = color;
        }
        break;
      }
      case ast.isShapeProperty(prop): {
        if (isTruthy(prop.value)) {
          result.shape = prop.value;
        }
        break;
      }
      case ast.isIconProperty(prop): {
        const icon = prop.libicon?.ref?.name ?? prop.value;
        if (isTruthy(icon)) {
          result.icon = icon;
        }
        break;
      }
      case ast.isOpacityProperty(prop): {
        result.opacity = parseAstOpacityProperty(prop);
        break;
      }
      case ast.isMultipleProperty(prop): {
        result.multiple = isBoolean(prop.value) ? prop.value : false;
        break;
      }
      default:
        nonexhaustive(prop);
    }
  }
  return result;
}
export function toRelationshipStyle(props, isValid) {
  const result = {};
  if (!props || props.length === 0) {
    return result;
  }
  for (const prop of props) {
    if (!isValid(prop)) {
      continue;
    }
    switch (true) {
      case ast.isColorProperty(prop): {
        const color = toColor(prop);
        if (isTruthy(color)) {
          result.color = color;
        }
        break;
      }
      case ast.isLineProperty(prop): {
        result.line = prop.value;
        break;
      }
      case ast.isArrowProperty(prop): {
        switch (prop.key) {
          case "head": {
            result.head = prop.value;
            break;
          }
          case "tail": {
            result.tail = prop.value;
            break;
          }
          default: {
            nonexhaustive(prop);
          }
        }
        break;
      }
      default: {
        nonexhaustive(prop);
      }
    }
  }
  return result;
}
export function toRelationshipStyleExcludeDefaults(props, isValid) {
  const { color, line, head, tail } = toRelationshipStyle(props?.filter(ast.isRelationshipStyleProperty), isValid);
  return {
    ...color && color !== DefaultRelationshipColor ? { color } : {},
    ...line && line !== DefaultLineStyle ? { line } : {},
    ...head && head !== DefaultArrowType ? { head } : {},
    ...tail ? { tail } : {}
  };
}
export function toColor(astNode) {
  return astNode?.themeColor ?? astNode?.customColor?.$refText;
}
export function toAutoLayout(rule) {
  const rankSep = rule.rankSep;
  const nodeSep = rule.nodeSep;
  let direction;
  switch (rule.direction) {
    case "TopBottom": {
      direction = "TB";
      break;
    }
    case "BottomTop": {
      direction = "BT";
      break;
    }
    case "LeftRight": {
      direction = "LR";
      break;
    }
    case "RightLeft": {
      direction = "RL";
      break;
    }
    default:
      nonexhaustive(rule.direction);
  }
  return {
    direction,
    ...nodeSep && { nodeSep },
    ...rankSep && { rankSep }
  };
}
export function toAstViewLayoutDirection(c4) {
  switch (c4) {
    case "TB": {
      return "TopBottom";
    }
    case "BT": {
      return "BottomTop";
    }
    case "LR": {
      return "LeftRight";
    }
    case "RL": {
      return "RightLeft";
    }
    default:
      nonexhaustive(c4);
  }
}
export function elementExpressionFromPredicate(predicate) {
  if (ast.isElementExpression(predicate)) {
    return predicate;
  }
  if (ast.isElementPredicateWhere(predicate)) {
    return predicate.subject;
  }
  if (ast.isElementPredicateWith(predicate)) {
    return elementExpressionFromPredicate(predicate.subject);
  }
  nonexhaustive(predicate);
}
