import {
  log
} from "./chunk-2ODBA7MQ.js";

// src/threading.ts
import { readdir, readFile, writeFile, mkdir } from "fs/promises";
import path from "path";
var STOP_WORDS = /* @__PURE__ */ new Set([
  "a",
  "an",
  "the",
  "is",
  "are",
  "was",
  "were",
  "be",
  "been",
  "being",
  "have",
  "has",
  "had",
  "do",
  "does",
  "did",
  "will",
  "would",
  "could",
  "should",
  "may",
  "might",
  "must",
  "shall",
  "can",
  "need",
  "to",
  "of",
  "in",
  "for",
  "on",
  "with",
  "at",
  "by",
  "from",
  "as",
  "into",
  "through",
  "during",
  "before",
  "after",
  "above",
  "below",
  "between",
  "this",
  "that",
  "these",
  "those",
  "i",
  "me",
  "my",
  "we",
  "our",
  "you",
  "your",
  "he",
  "she",
  "it",
  "they",
  "them",
  "their",
  "what",
  "which",
  "who",
  "how",
  "when",
  "where",
  "why",
  "and",
  "but",
  "or",
  "if",
  "because",
  "so",
  "just",
  "about",
  "like",
  "also",
  "very",
  "really",
  "here",
  "there"
]);
function extractKeywords(content, maxKeywords = 3) {
  const words = content.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length >= 4 && !STOP_WORDS.has(w));
  const freq = /* @__PURE__ */ new Map();
  for (const word of words) {
    freq.set(word, (freq.get(word) ?? 0) + 1);
  }
  return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, maxKeywords).map(([word]) => word);
}
function generateTitle(keywords) {
  if (keywords.length === 0) return "Untitled Thread";
  return keywords.map((k) => k.charAt(0).toUpperCase() + k.slice(1)).join(", ");
}
var ThreadingManager = class {
  constructor(threadsDir, gapMinutes = 30) {
    this.threadsDir = threadsDir;
    this.gapMinutes = gapMinutes;
  }
  threadsDir;
  gapMinutes;
  currentThreadId = null;
  lastTurnTimestamp = null;
  lastSessionKey = null;
  async ensureDirectory() {
    await mkdir(this.threadsDir, { recursive: true });
  }
  /**
   * Check if we should start a new thread based on session key and time gap.
   */
  shouldStartNewThread(turn) {
    const turnTime = new Date(turn.timestamp).getTime();
    if (turn.sessionKey && this.lastSessionKey && turn.sessionKey !== this.lastSessionKey) {
      return true;
    }
    if (this.lastTurnTimestamp) {
      const gapMs = turnTime - this.lastTurnTimestamp;
      const gapMinutes = gapMs / (1e3 * 60);
      if (gapMinutes > this.gapMinutes) {
        return true;
      }
    }
    if (!this.currentThreadId) {
      return true;
    }
    return false;
  }
  /**
   * Process a turn and return the thread ID it belongs to.
   * Creates a new thread if needed.
   */
  async processTurn(turn, episodeIds) {
    await this.ensureDirectory();
    const turnTime = new Date(turn.timestamp).getTime();
    if (this.shouldStartNewThread(turn)) {
      const threadId = `thread-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
      const thread = {
        id: threadId,
        title: "New Thread",
        // Will be updated later with keywords
        createdAt: turn.timestamp,
        updatedAt: turn.timestamp,
        sessionKey: turn.sessionKey,
        episodeIds: [...episodeIds],
        linkedThreadIds: []
      };
      await this.saveThread(thread);
      this.currentThreadId = threadId;
      log.debug(`created new thread ${threadId}`);
    } else if (this.currentThreadId) {
      const thread = await this.loadThread(this.currentThreadId);
      if (thread) {
        for (const id of episodeIds) {
          if (!thread.episodeIds.includes(id)) {
            thread.episodeIds.push(id);
          }
        }
        thread.updatedAt = turn.timestamp;
        await this.saveThread(thread);
      }
    }
    this.lastTurnTimestamp = turnTime;
    this.lastSessionKey = turn.sessionKey ?? null;
    return this.currentThreadId;
  }
  /**
   * Append persisted memory IDs to an existing thread.
   * Keeps IDs unique and updates the thread timestamp.
   */
  async appendEpisodeIds(threadId, episodeIds) {
    if (episodeIds.length === 0) return;
    const thread = await this.loadThread(threadId);
    if (!thread) return;
    let changed = false;
    for (const id of episodeIds) {
      if (!thread.episodeIds.includes(id)) {
        thread.episodeIds.push(id);
        changed = true;
      }
    }
    if (!changed) return;
    thread.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
    await this.saveThread(thread);
  }
  /**
   * Update thread title based on accumulated content.
   */
  async updateThreadTitle(threadId, content) {
    const thread = await this.loadThread(threadId);
    if (!thread) return;
    const keywords = extractKeywords(content);
    const title = generateTitle(keywords);
    if (title !== thread.title) {
      thread.title = title;
      thread.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
      await this.saveThread(thread);
      log.debug(`updated thread ${threadId} title: ${title}`);
    }
  }
  /**
   * Get all threads, sorted by updatedAt desc.
   */
  async getAllThreads() {
    try {
      const files = await readdir(this.threadsDir);
      const threads = [];
      for (const file of files) {
        if (!file.endsWith(".json")) continue;
        const thread = await this.loadThread(file.replace(".json", ""));
        if (thread) threads.push(thread);
      }
      return threads.sort(
        (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
      );
    } catch {
      return [];
    }
  }
  /**
   * Get a thread by ID.
   */
  async loadThread(threadId) {
    const filePath = path.join(this.threadsDir, `${threadId}.json`);
    try {
      const raw = await readFile(filePath, "utf-8");
      return JSON.parse(raw);
    } catch {
      return null;
    }
  }
  /**
   * Save a thread.
   */
  async saveThread(thread) {
    await this.ensureDirectory();
    const filePath = path.join(this.threadsDir, `${thread.id}.json`);
    await writeFile(filePath, JSON.stringify(thread, null, 2), "utf-8");
  }
  /**
   * Link two threads together.
   */
  async linkThreads(threadId1, threadId2) {
    const thread1 = await this.loadThread(threadId1);
    const thread2 = await this.loadThread(threadId2);
    if (!thread1 || !thread2) return false;
    if (!thread1.linkedThreadIds.includes(threadId2)) {
      thread1.linkedThreadIds.push(threadId2);
      await this.saveThread(thread1);
    }
    if (!thread2.linkedThreadIds.includes(threadId1)) {
      thread2.linkedThreadIds.push(threadId1);
      await this.saveThread(thread2);
    }
    return true;
  }
  /**
   * Get the current thread ID (if any).
   */
  getCurrentThreadId() {
    return this.currentThreadId;
  }
  /**
   * Reset threading state (e.g., on plugin restart).
   */
  reset() {
    this.currentThreadId = null;
    this.lastTurnTimestamp = null;
    this.lastSessionKey = null;
  }
};

export {
  ThreadingManager
};
//# sourceMappingURL=chunk-JRNQ3RNA.js.map