/**
 * PersonaTrigger: determines whether to trigger persona generation.
 * Implements the 5 trigger conditions from the legacy system.
 */

import fs from "node:fs/promises";
import path from "node:path";
import { CheckpointManager } from "../utils/checkpoint.js";
import { stripSceneNavigation } from "../scene/scene_navigation.js";

const TAG = "[unified-memory] [trigger]";

/**
 * @typedef {Object} TriggerLogger
 * @property {(message: string) => void} [debug]
 * @property {(message: string) => void} info
 * @property {(message: string) => void} warn
 * @property {(message: string) => void} error
 */

/**
 * @typedef {Object} TriggerResult
 * @property {boolean} should
 * @property {string} reason
 */

export class PersonaTrigger {
  /**
   * @param {{ dataDir: string, interval: number, logger?: TriggerLogger }} opts
   */
  constructor(opts) {
    this.dataDir = opts.dataDir;
    this.interval = opts.interval;
    this.logger = opts.logger;
  }

  /**
   * @returns {Promise<TriggerResult>}
   */
  async shouldGenerate() {
    const cpManager = new CheckpointManager(this.dataDir);
    const cp = await cpManager.read();
    this.logger?.debug?.(`${TAG} Evaluating: total_processed=${cp.total_processed}, last_persona_at=${cp.last_persona_at}, memories_since=${cp.memories_since_last_persona}, scenes=${cp.scenes_processed}`);

    // Priority 1: Agent explicitly requested persona update
    if (cp.request_persona_update) {
      const result = {
        should: true,
        reason: `主动请求: ${cp.persona_update_reason || "Agent 请求更新"}`,
      };
      this.logger?.debug?.(`${TAG} Trigger P1 (explicit request): ${result.reason}`);
      return result;
    }

    // Priority 2: Cold start — first extraction done, no persona yet, has scene files
    if (cp.scenes_processed > 0 && cp.last_persona_at === 0 && await this.hasSceneFiles()) {
      const result = { should: true, reason: "首次冷启动：首次提取完成且有场景文件" };
      this.logger?.debug?.(`${TAG} Trigger P2 (cold start): scenes_processed=${cp.scenes_processed}, total_processed=${cp.total_processed}`);
      return result;
    }

    // Priority 2.5: Recovery — persona was generated before but persona.md body is now empty
    if (cp.last_persona_at > 0 && await this.hasSceneFiles() && !await this.hasPersonaBody()) {
      const result = { should: true, reason: "恢复：persona.md 正文丢失或为空，需要重新生成" };
      this.logger?.debug?.(`${TAG} Trigger P2.5 (recovery): last_persona_at=${cp.last_persona_at}, persona body missing`);
      return result;
    }

    // Priority 3: First scene block extraction
    if (cp.scenes_processed === 1 && cp.memories_since_last_persona > 0) {
      const result = { should: true, reason: "首次 Scene Block 提取完成" };
      this.logger?.debug?.(`${TAG} Trigger P3 (first scene): scenes_processed=${cp.scenes_processed}`);
      return result;
    }

    // Priority 4: Reached threshold
    if (cp.memories_since_last_persona >= this.interval) {
      const result = {
        should: true,
        reason: `达到阈值: ${cp.memories_since_last_persona} >= ${this.interval}`,
      };
      this.logger?.debug?.(`${TAG} Trigger P4 (threshold): ${result.reason}`);
      return result;
    }

    this.logger?.debug?.(`${TAG} No trigger conditions met`);
    return { should: false, reason: "" };
  }

  /**
   * @returns {Promise<boolean>}
   */
  async hasSceneFiles() {
    const blocksDir = path.join(this.dataDir, "scene_blocks");
    try {
      const files = await fs.readdir(blocksDir);
      return files.some((f) => f.endsWith(".md"));
    } catch {
      return false;
    }
  }

  /**
   * @returns {Promise<boolean>}
   */
  async hasPersonaBody() {
    const personaPath = path.join(this.dataDir, "persona.md");
    try {
      const raw = await fs.readFile(personaPath, "utf-8");
      const body = stripSceneNavigation(raw).trim();
      return body.length > 0;
    } catch {
      return false;
    }
  }
}
