import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { LinearClient } from "./client.js";
import { LinearDuplicateDetector } from "./sync.js";
import { logger } from "../../core/monitoring/logger.js";
import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
import { join, dirname } from "path";
import { EventEmitter } from "events";
const DEFAULT_UNIFIED_CONFIG = {
  enabled: true,
  direction: "bidirectional",
  duplicateDetection: true,
  duplicateSimilarityThreshold: 0.85,
  mergeStrategy: "merge_content",
  conflictResolution: "newest_wins",
  taskPlanningEnabled: true,
  taskPlanFile: ".stackmemory/task-plan.md",
  autoCreateTaskPlan: true,
  maxBatchSize: 50,
  rateLimitDelay: 100,
  maxRetries: 3,
  autoSync: false,
  autoSyncInterval: 15
};
class UnifiedLinearSync extends EventEmitter {
  config;
  linearClient;
  taskStore;
  authManager;
  duplicateDetector;
  projectRoot;
  mappings = /* @__PURE__ */ new Map();
  // task.id -> linear.id
  lastSyncStats;
  syncInProgress = false;
  constructor(taskStore, authManager, projectRoot, config) {
    super();
    this.taskStore = taskStore;
    this.authManager = authManager;
    this.projectRoot = projectRoot;
    this.config = { ...DEFAULT_UNIFIED_CONFIG, ...config };
    this.linearClient = null;
    this.duplicateDetector = null;
    this.loadMappings();
  }
  /**
   * Initialize the sync system
   */
  async initialize() {
    try {
      const token = await this.authManager.getValidToken();
      if (!token) {
        throw new IntegrationError(
          'Linear authentication required. Run "stackmemory linear auth" first.',
          ErrorCode.LINEAR_AUTH_FAILED
        );
      }
      const isOAuth = this.authManager.isOAuth();
      this.linearClient = new LinearClient({
        apiKey: token,
        useBearer: isOAuth,
        teamId: this.config.defaultTeamId,
        onUnauthorized: isOAuth ? async () => {
          const refreshed = await this.authManager.refreshAccessToken();
          return refreshed.accessToken;
        } : void 0
      });
      if (this.config.duplicateDetection) {
        this.duplicateDetector = new LinearDuplicateDetector(this.linearClient);
      }
      if (this.config.taskPlanningEnabled) {
        await this.initializeTaskPlanning();
      }
      logger.info("Unified Linear sync initialized", {
        direction: this.config.direction,
        duplicateDetection: this.config.duplicateDetection,
        taskPlanning: this.config.taskPlanningEnabled
      });
    } catch (error) {
      logger.error("Failed to initialize Linear sync:", error);
      throw error;
    }
  }
  /**
   * Main sync method - orchestrates bidirectional sync
   */
  async sync() {
    if (this.syncInProgress) {
      throw new IntegrationError(
        "Sync already in progress",
        ErrorCode.LINEAR_SYNC_FAILED
      );
    }
    this.syncInProgress = true;
    const startTime = Date.now();
    const stats = {
      toLinear: { created: 0, updated: 0, skipped: 0, duplicatesMerged: 0 },
      fromLinear: { created: 0, updated: 0, skipped: 0 },
      conflicts: [],
      errors: [],
      duration: 0,
      timestamp: Date.now()
    };
    try {
      this.emit("sync:started", { config: this.config });
      switch (this.config.direction) {
        case "bidirectional":
          await this.syncFromLinear(stats);
          await this.syncToLinear(stats);
          break;
        case "from_linear":
          await this.syncFromLinear(stats);
          break;
        case "to_linear":
          await this.syncToLinear(stats);
          break;
      }
      if (this.config.taskPlanningEnabled) {
        await this.updateTaskPlan(stats);
      }
      this.saveMappings();
      stats.duration = Date.now() - startTime;
      this.lastSyncStats = stats;
      this.emit("sync:completed", { stats });
      logger.info("Unified sync completed", {
        duration: `${stats.duration}ms`,
        toLinear: stats.toLinear,
        fromLinear: stats.fromLinear,
        conflicts: stats.conflicts.length
      });
      return stats;
    } catch (error) {
      stats.errors.push(error.message);
      stats.duration = Date.now() - startTime;
      this.emit("sync:failed", { stats, error });
      logger.error("Unified sync failed:", error);
      throw error;
    } finally {
      this.syncInProgress = false;
    }
  }
  /**
   * Sync from Linear to local tasks
   */
  async syncFromLinear(stats) {
    try {
      logger.debug("Syncing from Linear...");
      const teamId = this.config.defaultTeamId || await this.getDefaultTeamId();
      const issues = await this.linearClient.getIssues({
        teamId,
        limit: this.config.maxBatchSize
      });
      for (const issue of issues) {
        try {
          await this.delay(this.config.rateLimitDelay);
          const localTaskId = this.findLocalTaskByLinearId(issue.id);
          if (localTaskId) {
            const localTask = await this.taskStore.getTask(localTaskId);
            if (localTask && this.hasChanges(localTask, issue)) {
              await this.updateLocalTask(localTask, issue);
              stats.fromLinear.updated++;
            } else {
              stats.fromLinear.skipped++;
            }
          } else {
            await this.createLocalTask(issue);
            stats.fromLinear.created++;
          }
        } catch (error) {
          stats.errors.push(
            `Failed to sync issue ${issue.identifier}: ${error.message}`
          );
        }
      }
    } catch (error) {
      logger.error("Failed to sync from Linear:", error);
      throw error;
    }
  }
  /**
   * Sync local tasks to Linear
   */
  async syncToLinear(stats) {
    try {
      logger.debug("Syncing to Linear...");
      const tasks = await this.taskStore.getAllTasks();
      const teamId = this.config.defaultTeamId || await this.getDefaultTeamId();
      for (const task of tasks) {
        try {
          await this.delay(this.config.rateLimitDelay);
          const linearId = this.mappings.get(task.id);
          if (linearId) {
            const linearIssue = await this.linearClient.getIssue(linearId);
            if (linearIssue && this.taskNeedsUpdate(task, linearIssue)) {
              await this.updateLinearIssue(linearIssue, task);
              stats.toLinear.updated++;
            } else {
              stats.toLinear.skipped++;
            }
          } else {
            if (this.config.duplicateDetection) {
              const duplicateCheck = await this.duplicateDetector.checkForDuplicate(
                task.title,
                teamId
              );
              if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {
                if (this.config.mergeStrategy === "merge_content") {
                  await this.mergeTaskIntoLinear(
                    task,
                    duplicateCheck.existingIssue
                  );
                  this.mappings.set(task.id, duplicateCheck.existingIssue.id);
                  stats.toLinear.duplicatesMerged++;
                } else if (this.config.mergeStrategy === "skip") {
                  stats.toLinear.skipped++;
                  continue;
                }
              } else {
                await this.createLinearIssue(task, teamId);
                stats.toLinear.created++;
              }
            } else {
              await this.createLinearIssue(task, teamId);
              stats.toLinear.created++;
            }
          }
        } catch (error) {
          stats.errors.push(
            `Failed to sync task ${task.id}: ${error.message}`
          );
        }
      }
    } catch (error) {
      logger.error("Failed to sync to Linear:", error);
      throw error;
    }
  }
  /**
   * Initialize task planning system
   */
  async initializeTaskPlanning() {
    const planFile = join(this.projectRoot, this.config.taskPlanFile);
    const planDir = dirname(planFile);
    if (!existsSync(planDir)) {
      mkdirSync(planDir, { recursive: true });
    }
    if (!existsSync(planFile) && this.config.autoCreateTaskPlan) {
      const defaultPlan = {
        version: "1.0.0",
        lastUpdated: /* @__PURE__ */ new Date(),
        phases: [
          {
            name: "Backlog",
            description: "Tasks to be prioritized",
            tasks: []
          },
          {
            name: "Current Sprint",
            description: "Active work items",
            tasks: []
          },
          {
            name: "Completed",
            description: "Finished tasks",
            tasks: []
          }
        ]
      };
      this.saveTaskPlan(defaultPlan);
      logger.info("Created default task plan", { path: planFile });
    }
  }
  /**
   * Update task plan with sync results
   */
  async updateTaskPlan(stats) {
    if (!this.config.taskPlanningEnabled) return;
    try {
      const plan = this.loadTaskPlan();
      const tasks = await this.taskStore.getAllTasks();
      plan.phases = [
        {
          name: "Backlog",
          description: "Tasks to be prioritized",
          tasks: tasks.filter((t) => t.status === "todo").map((t) => ({
            id: t.id,
            title: t.title,
            priority: t.priority || "medium",
            status: t.status,
            linearId: this.mappings.get(t.id)
          }))
        },
        {
          name: "In Progress",
          description: "Active work items",
          tasks: tasks.filter((t) => t.status === "in_progress").map((t) => ({
            id: t.id,
            title: t.title,
            priority: t.priority || "medium",
            status: t.status,
            linearId: this.mappings.get(t.id)
          }))
        },
        {
          name: "Completed",
          description: "Finished tasks",
          tasks: tasks.filter((t) => t.status === "done").slice(-20).map((t) => ({
            id: t.id,
            title: t.title,
            priority: t.priority || "medium",
            status: t.status,
            linearId: this.mappings.get(t.id)
          }))
        }
      ];
      plan.lastUpdated = /* @__PURE__ */ new Date();
      this.saveTaskPlan(plan);
      this.generateTaskReport(plan, stats);
    } catch (error) {
      logger.error("Failed to update task plan:", error);
    }
  }
  /**
   * Generate markdown task report
   */
  generateTaskReport(plan, stats) {
    const reportFile = join(this.projectRoot, ".stackmemory", "task-report.md");
    let content = `# Task Sync Report

`;
    content += `**Last Updated:** ${plan.lastUpdated.toLocaleString()}
`;
    content += `**Sync Duration:** ${stats.duration}ms

`;
    content += `## Sync Statistics

`;
    content += `### To Linear
`;
    content += `- Created: ${stats.toLinear.created}
`;
    content += `- Updated: ${stats.toLinear.updated}
`;
    content += `- Duplicates Merged: ${stats.toLinear.duplicatesMerged}
`;
    content += `- Skipped: ${stats.toLinear.skipped}

`;
    content += `### From Linear
`;
    content += `- Created: ${stats.fromLinear.created}
`;
    content += `- Updated: ${stats.fromLinear.updated}
`;
    content += `- Skipped: ${stats.fromLinear.skipped}

`;
    if (stats.conflicts.length > 0) {
      content += `### Conflicts
`;
      stats.conflicts.forEach((c) => {
        content += `- **${c.taskId}**: ${c.reason} (${c.resolution})
`;
      });
      content += "\n";
    }
    content += `## Task Overview

`;
    plan.phases.forEach((phase) => {
      content += `### ${phase.name} (${phase.tasks.length})
`;
      content += `> ${phase.description}

`;
      if (phase.tasks.length > 0) {
        phase.tasks.slice(0, 10).forEach((task) => {
          const linearLink = task.linearId ? ` [Linear]` : "";
          content += `- **${task.title}**${linearLink}
`;
        });
        if (phase.tasks.length > 10) {
          content += `- _...and ${phase.tasks.length - 10} more_
`;
        }
      }
      content += "\n";
    });
    writeFileSync(reportFile, content);
    logger.debug("Task report generated", { path: reportFile });
  }
  /**
   * Helper methods
   */
  async getDefaultTeamId() {
    const teams = await this.linearClient.getTeams();
    if (teams.length === 0) {
      throw new IntegrationError(
        "No Linear teams found",
        ErrorCode.LINEAR_API_ERROR
      );
    }
    return teams[0].id;
  }
  findLocalTaskByLinearId(linearId) {
    for (const [taskId, linId] of this.mappings) {
      if (linId === linearId) return taskId;
    }
    return void 0;
  }
  hasChanges(localTask, linearIssue) {
    return localTask.title !== linearIssue.title || localTask.description !== (linearIssue.description || "") || this.mapLinearStateToStatus(linearIssue.state.type) !== localTask.status;
  }
  taskNeedsUpdate(task, linearIssue) {
    return task.title !== linearIssue.title || task.description !== (linearIssue.description || "") || task.status !== this.mapLinearStateToStatus(linearIssue.state.type);
  }
  async createLocalTask(issue) {
    const task = await this.taskStore.createTask({
      title: issue.title,
      description: issue.description || "",
      status: this.mapLinearStateToStatus(issue.state.type),
      priority: this.mapLinearPriorityToPriority(issue.priority),
      metadata: {
        linear: {
          id: issue.id,
          identifier: issue.identifier,
          url: issue.url
        }
      }
    });
    this.mappings.set(task.id, issue.id);
  }
  async updateLocalTask(task, issue) {
    await this.taskStore.updateTask(task.id, {
      title: issue.title,
      description: issue.description || "",
      status: this.mapLinearStateToStatus(issue.state.type),
      priority: this.mapLinearPriorityToPriority(issue.priority)
    });
  }
  async createLinearIssue(task, teamId) {
    const input = {
      title: task.title,
      description: task.description || "",
      teamId,
      priority: this.mapPriorityToLinearPriority(task.priority)
    };
    const issue = await this.linearClient.createIssue(input);
    this.mappings.set(task.id, issue.id);
    await this.taskStore.updateTask(task.id, {
      metadata: {
        ...task.metadata,
        linear: {
          id: issue.id,
          identifier: issue.identifier,
          url: issue.url
        }
      }
    });
  }
  async updateLinearIssue(issue, task) {
    await this.linearClient.updateIssue(issue.id, {
      title: task.title,
      description: task.description,
      priority: this.mapPriorityToLinearPriority(task.priority)
    });
  }
  async mergeTaskIntoLinear(task, existingIssue) {
    await this.duplicateDetector.mergeIntoExisting(
      existingIssue,
      task.title,
      task.description,
      `StackMemory Task: ${task.id}
Merged: ${(/* @__PURE__ */ new Date()).toISOString()}`
    );
  }
  mapLinearStateToStatus(state) {
    switch (state.toLowerCase()) {
      case "backlog":
      case "unstarted":
        return "todo";
      case "started":
        return "in_progress";
      case "completed":
        return "done";
      case "cancelled":
        return "cancelled";
      default:
        return "todo";
    }
  }
  mapLinearPriorityToPriority(priority) {
    switch (priority) {
      case 1:
        return "urgent";
      case 2:
        return "high";
      case 3:
        return "medium";
      case 4:
        return "low";
      default:
        return void 0;
    }
  }
  mapPriorityToLinearPriority(priority) {
    switch (priority) {
      case "urgent":
        return 1;
      case "high":
        return 2;
      case "medium":
        return 3;
      case "low":
        return 4;
      default:
        return 0;
    }
  }
  loadMappings() {
    const mappingFile = join(
      this.projectRoot,
      ".stackmemory",
      "linear-mappings.json"
    );
    if (existsSync(mappingFile)) {
      try {
        const data = JSON.parse(readFileSync(mappingFile, "utf8"));
        this.mappings = new Map(Object.entries(data));
      } catch (error) {
        logger.error("Failed to load mappings:", error);
      }
    }
  }
  saveMappings() {
    const mappingFile = join(
      this.projectRoot,
      ".stackmemory",
      "linear-mappings.json"
    );
    const data = Object.fromEntries(this.mappings);
    writeFileSync(mappingFile, JSON.stringify(data, null, 2));
  }
  loadTaskPlan() {
    const planFile = join(this.projectRoot, this.config.taskPlanFile);
    if (existsSync(planFile)) {
      try {
        return JSON.parse(readFileSync(planFile, "utf8"));
      } catch (error) {
        logger.error("Failed to load task plan:", error);
      }
    }
    return {
      version: "1.0.0",
      lastUpdated: /* @__PURE__ */ new Date(),
      phases: []
    };
  }
  saveTaskPlan(plan) {
    const planFile = join(this.projectRoot, this.config.taskPlanFile);
    writeFileSync(planFile, JSON.stringify(plan, null, 2));
  }
  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  /**
   * Get last sync statistics
   */
  getLastSyncStats() {
    return this.lastSyncStats;
  }
  /**
   * Clear duplicate detector cache
   */
  clearCache() {
    if (this.duplicateDetector) {
      this.duplicateDetector.clearCache();
    }
  }
}
export {
  DEFAULT_UNIFIED_CONFIG,
  UnifiedLinearSync
};
//# sourceMappingURL=unified-sync.js.map
