import type { Spec, UIElement } from "@json-render/core";

/**
 * Visitor function for spec traversal
 */
export interface TreeVisitor {
  (
    element: UIElement,
    key: string,
    depth: number,
    parent: UIElement | null,
  ): void;
}

/**
 * Traverse a UI spec depth-first
 */
export function traverseSpec(
  spec: Spec,
  visitor: TreeVisitor,
  startKey?: string,
): void {
  if (!spec || !spec.root) return;

  const rootKey = startKey ?? spec.root;
  const rootElement = spec.elements[rootKey];
  if (!rootElement) return;

  function visit(key: string, depth: number, parent: UIElement | null): void {
    const element = spec.elements[key];
    if (!element) return;

    visitor(element, key, depth, parent);

    if (element.children) {
      for (const childKey of element.children) {
        visit(childKey, depth + 1, element);
      }
    }
  }

  visit(rootKey, 0, null);
}

/**
 * Collect all unique component types used in a spec
 */
export function collectUsedComponents(spec: Spec): Set<string> {
  const components = new Set<string>();

  traverseSpec(spec, (element, _key) => {
    components.add(element.type);
  });

  return components;
}

/**
 * Collect all state paths referenced in a spec
 */
export function collectStatePaths(spec: Spec): Set<string> {
  const paths = new Set<string>();

  traverseSpec(spec, (element, _key) => {
    // Check props for data paths
    for (const [propName, propValue] of Object.entries(element.props)) {
      // Check for path props (e.g., valuePath, dataPath, bindPath)
      if (typeof propValue === "string") {
        if (
          propName.endsWith("Path") ||
          propName === "bindPath" ||
          propName === "statePath"
        ) {
          paths.add(propValue);
        }
      }

      // Check for dynamic value objects with path
      if (
        propValue &&
        typeof propValue === "object" &&
        "path" in propValue &&
        typeof (propValue as { path: unknown }).path === "string"
      ) {
        paths.add((propValue as { path: string }).path);
      }
    }

    // Check visibility conditions for paths
    if (element.visible && typeof element.visible === "object") {
      collectPathsFromCondition(element.visible, paths);
    }
  });

  return paths;
}

function collectPathsFromCondition(
  condition: unknown,
  paths: Set<string>,
): void {
  if (!condition || typeof condition !== "object") return;

  const cond = condition as Record<string, unknown>;

  if ("path" in cond && typeof cond.path === "string") {
    paths.add(cond.path);
  }

  if ("and" in cond && Array.isArray(cond.and)) {
    for (const sub of cond.and) {
      collectPathsFromCondition(sub, paths);
    }
  }

  if ("or" in cond && Array.isArray(cond.or)) {
    for (const sub of cond.or) {
      collectPathsFromCondition(sub, paths);
    }
  }

  if ("not" in cond) {
    collectPathsFromCondition(cond.not, paths);
  }

  // Check comparison operators
  for (const op of ["eq", "neq", "gt", "gte", "lt", "lte"]) {
    if (op in cond && Array.isArray(cond[op])) {
      for (const operand of cond[op] as unknown[]) {
        if (
          operand &&
          typeof operand === "object" &&
          "path" in operand &&
          typeof (operand as { path: unknown }).path === "string"
        ) {
          paths.add((operand as { path: string }).path);
        }
      }
    }
  }
}

/**
 * Collect all action names used in a spec
 */
export function collectActions(spec: Spec): Set<string> {
  const actions = new Set<string>();

  traverseSpec(spec, (element, _key) => {
    for (const propValue of Object.values(element.props)) {
      // Check for action prop (string action name)
      if (typeof propValue === "string" && propValue.startsWith("action:")) {
        actions.add(propValue.slice(7));
      }

      // Check for action objects
      if (
        propValue &&
        typeof propValue === "object" &&
        "name" in propValue &&
        typeof (propValue as { name: unknown }).name === "string"
      ) {
        actions.add((propValue as { name: string }).name);
      }
    }

    // Also check direct action prop
    const actionProp = element.props.action;
    if (typeof actionProp === "string") {
      actions.add(actionProp);
    }
  });

  return actions;
}
