import {
  inferAgentPresetFromContext,
  resolveAgentPolicy
} from "@/lib/openclaw/agent-presets";
import { filterAgentPolicySkills } from "@/lib/openclaw/domains/agent-config";
import {
  normalizeOptionalValue,
  resolveAgentAction,
  resolveAgentStatus,
  unique
} from "@/lib/openclaw/domains/control-plane-normalization";
import { sortRuntimesByUpdatedAtDesc } from "@/lib/openclaw/domains/runtime-history";
import type {
  AgentConfigPayload,
  AgentPayload
} from "@/lib/openclaw/client/gateway-client";
import type {
  OpenClawAgent,
  RelationshipRecord,
  RuntimeRecord
} from "@/lib/openclaw/types";
import type { WorkspaceProjectManifestAgent } from "@/lib/openclaw/domains/workspace-manifest";

type AgentIdentityOverrides = {
  name?: string | null;
  emoji?: string | null;
  theme?: string | null;
  avatar?: string | null;
} | null;

export type SnapshotAgentEntry = {
  agent: OpenClawAgent;
  workspacePath: string;
  workspaceId: string;
  primaryModel: string;
  sessionCount: number;
  activeRuntimeIds: string[];
  relationships: RelationshipRecord[];
};

export function buildSnapshotAgentEntry(input: {
  rawAgent: AgentPayload[number];
  configured: AgentConfigPayload[number] | undefined;
  identityOverrides: AgentIdentityOverrides;
  workspaceId: string;
  sessionList: Array<{ updatedAt?: number | null }>;
  heartbeat?: {
    enabled?: boolean;
    every?: string | null;
    everyMs?: number | null;
  } | null;
  manifestAgent: WorkspaceProjectManifestAgent | null;
  agentRuntimes: RuntimeRecord[];
  gatewayRpcOk: boolean;
  profile: OpenClawAgent["profile"];
}) {
  const configuredSkills = filterAgentPolicySkills(input.configured?.skills ?? []);
  const agentName = resolveSnapshotAgentDisplayName(
    input.rawAgent.id,
    [
      input.identityOverrides?.name,
      input.configured?.identity?.name,
      input.configured?.name,
      input.manifestAgent?.name,
      input.rawAgent.identityName,
      input.rawAgent.name,
      input.rawAgent.id
    ],
    {
      workspaceId: input.workspaceId
    }
  );
  const policy =
    input.manifestAgent?.policy ??
    resolveAgentPolicy(
      inferAgentPresetFromContext({
        skills: configuredSkills,
        id: input.rawAgent.id,
        name: agentName
      }),
      {
        fileAccess: input.configured?.tools?.fs?.workspaceOnly ? "workspace-only" : "extended"
      }
    );
  const configuredTools = unique([
    ...(input.manifestAgent?.toolIds ?? []),
    ...(policy.fileAccess === "workspace-only" ? ["fs.workspaceOnly"] : [])
  ]);
  const primaryModel = input.configured?.model || input.manifestAgent?.modelId || input.rawAgent.model || "unassigned";
  const agentRuntimes = input.agentRuntimes.sort(sortRuntimesByUpdatedAtDesc);
  const observedToolNames = unique(agentRuntimes.flatMap((runtime) => runtime.toolNames ?? []));
  const activeRuntimeIds = agentRuntimes.map((runtime) => runtime.id);
  const latestRuntime = agentRuntimes[0];
  const heartbeat = input.heartbeat ?? (
    input.configured?.heartbeat?.every
      ? {
          enabled: true,
          every: input.configured.heartbeat.every,
          everyMs: null
        }
      : null
  );
  const lastActiveAt =
    input.sessionList
      .map((entry) => entry.updatedAt ?? 0)
      .sort((left, right) => right - left)
      .at(0) || null;
  const statusValue = resolveAgentStatus({
    rpcOk: input.gatewayRpcOk,
    activeRuntime: latestRuntime,
    heartbeatEnabled: Boolean(heartbeat?.enabled),
    lastActiveAt
  });

  const agent: OpenClawAgent = {
    id: input.rawAgent.id,
    name: agentName,
    identityName:
      resolveSnapshotAgentIdentityName(
        input.rawAgent.id,
        [
          input.identityOverrides?.name,
          input.configured?.identity?.name,
          input.rawAgent.identityName,
          input.manifestAgent?.name
        ],
        {
          workspaceId: input.workspaceId
        }
      ) ||
      undefined,
    workspaceId: input.workspaceId,
    workspacePath: input.rawAgent.workspace,
    agentDir: input.rawAgent.agentDir,
    modelId: primaryModel,
    isDefault: Boolean(input.rawAgent.isDefault || input.configured?.default),
    status: statusValue,
    sessionCount: input.sessionList.length,
    lastActiveAt,
    currentAction: resolveAgentAction({
      runtime: latestRuntime,
      heartbeatEvery: heartbeat?.every ?? null,
      status: statusValue
    }),
    activeRuntimeIds,
    heartbeat: {
      enabled: Boolean(heartbeat?.enabled),
      every: heartbeat?.every ?? null,
      everyMs: heartbeat?.everyMs ?? null
    },
    identity: {
      emoji:
        normalizeOptionalValue(input.identityOverrides?.emoji) ||
        input.manifestAgent?.emoji ||
        input.configured?.identity?.emoji ||
        input.rawAgent.identityEmoji,
      theme:
        normalizeOptionalValue(input.identityOverrides?.theme) ||
        input.manifestAgent?.theme ||
        input.configured?.identity?.theme,
      avatar: normalizeOptionalValue(input.identityOverrides?.avatar) || input.configured?.identity?.avatar,
      source: input.rawAgent.identitySource
    },
    profile: input.profile,
    skills: configuredSkills,
    tools: configuredTools,
    observedTools: observedToolNames,
    policy
  };

  const relationships: RelationshipRecord[] = [
    {
      id: `edge:${input.workspaceId}:${agent.id}:contains`,
      sourceId: input.workspaceId,
      targetId: agent.id,
      kind: "contains",
      label: "workspace member"
    },
    {
      id: `edge:${agent.id}:${primaryModel}:model`,
      sourceId: agent.id,
      targetId: primaryModel,
      kind: "uses-model",
      label: "model assignment"
    },
    ...activeRuntimeIds.map((runtimeId) => ({
      id: `edge:${agent.id}:${runtimeId}:run`,
      sourceId: agent.id,
      targetId: runtimeId,
      kind: "active-run" as const,
      label: "runtime"
    }))
  ];

  return {
    agent,
    workspacePath: input.rawAgent.workspace,
    workspaceId: input.workspaceId,
    primaryModel,
    sessionCount: input.sessionList.length,
    activeRuntimeIds,
    relationships
  } satisfies SnapshotAgentEntry;
}

export function resolveSnapshotAgentDisplayName(
  agentId: string,
  candidates: Array<string | null | undefined>,
  options: {
    workspaceId?: string | null;
  } = {}
) {
  const idLikeName = candidates
    .map((candidate) => normalizeOptionalValue(candidate))
    .find((candidate) => candidate && isAgentIdLikeDisplayName(agentId, candidate));
  const displayName = candidates
    .map((candidate) => normalizeOptionalValue(candidate))
    .find((candidate) => candidate && !isAgentIdLikeDisplayName(agentId, candidate));

  return displayName ?? inferScopedAgentDisplayName(agentId, options.workspaceId) ?? idLikeName ?? agentId;
}

function resolveSnapshotAgentIdentityName(
  agentId: string,
  candidates: Array<string | null | undefined>,
  options: {
    workspaceId?: string | null;
  } = {}
) {
  const displayName = candidates
    .map((candidate) => normalizeOptionalValue(candidate))
    .find((candidate) => candidate && !isAgentIdLikeDisplayName(agentId, candidate));

  return displayName ?? inferScopedAgentDisplayName(agentId, options.workspaceId) ?? null;
}

function isAgentIdLikeDisplayName(agentId: string, value: string) {
  const normalizedId = slugifyAgentName(agentId);
  const normalizedValue = slugifyAgentName(value);

  return Boolean(normalizedId && normalizedValue && normalizedId === normalizedValue);
}

function slugifyAgentName(value: string) {
  return value
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

function inferScopedAgentDisplayName(agentId: string, workspaceId: string | null | undefined) {
  const normalizedWorkspaceId = normalizeOptionalValue(workspaceId);

  if (!normalizedWorkspaceId) {
    return null;
  }

  const prefix = `${normalizedWorkspaceId}-`;

  if (!agentId.startsWith(prefix)) {
    return null;
  }

  const localId = agentId.slice(prefix.length);
  const words = localId
    .split(/[-_\s]+/)
    .map((part) => part.trim())
    .filter(Boolean);

  if (words.length === 0) {
    return null;
  }

  return words
    .map((word) => word.length <= 2 ? word.toUpperCase() : word[0]?.toUpperCase() + word.slice(1))
    .join(" ");
}
