import { EventEmitter } from "events";
import { createHash } from "crypto";
import { appendFile, existsSync, mkdirSync, readFileSync } from "fs";
import { join } from "path";
import { logger } from "../../core/monitoring/logger.js";
import {
  DatabaseError,
  TaskError,
  SystemError,
  ErrorCode,
  createErrorHandler
} from "../../core/errors/index.js";
import { retry } from "../../core/errors/recovery.js";
import { StreamingJSONLParser } from "../../core/performance/streaming-jsonl-parser.js";
import { ContextCache } from "../../core/performance/context-cache.js";
class PebblesTaskStore extends EventEmitter {
  db;
  projectRoot;
  tasksFile;
  cacheFile;
  jsonlParser;
  taskCache;
  constructor(projectRoot, db) {
    super();
    this.projectRoot = projectRoot;
    this.db = db;
    const stackmemoryDir = join(projectRoot, ".stackmemory");
    if (!existsSync(stackmemoryDir)) {
      mkdirSync(stackmemoryDir, { recursive: true });
    }
    this.tasksFile = join(stackmemoryDir, "tasks.jsonl");
    this.cacheFile = join(stackmemoryDir, "cache.db");
    this.jsonlParser = new StreamingJSONLParser();
    this.taskCache = new ContextCache({
      maxSize: 10 * 1024 * 1024,
      // 10MB for tasks
      maxItems: 1e3,
      defaultTTL: 36e5
      // 1 hour
    });
    this.initializeCache();
    this.loadFromJSONLSync();
  }
  initializeCache() {
    const errorHandler = createErrorHandler({
      operation: "initializeCache",
      projectRoot: this.projectRoot
    });
    try {
      this.db.exec(`
        CREATE TABLE IF NOT EXISTS task_cache (
          id TEXT PRIMARY KEY,
          type TEXT NOT NULL,
          timestamp INTEGER NOT NULL,
          parent_id TEXT,
          frame_id TEXT NOT NULL,
          title TEXT NOT NULL,
          description TEXT,
          status TEXT NOT NULL,
          priority TEXT NOT NULL,
          assignee TEXT,
          created_at INTEGER NOT NULL,
          started_at INTEGER,
          completed_at INTEGER,
          estimated_effort INTEGER,
          actual_effort INTEGER,
          depends_on TEXT DEFAULT '[]',
          blocks TEXT DEFAULT '[]',
          tags TEXT DEFAULT '[]',
          external_refs TEXT DEFAULT '{}',
          context_score REAL DEFAULT 0.5,
          last_accessed INTEGER
        );
        
        CREATE INDEX IF NOT EXISTS idx_task_status ON task_cache(status);
        CREATE INDEX IF NOT EXISTS idx_task_priority ON task_cache(priority);
        CREATE INDEX IF NOT EXISTS idx_task_frame ON task_cache(frame_id);
        CREATE INDEX IF NOT EXISTS idx_task_timestamp ON task_cache(timestamp);
        CREATE INDEX IF NOT EXISTS idx_task_parent ON task_cache(parent_id);
      `);
    } catch (error) {
      const dbError = errorHandler(error, {
        operation: "initializeCache",
        schema: "task_cache"
      });
      throw new DatabaseError(
        "Failed to initialize task cache schema",
        ErrorCode.DB_MIGRATION_FAILED,
        {
          projectRoot: this.projectRoot,
          cacheFile: this.cacheFile,
          operation: "initializeCache"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  /**
   * Load existing tasks from JSONL into SQLite cache (optimized)
   */
  async loadFromJSONL() {
    if (!existsSync(this.tasksFile)) return;
    const errorHandler = createErrorHandler({
      operation: "loadFromJSONL",
      tasksFile: this.tasksFile
    });
    try {
      let loaded = 0;
      let errors = 0;
      for await (const batch of this.jsonlParser.parseStream(
        this.tasksFile,
        {
          batchSize: 100,
          filter: (obj) => obj.type && obj.id && obj.title,
          // Basic validation
          onProgress: (count) => {
            if (count % 500 === 0) {
              logger.debug("Loading tasks progress", { loaded: count });
            }
          }
        }
      )) {
        for (const task of batch) {
          try {
            this.upsertToCache(task);
            this.taskCache.set(task.id, task, {
              ttl: 36e5
              // 1 hour cache
            });
            loaded++;
          } catch (error) {
            errors++;
            logger.warn("Failed to cache task", {
              taskId: task.id,
              error: error instanceof Error ? error.message : String(error)
            });
          }
        }
      }
      logger.info("Loaded tasks from JSONL", {
        loaded,
        errors,
        file: this.tasksFile,
        cacheStats: this.taskCache.getStats()
      });
    } catch (error) {
      const systemError = errorHandler(error, {
        operation: "loadFromJSONL",
        file: this.tasksFile
      });
      throw new SystemError(
        "Failed to load tasks from JSONL file",
        ErrorCode.INTERNAL_ERROR,
        {
          tasksFile: this.tasksFile,
          operation: "loadFromJSONL"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  /**
   * Load existing tasks from JSONL synchronously (for constructor)
   */
  loadFromJSONLSync() {
    if (!existsSync(this.tasksFile)) return;
    try {
      const content = readFileSync(this.tasksFile, "utf-8");
      const lines = content.split("\n").filter((line) => line.trim());
      let loaded = 0;
      let errors = 0;
      for (const line of lines) {
        try {
          const task = JSON.parse(line);
          if (task.type && task.id && task.title) {
            this.upsertToCache(task);
            this.taskCache.set(task.id, task, {
              ttl: 36e5
              // 1 hour cache
            });
            loaded++;
          }
        } catch (error) {
          errors++;
          logger.warn("Failed to parse JSONL line", {
            error: error instanceof Error ? error.message : String(error)
          });
        }
      }
      logger.info("Loaded tasks from JSONL", {
        loaded,
        errors,
        file: this.tasksFile
      });
    } catch (error) {
      logger.error("Failed to load tasks from JSONL", error);
    }
  }
  /**
   * Create a new task with content-hash ID
   */
  createTask(options) {
    const now = Math.floor(Date.now() / 1e3);
    const content = `${options.title}:${options.frameId}:${now}:${Math.random()}`;
    const id = this.generateTaskId(content);
    const task = {
      id,
      type: "task_create",
      timestamp: now,
      parent_id: options.parentId,
      frame_id: options.frameId,
      title: options.title,
      description: options.description,
      status: "pending",
      priority: options.priority || "medium",
      assignee: options.assignee,
      created_at: now,
      estimated_effort: options.estimatedEffort,
      depends_on: options.dependsOn || [],
      blocks: [],
      tags: options.tags || [],
      context_score: 0.5
    };
    this.appendTask(task);
    this.emit("task:created", task);
    this.emit("sync:needed", "task:created");
    return id;
  }
  /**
   * Update task status with new event
   */
  updateTaskStatus(taskId, newStatus, _reason) {
    const existing = this.getTask(taskId);
    if (!existing) {
      throw new TaskError(
        `Task not found: ${taskId}`,
        ErrorCode.TASK_NOT_FOUND,
        {
          taskId,
          newStatus,
          operation: "updateTaskStatus"
        }
      );
    }
    if (existing.status === "completed" && newStatus !== "cancelled") {
      throw new TaskError(
        `Cannot change completed task status from ${existing.status} to ${newStatus}`,
        ErrorCode.TASK_INVALID_STATE,
        {
          taskId,
          currentStatus: existing.status,
          newStatus,
          operation: "updateTaskStatus"
        }
      );
    }
    const now = Math.floor(Date.now() / 1e3);
    const updates = {
      status: newStatus,
      timestamp: now
    };
    if (newStatus === "in_progress" && existing.status === "pending") {
      updates.started_at = now;
      updates.type = "task_update";
    } else if (newStatus === "completed" && existing.status === "in_progress") {
      updates.completed_at = now;
      updates.type = "task_complete";
      if (existing.started_at) {
        updates.actual_effort = Math.floor((now - existing.started_at) / 60);
      }
    } else if (newStatus === "blocked") {
      updates.type = "task_block";
    }
    const updatedTask = { ...existing, ...updates };
    this.appendTask(updatedTask);
    if (newStatus === "completed") {
      this.emit("task:completed", updatedTask);
    }
    this.emit("sync:needed", "task:updated");
  }
  /**
   * Add dependency relationship
   */
  addDependency(taskId, dependsOnId) {
    const task = this.getTask(taskId);
    const dependsOnTask = this.getTask(dependsOnId);
    if (!task) {
      throw new TaskError(
        `Task not found: ${taskId}`,
        ErrorCode.TASK_NOT_FOUND,
        {
          taskId,
          operation: "addDependency"
        }
      );
    }
    if (!dependsOnTask) {
      throw new TaskError(
        `Dependency task not found: ${dependsOnId}`,
        ErrorCode.TASK_NOT_FOUND,
        {
          dependsOnId,
          taskId,
          operation: "addDependency"
        }
      );
    }
    if (this.wouldCreateCircularDependency(taskId, dependsOnId)) {
      throw new TaskError(
        `Adding dependency would create circular dependency: ${taskId} -> ${dependsOnId}`,
        ErrorCode.TASK_CIRCULAR_DEPENDENCY,
        {
          taskId,
          dependsOnId,
          operation: "addDependency"
        }
      );
    }
    const updatedTask = {
      ...task,
      depends_on: [.../* @__PURE__ */ new Set([...task.depends_on, dependsOnId])],
      timestamp: Math.floor(Date.now() / 1e3),
      type: "task_update"
    };
    const updatedBlockingTask = {
      ...dependsOnTask,
      blocks: [.../* @__PURE__ */ new Set([...dependsOnTask.blocks, taskId])],
      timestamp: Math.floor(Date.now() / 1e3),
      type: "task_update"
    };
    this.appendTask(updatedTask);
    this.appendTask(updatedBlockingTask);
  }
  /**
   * Get current active tasks
   */
  getActiveTasks(frameId) {
    try {
      let query = `
        SELECT * FROM task_cache 
        WHERE status IN ('pending', 'in_progress')
      `;
      const params = [];
      if (frameId) {
        query += ` AND frame_id = ?`;
        params.push(frameId);
      }
      query += ` ORDER BY priority DESC, created_at ASC`;
      const rows = this.db.prepare(query).all(...params);
      return this.hydrateTasks(rows);
    } catch (error) {
      throw new DatabaseError(
        "Failed to get active tasks",
        ErrorCode.DB_QUERY_FAILED,
        {
          frameId,
          operation: "getActiveTasks"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  /**
   * Get task by ID (latest version)
   */
  getTask(taskId) {
    try {
      const row = this.db.prepare(
        `
        SELECT * FROM task_cache WHERE id = ?
      `
      ).get(taskId);
      return row ? this.hydrateTask(row) : void 0;
    } catch (error) {
      throw new DatabaseError(
        `Failed to get task: ${taskId}`,
        ErrorCode.DB_QUERY_FAILED,
        {
          taskId,
          operation: "getTask"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  /**
   * Get tasks that are blocking other tasks
   */
  getBlockingTasks() {
    try {
      const rows = this.db.prepare(
        `
        SELECT * FROM task_cache 
        WHERE JSON_ARRAY_LENGTH(blocks) > 0 
        AND status NOT IN ('completed', 'cancelled')
        ORDER BY priority DESC
      `
      ).all();
      return this.hydrateTasks(rows);
    } catch (error) {
      throw new DatabaseError(
        "Failed to get blocking tasks",
        ErrorCode.DB_QUERY_FAILED,
        {
          operation: "getBlockingTasks"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  /**
   * Get metrics for current project
   */
  getMetrics() {
    try {
      const statusCounts = this.db.prepare(
        `
        SELECT status, COUNT(*) as count 
        FROM task_cache 
        GROUP BY status
      `
      ).all();
      const priorityCounts = this.db.prepare(
        `
        SELECT priority, COUNT(*) as count 
        FROM task_cache 
        GROUP BY priority  
      `
      ).all();
      const totalTasks = statusCounts.reduce((sum, s) => sum + s.count, 0);
      const completedTasks = statusCounts.find((s) => s.status === "completed")?.count || 0;
      const blockedTasks = statusCounts.find((s) => s.status === "blocked")?.count || 0;
      const effortRows = this.db.prepare(
        `
        SELECT estimated_effort, actual_effort 
        FROM task_cache 
        WHERE estimated_effort IS NOT NULL 
        AND actual_effort IS NOT NULL
      `
      ).all();
      let avgEffortAccuracy = 0;
      if (effortRows.length > 0) {
        const accuracies = effortRows.map(
          (r) => 1 - Math.abs(r.estimated_effort - r.actual_effort) / Math.max(r.estimated_effort, 1)
        );
        avgEffortAccuracy = accuracies.reduce((sum, acc) => sum + acc, 0) / accuracies.length;
      }
      return {
        total_tasks: totalTasks,
        by_status: Object.fromEntries(
          statusCounts.map((s) => [s.status, s.count])
        ),
        by_priority: Object.fromEntries(
          priorityCounts.map((p) => [p.priority, p.count])
        ),
        completion_rate: totalTasks > 0 ? completedTasks / totalTasks : 0,
        avg_effort_accuracy: avgEffortAccuracy,
        blocked_tasks: blockedTasks,
        overdue_tasks: 0
        // TODO: implement due dates
      };
    } catch (error) {
      throw new DatabaseError(
        "Failed to get task metrics",
        ErrorCode.DB_QUERY_FAILED,
        {
          operation: "getMetrics"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  /**
   * Export tasks for Linear integration (Phase 2)
   */
  exportForLinear() {
    const tasks = this.db.prepare(
      `
      SELECT * FROM task_cache 
      WHERE external_refs IS NULL OR JSON_EXTRACT(external_refs, '$.linear') IS NULL
      ORDER BY created_at ASC
    `
    ).all();
    return tasks.map((task) => ({
      title: task.title,
      description: task.description,
      priority: this.mapPriorityToLinear(task.priority),
      status: this.mapStatusToLinear(task.status),
      estimate: task.estimated_effort,
      dependencies: JSON.parse(task.depends_on || "[]")
    }));
  }
  // Private methods
  appendTask(task) {
    try {
      const jsonLine = JSON.stringify(task) + "\n";
      appendFile(this.tasksFile, jsonLine, (err) => {
        if (err) {
          logger.error(
            `Failed to append task ${task.id} to JSONL: ${err.message}`,
            err,
            {
              taskId: task.id,
              tasksFile: this.tasksFile
            }
          );
        }
      });
      retry(() => Promise.resolve(this.upsertToCache(task)), {
        maxAttempts: 3,
        initialDelay: 100,
        onRetry: (attempt, error) => {
          logger.warn(`Retrying task cache upsert (attempt ${attempt})`, {
            taskId: task.id,
            errorMessage: error instanceof Error ? error.message : String(error)
          });
        }
      }).catch((error) => {
        logger.error(
          "Failed to upsert task to cache after retries",
          error instanceof Error ? error : new Error(String(error)),
          {
            taskId: task.id
          }
        );
        throw error;
      });
      logger.info("Appended task", {
        id: task.id,
        type: task.type,
        title: task.title,
        status: task.status
      });
    } catch (error) {
      throw new SystemError(
        `Failed to append task: ${task.id}`,
        ErrorCode.INTERNAL_ERROR,
        {
          taskId: task.id,
          operation: "appendTask"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  upsertToCache(task) {
    try {
      this.db.prepare(
        `
        INSERT OR REPLACE INTO task_cache (
          id, type, timestamp, parent_id, frame_id, title, description,
          status, priority, assignee, created_at, started_at, completed_at,
          estimated_effort, actual_effort, depends_on, blocks, tags,
          external_refs, context_score, last_accessed
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `
      ).run(
        task.id,
        task.type,
        task.timestamp,
        task.parent_id,
        task.frame_id,
        task.title,
        task.description,
        task.status,
        task.priority,
        task.assignee,
        task.created_at,
        task.started_at,
        task.completed_at,
        task.estimated_effort,
        task.actual_effort,
        JSON.stringify(task.depends_on),
        JSON.stringify(task.blocks),
        JSON.stringify(task.tags),
        JSON.stringify(task.external_refs || {}),
        task.context_score,
        task.last_accessed
      );
    } catch (error) {
      throw new DatabaseError(
        `Failed to upsert task to cache: ${task.id}`,
        ErrorCode.DB_QUERY_FAILED,
        {
          taskId: task.id,
          taskTitle: task.title,
          operation: "upsertToCache"
        },
        error instanceof Error ? error : void 0
      );
    }
  }
  generateTaskId(content) {
    const hash = createHash("sha256").update(content).digest("hex");
    return `tsk-${hash.substring(0, 8)}`;
  }
  hydrateTask = (row) => ({
    ...row,
    depends_on: JSON.parse(row.depends_on || "[]"),
    blocks: JSON.parse(row.blocks || "[]"),
    tags: JSON.parse(row.tags || "[]"),
    external_refs: JSON.parse(row.external_refs || "{}")
  });
  hydrateTasks(rows) {
    return rows.map(this.hydrateTask);
  }
  mapPriorityToLinear(priority) {
    const map = { low: 1, medium: 2, high: 3, urgent: 4 };
    return map[priority] || 2;
  }
  mapStatusToLinear(status) {
    const map = {
      pending: "Backlog",
      in_progress: "In Progress",
      completed: "Done",
      blocked: "Blocked",
      cancelled: "Cancelled"
    };
    return map[status] || "Backlog";
  }
  /**
   * Check if adding a dependency would create a circular dependency
   */
  wouldCreateCircularDependency(taskId, dependsOnId) {
    const visited = /* @__PURE__ */ new Set();
    const stack = [dependsOnId];
    while (stack.length > 0) {
      const currentId = stack.pop();
      if (currentId === taskId) {
        return true;
      }
      if (visited.has(currentId)) {
        continue;
      }
      visited.add(currentId);
      const currentTask = this.getTask(currentId);
      if (currentTask) {
        stack.push(...currentTask.depends_on);
      }
    }
    return false;
  }
}
export {
  PebblesTaskStore
};
//# sourceMappingURL=pebbles-task-store.js.map
