import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import "dotenv/config";
import Redis from "ioredis";
import { v4 as uuidv4 } from "uuid";
import { logger } from "../monitoring/logger.js";
function getEnv(key, defaultValue) {
  const value = process.env[key];
  if (value === void 0) {
    if (defaultValue !== void 0) return defaultValue;
    throw new Error(`Environment variable ${key} is required`);
  }
  return value;
}
function getOptionalEnv(key) {
  return process.env[key];
}
import {
  REDIS_KEYS,
  CACHE_TTL,
  calculateSkillTTL,
  SkillSchema,
  JournalEntrySchema
} from "./types.js";
class SkillStorageService {
  redis;
  userId;
  keyPrefix;
  enableMetrics;
  // Metrics tracking
  metrics = {
    cacheHits: 0,
    cacheMisses: 0
  };
  constructor(config) {
    this.redis = new Redis(config.redisUrl);
    this.userId = config.userId;
    this.keyPrefix = config.keyPrefix || "sm:skills";
    this.enableMetrics = config.enableMetrics ?? true;
    this.redis.on("error", (err) => {
      logger.error("Redis connection error in SkillStorage", err);
    });
    this.redis.on("connect", () => {
      logger.info("SkillStorage connected to Redis");
    });
    logger.info("SkillStorageService initialized", {
      userId: this.userId,
      keyPrefix: this.keyPrefix,
      enableMetrics: this.enableMetrics
    });
  }
  key(pattern) {
    return `${this.keyPrefix}:${pattern}`;
  }
  /**
   * Get the current user ID
   */
  getUserId() {
    return this.userId;
  }
  // ============================================================
  // SKILL CRUD OPERATIONS
  // ============================================================
  /**
   * Create a new skill
   */
  async createSkill(input) {
    const now = (/* @__PURE__ */ new Date()).toISOString();
    const skill = {
      ...input,
      id: uuidv4(),
      createdAt: now,
      updatedAt: now,
      validatedCount: 0
    };
    SkillSchema.parse(skill);
    const pipeline = this.redis.pipeline();
    const skillKey = this.key(REDIS_KEYS.skill(this.userId, skill.id));
    pipeline.setex(
      skillKey,
      calculateSkillTTL(skill.validatedCount),
      JSON.stringify(skill)
    );
    if (skill.tool) {
      pipeline.zadd(
        this.key(REDIS_KEYS.skillsByTool(this.userId, skill.tool)),
        Date.now(),
        skill.id
      );
    }
    pipeline.zadd(
      this.key(REDIS_KEYS.skillsByCategory(this.userId, skill.category)),
      this.priorityScore(skill.priority),
      skill.id
    );
    for (const tag of skill.tags) {
      pipeline.zadd(
        this.key(REDIS_KEYS.skillsByTag(this.userId, tag)),
        Date.now(),
        skill.id
      );
    }
    pipeline.zadd(
      this.key(REDIS_KEYS.skillsRecent(this.userId)),
      Date.now(),
      skill.id
    );
    pipeline.zremrangebyrank(
      this.key(REDIS_KEYS.skillsRecent(this.userId)),
      0,
      -1001
    );
    await pipeline.exec();
    logger.info("Created skill", {
      userId: this.userId,
      id: skill.id,
      category: skill.category,
      tool: skill.tool
    });
    return skill;
  }
  /**
   * Get skill by ID
   */
  async getSkill(id) {
    const skillKey = this.key(REDIS_KEYS.skill(this.userId, id));
    const data = await this.redis.get(skillKey);
    if (!data) {
      this.metrics.cacheMisses++;
      return null;
    }
    this.metrics.cacheHits++;
    return JSON.parse(data);
  }
  /**
   * Update an existing skill
   */
  async updateSkill(input) {
    const existing = await this.getSkill(input.id);
    if (!existing) {
      return null;
    }
    const updated = {
      ...existing,
      ...input,
      updatedAt: (/* @__PURE__ */ new Date()).toISOString()
    };
    SkillSchema.parse(updated);
    const skillKey = this.key(REDIS_KEYS.skill(this.userId, updated.id));
    await this.redis.setex(
      skillKey,
      calculateSkillTTL(updated.validatedCount),
      JSON.stringify(updated)
    );
    logger.info("Updated skill", { userId: this.userId, id: updated.id });
    return updated;
  }
  /**
   * Validate a skill (increment validation count)
   */
  async validateSkill(id) {
    const skill = await this.getSkill(id);
    if (!skill) {
      return null;
    }
    skill.validatedCount++;
    skill.lastValidated = (/* @__PURE__ */ new Date()).toISOString();
    skill.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
    const skillKey = this.key(REDIS_KEYS.skill(this.userId, id));
    await this.redis.setex(
      skillKey,
      calculateSkillTTL(skill.validatedCount),
      JSON.stringify(skill)
    );
    await this.redis.zadd(
      this.key(REDIS_KEYS.skillsValidated(this.userId)),
      skill.validatedCount,
      id
    );
    if (skill.validatedCount >= 3 && skill.priority !== "critical") {
      await this.redis.sadd(
        this.key(REDIS_KEYS.promotionCandidates(this.userId)),
        id
      );
    }
    logger.info("Validated skill", {
      userId: this.userId,
      id,
      validatedCount: skill.validatedCount
    });
    return skill;
  }
  /**
   * Delete a skill
   */
  async deleteSkill(id) {
    const skill = await this.getSkill(id);
    if (!skill) {
      return false;
    }
    const pipeline = this.redis.pipeline();
    const skillKey = this.key(REDIS_KEYS.skill(this.userId, id));
    pipeline.del(skillKey);
    if (skill.tool) {
      pipeline.zrem(
        this.key(REDIS_KEYS.skillsByTool(this.userId, skill.tool)),
        id
      );
    }
    pipeline.zrem(
      this.key(REDIS_KEYS.skillsByCategory(this.userId, skill.category)),
      id
    );
    for (const tag of skill.tags) {
      pipeline.zrem(this.key(REDIS_KEYS.skillsByTag(this.userId, tag)), id);
    }
    pipeline.zrem(this.key(REDIS_KEYS.skillsRecent(this.userId)), id);
    pipeline.zrem(this.key(REDIS_KEYS.skillsValidated(this.userId)), id);
    pipeline.srem(this.key(REDIS_KEYS.promotionCandidates(this.userId)), id);
    await pipeline.exec();
    logger.info("Deleted skill", { userId: this.userId, id });
    return true;
  }
  // ============================================================
  // SKILL QUERIES
  // ============================================================
  /**
   * Query skills with filters
   */
  async querySkills(query) {
    let skillIds = [];
    if (query.tool) {
      skillIds = await this.redis.zrevrange(
        this.key(REDIS_KEYS.skillsByTool(this.userId, query.tool)),
        0,
        -1
      );
    } else if (query.categories && query.categories.length === 1) {
      skillIds = await this.redis.zrevrange(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, query.categories[0])),
        0,
        -1
      );
    } else if (query.tags && query.tags.length === 1) {
      skillIds = await this.redis.zrevrange(
        this.key(REDIS_KEYS.skillsByTag(this.userId, query.tags[0])),
        0,
        -1
      );
    } else {
      skillIds = await this.redis.zrevrange(
        this.key(REDIS_KEYS.skillsRecent(this.userId)),
        0,
        query.limit + query.offset
      );
    }
    if (skillIds.length === 0) {
      return [];
    }
    const pipeline = this.redis.pipeline();
    for (const id of skillIds) {
      pipeline.get(this.key(REDIS_KEYS.skill(this.userId, id)));
    }
    const results = await pipeline.exec();
    if (!results) {
      return [];
    }
    let skills = results.map(([err, data]) => {
      if (err || !data) return null;
      try {
        return JSON.parse(data);
      } catch {
        return null;
      }
    }).filter((s) => s !== null);
    if (query.categories && query.categories.length > 0) {
      skills = skills.filter((s) => query.categories.includes(s.category));
    }
    if (query.priorities && query.priorities.length > 0) {
      skills = skills.filter((s) => query.priorities.includes(s.priority));
    }
    if (query.minValidatedCount !== void 0) {
      skills = skills.filter(
        (s) => s.validatedCount >= query.minValidatedCount
      );
    }
    if (query.language) {
      skills = skills.filter((s) => s.language === query.language);
    }
    if (query.framework) {
      skills = skills.filter((s) => s.framework === query.framework);
    }
    skills.sort((a, b) => {
      let aVal, bVal;
      switch (query.sortBy) {
        case "priority":
          aVal = this.priorityScore(a.priority);
          bVal = this.priorityScore(b.priority);
          break;
        case "validatedCount":
          aVal = a.validatedCount;
          bVal = b.validatedCount;
          break;
        case "createdAt":
          aVal = new Date(a.createdAt).getTime();
          bVal = new Date(b.createdAt).getTime();
          break;
        case "updatedAt":
          aVal = new Date(a.updatedAt).getTime();
          bVal = new Date(b.updatedAt).getTime();
          break;
        default:
          aVal = this.priorityScore(a.priority);
          bVal = this.priorityScore(b.priority);
      }
      return query.sortOrder === "desc" ? bVal - aVal : aVal - bVal;
    });
    return skills.slice(query.offset, query.offset + query.limit);
  }
  /**
   * Get skills relevant to current context
   */
  async getRelevantSkills(context) {
    const skills = [];
    const seenIds = /* @__PURE__ */ new Set();
    const criticalIds = await this.redis.zrevrange(
      this.key(REDIS_KEYS.skillsByCategory(this.userId, "correction")),
      0,
      -1
    );
    for (const id of criticalIds) {
      const skill = await this.getSkill(id);
      if (skill && skill.priority === "critical" && !seenIds.has(id)) {
        skills.push(skill);
        seenIds.add(id);
      }
    }
    if (context.tool) {
      const toolSkills = await this.querySkills({
        tool: context.tool,
        limit: 20,
        offset: 0,
        sortBy: "priority",
        sortOrder: "desc"
      });
      for (const skill of toolSkills) {
        if (!seenIds.has(skill.id)) {
          skills.push(skill);
          seenIds.add(skill.id);
        }
      }
    }
    const validatedIds = await this.redis.zrevrange(
      this.key(REDIS_KEYS.skillsValidated(this.userId)),
      0,
      10
    );
    for (const id of validatedIds) {
      if (!seenIds.has(id)) {
        const skill = await this.getSkill(id);
        if (skill) {
          skills.push(skill);
          seenIds.add(id);
        }
      }
    }
    return skills.slice(0, 50);
  }
  // ============================================================
  // SESSION JOURNAL
  // ============================================================
  /**
   * Create a journal entry
   */
  async createJournalEntry(sessionId, type, title, content, context) {
    const entry = {
      id: uuidv4(),
      sessionId,
      type,
      title,
      content,
      context,
      createdAt: (/* @__PURE__ */ new Date()).toISOString()
    };
    JournalEntrySchema.parse(entry);
    const pipeline = this.redis.pipeline();
    pipeline.setex(
      this.key(REDIS_KEYS.journalEntry(this.userId, entry.id)),
      CACHE_TTL.journal,
      JSON.stringify(entry)
    );
    pipeline.zadd(
      this.key(REDIS_KEYS.journalSession(this.userId, sessionId)),
      Date.now(),
      entry.id
    );
    pipeline.zadd(
      this.key(REDIS_KEYS.journalRecent(this.userId)),
      Date.now(),
      entry.id
    );
    pipeline.zremrangebyrank(
      this.key(REDIS_KEYS.journalRecent(this.userId)),
      0,
      -501
    );
    await pipeline.exec();
    logger.info("Created journal entry", {
      userId: this.userId,
      id: entry.id,
      sessionId,
      type,
      title
    });
    return entry;
  }
  /**
   * Get journal entries for a session
   */
  async getSessionJournal(sessionId) {
    const entryIds = await this.redis.zrevrange(
      this.key(REDIS_KEYS.journalSession(this.userId, sessionId)),
      0,
      -1
    );
    if (entryIds.length === 0) {
      return [];
    }
    const pipeline = this.redis.pipeline();
    for (const id of entryIds) {
      pipeline.get(this.key(REDIS_KEYS.journalEntry(this.userId, id)));
    }
    const results = await pipeline.exec();
    if (!results) {
      return [];
    }
    return results.map(([err, data]) => {
      if (err || !data) return null;
      try {
        return JSON.parse(data);
      } catch {
        return null;
      }
    }).filter((e) => e !== null);
  }
  /**
   * Promote a journal entry to a skill
   */
  async promoteToSkill(entryId, category, priority = "medium") {
    const entryData = await this.redis.get(
      this.key(REDIS_KEYS.journalEntry(this.userId, entryId))
    );
    if (!entryData) {
      return null;
    }
    const entry = JSON.parse(entryData);
    const skill = await this.createSkill({
      content: entry.content,
      summary: entry.title,
      category,
      priority,
      tags: [],
      tool: entry.context?.tool,
      source: "observation",
      sessionId: entry.sessionId
    });
    entry.promotedToSkillId = skill.id;
    await this.redis.setex(
      this.key(REDIS_KEYS.journalEntry(this.userId, entryId)),
      CACHE_TTL.journal,
      JSON.stringify(entry)
    );
    logger.info("Promoted journal entry to skill", {
      userId: this.userId,
      entryId,
      skillId: skill.id
    });
    return skill;
  }
  // ============================================================
  // SESSION MANAGEMENT
  // ============================================================
  /**
   * Start tracking a new session
   */
  async startSession(sessionId) {
    const summary = {
      sessionId,
      startedAt: (/* @__PURE__ */ new Date()).toISOString(),
      entriesCount: 0,
      correctionsCount: 0,
      decisionsCount: 0,
      keyLearnings: [],
      promotedSkillIds: []
    };
    await this.redis.setex(
      this.key(REDIS_KEYS.sessionSummary(this.userId, sessionId)),
      CACHE_TTL.session,
      JSON.stringify(summary)
    );
    await this.redis.sadd(
      this.key(REDIS_KEYS.sessionsActive(this.userId)),
      sessionId
    );
    logger.info("Started session tracking", { userId: this.userId, sessionId });
  }
  /**
   * End a session and generate summary
   */
  async endSession(sessionId) {
    const summaryData = await this.redis.get(
      this.key(REDIS_KEYS.sessionSummary(this.userId, sessionId))
    );
    if (!summaryData) {
      return null;
    }
    const summary = JSON.parse(summaryData);
    summary.endedAt = (/* @__PURE__ */ new Date()).toISOString();
    const entries = await this.getSessionJournal(sessionId);
    summary.entriesCount = entries.length;
    summary.correctionsCount = entries.filter(
      (e) => e.type === "correction"
    ).length;
    summary.decisionsCount = entries.filter(
      (e) => e.type === "decision"
    ).length;
    summary.keyLearnings = entries.filter((e) => e.type === "correction" || e.type === "resolution").slice(0, 5).map((e) => e.title);
    summary.promotedSkillIds = entries.filter((e) => e.promotedToSkillId).map((e) => e.promotedToSkillId);
    await this.redis.setex(
      this.key(REDIS_KEYS.sessionSummary(this.userId, sessionId)),
      CACHE_TTL.session,
      JSON.stringify(summary)
    );
    await this.redis.srem(
      this.key(REDIS_KEYS.sessionsActive(this.userId)),
      sessionId
    );
    logger.info("Ended session", {
      userId: this.userId,
      sessionId,
      entriesCount: summary.entriesCount,
      keyLearnings: summary.keyLearnings.length
    });
    return summary;
  }
  /**
   * Get session summary
   */
  async getSessionSummary(sessionId) {
    const data = await this.redis.get(
      this.key(REDIS_KEYS.sessionSummary(this.userId, sessionId))
    );
    if (!data) {
      return null;
    }
    return JSON.parse(data);
  }
  // ============================================================
  // KNOWLEDGE HYGIENE
  // ============================================================
  /**
   * Get skills eligible for promotion
   */
  async getPromotionCandidates() {
    const ids = await this.redis.smembers(
      this.key(REDIS_KEYS.promotionCandidates(this.userId))
    );
    const skills = [];
    for (const id of ids) {
      const skill = await this.getSkill(id);
      if (skill && skill.validatedCount >= 3) {
        skills.push(skill);
      }
    }
    return skills;
  }
  /**
   * Promote a skill (increase priority)
   */
  async promoteSkill(id) {
    const skill = await this.getSkill(id);
    if (!skill) {
      return null;
    }
    const priorityOrder = [
      "low",
      "medium",
      "high",
      "critical"
    ];
    const currentIndex = priorityOrder.indexOf(skill.priority);
    if (currentIndex < priorityOrder.length - 1) {
      skill.priority = priorityOrder[currentIndex + 1];
      skill.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
      await this.redis.setex(
        this.key(REDIS_KEYS.skill(this.userId, id)),
        calculateSkillTTL(skill.validatedCount),
        JSON.stringify(skill)
      );
      await this.redis.zadd(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, skill.category)),
        this.priorityScore(skill.priority),
        id
      );
      if (skill.priority === "critical") {
        await this.redis.srem(
          this.key(REDIS_KEYS.promotionCandidates(this.userId)),
          id
        );
      }
      logger.info("Promoted skill", {
        userId: this.userId,
        id,
        newPriority: skill.priority
      });
    }
    return skill;
  }
  /**
   * Archive stale skills (not validated in 90 days)
   */
  async archiveStaleSkills(daysThreshold = 90) {
    const cutoff = Date.now() - daysThreshold * 24 * 60 * 60 * 1e3;
    let archivedCount = 0;
    const skillIds = await this.redis.zrangebyscore(
      this.key(REDIS_KEYS.skillsRecent(this.userId)),
      0,
      cutoff
    );
    for (const id of skillIds) {
      const skill = await this.getSkill(id);
      if (skill && skill.priority !== "critical") {
        if (!skill.lastValidated || new Date(skill.lastValidated).getTime() < cutoff) {
          if (skill.priority !== "low") {
            skill.priority = "low";
            skill.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
            await this.redis.setex(
              this.key(REDIS_KEYS.skill(this.userId, id)),
              calculateSkillTTL(skill.validatedCount),
              JSON.stringify(skill)
            );
            archivedCount++;
          }
        }
      }
    }
    logger.info("Archived stale skills", {
      userId: this.userId,
      archivedCount,
      daysThreshold
    });
    return archivedCount;
  }
  // ============================================================
  // METRICS & UTILITIES
  // ============================================================
  /**
   * Get storage metrics
   */
  async getMetrics() {
    const [
      skillsTotal,
      toolSkills,
      workflowSkills,
      correctionSkills,
      patternSkills,
      preferenceSkills,
      pitfallSkills,
      optimizationSkills,
      journalTotal,
      sessionsActive
    ] = await Promise.all([
      this.redis.zcard(this.key(REDIS_KEYS.skillsRecent(this.userId))),
      this.redis.zcard(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, "tool"))
      ),
      this.redis.zcard(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, "workflow"))
      ),
      this.redis.zcard(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, "correction"))
      ),
      this.redis.zcard(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, "pattern"))
      ),
      this.redis.zcard(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, "preference"))
      ),
      this.redis.zcard(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, "pitfall"))
      ),
      this.redis.zcard(
        this.key(REDIS_KEYS.skillsByCategory(this.userId, "optimization"))
      ),
      this.redis.zcard(this.key(REDIS_KEYS.journalRecent(this.userId))),
      this.redis.scard(this.key(REDIS_KEYS.sessionsActive(this.userId)))
    ]);
    return {
      userId: this.userId,
      skillsTotal,
      skillsByCategory: {
        tool: toolSkills,
        workflow: workflowSkills,
        correction: correctionSkills,
        pattern: patternSkills,
        preference: preferenceSkills,
        pitfall: pitfallSkills,
        optimization: optimizationSkills
      },
      journalEntriesTotal: journalTotal,
      sessionsTracked: sessionsActive,
      cacheHits: this.metrics.cacheHits,
      cacheMisses: this.metrics.cacheMisses
    };
  }
  /**
   * Priority to numeric score for sorting
   */
  priorityScore(priority) {
    const scores = {
      critical: 1e3,
      high: 100,
      medium: 10,
      low: 1
    };
    return scores[priority];
  }
  /**
   * Close Redis connection
   */
  async close() {
    await this.redis.quit();
    logger.info("SkillStorageService closed");
  }
}
const userStorageInstances = /* @__PURE__ */ new Map();
function getSkillStorage(config) {
  const existing = userStorageInstances.get(config.userId);
  if (existing) {
    return existing;
  }
  const instance = new SkillStorageService(config);
  userStorageInstances.set(config.userId, instance);
  return instance;
}
function initializeSkillStorage(userId, redisUrl) {
  const url = redisUrl || process.env["REDIS_URL"];
  if (!url) {
    throw new Error("REDIS_URL environment variable not set");
  }
  return getSkillStorage({ redisUrl: url, userId });
}
function getDefaultUserId() {
  return process.env["STACKMEMORY_USER_ID"] || process.env["USER"] || process.env["USERNAME"] || "default";
}
export {
  SkillStorageService,
  getDefaultUserId,
  getSkillStorage,
  initializeSkillStorage
};
//# sourceMappingURL=skill-storage.js.map
