/**
 * workflow-activity-store.js —— 右侧活动卡（workflow + subagent）的持久化背书
 *
 * ActivityHub 是内存广播层，进程重启即清空，导致右侧卡（WorkflowCard / AgentActivityCard）
 * 重启消失。这个 store 把 ActivityHub 标记为可持久的活动（workflow / workflow_agent / subagent，
 * 见 activity-hub.js 的 PERSISTABLE_KINDS）落盘（hanakoHome/workflow-activity.json），作为
 * ActivityHub 的「持久化背书」：upsert 写穿、重启回灌、会话退场清理、72h TTL 修剪。
 * 名称沿用 workflow-activity（首次落地时仅 workflow），实为 ActivityHub 通用持久层。
 *
 * 归属：每条 entry 自带 sessionPath，按 path 存取（listBySession / removeBySession），
 * 不从焦点指针推导（状态归属唯一确定）。这是 dumb 持久层——entry 的规范化由 ActivityHub 负责。
 */
import fs from "node:fs";
import path from "node:path";
import { atomicWriteSync } from "../shared/safe-fs.ts";

export const WORKFLOW_ACTIVITY_STORE_VERSION = 1;

export class WorkflowActivityStore {
  declare _persistPath: string | null;
  declare _entries: Map<string, any>;

  constructor(persistPath: any) {
    this._persistPath = persistPath || null;
    /** @type {Map<string, object>} */
    this._entries = new Map();
    if (this._persistPath) this._load();
  }

  upsert(entry: any) {
    if (!entry || typeof entry.id !== "string" || !entry.id) return null;
    const next = { ...entry };
    this._entries.set(next.id, next);
    this._save();
    return { ...next };
  }

  get(id: string) {
    const e = this._entries.get(id);
    return e ? { ...e } : null;
  }

  list() {
    return [...this._entries.values()].map((e) => ({ ...e }));
  }

  listBySession(sessionPath: string) {
    if (!sessionPath) return [];
    const out = [];
    for (const e of this._entries.values()) {
      if (e.sessionPath === sessionPath) out.push({ ...e });
    }
    return out;
  }

  /** 会话退场（删除 / 归档 / 冷清理）时回收该 session 的活动，返回删除条数。 */
  removeBySession(sessionPath: string) {
    if (!sessionPath) return 0;
    let removed = 0;
    for (const [id, e] of this._entries) {
      if (e.sessionPath === sessionPath) {
        this._entries.delete(id);
        removed++;
      }
    }
    if (removed) this._save();
    return removed;
  }

  /**
   * 删除早于 maxAgeMs 的 entry（按 finishedAt，回退 startedAt）。nowMs 由调用方传入
   * （服务端 Date.now()，测试可注入），返回删除条数。与 session 72h 冷清理对齐。
   */
  prune(maxAgeMs: number, nowMs: number) {
    if (!Number.isFinite(maxAgeMs) || !Number.isFinite(nowMs)) return 0;
    const cutoff = nowMs - maxAgeMs;
    let removed = 0;
    for (const [id, e] of this._entries) {
      const ts = Number.isFinite(e.finishedAt)
        ? e.finishedAt
        : (Number.isFinite(e.startedAt) ? e.startedAt : null);
      if (ts != null && ts < cutoff) {
        this._entries.delete(id);
        removed++;
      }
    }
    if (removed) this._save();
    return removed;
  }

  get size() {
    return this._entries.size;
  }

  _save() {
    if (!this._persistPath) return;
    const data = {
      schemaVersion: WORKFLOW_ACTIVITY_STORE_VERSION,
      entries: Object.fromEntries(this._entries.entries()),
    };
    fs.mkdirSync(path.dirname(this._persistPath), { recursive: true });
    atomicWriteSync(this._persistPath, JSON.stringify(data, null, 2) + "\n");
  }

  _load() {
    if (!this._persistPath || !fs.existsSync(this._persistPath)) return;
    let raw;
    try {
      raw = JSON.parse(fs.readFileSync(this._persistPath, "utf-8"));
    } catch {
      // 损坏文件不崩：按空账本起步，下次 _save 覆盖。
      return;
    }
    const entries = raw?.entries && typeof raw.entries === "object" ? raw.entries : {};
    for (const [id, value] of Object.entries(entries)) {
      if (!id || !value || typeof value !== "object") continue;
      this._entries.set(id, { ...value, id });
    }
  }
}
