import {
  allExtractors,
  collapseSvgContainers,
  parseAPIResponse,
  simplifyRawFigmaObject
} from "./chunk-5UQ3B3EM.js";
import {
  Logger,
  writeLogs
} from "./chunk-7KVK3LMR.js";

// src/mcp/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

// src/services/figma.ts
import path from "path";

// src/utils/image-processing.ts
import fs from "fs";
import sharp from "sharp";
async function applyCropTransform(imagePath, cropTransform) {
  const { Logger: Logger2 } = await import("./logger-ATYEW4KX.js");
  try {
    const scaleX = cropTransform[0]?.[0] ?? 1;
    const skewX = cropTransform[0]?.[1] ?? 0;
    const translateX = cropTransform[0]?.[2] ?? 0;
    const skewY = cropTransform[1]?.[0] ?? 0;
    const scaleY = cropTransform[1]?.[1] ?? 1;
    const translateY = cropTransform[1]?.[2] ?? 0;
    const image = sharp(imagePath);
    const metadata = await image.metadata();
    if (!metadata.width || !metadata.height) {
      throw new Error(`Could not get image dimensions for ${imagePath}`);
    }
    const { width, height } = metadata;
    const cropLeft = Math.max(0, Math.round(translateX * width));
    const cropTop = Math.max(0, Math.round(translateY * height));
    const cropWidth = Math.min(width - cropLeft, Math.round(scaleX * width));
    const cropHeight = Math.min(height - cropTop, Math.round(scaleY * height));
    if (cropWidth <= 0 || cropHeight <= 0) {
      Logger2.log(`Invalid crop dimensions for ${imagePath}, using original image`);
      return imagePath;
    }
    const tempPath = imagePath + ".tmp";
    await image.extract({
      left: cropLeft,
      top: cropTop,
      width: cropWidth,
      height: cropHeight
    }).toFile(tempPath);
    fs.renameSync(tempPath, imagePath);
    Logger2.log(`Cropped image saved (overwritten): ${imagePath}`);
    Logger2.log(
      `Crop region: ${cropLeft}, ${cropTop}, ${cropWidth}x${cropHeight} from ${width}x${height}`
    );
    return imagePath;
  } catch (error) {
    Logger2.error(`Error cropping image ${imagePath}:`, error);
    return imagePath;
  }
}
async function getImageDimensions(imagePath) {
  const { Logger: Logger2 } = await import("./logger-ATYEW4KX.js");
  try {
    const metadata = await sharp(imagePath).metadata();
    if (!metadata.width || !metadata.height) {
      throw new Error(`Could not get image dimensions for ${imagePath}`);
    }
    return {
      width: metadata.width,
      height: metadata.height
    };
  } catch (error) {
    Logger2.error(`Error getting image dimensions for ${imagePath}:`, error);
    return { width: 1e3, height: 1e3 };
  }
}
async function downloadAndProcessImage(fileName, localPath, imageUrl, needsCropping = false, cropTransform, requiresImageDimensions = false) {
  const { Logger: Logger2 } = await import("./logger-ATYEW4KX.js");
  const processingLog = [];
  const { downloadFigmaImage: downloadFigmaImage2 } = await import("./common-SNTHDYIQ.js");
  const originalPath = await downloadFigmaImage2(fileName, localPath, imageUrl);
  Logger2.log(`Downloaded original image: ${originalPath}`);
  const originalDimensions = await getImageDimensions(originalPath);
  Logger2.log(`Original dimensions: ${originalDimensions.width}x${originalDimensions.height}`);
  let finalPath = originalPath;
  let wasCropped = false;
  let cropRegion;
  if (needsCropping && cropTransform) {
    Logger2.log("Applying crop transform...");
    const scaleX = cropTransform[0]?.[0] ?? 1;
    const scaleY = cropTransform[1]?.[1] ?? 1;
    const translateX = cropTransform[0]?.[2] ?? 0;
    const translateY = cropTransform[1]?.[2] ?? 0;
    const cropLeft = Math.max(0, Math.round(translateX * originalDimensions.width));
    const cropTop = Math.max(0, Math.round(translateY * originalDimensions.height));
    const cropWidth = Math.min(
      originalDimensions.width - cropLeft,
      Math.round(scaleX * originalDimensions.width)
    );
    const cropHeight = Math.min(
      originalDimensions.height - cropTop,
      Math.round(scaleY * originalDimensions.height)
    );
    if (cropWidth > 0 && cropHeight > 0) {
      cropRegion = { left: cropLeft, top: cropTop, width: cropWidth, height: cropHeight };
      finalPath = await applyCropTransform(originalPath, cropTransform);
      wasCropped = true;
      Logger2.log(`Cropped to region: ${cropLeft}, ${cropTop}, ${cropWidth}x${cropHeight}`);
    } else {
      Logger2.log("Invalid crop dimensions, keeping original image");
    }
  }
  const finalDimensions = await getImageDimensions(finalPath);
  Logger2.log(`Final dimensions: ${finalDimensions.width}x${finalDimensions.height}`);
  let cssVariables;
  if (requiresImageDimensions) {
    cssVariables = generateImageCSSVariables(finalDimensions);
  }
  return {
    filePath: finalPath,
    originalDimensions,
    finalDimensions,
    wasCropped,
    cropRegion,
    cssVariables,
    processingLog
  };
}
function generateImageCSSVariables({
  width,
  height
}) {
  return `--original-width: ${width}px; --original-height: ${height}px;`;
}

// src/utils/fetch-with-retry.ts
import { execFile } from "child_process";
import { promisify } from "util";
var execFileAsync = promisify(execFile);
async function fetchWithRetry(url, options = {}) {
  try {
    const response = await fetch(url, options);
    console.log("Remaining:", response.headers.get("Retry-After"));
    console.log("X-Figma-Upgrade-Link:", response.headers.get("X-Figma-Upgrade-Link"));
    if (!response.ok) {
      throw new Error(`Fetch failed with status ${response.status}: ${response.statusText}`);
    }
    return await response.json();
  } catch (fetchError) {
    Logger.log(
      `[fetchWithRetry] Initial fetch failed for ${url}: ${fetchError.message}. Likely a corporate proxy or SSL issue. Attempting curl fallback.`
    );
    const curlHeaders = formatHeadersForCurl(options.headers);
    const curlArgs = ["-s", "-S", "--fail-with-body", "-L", ...curlHeaders, url];
    try {
      Logger.log(`[fetchWithRetry] Executing curl with args: ${JSON.stringify(curlArgs)}`);
      const { stdout, stderr } = await execFileAsync("curl", curlArgs);
      if (stderr) {
        if (!stdout || stderr.toLowerCase().includes("error") || stderr.toLowerCase().includes("fail")) {
          throw new Error(`Curl command failed with stderr: ${stderr}`);
        }
        Logger.log(
          `[fetchWithRetry] Curl command for ${url} produced stderr (but might be informational): ${stderr}`
        );
      }
      if (!stdout) {
        throw new Error("Curl command returned empty stdout.");
      }
      const result = JSON.parse(stdout);
      if (result.status && result.status !== 200) {
        throw new Error(`Curl command failed: ${result}`);
      }
      return result;
    } catch (curlError) {
      Logger.error(`[fetchWithRetry] Curl fallback also failed for ${url}: ${curlError.message}`);
      throw fetchError;
    }
  }
}
function formatHeadersForCurl(headers) {
  if (!headers) {
    return [];
  }
  const headerArgs = [];
  for (const [key, value] of Object.entries(headers)) {
    headerArgs.push("-H", `${key}: ${value}`);
  }
  return headerArgs;
}

// src/services/figma.ts
var FigmaService = class {
  apiKey;
  oauthToken;
  useOAuth;
  baseUrl = "https://api.figma.com/v1";
  constructor({ figmaApiKey, figmaOAuthToken, useOAuth }) {
    this.apiKey = figmaApiKey || "";
    this.oauthToken = figmaOAuthToken || "";
    this.useOAuth = !!useOAuth && !!this.oauthToken;
  }
  getAuthHeaders() {
    if (this.useOAuth) {
      Logger.log("Using OAuth Bearer token for authentication");
      return { Authorization: `Bearer ${this.oauthToken}` };
    } else {
      Logger.log("Using Personal Access Token for authentication");
      return { "X-Figma-Token": this.apiKey };
    }
  }
  /**
   * Filters out null values from Figma image responses. This ensures we only work with valid image URLs.
   */
  filterValidImages(images) {
    if (!images) return {};
    return Object.fromEntries(Object.entries(images).filter(([, value]) => !!value));
  }
  async request(endpoint) {
    try {
      Logger.log(`Calling ${this.baseUrl}${endpoint}`);
      const headers = this.getAuthHeaders();
      return await fetchWithRetry(`${this.baseUrl}${endpoint}`, {
        headers
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      throw new Error(
        `Failed to make request to Figma API endpoint '${endpoint}': ${errorMessage}`
      );
    }
  }
  /**
   * Builds URL query parameters for SVG image requests.
   */
  buildSvgQueryParams(svgIds, svgOptions) {
    const params = new URLSearchParams({
      ids: svgIds.join(","),
      format: "svg",
      svg_outline_text: String(svgOptions.outlineText),
      svg_include_id: String(svgOptions.includeId),
      svg_simplify_stroke: String(svgOptions.simplifyStroke)
    });
    return params.toString();
  }
  /**
   * Gets download URLs for image fills without downloading them.
   *
   * @returns Map of imageRef to download URL
   */
  async getImageFillUrls(fileKey) {
    const endpoint = `/files/${fileKey}/images`;
    const response = await this.request(endpoint);
    return response.meta.images || {};
  }
  /**
   * Gets download URLs for rendered nodes without downloading them.
   *
   * @returns Map of node ID to download URL
   */
  async getNodeRenderUrls(fileKey, nodeIds, format, options = {}) {
    if (nodeIds.length === 0) return {};
    if (format === "png") {
      const scale = options.pngScale || 2;
      const endpoint = `/images/${fileKey}?ids=${nodeIds.join(",")}&format=png&scale=${scale}`;
      const response = await this.request(endpoint);
      return this.filterValidImages(response.images);
    } else {
      const svgOptions = options.svgOptions || {
        outlineText: true,
        includeId: false,
        simplifyStroke: true
      };
      const params = this.buildSvgQueryParams(nodeIds, svgOptions);
      const endpoint = `/images/${fileKey}?${params}`;
      const response = await this.request(endpoint);
      return this.filterValidImages(response.images);
    }
  }
  /**
   * Download images method with post-processing support for cropping and returning image dimensions.
   *
   * Supports:
   * - Image fills vs rendered nodes (based on imageRef vs nodeId)
   * - PNG vs SVG format (based on filename extension)
   * - Image cropping based on transform matrices
   * - CSS variable generation for image dimensions
   *
   * @returns Array of local file paths for successfully downloaded images
   */
  async downloadImages(fileKey, localPath, items, options = {}) {
    if (items.length === 0) return [];
    const sanitizedPath = path.normalize(localPath).replace(/^(\.\.(\/|\\|$))+/, "");
    const resolvedPath = path.resolve(sanitizedPath);
    if (!resolvedPath.startsWith(path.resolve(process.cwd()))) {
      throw new Error("Invalid path specified. Directory traversal is not allowed.");
    }
    const { pngScale = 2, svgOptions } = options;
    const downloadPromises = [];
    const imageFills = items.filter(
      (item) => !!item.imageRef
    );
    const renderNodes = items.filter(
      (item) => !!item.nodeId
    );
    if (imageFills.length > 0) {
      const fillUrls = await this.getImageFillUrls(fileKey);
      const fillDownloads = imageFills.map(({ imageRef, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
        const imageUrl = fillUrls[imageRef];
        return imageUrl ? downloadAndProcessImage(
          fileName,
          resolvedPath,
          imageUrl,
          needsCropping,
          cropTransform,
          requiresImageDimensions
        ) : null;
      }).filter((promise) => promise !== null);
      if (fillDownloads.length > 0) {
        downloadPromises.push(Promise.all(fillDownloads));
      }
    }
    if (renderNodes.length > 0) {
      const pngNodes = renderNodes.filter((node) => !node.fileName.toLowerCase().endsWith(".svg"));
      const svgNodes = renderNodes.filter((node) => node.fileName.toLowerCase().endsWith(".svg"));
      if (pngNodes.length > 0) {
        const pngUrls = await this.getNodeRenderUrls(
          fileKey,
          pngNodes.map((n) => n.nodeId),
          "png",
          { pngScale }
        );
        const pngDownloads = pngNodes.map(({ nodeId, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
          const imageUrl = pngUrls[nodeId];
          return imageUrl ? downloadAndProcessImage(
            fileName,
            resolvedPath,
            imageUrl,
            needsCropping,
            cropTransform,
            requiresImageDimensions
          ) : null;
        }).filter((promise) => promise !== null);
        if (pngDownloads.length > 0) {
          downloadPromises.push(Promise.all(pngDownloads));
        }
      }
      if (svgNodes.length > 0) {
        const svgUrls = await this.getNodeRenderUrls(
          fileKey,
          svgNodes.map((n) => n.nodeId),
          "svg",
          { svgOptions }
        );
        const svgDownloads = svgNodes.map(({ nodeId, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
          const imageUrl = svgUrls[nodeId];
          return imageUrl ? downloadAndProcessImage(
            fileName,
            resolvedPath,
            imageUrl,
            needsCropping,
            cropTransform,
            requiresImageDimensions
          ) : null;
        }).filter((promise) => promise !== null);
        if (svgDownloads.length > 0) {
          downloadPromises.push(Promise.all(svgDownloads));
        }
      }
    }
    const results = await Promise.all(downloadPromises);
    return results.flat();
  }
  /**
   * Get raw Figma API response for a file (for use with flexible extractors)
   */
  async getRawFile(fileKey, depth) {
    const endpoint = `/files/${fileKey}${depth ? `?depth=${depth}` : ""}`;
    Logger.log(`Retrieving raw Figma file: ${fileKey} (depth: ${depth ?? "default"})`);
    const response = await this.request(endpoint);
    writeLogs("figma-raw.json", response);
    return response;
  }
  /**
   * Get raw Figma API response for specific nodes (for use with flexible extractors)
   */
  async getRawNode(fileKey, nodeId, depth) {
    const endpoint = `/files/${fileKey}/nodes?ids=${nodeId}${depth ? `&depth=${depth}` : ""}`;
    Logger.log(
      `Retrieving raw Figma node: ${nodeId} from ${fileKey} (depth: ${depth ?? "default"})`
    );
    const response = await this.request(endpoint);
    writeLogs("figma-raw.json", response);
    return response;
  }
};

// src/mcp/tools/get-figma-data-tool.ts
import { z } from "zod";

// src/utils/gradient.ts
var GradientConverter = class {
  // 匹配 linear-gradient(角度deg, rgba(r,g,b,a) 位置%, rgba(r,g,b,a) 位置%)
  static CSS_GRADIENT_REGEX = /linear-gradient\((\d+)deg,\s*rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)\s*(\d+)%,\s*rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)\s*(\d+)%\)/;
  /**
   * 将角度转换为 Android Orientation 枚举对应的字符串
   */
  static angleToDirection(angle) {
    const normalizedAngle = (angle % 360 + 360) % 360;
    if (normalizedAngle >= 337.5 || normalizedAngle < 22.5) {
      return "t2b" /* TOP_BOTTOM */;
    } else if (normalizedAngle >= 22.5 && normalizedAngle < 67.5) {
      return "tl2br" /* TL_BR */;
    } else if (normalizedAngle >= 67.5 && normalizedAngle < 112.5) {
      return "l2r" /* LEFT_RIGHT */;
    } else if (normalizedAngle >= 112.5 && normalizedAngle < 157.5) {
      return "bl2tr" /* BL_TR */;
    } else if (normalizedAngle >= 157.5 && normalizedAngle < 202.5) {
      return "b2t" /* BOTTOM_TOP */;
    } else if (normalizedAngle >= 202.5 && normalizedAngle < 247.5) {
      return "br2tl" /* BR_TL */;
    } else if (normalizedAngle >= 247.5 && normalizedAngle < 292.5) {
      return "r2l" /* RIGHT_LEFT */;
    } else {
      return "tr2bl" /* TR_BL */;
    }
  }
  /**
   * 将 RGBA 颜色转换为 8 位 HEX 颜色 (带透明度)
   */
  static rgbaToHex8(r, g, b, a) {
    const toHex = (value) => {
      const hex = Math.round(value).toString(16);
      return hex.length === 1 ? "0" + hex : hex;
    };
    if (a == 1) {
      return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
    }
    const alpha = Math.round(a * 255);
    return `#${toHex(r)}${toHex(g)}${toHex(b)}${toHex(alpha)}`.toUpperCase();
  }
  /**
   * 解析 CSS 渐变字符串并转换为自定义格式
   */
  static convertToCustomFormat(cssGradient) {
    if (!cssGradient?.trim()) {
      throw new Error("\u6E10\u53D8\u5B57\u7B26\u4E32\u4E0D\u80FD\u4E3A\u7A7A");
    }
    const gradient = this.simplifyGradient(cssGradient).simplified;
    const match = gradient.trim().match(this.CSS_GRADIENT_REGEX);
    if (!match) {
      throw new Error("\u65E0\u6548\u7684 CSS \u6E10\u53D8\u683C\u5F0F : " + cssGradient);
    }
    try {
      const angle = parseInt(match[1], 10);
      const startR = parseInt(match[2], 10);
      const startG = parseInt(match[3], 10);
      const startB = parseInt(match[4], 10);
      const startA = parseFloat(match[5]);
      const endR = parseInt(match[7], 10);
      const endG = parseInt(match[8], 10);
      const endB = parseInt(match[9], 10);
      const endA = parseFloat(match[10]);
      const direction = this.angleToDirection(angle);
      const startColorHex = this.rgbaToHex8(startR, startG, startB, startA);
      const endColorHex = this.rgbaToHex8(endR, endG, endB, endA);
      return `\${gradient:linear("${direction}","${startColorHex}","${endColorHex}")}`;
    } catch (error) {
      throw new Error(`\u89E3\u6790\u6E10\u53D8\u5B57\u7B26\u4E32\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`);
    }
  }
  /**
   * 批量转换多个渐变字符串
   */
  static convertMultiple(gradients) {
    return gradients.map((original) => {
      try {
        const converted = this.convertToCustomFormat(original);
        return { original, converted };
      } catch (error) {
        return {
          original,
          converted: `\u9519\u8BEF: ${error instanceof Error ? error.message : "\u8F6C\u6362\u5931\u8D25"}`
        };
      }
    });
  }
  //
  /**
  * 高级渐变解析，支持各种边界情况
  */
  static parseGradientStopsAdvanced(gradientString) {
    if (!gradientString.includes("gradient")) {
      throw new Error("\u4E0D\u662F\u6709\u6548\u7684\u6E10\u53D8\u5B57\u7B26\u4E32");
    }
    const cleaned = gradientString.replace(/\s+/g, " ").trim();
    const colorStopRegex = /(rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*[\d.]+)?\s*\))\s*(\d+(?:\.\d+)?%)/gi;
    const colorStops = [];
    let match;
    colorStopRegex.lastIndex = 0;
    while ((match = colorStopRegex.exec(cleaned)) !== null) {
      colorStops.push({
        color: match[1].replace(/\s*,\s*/g, ", "),
        // 标准化颜色格式
        position: match[2]
      });
    }
    if (colorStops.length === 0) {
      console.warn("\u8B66\u544A\uFF1A\u672A\u89E3\u6790\u5230\u4EFB\u4F55\u989C\u8272\u505C\u6B62\u70B9", gradientString);
    }
    return colorStops;
  }
  /**
   * 简化渐变，支持任意数量的颜色
   */
  static simplifyGradient(gradientString) {
    const colorStops = this.parseGradientStopsAdvanced(gradientString);
    const originalColorCount = colorStops.length;
    if (originalColorCount === 0) {
      return {
        original: gradientString,
        simplified: gradientString,
        firstColor: "",
        lastColor: "",
        originalColorCount: 0,
        removedColorCount: 0
      };
    }
    const gradientInfo = this.extractGradientInfo(gradientString);
    if (originalColorCount <= 2) {
      return {
        original: gradientString,
        simplified: gradientString,
        firstColor: colorStops[0].color,
        lastColor: colorStops[colorStops.length - 1].color,
        originalColorCount,
        removedColorCount: 0
      };
    }
    const firstStop = colorStops[0];
    const lastStop = colorStops[colorStops.length - 1];
    const simplifiedGradient = this.buildSimplifiedGradient(
      gradientInfo.type,
      gradientInfo.direction,
      firstStop,
      lastStop
    );
    return {
      original: gradientString,
      simplified: simplifiedGradient,
      firstColor: firstStop.color,
      lastColor: lastStop.color,
      originalColorCount,
      removedColorCount: originalColorCount - 2
    };
  }
  /**
   * 提取渐变类型和方向信息
   */
  static extractGradientInfo(gradientString) {
    const typeMatch = gradientString.match(/^([a-zA-Z-]+)\(/);
    const type = typeMatch ? typeMatch[1] : "linear-gradient";
    const content = gradientString.substring(gradientString.indexOf("(") + 1);
    const firstComma = content.indexOf(",");
    const direction = firstComma !== -1 ? content.substring(0, firstComma).trim() : "180deg";
    return { type, direction };
  }
  /**
   * 构建简化后的渐变字符串
   */
  static buildSimplifiedGradient(type, direction, firstStop, lastStop) {
    return `${type}(${direction}, ${firstStop.color} ${firstStop.position}, ${lastStop.color} ${lastStop.position})`;
  }
  /**
   * 批量处理渐变数组
   */
  static simplifyGradients(gradients) {
    return gradients.map((gradient) => this.simplifyGradient(gradient));
  }
  /**
   * 检查渐变是否需要简化
   */
  static needsSimplification(gradientString) {
    try {
      const colorStops = this.parseGradientStopsAdvanced(gradientString);
      return colorStops.length > 2;
    } catch {
      return false;
    }
  }
  /**
   * 获取渐变统计信息
   */
  static getGradientStats(gradientString) {
    try {
      const colorStops = this.parseGradientStopsAdvanced(gradientString);
      const gradientInfo = this.extractGradientInfo(gradientString);
      return {
        colorCount: colorStops.length,
        needsSimplification: colorStops.length > 2,
        type: gradientInfo.type
      };
    } catch (error) {
      return {
        colorCount: 0,
        needsSimplification: false,
        type: "unknown"
      };
    }
  }
};

// src/mcp/tools/get-figma-to-dsl.ts
var FigmaToDSLConverter = class {
  counter = 0;
  globalStyles;
  currentTimestamp;
  nodeCache = /* @__PURE__ */ new Map();
  constructor(globalStyles) {
    this.globalStyles = globalStyles;
    this.currentTimestamp = Date.now().toString().slice(-6);
  }
  convertRoot(nodes) {
    if (!nodes || nodes.length === 0) {
      throw new Error("No nodes provided");
    }
    const rootNode = nodes[0];
    return this.convertNode(null, rootNode);
  }
  convertNode(parentNode, node) {
    const cacheKey = this.generateCacheKey(node);
    if (this.nodeCache.has(cacheKey)) {
      return this.nodeCache.get(cacheKey);
    }
    const dslNode = {
      type: this.mapNodeType(node.type),
      attrs: this.extractAttributes(parentNode, node)
    };
    if (dslNode.type && dslNode.attrs.flexDirection) {
      const type = dslNode.type;
      const flexDirection = dslNode.attrs.flexDirection;
      if (type != flexDirection) {
        dslNode.type = flexDirection;
      }
    }
    delete dslNode.attrs.flexDirection;
    if (node.children && node.children.length > 0) {
      const children = this.convertChildren(dslNode, node.children);
      if (dslNode.attrs.gap && dslNode.type) {
        dslNode.children = this.applyGapToChildren(
          children,
          dslNode.attrs.gap,
          dslNode.type
        );
        delete dslNode.attrs.gap;
      } else {
        dslNode.children = children;
      }
    }
    this.nodeCache.set(cacheKey, dslNode);
    return dslNode;
  }
  handleBorderRadius(parentNode, node) {
    const { attrs: pAttrs } = parentNode || {};
    const { attrs: dAttrs } = node;
    const hasNoBorderRadius = !dAttrs.borderRadius && !dAttrs.borderLeftTopRadius && !dAttrs.borderRightTopRadius && !dAttrs.borderLeftBottomRadius && !dAttrs.borderRightBottomRadius;
    const hasBackground = dAttrs.background || dAttrs.backgroundColor;
    const sameSize = dAttrs.width === pAttrs?.width && dAttrs.height === pAttrs?.height;
    if (pAttrs && hasNoBorderRadius && hasBackground && sameSize) {
      const radiusProps = [
        "borderRadius",
        "borderLeftTopRadius",
        "borderRightTopRadius",
        "borderLeftBottomRadius",
        "borderRightBottomRadius"
      ];
      radiusProps.forEach((prop) => {
        if (pAttrs[prop]) {
          dAttrs[prop] = pAttrs[prop];
        }
      });
    }
  }
  // 新增方法：处理子节点的 gap
  applyGapToChildren(children, gap, layoutMode) {
    if (!children || children.length === 0 || gap <= 0) {
      return children;
    }
    return children.map((child, index) => {
      if (index === 0) {
        return child;
      }
      if (layoutMode === "row") {
        child.attrs.marginLeft = gap;
      } else if (layoutMode === "column") {
        child.attrs.marginTop = gap;
      }
      return child;
    });
  }
  convertChildren(parentNode, children) {
    const filterNodes = children.filter((child) => child.type !== "GROUP");
    return filterNodes.map((child) => this.convertNode(parentNode, child));
  }
  generateCacheKey(node) {
    return `${node.id}_${node.type}_${node.layout || ""}_${node.textStyle || ""}`;
  }
  mapNodeType(originalType) {
    const typeMap = {
      "FRAME": "column",
      "RECTANGLE": "Image",
      "TEXT": "Text",
      "INSTANCE": "Image",
      "GROUP": "GROUP",
      "BOOLEAN_OPERATION": "column",
      "ELLIPSE": "column",
      "IMAGE-SVG": "column"
    };
    return typeMap[originalType] || "div";
  }
  extractAttributes(parentNode, node) {
    const attrs = {
      name: this.generateName(node)
    };
    try {
      this.extractLayoutAttributes(parentNode, node, attrs);
    } catch (error) {
      Logger.error(error);
      throw new Error("extractLayoutAttributes: ");
    }
    try {
      this.extractTextAttributes(node, attrs);
    } catch (error) {
      Logger.error(error);
      throw new Error("extractTextAttributes: ");
    }
    try {
      this.extractFillAttributes(node, attrs);
    } catch (error) {
      Logger.error(error);
      throw new Error("extractFillAttributes: ");
    }
    try {
      this.extractBorderAttributes(node, attrs);
    } catch (error) {
      Logger.error(error);
      throw new Error("extractBorderAttributes: ");
    }
    try {
      this.extractEffectAttributes(node, attrs);
    } catch (error) {
      Logger.error(error);
      throw new Error("extractEffectAttributes: ");
    }
    try {
      this.extractSpecialAttributes(parentNode, node, attrs);
    } catch (error) {
      Logger.error(error);
      throw new Error("extractSpecialAttributes: ");
    }
    return this.normalizeAttributes(attrs);
  }
  extractLayoutAttributes(parentNode, node, attrs) {
    if (!node.layout || !this.globalStyles[node.layout]) return;
    const layoutStyle = this.globalStyles[node.layout];
    if (layoutStyle.dimensions) {
      if (layoutStyle.dimensions.width) {
        attrs.width = Math.round(layoutStyle.dimensions.width);
      }
      if (layoutStyle.dimensions.height) {
        attrs.height = Math.round(layoutStyle.dimensions.height);
      }
    }
    if (layoutStyle.locationRelativeToParent) {
      attrs.position = "absolute";
      attrs.left = Math.round(layoutStyle.locationRelativeToParent.x);
      attrs.top = Math.round(layoutStyle.locationRelativeToParent.y);
    } else if (layoutStyle.position) {
      attrs.position = layoutStyle.position;
    }
    if (layoutStyle.mode) {
      this.handleLayoutMode(layoutStyle, attrs);
    }
    if (layoutStyle.padding) {
      this.handlePadding(layoutStyle.padding, attrs);
    }
    if (layoutStyle.gap) {
      this.handleGap(node, layoutStyle, attrs);
    }
  }
  /**
  * 判断元素是否需要绝对定位
  */
  shouldUseAbsolutePosition(parentNode, node, globalStyles) {
    if (!parentNode) return true;
    if (node.type == "TEXT") {
      Logger.error("shouldUseAbsolutePosition: " + parentNode.type + " , " + node.text + " , " + globalStyles.locationRelativeToParent);
      if (parentNode.type == "row" || parentNode.type == "column") {
        return false;
      }
      const needsExplicitPositioning = this.needsExplicitPositioning(globalStyles);
      return needsExplicitPositioning;
    } else {
      return true;
    }
  }
  /**
    * 判断是否需要显式定位
    */
  needsExplicitPositioning(globalStyles) {
    if (globalStyles.locationRelativeToParent && globalStyles.locationRelativeToParent.x !== 0 && globalStyles.locationRelativeToParent.y !== 0) {
      return true;
    }
    if (globalStyles.mode === "none" && globalStyles.sizing?.horizontal === "fixed" && globalStyles.sizing?.vertical === "fixed") {
      return true;
    }
    return false;
  }
  handleLayoutMode(layoutStyle, attrs) {
    const mode = layoutStyle.mode;
    if (mode === "row" || mode === "column") {
      attrs.flexDirection = mode === "row" ? "row" : "column";
    }
    if (layoutStyle.alignItems) {
      attrs.alignItems = this.mapAlignItems(layoutStyle.alignItems);
    }
    if (layoutStyle.justifyContent) {
      attrs.justifyContent = this.mapJustifyContent(layoutStyle.justifyContent);
    }
    if (layoutStyle.marginLeft !== void 0 && layoutStyle.marginLeft !== null) {
      attrs.marginLeft = layoutStyle.marginLeft;
    }
    if (layoutStyle.marginTop !== void 0 && layoutStyle.marginTop !== null) {
      attrs.marginTop = layoutStyle.marginTop;
    }
  }
  handlePadding(padding, attrs) {
    const paddingValues = padding.split(" ").map((val) => parseInt(val));
    if (paddingValues.length === 1) {
      attrs.padding = paddingValues[0];
    } else if (paddingValues.length === 2) {
      attrs.paddingTop = paddingValues[0];
      attrs.paddingBottom = paddingValues[0];
      attrs.paddingRight = paddingValues[1];
      attrs.paddingLeft = paddingValues[1];
    } else if (paddingValues.length === 4) {
      attrs.paddingTop = paddingValues[0];
      attrs.paddingRight = paddingValues[1];
      attrs.paddingBottom = paddingValues[2];
      attrs.paddingLeft = paddingValues[3];
    }
  }
  handleGap(node, layoutStyle, attrs) {
    const gapValue = parseInt(layoutStyle.gap);
    if (!isNaN(gapValue)) {
      attrs.gap = gapValue;
    }
  }
  extractTextAttributes(node, attrs) {
    if (node.text) {
      attrs.text = node.text;
    }
    if (!node.textStyle || !this.globalStyles[node.textStyle]) return;
    const textStyle = this.globalStyles[node.textStyle];
    if (textStyle.fontSize) {
      attrs.fontSize = Math.round(textStyle.fontSize);
    }
    if (textStyle.fontWeight) {
      if (textStyle.fontWeight > 400) {
        attrs.textStyle = "bold";
      }
      delete attrs.fontWeight;
    }
    if (textStyle.fontFamily) {
      attrs.typeface = textStyle.fontFamily;
    }
    if (textStyle.textLineHeight) {
      attrs.lineHeight = this.parseLineHeight(textStyle.textLineHeight);
    }
    if (textStyle.textAlignHorizontal) {
      attrs.textAlign = this.mapTextAlign(textStyle.textAlignHorizontal);
    }
    if (textStyle.letterSpacing) {
      attrs.letterSpacing = this.parseLetterSpacing(textStyle.letterSpacing);
    }
  }
  extractFillAttributes(node, attrs) {
    if (!node.fills) return;
    if (!node.fills || !this.globalStyles[node.fills]) return;
    const layoutFills = this.globalStyles[node.fills];
    if (this.mapNodeType(node.type) == "Text") {
      if (Array.isArray(layoutFills)) {
        const firstFill = layoutFills[0];
        if (typeof firstFill === "string") {
          attrs.textColor = this.normalizeColor(firstFill);
        } else if (firstFill && typeof firstFill === "object") {
          if (firstFill.colors && Array.isArray(firstFill.colors)) {
            attrs.textColor = this.normalizeColor(firstFill.colors[0]);
          } else if (firstFill.type == "GRADIENT_LINEAR" && firstFill.gradient) {
            attrs.textColor = GradientConverter.convertToCustomFormat(firstFill.gradient);
          }
        }
      } else if (typeof layoutFills === "string") {
        attrs.textColor = this.normalizeColor(node.fills);
      }
    } else {
      if (Array.isArray(layoutFills)) {
        const firstFill = layoutFills[0];
        if (typeof firstFill === "string") {
          attrs.backgroundColor = this.normalizeColor(firstFill);
        } else if (firstFill && typeof firstFill === "object") {
          this.handleComplexFill(firstFill, attrs);
        }
      } else if (typeof layoutFills === "string") {
        attrs.backgroundColor = this.normalizeColor(node.fills);
      }
    }
  }
  handleComplexFill(fill, attrs) {
    if (fill.type === "GRADIENT_LINEAR" && fill.gradient) {
      attrs.background = GradientConverter.convertToCustomFormat(fill.gradient);
    }
    if (fill.type == "IMAGE" && fill.src) {
      attrs.src = fill.src;
    }
    if (fill.colors && Array.isArray(fill.colors)) {
      attrs.backgroundColor = this.normalizeColor(fill.colors[0]);
    }
  }
  extractBorderAttributes(node, attrs) {
    if (node.borderRadius) {
      const radius = parseInt(node.borderRadius);
      if (!isNaN(radius)) {
        attrs.borderRadius = Math.round(radius);
      }
    }
    if (node.strokes) {
      this.handleStrokes(node, attrs);
    }
  }
  /**
   * 获取figma strokes属性
   * @param node 
   * @param attrs 
   * @returns 
   */
  handleStrokes(node, attrs) {
    if (!node.strokes) return;
    if (!node.strokes || !this.globalStyles[node.strokes]) return;
    const strokes = this.globalStyles[node.strokes];
    if (typeof strokes === "object" && strokes.colors) {
      attrs.borderColor = this.normalizeColor(strokes.colors[0]);
      if (strokes.strokeWeight) {
        const weight = parseFloat(strokes.strokeWeight);
        if (!isNaN(weight)) {
          attrs.borderWidth = weight;
        }
      }
    }
  }
  extractEffectAttributes(node, attrs) {
    if (!node.effects || !this.globalStyles[node.effects]) return;
    const effectStyle = this.globalStyles[node.effects];
  }
  extractSpecialAttributes(parentNode, node, attrs) {
    if (node.opacity !== void 0) {
      if (attrs.backgroundColor) {
        attrs.backgroundColor = this.colorWithOpacitySimple(attrs.backgroundColor, node.opacity);
      }
      if (attrs.textColor) {
        attrs.textColor = this.colorWithOpacitySimple(attrs.textColor, node.opacity);
      }
    }
    if (node.type == "TEXT") {
      if (parentNode && parentNode.attrs && parentNode.attrs.height && attrs.height) {
        if (attrs.height >= parentNode.attrs.height) {
          delete attrs.height;
        }
        if (parentNode && parentNode.attrs && parentNode.attrs.width && attrs.width) {
          if (attrs.width >= parentNode.attrs.width) {
            delete attrs.width;
          }
        }
      }
    }
  }
  colorWithOpacitySimple(color, opacity) {
    if (opacity < 0 || opacity > 1) {
      throw new Error("Opacity must be between 0 and 1");
    }
    const alpha = Math.round(opacity * 255);
    const alphaHex = alpha.toString(16).padStart(2, "0").toUpperCase();
    if (color.startsWith("#")) {
      const hex = color.replace("#", "");
      const fullHex = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
      if (fullHex.length === 6) {
        return `#${fullHex}${alphaHex}`;
      }
    }
    if (color.startsWith("rgb")) {
      const match = color.match(/(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
      if (match) {
        const [_, r, g, b] = match;
        const rHex = parseInt(r).toString(16).padStart(2, "0");
        const gHex = parseInt(g).toString(16).padStart(2, "0");
        const bHex = parseInt(b).toString(16).padStart(2, "0");
        return `#${rHex}${gHex}${bHex}${alphaHex}`.toUpperCase();
      }
    }
    throw new Error("Unsupported color format");
  }
  generateName(node) {
    this.counter++;
    return `${this.currentTimestamp}_${this.mapNodeType(node.type)}_${this.counter}`;
  }
  normalizeColor(color) {
    if (typeof color === "object" && color) {
      if (color.type == "GRADIENT_LINEAR") {
        return GradientConverter.convertToCustomFormat(color.gradient);
      }
    } else {
      if (color.startsWith("rgba")) {
        const rgbaRegex = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*([\d.]+)\s*)?\)/i;
        const match = color.match(rgbaRegex);
        if (match) {
          return color;
        }
      } else if (color.startsWith("rgb")) {
        return color;
      }
    }
    return color.startsWith("#") ? color : "#000000";
  }
  toHex(value) {
    return value.toString(16).padStart(2, "0").toUpperCase();
  }
  parseLineHeight(lineHeight) {
    return parseInt(lineHeight);
  }
  parseLetterSpacing(letterSpacing) {
    if (letterSpacing.includes("%")) {
      const percentValue = parseFloat(letterSpacing) / 100;
      return parseFloat(percentValue.toFixed(2));
    }
    return parseFloat(letterSpacing) || 0;
  }
  mapAlignItems(alignment) {
    const alignmentMap = {
      "center": "center",
      "flex-start": "flex-start",
      "flex-end": "flex-end",
      "stretch": "stretch"
    };
    return alignmentMap[alignment] || "flex-start";
  }
  mapJustifyContent(justify) {
    const justifyMap = {
      "center": "center",
      "flex-start": "flex-start",
      "flex-end": "flex-end",
      "space-between": "space-between",
      "space-around": "space-around"
    };
    return justifyMap[justify] || "flex-start";
  }
  mapTextAlign(alignment) {
    const alignmentMap = {
      "LEFT": "left",
      "CENTER": "center",
      "RIGHT": "right",
      "JUSTIFY": "justify"
    };
    return alignmentMap[alignment] || "left";
  }
  normalizeAttributes(attrs) {
    const normalized = {};
    for (const [key, value] of Object.entries(attrs)) {
      if (typeof value === "number") {
        normalized[key] = Math.round(value);
      } else if (key === "letterSpacing" && typeof value === "string" && value.includes("%")) {
        normalized[key] = this.parseLetterSpacing(value);
      } else {
        normalized[key] = value;
      }
    }
    return normalized;
  }
};
function convertFigmaToDSL(nodes, globalStyles) {
  const converter = new FigmaToDSLConverter(globalStyles);
  return converter.convertRoot(nodes);
}
function parseFigmaJSON(jsonString) {
  try {
    const data = JSON.parse(jsonString);
    return {
      nodes: data.nodes || [],
      globalStyles: data.globalVars?.styles || {}
    };
  } catch (error) {
    throw new Error("Invalid JSON format");
  }
}

// src/mcp/tools/get-figma-data-tool.ts
import "js-yaml";

// src/utils/dsl-page-colletion.ts
var DSLCollection = class _DSLCollection {
  items;
  nextId;
  constructor(initialData) {
    this.items = /* @__PURE__ */ new Map();
    this.nextId = 0;
    if (initialData) {
      this.loadInitialData(initialData);
    }
  }
  /**
   * 添加 DSL 数据
   */
  add(data) {
    const id = this.nextId++;
    this.items.set(id, data);
    return id;
  }
  /**
   * 添加带有指定 ID 的数据
   */
  addWithId(id, data) {
    if (this.items.has(id)) {
      throw new Error(`ID ${id} \u5DF2\u5B58\u5728`);
    }
    this.items.set(id, data);
    if (id >= this.nextId) {
      this.nextId = id + 1;
    }
  }
  /**
   * 获取 DSL 数据
   */
  get(id) {
    return this.items.get(id);
  }
  /**
   * 更新 DSL 数据
   */
  update(id, data) {
    if (!this.items.has(id)) {
      return false;
    }
    this.items.set(id, data);
    return true;
  }
  /**
   * 删除 DSL 数据
   */
  delete(id) {
    return this.items.delete(id);
  }
  /**
   * 检查是否存在指定 ID
   */
  has(id) {
    return this.items.has(id);
  }
  /**
   * 获取所有 ID
   */
  getIds() {
    return Array.from(this.items.keys());
  }
  /**
   * 获取所有数据
   */
  getAll() {
    return new Map(this.items);
  }
  /**
   * 清空集合
   */
  clear() {
    this.items.clear();
    this.nextId = 0;
  }
  /**
   * 获取集合大小
   */
  get size() {
    return this.items.size;
  }
  /**
   * 转换为目标 JSON 结构
   */
  toJSON() {
    const result = [];
    const sortedIds = Array.from(this.items.keys()).sort((a, b) => a - b);
    for (const id of sortedIds) {
      const data = this.items.get(id);
      if (data) {
        try {
          const dslString = this.safeStringify(data);
          result.push({ id, dsl: dslString });
        } catch (error) {
          console.error(`ID ${id} \u7684\u6570\u636E\u8F6C\u6362\u5931\u8D25:`, error);
          result.push({ id, dsl: "{}" });
        }
      }
    }
    return result;
  }
  /**
   * 安全的 JSON 序列化，处理转义和循环引用
   */
  safeStringify(obj) {
    try {
      return JSON.stringify(obj, this.getCircularReplacer());
    } catch (error) {
      console.warn("JSON.stringify \u5931\u8D25\uFF0C\u4F7F\u7528\u5907\u7528\u65B9\u6848:", error);
      return this.fallbackStringify(obj);
    }
  }
  /**
   * 处理循环引用的 replacer
   */
  getCircularReplacer() {
    const seen = /* @__PURE__ */ new WeakSet();
    return (key, value) => {
      if (typeof value === "object" && value !== null) {
        if (seen.has(value)) {
          return "[Circular Reference]";
        }
        seen.add(value);
      }
      return value;
    };
  }
  /**
   * 备用序列化方案
   */
  fallbackStringify(obj) {
    const simpleObj = {};
    for (const [key, value] of Object.entries(obj)) {
      if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value) && this.isSerializableArray(value)) {
        simpleObj[key] = value;
      } else if (typeof value === "object") {
        simpleObj[key] = "[Complex Object]";
      }
    }
    return JSON.stringify(simpleObj);
  }
  /**
   * 检查数组是否可序列化
   */
  isSerializableArray(arr) {
    return arr.every(
      (item) => item === null || typeof item === "string" || typeof item === "number" || typeof item === "boolean"
    );
  }
  /**
   * 从 JSON 结构加载数据
   */
  static fromJSON(jsonArray) {
    const collection = new _DSLCollection();
    for (const item of jsonArray) {
      try {
        const data = JSON.parse(item.dsl);
        collection.addWithId(item.id, data);
      } catch (error) {
        console.error(`\u89E3\u6790 ID ${item.id} \u7684 DSL \u6570\u636E\u5931\u8D25:`, error);
        collection.addWithId(item.id, {});
      }
    }
    return collection;
  }
  /**
   * 加载初始数据
   */
  loadInitialData(initialData) {
    for (const item of initialData) {
      if (item.id !== void 0) {
        this.addWithId(item.id, item.data);
      } else {
        this.add(item.data);
      }
    }
  }
  /**
   * 批量添加数据
   */
  addBatch(dataArray) {
    const ids = [];
    for (const data of dataArray) {
      const id = this.add(data);
      ids.push(id);
    }
    return ids;
  }
  /**
   * 过滤数据
   */
  filter(predicate) {
    const filteredCollection = new _DSLCollection();
    for (const [id, data] of this.items) {
      if (predicate(data, id)) {
        filteredCollection.addWithId(id, data);
      }
    }
    return filteredCollection;
  }
  /**
   * 映射数据
   */
  map(mapper) {
    const result = [];
    for (const [id, data] of this.items) {
      result.push(mapper(data, id));
    }
    return result;
  }
  /**
   * 迭代器，支持 for...of
   */
  *[Symbol.iterator]() {
    yield* this.items;
  }
};

// src/mcp/tools/get-figma-data-tool.ts
var parameters = {
  fileKey: z.string().regex(/^[a-zA-Z0-9]+$/, "File key must be alphanumeric").describe(
    "The key of the Figma file to fetch, often found in a provided URL like figma.com/(file|design)/<fileKey>/..."
  ),
  nodeId: z.string().regex(
    /^I?\d+[:|-]\d+(?:;\d+[:|-]\d+)*$/,
    "Node ID must be like '1234:5678' or 'I5666:180910;1:10515;1:10336'"
  ).optional().describe(
    "The ID of the node to fetch, often found as URL parameter node-id=<nodeId>, always use if provided. Use format '1234:5678' or 'I5666:180910;1:10515;1:10336' for multiple nodes."
  ),
  depth: z.number().optional().describe(
    "OPTIONAL. Do NOT use unless explicitly requested by the user. Controls how many levels deep to traverse the node tree."
  )
};
var parametersSchema = z.object(parameters);
async function getFigmaData(params, figmaService, outputFormat) {
  try {
    const { fileKey, nodeId: rawNodeId, depth } = parametersSchema.parse(params);
    const nodeId = rawNodeId?.replace(/-/g, ":");
    Logger.log(
      `Fetching ${depth ? `${depth} layers deep` : "all layers"} of ${nodeId ? `node ${nodeId} from file` : `full file`} ${fileKey}`
    );
    let startTime = Date.now();
    let rawApiResponse;
    if (nodeId) {
      rawApiResponse = await figmaService.getRawNode(fileKey, nodeId, depth);
    } else {
      rawApiResponse = await figmaService.getRawFile(fileKey, depth);
    }
    const apiCost = Date.now() - startTime;
    startTime = Date.now();
    const baseUrl = "https://www.figma.com";
    const endpoint = `/file/${fileKey}/image/`;
    const fileUrl = baseUrl + endpoint;
    writeLogs("figma-rawApiResponse" + nodeId?.replace(":", "_") + ".json", rawApiResponse);
    const simplifiedDesign = simplifyRawFigmaObject(fileUrl, rawApiResponse, allExtractors, {
      maxDepth: depth,
      afterChildren: collapseSvgContainers
    });
    const convertFigmaCost = Date.now() - startTime;
    startTime = Date.now();
    writeLogs("figma-simplified" + nodeId?.replace(":", "_") + ".json", simplifiedDesign);
    const figmaData = parseFigmaJSON(JSON.stringify(simplifiedDesign, null, 2));
    let formattedResult;
    if (figmaData.nodes && figmaData.nodes.length > 0) {
      const dslResult = convertFigmaToDSL(figmaData.nodes, figmaData.globalStyles);
      formattedResult = JSON.stringify(dslResult, null, 2);
    } else {
      formattedResult = "{}";
    }
    const convertDslCost = Date.now() - startTime;
    writeLogs("formattedResult.json", formattedResult);
    return {
      content: [{ type: "text", text: formattedResult }]
    };
  } catch (error) {
    const message = error instanceof Error ? error.message : JSON.stringify(error);
    Logger.error(`Error fetching file ${params.fileKey}:`, message);
    return {
      isError: true,
      content: [{ type: "text", text: `Error fetching file: ${message}` }]
    };
  }
}
var getFigmaDataTool = {
  name: "get_figma_data",
  description: "Get comprehensive Figma file data including layout, content, visuals, and component information",
  parameters,
  handler: getFigmaData
};
async function getFigmaPage(params, figmaService, outputFormat) {
  try {
    const { fileKey, nodeId: rawNodeId, depth } = parametersSchema.parse(params);
    const nodeId = rawNodeId?.replace(/-/g, ":");
    Logger.log(
      `Fetching ${depth ? `${depth} layers deep page` : "all layers page"} of ${nodeId ? `node ${nodeId} from file` : `full file`} ${fileKey}`
    );
    let pageApiResponse;
    if (nodeId) {
      pageApiResponse = await figmaService.getRawNode(fileKey, nodeId, depth);
      const { metadata, rawNodes, components, componentSets, extraStyles } = parseAPIResponse(pageApiResponse);
      const pageRootNodes = rawNodes[0];
      const baseUrl = "https://www.figma.com";
      const endpoint = `/file/${fileKey}/image/`;
      const fileUrl = baseUrl + endpoint;
      let contentJSON;
      if ("children" in pageRootNodes) {
        const collection = new DSLCollection();
        const pageChildNodes = Object.values(pageRootNodes.children);
        pageChildNodes.forEach((pageChildNode) => {
          const nodes_ = {
            [pageChildNode.id]: {
              document: pageChildNode,
              components: {},
              // 根据实际情况填充或留空对象
              componentSets: {},
              // 根据实际情况填充或留空对象
              schemaVersion: 0,
              // 设置正确的 schema 版本号
              styles: {}
              // 根据实际情况填充或留空对象
            }
          };
          const pageChildConverNode = {
            // 根据 GetFileResponse 的构造函数参数来映射字段
            nodes: nodes_,
            name: pageChildNode.name,
            lastModified: (/* @__PURE__ */ new Date()).toISOString(),
            thumbnailUrl: "",
            version: "2283563295863919804",
            role: "owner",
            editorType: "figma"
            // 其他需要的属性...
            // 根据你的实际需求添加更多字段映射
          };
          Logger.logA("------- " + pageChildNode + " , " + pageChildNode.id);
          const pageChildNodeSimplifiedDesign = simplifyRawFigmaObject(fileUrl, pageChildConverNode, allExtractors, {
            maxDepth: depth
            // afterChildren: collapseSvgContainers,
          });
          writeLogs("page-simplified-" + pageChildNode.id.replace(":", "-") + ".json", pageChildNodeSimplifiedDesign);
          const figmaData = parseFigmaJSON(JSON.stringify(pageChildNodeSimplifiedDesign, null, 2));
          try {
            if (figmaData.nodes && figmaData.nodes.length > 0) {
              const dslResult = convertFigmaToDSL(figmaData.nodes, figmaData.globalStyles);
              if ((dslResult.type == "column" || dslResult.type == "row") && !!dslResult.children && dslResult.children.length > 0) {
                writeLogs("dsl-" + pageChildNode.id.replace(":", "-") + ".json", dslResult);
                collection.add(dslResult);
                writeLogs("page-origin-" + pageChildNode.id.replace(":", "-") + ".json", pageChildConverNode);
              } else {
                Logger.error("dsl \u4E3A\u7A7A");
              }
            }
          } catch (error) {
            Logger.error(error);
            throw new Error("extractFillAttributes: ");
          }
        });
        contentJSON = JSON.stringify(collection.toJSON(), null, 2);
      } else {
        contentJSON = "[]";
      }
      Logger.log("Sending result to client : " + contentJSON);
      writeLogs("formattedResult.json", contentJSON);
      return {
        content: [{ type: "text", text: contentJSON }]
      };
    } else {
      Logger.logA("nodeId is null: " + nodeId);
      return {
        content: [{ type: "text", text: "" }]
      };
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : JSON.stringify(error);
    Logger.error(`Error fetching file ${params.fileKey}:`, message);
    return {
      isError: true,
      content: [{ type: "text", text: `Error fetching file: ${message}` }]
    };
  }
}
var getFigmaPageTool = {
  name: "get_figma_page",
  description: "Get comprehensive Figma file data including layout, content, visuals, and component information",
  parameters,
  handler: getFigmaPage
};

// src/mcp/tools/download-figma-images-tool.ts
import { z as z2 } from "zod";
var parameters2 = {
  fileKey: z2.string().regex(/^[a-zA-Z0-9]+$/, "File key must be alphanumeric").describe("The key of the Figma file containing the images"),
  nodes: z2.object({
    nodeId: z2.string().regex(
      /^I?\d+[:|-]\d+(?:;\d+[:|-]\d+)*$/,
      "Node ID must be like '1234:5678' or 'I5666:180910;1:10515;1:10336'"
    ).describe("The ID of the Figma image node to fetch, formatted as 1234:5678"),
    imageRef: z2.string().optional().describe(
      "If a node has an imageRef fill, you must include this variable. Leave blank when downloading Vector SVG images."
    ),
    fileName: z2.string().regex(
      /^[a-zA-Z0-9_.-]+\.(png|svg)$/,
      "File names must contain only letters, numbers, underscores, dots, or hyphens, and end with .png or .svg."
    ).describe(
      "The local name for saving the fetched file, including extension. Either png or svg."
    ),
    needsCropping: z2.boolean().optional().describe("Whether this image needs cropping based on its transform matrix"),
    cropTransform: z2.array(z2.array(z2.number())).optional().describe("Figma transform matrix for image cropping"),
    requiresImageDimensions: z2.boolean().optional().describe("Whether this image requires dimension information for CSS variables"),
    filenameSuffix: z2.string().optional().describe(
      "Suffix to add to filename for unique cropped images, provided in the Figma data (e.g., 'abc123')"
    )
  }).array().describe("The nodes to fetch as images"),
  pngScale: z2.number().positive().optional().default(2).describe(
    "Export scale for PNG images. Optional, defaults to 2 if not specified. Affects PNG images only."
  ),
  localPath: z2.string().describe(
    "The absolute path to the directory where images are stored in the project. If the directory does not exist, it will be created. The format of this path should respect the directory format of the operating system you are running on. Don't use any special character escaping in the path name either."
  )
};
var parametersSchema2 = z2.object(parameters2);
async function downloadFigmaImages(params, figmaService) {
  try {
    const { fileKey, nodes, localPath, pngScale = 2 } = parametersSchema2.parse(params);
    const downloadItems = [];
    const downloadToRequests = /* @__PURE__ */ new Map();
    const seenDownloads = /* @__PURE__ */ new Map();
    for (const rawNode of nodes) {
      const { nodeId: rawNodeId, ...node } = rawNode;
      const nodeId = rawNodeId?.replace(/-/g, ":");
      let finalFileName = node.fileName;
      if (node.filenameSuffix && !finalFileName.includes(node.filenameSuffix)) {
        const ext = finalFileName.split(".").pop();
        const nameWithoutExt = finalFileName.substring(0, finalFileName.lastIndexOf("."));
        finalFileName = `${nameWithoutExt}-${node.filenameSuffix}.${ext}`;
      }
      const downloadItem = {
        fileName: finalFileName,
        needsCropping: node.needsCropping || false,
        cropTransform: node.cropTransform,
        requiresImageDimensions: node.requiresImageDimensions || false
      };
      if (node.imageRef) {
        const uniqueKey = `${node.imageRef}-${node.filenameSuffix || "none"}`;
        if (!node.filenameSuffix && seenDownloads.has(uniqueKey)) {
          const downloadIndex = seenDownloads.get(uniqueKey);
          const requests = downloadToRequests.get(downloadIndex);
          if (!requests.includes(finalFileName)) {
            requests.push(finalFileName);
          }
          if (downloadItem.requiresImageDimensions) {
            downloadItems[downloadIndex].requiresImageDimensions = true;
          }
        } else {
          const downloadIndex = downloadItems.length;
          downloadItems.push({ ...downloadItem, imageRef: node.imageRef });
          downloadToRequests.set(downloadIndex, [finalFileName]);
          seenDownloads.set(uniqueKey, downloadIndex);
        }
      } else {
        const downloadIndex = downloadItems.length;
        downloadItems.push({ ...downloadItem, nodeId });
        downloadToRequests.set(downloadIndex, [finalFileName]);
      }
    }
    const allDownloads = await figmaService.downloadImages(fileKey, localPath, downloadItems, {
      pngScale
    });
    const successCount = allDownloads.filter(Boolean).length;
    const imagesList = allDownloads.map((result, index) => {
      const fileName = result.filePath.split("/").pop() || result.filePath;
      const dimensions = `${result.finalDimensions.width}x${result.finalDimensions.height}`;
      const cropStatus = result.wasCropped ? " (cropped)" : "";
      const dimensionInfo = result.cssVariables ? `${dimensions} | ${result.cssVariables}` : dimensions;
      const requestedNames = downloadToRequests.get(index) || [fileName];
      const aliasText = requestedNames.length > 1 ? ` (also requested as: ${requestedNames.filter((name) => name !== fileName).join(", ")})` : "";
      return `- ${fileName}: ${dimensionInfo}${cropStatus}${aliasText}`;
    }).join("\n");
    return {
      content: [
        {
          type: "text",
          text: `Downloaded ${successCount} images:
${imagesList}`
        }
      ]
    };
  } catch (error) {
    Logger.error(`Error downloading images from ${params.fileKey}:`, error);
    return {
      isError: true,
      content: [
        {
          type: "text",
          text: `Failed to download images: ${error instanceof Error ? error.message : String(error)}`
        }
      ]
    };
  }
}
var downloadFigmaImagesTool = {
  name: "download_figma_images",
  description: "Download SVG and PNG images used in a Figma file based on the IDs of image or icon nodes",
  parameters: parameters2,
  handler: downloadFigmaImages
};

// src/mcp/index.ts
var serverInfo = {
  name: "Figma MCP Server",
  version: "0.6.4"
};
function createServer(authOptions, { isHTTP = true, outputFormat = "json", skipImageDownloads = false } = {}) {
  const server = new McpServer(serverInfo);
  const figmaService = new FigmaService(authOptions);
  registerTools(server, figmaService, { outputFormat, skipImageDownloads });
  Logger.isHTTP = isHTTP;
  return server;
}
function registerTools(server, figmaService, options) {
  server.tool(
    getFigmaDataTool.name,
    getFigmaDataTool.description,
    getFigmaDataTool.parameters,
    (params) => getFigmaDataTool.handler(params, figmaService, options.outputFormat)
  );
  if (!options.skipImageDownloads) {
    server.tool(
      downloadFigmaImagesTool.name,
      downloadFigmaImagesTool.description,
      downloadFigmaImagesTool.parameters,
      (params) => downloadFigmaImagesTool.handler(params, figmaService)
    );
  }
  server.tool(
    getFigmaPageTool.name,
    getFigmaPageTool.description,
    getFigmaPageTool.parameters,
    (params) => getFigmaPageTool.handler(params, figmaService, options.outputFormat)
  );
}

// src/config.ts
import { config as loadEnv } from "dotenv";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { resolve } from "path";
function maskApiKey(key) {
  if (!key || key.length <= 4) return "****";
  return `****${key.slice(-4)}`;
}
function getServerConfig(isStdioMode) {
  const argv = yargs(hideBin(process.argv)).options({
    "figma-api-key": {
      type: "string",
      description: "Figma API key (Personal Access Token)"
    },
    "figma-oauth-token": {
      type: "string",
      description: "Figma OAuth Bearer token"
    },
    env: {
      type: "string",
      description: "Path to custom .env file to load environment variables from"
    },
    port: {
      type: "number",
      description: "Port to run the server on"
    },
    json: {
      type: "boolean",
      description: "Output data from tools in JSON format instead of YAML",
      default: false
    },
    "skip-image-downloads": {
      type: "boolean",
      description: "Do not register the download_figma_images tool (skip image downloads)",
      default: false
    }
  }).help().version("0.6.4").parseSync();
  let envFilePath;
  let envFileSource;
  if (argv["env"]) {
    envFilePath = resolve(argv["env"]);
    envFileSource = "cli";
  } else {
    envFilePath = resolve(process.cwd(), ".env");
    envFileSource = "default";
  }
  loadEnv({ path: envFilePath, override: true });
  const auth = {
    figmaApiKey: "",
    figmaOAuthToken: "",
    useOAuth: false
  };
  const config = {
    port: 3333,
    outputFormat: "yaml",
    skipImageDownloads: false,
    configSources: {
      figmaApiKey: "env",
      figmaOAuthToken: "none",
      port: "default",
      outputFormat: "default",
      envFile: envFileSource,
      skipImageDownloads: "default"
    }
  };
  if (argv["figma-api-key"]) {
    auth.figmaApiKey = argv["figma-api-key"];
    config.configSources.figmaApiKey = "cli";
  } else if (process.env.FIGMA_API_KEY) {
    auth.figmaApiKey = process.env.FIGMA_API_KEY;
    config.configSources.figmaApiKey = "env";
  }
  if (argv["figma-oauth-token"]) {
    auth.figmaOAuthToken = argv["figma-oauth-token"];
    config.configSources.figmaOAuthToken = "cli";
    auth.useOAuth = true;
  } else if (process.env.FIGMA_OAUTH_TOKEN) {
    auth.figmaOAuthToken = process.env.FIGMA_OAUTH_TOKEN;
    config.configSources.figmaOAuthToken = "env";
    auth.useOAuth = true;
  }
  if (argv.port) {
    config.port = argv.port;
    config.configSources.port = "cli";
  } else if (process.env.PORT) {
    config.port = parseInt(process.env.PORT, 10);
    config.configSources.port = "env";
  }
  if (argv.json) {
    config.outputFormat = "json";
    config.configSources.outputFormat = "cli";
  } else if (process.env.OUTPUT_FORMAT) {
    config.outputFormat = process.env.OUTPUT_FORMAT;
    config.configSources.outputFormat = "env";
  }
  if (argv["skip-image-downloads"]) {
    config.skipImageDownloads = true;
    config.configSources.skipImageDownloads = "cli";
  } else if (process.env.SKIP_IMAGE_DOWNLOADS === "true") {
    config.skipImageDownloads = true;
    config.configSources.skipImageDownloads = "env";
  }
  if (!auth.figmaApiKey && !auth.figmaOAuthToken) {
    console.error(
      "Either FIGMA_API_KEY or FIGMA_OAUTH_TOKEN is required (via CLI argument or .env file)"
    );
    process.exit(1);
  }
  if (!isStdioMode) {
    console.log("\nConfiguration:");
    console.log(`- ENV_FILE: ${envFilePath} (source: ${config.configSources.envFile})`);
    if (auth.useOAuth) {
      console.log(
        `- FIGMA_OAUTH_TOKEN: ${maskApiKey(auth.figmaOAuthToken)} (source: ${config.configSources.figmaOAuthToken})`
      );
      console.log("- Authentication Method: OAuth Bearer Token");
    } else {
      console.log(
        `- FIGMA_API_KEY: ${maskApiKey(auth.figmaApiKey)} (source: ${config.configSources.figmaApiKey})`
      );
      console.log("- Authentication Method: Personal Access Token (X-Figma-Token)");
    }
    console.log(`- PORT: ${config.port} (source: ${config.configSources.port})`);
    console.log(
      `- OUTPUT_FORMAT: ${config.outputFormat} (source: ${config.configSources.outputFormat})`
    );
    console.log(
      `- SKIP_IMAGE_DOWNLOADS: ${config.skipImageDownloads} (source: ${config.configSources.skipImageDownloads})`
    );
    console.log();
  }
  return {
    ...config,
    auth
  };
}

// src/server.ts
import { randomUUID } from "node:crypto";
import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
var httpServer = null;
var transports = {
  streamable: {},
  sse: {}
};
async function startServer() {
  const isStdioMode = process.env.NODE_ENV === "cli" || process.argv.includes("--stdio");
  const config = getServerConfig(isStdioMode);
  const server = createServer(config.auth, {
    isHTTP: !isStdioMode,
    outputFormat: config.outputFormat,
    skipImageDownloads: config.skipImageDownloads
  });
  if (isStdioMode) {
    const transport = new StdioServerTransport();
    await server.connect(transport);
  } else {
    console.log(`Initializing Figma MCP Server in HTTP mode on port ${config.port}...`);
    await startHttpServer(config.port, server);
  }
}
async function startHttpServer(port, mcpServer) {
  const app = express();
  app.use("/mcp", express.json());
  app.post("/mcp", async (req, res) => {
    Logger.log("Received StreamableHTTP request");
    const sessionId = req.headers["mcp-session-id"];
    let transport;
    if (sessionId && transports.streamable[sessionId]) {
      Logger.log("Reusing existing StreamableHTTP transport for sessionId", sessionId);
      transport = transports.streamable[sessionId];
    } else if (!sessionId && isInitializeRequest(req.body)) {
      Logger.log("New initialization request for StreamableHTTP sessionId", sessionId);
      transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: () => randomUUID(),
        onsessioninitialized: (sessionId2) => {
          transports.streamable[sessionId2] = transport;
        }
      });
      transport.onclose = () => {
        if (transport.sessionId) {
          delete transports.streamable[transport.sessionId];
        }
      };
      await mcpServer.connect(transport);
    } else {
      Logger.log("Invalid request:", req.body);
      res.status(400).json({
        jsonrpc: "2.0",
        error: {
          code: -32e3,
          message: "Bad Request: No valid session ID provided"
        },
        id: null
      });
      return;
    }
    let progressInterval = null;
    const progressToken = req.body.params?._meta?.progressToken;
    let progress = 0;
    if (progressToken) {
      Logger.log(
        `Setting up progress notifications for token ${progressToken} on session ${sessionId}`
      );
      progressInterval = setInterval(async () => {
        Logger.log("Sending progress notification", progress);
        await mcpServer.server.notification({
          method: "notifications/progress",
          params: {
            progress,
            progressToken
          }
        });
        progress++;
      }, 1e3);
    }
    Logger.log("Handling StreamableHTTP request");
    await transport.handleRequest(req, res, req.body);
    if (progressInterval) {
      clearInterval(progressInterval);
    }
    Logger.log("StreamableHTTP request handled");
  });
  const handleSessionRequest = async (req, res) => {
    const sessionId = req.headers["mcp-session-id"];
    if (!sessionId || !transports.streamable[sessionId]) {
      res.status(400).send("Invalid or missing session ID");
      return;
    }
    console.log(`Received session termination request for session ${sessionId}`);
    try {
      const transport = transports.streamable[sessionId];
      await transport.handleRequest(req, res);
    } catch (error) {
      console.error("Error handling session termination:", error);
      if (!res.headersSent) {
        res.status(500).send("Error processing session termination");
      }
    }
  };
  app.get("/mcp", handleSessionRequest);
  app.delete("/mcp", handleSessionRequest);
  app.get("/sse", async (req, res) => {
    Logger.log("Establishing new SSE connection");
    const transport = new SSEServerTransport("/messages", res);
    Logger.log(`New SSE connection established for sessionId ${transport.sessionId}`);
    Logger.log("/sse request headers:", req.headers);
    Logger.log("/sse request body:", req.body);
    transports.sse[transport.sessionId] = transport;
    res.on("close", () => {
      delete transports.sse[transport.sessionId];
    });
    await mcpServer.connect(transport);
  });
  app.post("/messages", async (req, res) => {
    const sessionId = req.query.sessionId;
    const transport = transports.sse[sessionId];
    if (transport) {
      Logger.log(`Received SSE message for sessionId ${sessionId}`);
      Logger.log("/messages request headers:", req.headers);
      Logger.log("/messages request body:", req.body);
      await transport.handlePostMessage(req, res);
    } else {
      res.status(400).send(`No transport found for sessionId ${sessionId}`);
      return;
    }
  });
  httpServer = app.listen(port, "127.0.0.1", () => {
    Logger.log(`HTTP server listening on port ${port}`);
    Logger.log(`SSE endpoint available at http://localhost:${port}/sse`);
    Logger.log(`Message endpoint available at http://localhost:${port}/messages`);
    Logger.log(`StreamableHTTP endpoint available at http://localhost:${port}/mcp`);
  });
  process.on("SIGINT", async () => {
    Logger.log("Shutting down server...");
    await closeTransports(transports.sse);
    await closeTransports(transports.streamable);
    Logger.log("Server shutdown complete");
    process.exit(0);
  });
}
async function closeTransports(transports2) {
  for (const sessionId in transports2) {
    try {
      await transports2[sessionId]?.close();
      delete transports2[sessionId];
    } catch (error) {
      console.error(`Error closing transport for session ${sessionId}:`, error);
    }
  }
}
async function stopHttpServer() {
  if (!httpServer) {
    throw new Error("HTTP server is not running");
  }
  return new Promise((resolve2, reject) => {
    httpServer.close((err) => {
      if (err) {
        reject(err);
        return;
      }
      httpServer = null;
      const closing = Object.values(transports.sse).map((transport) => {
        return transport.close();
      });
      Promise.all(closing).then(() => {
        resolve2();
      });
    });
  });
}

export {
  createServer,
  getServerConfig,
  startServer,
  startHttpServer,
  stopHttpServer
};
