import { logger } from "../../core/monitoring/logger.js";
class LinearDuplicateDetector {
  linearClient;
  titleCache = /* @__PURE__ */ new Map();
  cacheExpiry = 5 * 60 * 1e3;
  // 5 minutes
  lastCacheRefresh = 0;
  constructor(linearClient) {
    this.linearClient = linearClient;
  }
  /**
   * Search for existing Linear issues with similar titles
   */
  async searchByTitle(title, teamId) {
    const normalizedTitle = this.normalizeTitle(title);
    if (this.isCacheValid()) {
      const cached = this.titleCache.get(normalizedTitle);
      if (cached) return cached;
    }
    try {
      const allIssues = await this.linearClient.getIssues({
        teamId,
        limit: 100
        // Use smaller limit to avoid API errors
      });
      const matchingIssues = allIssues.filter((issue) => {
        const issueNormalized = this.normalizeTitle(issue.title);
        if (issueNormalized === normalizedTitle) return true;
        const similarity = this.calculateSimilarity(normalizedTitle, issueNormalized);
        return similarity > 0.85;
      });
      this.titleCache.set(normalizedTitle, matchingIssues);
      this.lastCacheRefresh = Date.now();
      return matchingIssues;
    } catch (error) {
      logger.error("Failed to search Linear issues by title:", error);
      return [];
    }
  }
  /**
   * Check if a task title would create a duplicate in Linear
   */
  async checkForDuplicate(title, teamId) {
    const existingIssues = await this.searchByTitle(title, teamId);
    if (existingIssues.length === 0) {
      return { isDuplicate: false };
    }
    let bestMatch;
    let bestSimilarity = 0;
    for (const issue of existingIssues) {
      const similarity = this.calculateSimilarity(
        this.normalizeTitle(title),
        this.normalizeTitle(issue.title)
      );
      if (similarity > bestSimilarity) {
        bestSimilarity = similarity;
        bestMatch = issue;
      }
    }
    return {
      isDuplicate: true,
      existingIssue: bestMatch,
      similarity: bestSimilarity
    };
  }
  /**
   * Merge task content into existing Linear issue
   */
  async mergeIntoExisting(existingIssue, newTitle, newDescription, additionalContext) {
    try {
      let mergedDescription = existingIssue.description || "";
      if (newDescription && !mergedDescription.includes(newDescription)) {
        mergedDescription += `

## Additional Context (${(/* @__PURE__ */ new Date()).toISOString()})
`;
        mergedDescription += newDescription;
      }
      if (additionalContext) {
        mergedDescription += `

---
${additionalContext}`;
      }
      const updateQuery = `
        mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
          issueUpdate(id: $id, input: $input) {
            issue {
              id
              identifier
              title
              description
              updatedAt
            }
          }
        }
      `;
      const variables = {
        id: existingIssue.id,
        input: {
          description: mergedDescription
        }
      };
      const response = await this.linearClient.graphql(updateQuery, variables);
      const updatedIssue = response.issueUpdate?.issue;
      if (updatedIssue) {
        logger.info(
          `Merged content into existing Linear issue ${existingIssue.identifier}: ${existingIssue.title}`
        );
        return updatedIssue;
      }
      return existingIssue;
    } catch (error) {
      logger.error("Failed to merge into existing Linear issue:", error);
      return existingIssue;
    }
  }
  /**
   * Normalize title for comparison
   */
  normalizeTitle(title) {
    return title.toLowerCase().trim().replace(/\s+/g, " ").replace(/[^\w\s-]/g, "").replace(/^(sta|eng|bug|feat|task|tsk)[-\s]\d+[-\s:]*/, "").trim();
  }
  /**
   * Calculate similarity between two strings (Levenshtein distance based)
   */
  calculateSimilarity(str1, str2) {
    if (str1 === str2) return 1;
    if (str1.length === 0 || str2.length === 0) return 0;
    const distance = this.levenshteinDistance(str1, str2);
    const maxLength = Math.max(str1.length, str2.length);
    return 1 - distance / maxLength;
  }
  /**
   * Calculate Levenshtein distance between two strings
   */
  levenshteinDistance(str1, str2) {
    const m = str1.length;
    const n = str2.length;
    const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
    for (let i = 0; i <= m; i++) dp[i][0] = i;
    for (let j = 0; j <= n; j++) dp[0][j] = j;
    for (let i = 1; i <= m; i++) {
      for (let j = 1; j <= n; j++) {
        if (str1[i - 1] === str2[j - 1]) {
          dp[i][j] = dp[i - 1][j - 1];
        } else {
          dp[i][j] = 1 + Math.min(
            dp[i - 1][j],
            // deletion
            dp[i][j - 1],
            // insertion
            dp[i - 1][j - 1]
            // substitution
          );
        }
      }
    }
    return dp[m][n];
  }
  /**
   * Check if cache is still valid
   */
  isCacheValid() {
    return Date.now() - this.lastCacheRefresh < this.cacheExpiry;
  }
  /**
   * Clear the title cache
   */
  clearCache() {
    this.titleCache.clear();
    this.lastCacheRefresh = 0;
  }
}
async function syncToLinearWithDuplicateCheck(linearClient, task, teamId) {
  const detector = new LinearDuplicateDetector(linearClient);
  const duplicateCheck = await detector.checkForDuplicate(task.title, teamId);
  if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {
    logger.info(
      `Found existing Linear issue for "${task.title}": ${duplicateCheck.existingIssue.identifier} (${Math.round((duplicateCheck.similarity || 0) * 100)}% match)`
    );
    const mergedIssue = await detector.mergeIntoExisting(
      duplicateCheck.existingIssue,
      task.title,
      task.description,
      `StackMemory Task ID: ${task.id}`
    );
    return { issue: mergedIssue, wasmerged: true };
  }
  const newIssue = await linearClient.createIssue({
    title: task.title,
    description: task.description,
    teamId
  });
  logger.info(`Created new Linear issue: ${newIssue.identifier}`);
  return { issue: newIssue, wasmerged: false };
}
export {
  LinearDuplicateDetector,
  syncToLinearWithDuplicateCheck
};
//# sourceMappingURL=sync-enhanced.js.map
