import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import { abortEmbeddedPiRun } from "../../agents/pi-embedded.js";
import { listSubagentRunsForRequester } from "../../agents/subagent-registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
  loadSessionStore,
  resolveStorePath,
  type SessionEntry,
  updateSessionStore,
} from "../../config/sessions.js";
import { parseAgentSessionKey } from "../../routing/session-key.js";
import { resolveCommandAuthorization } from "../command-auth.js";
import { normalizeCommandBody } from "../commands-registry.js";
import type { FinalizedMsgContext, MsgContext } from "../templating.js";
import { logVerbose } from "../../globals.js";
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
import { clearSessionQueues } from "./queue.js";
import {
  resolveInternalSessionKey,
  resolveMainSessionAlias,
} from "../../agents/tools/sessions-helpers.js";

const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit", "interrupt"]);
const ABORT_MEMORY = new Map<string, boolean>();
const ABORT_MEMORY_MAX = 2000;

export function isAbortTrigger(text?: string): boolean {
  if (!text) return false;
  const normalized = text.trim().toLowerCase();
  return ABORT_TRIGGERS.has(normalized);
}

export function getAbortMemory(key: string): boolean | undefined {
  const normalized = key.trim();
  if (!normalized) {
    return undefined;
  }
  return ABORT_MEMORY.get(normalized);
}

function pruneAbortMemory(): void {
  if (ABORT_MEMORY.size <= ABORT_MEMORY_MAX) {
    return;
  }
  const excess = ABORT_MEMORY.size - ABORT_MEMORY_MAX;
  let removed = 0;
  for (const entryKey of ABORT_MEMORY.keys()) {
    ABORT_MEMORY.delete(entryKey);
    removed += 1;
    if (removed >= excess) {
      break;
    }
  }
}

export function setAbortMemory(key: string, value: boolean): void {
  const normalized = key.trim();
  if (!normalized) {
    return;
  }
  if (!value) {
    ABORT_MEMORY.delete(normalized);
    return;
  }
  // Refresh insertion order so active keys are less likely to be evicted.
  if (ABORT_MEMORY.has(normalized)) {
    ABORT_MEMORY.delete(normalized);
  }
  ABORT_MEMORY.set(normalized, true);
  pruneAbortMemory();
}

export function getAbortMemorySizeForTest(): number {
  return ABORT_MEMORY.size;
}

export function resetAbortMemoryForTest(): void {
  ABORT_MEMORY.clear();
}

export function formatAbortReplyText(stoppedSubagents?: number): string {
  if (typeof stoppedSubagents !== "number" || stoppedSubagents <= 0) {
    return "⚙️ 已中止代理。";
  }
  return `⚙️ 已中止代理。停止了 ${stoppedSubagents} 个子代理。`;
}

function resolveSessionEntryForKey(
  store: Record<string, SessionEntry> | undefined,
  sessionKey: string | undefined,
) {
  if (!store || !sessionKey) return {};
  const direct = store[sessionKey];
  if (direct) return { entry: direct, key: sessionKey };
  return {};
}

function resolveAbortTargetKey(ctx: MsgContext): string | undefined {
  const target = ctx.CommandTargetSessionKey?.trim();
  if (target) return target;
  const sessionKey = ctx.SessionKey?.trim();
  return sessionKey || undefined;
}

function normalizeRequesterSessionKey(
  cfg: ClawdbotConfig,
  key: string | undefined,
): string | undefined {
  const cleaned = key?.trim();
  if (!cleaned) return undefined;
  const { mainKey, alias } = resolveMainSessionAlias(cfg);
  return resolveInternalSessionKey({ key: cleaned, alias, mainKey });
}

export function stopSubagentsForRequester(params: {
  cfg: ClawdbotConfig;
  requesterSessionKey?: string;
}): { stopped: number } {
  const requesterKey = normalizeRequesterSessionKey(params.cfg, params.requesterSessionKey);
  if (!requesterKey) return { stopped: 0 };
  const runs = listSubagentRunsForRequester(requesterKey);
  if (runs.length === 0) return { stopped: 0 };

  const storeCache = new Map<string, Record<string, SessionEntry>>();
  const seenChildKeys = new Set<string>();
  let stopped = 0;

  for (const run of runs) {
    if (run.endedAt) continue;
    const childKey = run.childSessionKey?.trim();
    if (!childKey || seenChildKeys.has(childKey)) continue;
    seenChildKeys.add(childKey);

    const cleared = clearSessionQueues([childKey]);
    const parsed = parseAgentSessionKey(childKey);
    const storePath = resolveStorePath(params.cfg.session?.store, { agentId: parsed?.agentId });
    let store = storeCache.get(storePath);
    if (!store) {
      store = loadSessionStore(storePath);
      storeCache.set(storePath, store);
    }
    const entry = store[childKey];
    const sessionId = entry?.sessionId;
    const aborted = sessionId ? abortEmbeddedPiRun(sessionId) : false;

    if (aborted || cleared.followupCleared > 0 || cleared.laneCleared > 0) {
      stopped += 1;
    }
  }

  if (stopped > 0) {
    logVerbose(`abort: stopped ${stopped} subagent run(s) for ${requesterKey}`);
  }
  return { stopped };
}

export async function tryFastAbortFromMessage(params: {
  ctx: FinalizedMsgContext;
  cfg: ClawdbotConfig;
}): Promise<{ handled: boolean; aborted: boolean; stoppedSubagents?: number }> {
  const { ctx, cfg } = params;
  const targetKey = resolveAbortTargetKey(ctx);
  const agentId = resolveSessionAgentId({
    sessionKey: targetKey ?? ctx.SessionKey ?? "",
    config: cfg,
  });
  // Use RawBody/CommandBody for abort detection (clean message without structural context).
  const raw = stripStructuralPrefixes(ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "");
  const isGroup = ctx.ChatType?.trim().toLowerCase() === "group";
  const stripped = isGroup ? stripMentions(raw, ctx, cfg, agentId) : raw;
  const normalized = normalizeCommandBody(stripped);
  const abortRequested = normalized === "/stop" || isAbortTrigger(stripped);
  if (!abortRequested) return { handled: false, aborted: false };

  const commandAuthorized = ctx.CommandAuthorized;
  const auth = resolveCommandAuthorization({
    ctx,
    cfg,
    commandAuthorized,
  });
  if (!auth.isAuthorizedSender) return { handled: false, aborted: false };

  const abortKey = targetKey ?? auth.from ?? auth.to;
  const requesterSessionKey = targetKey ?? ctx.SessionKey ?? abortKey;

  if (targetKey) {
    const storePath = resolveStorePath(cfg.session?.store, { agentId });
    const store = loadSessionStore(storePath);
    const { entry, key } = resolveSessionEntryForKey(store, targetKey);
    const sessionId = entry?.sessionId;
    const aborted = sessionId ? abortEmbeddedPiRun(sessionId) : false;
    const cleared = clearSessionQueues([key ?? targetKey, sessionId]);
    if (cleared.followupCleared > 0 || cleared.laneCleared > 0) {
      logVerbose(
        `abort: cleared followups=${cleared.followupCleared} lane=${cleared.laneCleared} keys=${cleared.keys.join(",")}`,
      );
    }
    if (entry && key) {
      entry.abortedLastRun = true;
      entry.updatedAt = Date.now();
      store[key] = entry;
      await updateSessionStore(storePath, (nextStore) => {
        const nextEntry = nextStore[key] ?? entry;
        if (!nextEntry) return;
        nextEntry.abortedLastRun = true;
        nextEntry.updatedAt = Date.now();
        nextStore[key] = nextEntry;
      });
    } else if (abortKey) {
      setAbortMemory(abortKey, true);
    }
    const { stopped } = stopSubagentsForRequester({ cfg, requesterSessionKey });
    return { handled: true, aborted, stoppedSubagents: stopped };
  }

  if (abortKey) {
    setAbortMemory(abortKey, true);
  }
  const { stopped } = stopSubagentsForRequester({ cfg, requesterSessionKey });
  return { handled: true, aborted: false, stoppedSubagents: stopped };
}
