import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { AnyAgentTool } from '../tools/common.js';
import { NetaClawAgentEntity } from '../entity/agent.js';
import type { NetaClawAgentRuntimeConfig } from '../entity/agent.js';
import { ModelCapability } from '../entity/model_channel.js';
import { NetaClawToolRegistryService } from './tool_registry.js';
import { TOOLSET_DEFAULTS, getToolNamesByToolsets } from '../tools/catalog.js';
import { createSkillBashEnvProvider, createBashTool } from '../tools/builtin/bash.js';
import { SkillSecretService } from './skill_secret.js';
import { SkillConfigService } from './skill_config.js';
import { createReadFileTool, createWriteFileTool, createListDirTool } from '../tools/builtin/file.js';
import { createFindFilesTool, createGrepTool } from '../tools/builtin/search.js';
import { createEditTool } from '../tools/builtin/edit.js';
import { createPatchTool } from '../tools/builtin/patch.js';
import { clarifyTool } from '../tools/builtin/clarify.js';
import { createMemoryRecallTool, createMemorySaveTool } from '../tools/builtin/memory.js';
import { createMemoryListTypesTool, createMemoryStatsTool } from '../tools/builtin/memory_types.js';
import { SkillLoaderService } from './skill_loader.js';
import { createReadSkillTool } from '../tools/builtin/read_skill.js';
import { createReadSkillFileTool } from '../tools/builtin/read_skill_file.js';
import { createSkillManageTool } from '../tools/builtin/skill_manage.js';
import { NetaClawMemoryEntity } from '../entity/memory.js';
import { NetaClawMemoryTypeEntity } from '../entity/memory_type.js';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { AgentMemoryConfig } from '../memory/provider.js';
import { createMemoryProvider } from '../memory/factory.js';
import { MemoryProviderRegistry } from '../memory/registry.js';
import { createDelegateTaskTool } from '../tools/builtin/delegate_task.js';
import { createDelegateParallelTool } from '../tools/builtin/delegate_parallel.js';
import { createEscalateTool } from '../tools/builtin/escalate.js';
import { createImageRecognizeTool } from '../tools/builtin/image_recognize.js';
import { createTextToImageTool } from '../tools/builtin/text_to_image.js';
import { createImageToImageTool } from '../tools/builtin/image_to_image.js';
import { getImageProvider, type ImageProviderCredentials } from '../image_providers/types.js';
import '../image_providers/ark.js';
import '../image_providers/minimax.js';
import { ImageStorageService } from './image_storage.js';
import { createExecuteSkillTool } from '../tools/builtin/execute_skill.js';
import { SkillExecutorService } from './skill_executor.js';
import { NetaClawModelChannelService } from './model_channel.js';
import { CrewRunContext, ParallelTaskItem } from './crew_types.js';
import type { NetaClawSubagentTask } from './subagent.js';
import type { SubagentToolManifestItem, SubagentWorkerPolicy } from '../subagent/process_protocol.js';
import {
  buildToolManifestItem,
  getBaseWorkerRoutingHint,
  type ToolWorkerRoutingStrategy,
} from '../tools/manifest.js';
import {
  buildSubprocessWorkerPolicy,
  mapWorkerStatusToRuntimeRoute,
  normalizeWorkspaceRoots,
  resolveToolWorkerRuntime,
} from '../tools/runtime_policy.js';
import { buildToolRendererCapabilityProjection } from '../tools/presentation.js';
import { getDefaultOperations, type ToolOperations } from '../tools/operations/index.js';
import { createWeixinSendTextTool } from '../tools/builtin/weixin_send_text.js';
import { DesktopOpService } from '../../desktop_op/service/desktop_op.js';
import { NetaClawAgentChannelEntity } from '../entity/agent_channel.js';
import { NetaClawDataSourceService } from './data_source.js';
import { MysqlIntrospectionService } from './mysql_schema.js';
import { MysqlQueryService } from './mysql_query.js';
import {
  createMysqlListSourcesTool,
  createMysqlQueryTool,
  createMysqlSchemaTool,
  createMysqlTableSampleTool,
} from '../tools/builtin/mysql.js';

type DelegationMemberAgent = {
  id?: number;
  name: string;
  label?: string;
  [key: string]: any;
};

type SessionDelegationRunner = {
  runSingle: (task: NetaClawSubagentTask, options?: { parentToolCallId?: string | null }) => Promise<any>;
  runBatch: (
    tasks: NetaClawSubagentTask[],
    options?: { parentToolCallId?: string | null; mode?: 'parallel' | 'single' }
  ) => Promise<any>;
};

type CrewDelegateToolContext = {
  memberAgents: DelegationMemberAgent[];
  _delegate: {
    executeSubAgent: (
      _ctx: unknown,
      member: DelegationMemberAgent,
      taskDescription: string,
      taskContext?: string,
    ) => Promise<any>;
    executeParallel: (_ctx: unknown, tasks: ParallelTaskItem[]) => Promise<any>;
  };
};

export interface SessionSubagentDelegationContext {
  kind: 'session-subagent';
  sessionId?: string;
  parentMessageId?: number;
  memberAgents: DelegationMemberAgent[];
  runSingle?: SessionDelegationRunner['runSingle'];
  runBatch?: SessionDelegationRunner['runBatch'];
  subagentService?: SessionDelegationRunner;
}

export type SessionDelegateToolContext = SessionSubagentDelegationContext & {
  kind: 'session-subagent';
  runSingle: SessionDelegationRunner['runSingle'];
  runBatch: SessionDelegationRunner['runBatch'];
};

export interface ResolveToolParams {
  agent: NetaClawAgentEntity | null;
  modelCapability?: ModelCapability;
  memoryEnabled?: boolean;
  hasSkills?: boolean;
  crewRole?: 'master' | 'sub';
  delegationRole?: 'supervisor' | 'subagent';
  subagentContext?: SessionSubagentDelegationContext | any;
  userId?: string;
  crewContext?: any;
  runtimePolicy?: {
    sessionCwd?: string | null;
    workspaceRoots?: string[];
    allowShell?: boolean;
    readonly?: boolean;
  };
  operations?: ToolOperations;
  catalogOverrides?: Map<string, { capability?: string; visibility?: string; extra?: unknown; status?: number; promptHint?: string | null }>;
}

export interface ResolvedToolRuntimeRoute {
  name: string;
  kind: SubagentToolManifestItem['kind'];
  executionMode: SubagentToolManifestItem['executionMode'];
  supportedInWorker: boolean;
  workerRoutingHint: 'local' | 'main-process-proxy' | 'disabled';
  runtimeRoute: 'worker-local' | 'main-process-proxy' | 'disabled';
  blockedReason: string | null;
  policySources: string[];
  policyDerived: boolean;
}

export interface ResolveToolResult {
  tools: AnyAgentTool[];
  toolNames: string[];
  toolManifest: SubagentToolManifestItem[];
  toolRuntimeRoutes: ResolvedToolRuntimeRoute[];
  builtinToolNames: string[];
  toolPromptHints: Record<string, string>;
  disabledReasons: Array<{ name: string; reason: string }>;
}

type AgentPerToolConfig = {
  enabled?: boolean;
  promptHint?: string;
  allowInSubagent?: boolean;
  workerRoutingStrategy?: ToolWorkerRoutingStrategy;
};

export interface ToolRuntimeDiagnosticParams {
  agent: NetaClawAgentEntity | null;
  modelCapability?: ModelCapability;
  memoryEnabled?: boolean;
  hasSkills?: boolean;
  sessionCwd?: string | null;
  workspaceRoots?: string[];
  allowShell?: boolean;
  readonly?: boolean;
  draftToolOverride?: {
    name: string;
    enabled?: boolean;
    status?: number;
    promptHint?: string | null;
    allowInSubagent?: boolean;
    workerRoutingStrategy?: ToolWorkerRoutingStrategy;
  } | null;
  draftAgentTools?: NetaClawAgentEntity['tools'] | null;
}

export interface ToolRuntimeDiagnosticItem {
  name: string;
  label: string;
  description: string | null;
  toolset: string;
  visibility: string;
  capability: string;
  status: number;
  isCore: number;
  canDisable: number;
  supportsPromptHint: number;
  promptHint: string | null;
  agentOverride: {
    enabled?: boolean;
    promptHint?: string;
    allowInSubagent?: boolean;
    workerRoutingStrategy?: ToolWorkerRoutingStrategy;
    hasOverride: boolean;
  };
  manifest: {
    kind: SubagentToolManifestItem['kind'];
    executionMode: SubagentToolManifestItem['executionMode'];
    supportedInWorker: boolean;
    workerRoutingHint: 'local' | 'main-process-proxy' | 'disabled';
    requiresShell: boolean;
    requiresWrite: boolean;
    requiresSkillScope: boolean;
  };
  runtime: {
    supervisorInProcess: {
      enabled: boolean;
      blockedReason: string | null;
      policySources: string[];
    };
    subagentInProcess: {
      enabled: boolean;
      blockedReason: string | null;
      policySources: string[];
    };
    subprocessWorker: {
      status: 'worker-local' | 'main-process-proxied' | 'disabled-in-worker' | 'not-selected';
      blockedReason: string | null;
      policyDerived: boolean;
      policySources: string[];
    };
  };
  renderer: {
    key: string;
    bindingStatus: 'custom' | 'default';
    custom: boolean;
    streaming: boolean;
    metaKinds: Array<'path' | 'offset' | 'log' | 'count' | 'mode'>;
    showsArgs: 'never' | 'running' | 'always';
    collapsibleResult: boolean;
    description: string;
  };
}

export interface ToolRuntimeDiagnosticResult {
  agentId: number | null;
  modelCapability: ModelCapability;
  workerPolicy: SubagentWorkerPolicy;
  toolRuntimeRoutes: ResolvedToolRuntimeRoute[];
  items: ToolRuntimeDiagnosticItem[];
}

@Provide()
@Scope(ScopeEnum.Singleton)
export class NetaClawToolResolverService {
  @Config('netaclaw.subagent')
  subagentDefaults: {
    enabled?: boolean;
    maxConcurrent?: number;
    allowedToolNames?: string[];
  };

  private readonly subagentBlockedToolNames = new Set([
    'delegate_task',
    'delegate_parallel',
    'clarify',
    'memory_save',
    'memory_recall',
    'escalate',
  ]);

  private readonly delegateToolNames = new Set([
    'delegate_task',
    'delegate_parallel',
  ]);

  @Inject()
  toolRegistry: NetaClawToolRegistryService;

  @Inject()
  skillLoader: SkillLoaderService;

  @InjectEntityModel(NetaClawMemoryEntity)
  memoryRepo: Repository<NetaClawMemoryEntity>;

  @InjectEntityModel(NetaClawMemoryTypeEntity)
  memoryTypeRepo: Repository<NetaClawMemoryTypeEntity>;

  @Inject()
  memoryProviderRegistry: MemoryProviderRegistry;

  @Inject()
  modelChannelService: NetaClawModelChannelService;

  @Inject()
  skillSecret: SkillSecretService;

  @Inject()
  skillConfig: SkillConfigService;

  @Inject()
  skillExecutor: SkillExecutorService;

  @Inject()
  imageStorageService: ImageStorageService;

  @Inject()
  desktopOpService!: DesktopOpService;

  @InjectEntityModel(NetaClawAgentChannelEntity)
  channelRepo!: Repository<NetaClawAgentChannelEntity>;

  @Inject()
  dataSourceService!: NetaClawDataSourceService;

  @Inject()
  mysqlIntrospection!: MysqlIntrospectionService;

  @Inject()
  mysqlQuery!: MysqlQueryService;

  private getGovernanceExtra(config: { extra?: unknown } | undefined): {
    allowInSubagent?: boolean;
    workerRoutingStrategy?: ToolWorkerRoutingStrategy;
  } {
    if (!config?.extra || typeof config.extra !== 'object' || Array.isArray(config.extra)) {
      return {};
    }
    return config.extra as any;
  }

  private getAgentPerToolConfig(agent: NetaClawAgentEntity | null | undefined): Record<string, AgentPerToolConfig> {
    const perTool = agent?.tools?.perTool;
    if (!perTool || typeof perTool !== 'object' || Array.isArray(perTool)) {
      return {};
    }
    return perTool as Record<string, AgentPerToolConfig>;
  }

  private mergeGovernance(
    registryGovernance: { allowInSubagent?: boolean; workerRoutingStrategy?: ToolWorkerRoutingStrategy },
    agentOverride: AgentPerToolConfig | undefined
  ) {
    return {
      allowInSubagent: agentOverride?.allowInSubagent ?? registryGovernance.allowInSubagent,
      workerRoutingStrategy: agentOverride?.workerRoutingStrategy ?? registryGovernance.workerRoutingStrategy,
    };
  }

  matchCapability(toolCap: string, modelCap: ModelCapability = 'text'): boolean {
    if (toolCap === 'vision') return modelCap === 'vision' || modelCap === 'multimodal';
    if (toolCap === 'multimodal') return modelCap === 'multimodal';
    return true;
  }

  private getBaseToolMap(operations: ToolOperations) {
    return new Map<string, AnyAgentTool>([
      ['read_file', createReadFileTool({ operations })],
      ['write_file', createWriteFileTool({ operations })],
      ['list_dir', createListDirTool({ operations })],
      ['find_files', createFindFilesTool({ operations })],
      ['grep', createGrepTool({ operations })],
      ['edit', createEditTool({ operations })],
      ['patch', createPatchTool({ operations })],
      ['clarify', clarifyTool],
      ['weixin_send_text', createWeixinSendTextTool({
        desktopOpService: this.desktopOpService,
        channelRepo: this.channelRepo,
      })],
      ['mysql_list_sources', createMysqlListSourcesTool({
        dataSourceService: this.dataSourceService,
      })],
      ['mysql_schema', createMysqlSchemaTool({
        dataSourceService: this.dataSourceService,
        mysqlIntrospection: this.mysqlIntrospection,
      })],
      ['mysql_table_sample', createMysqlTableSampleTool({
        dataSourceService: this.dataSourceService,
        mysqlIntrospection: this.mysqlIntrospection,
      })],
      ['mysql_query', createMysqlQueryTool({
        dataSourceService: this.dataSourceService,
        mysqlQuery: this.mysqlQuery,
      })],
    ]);
  }

  private normalizeNames(names: string[] = []): string[] {
    return [...new Set((names || []).filter(Boolean))];
  }

  private buildToolManifest(
    toolNames: string[],
    toolConfigMap: Map<string, { capability?: string; visibility?: string; extra?: unknown }>,
    agentPerTool: Record<string, AgentPerToolConfig> = {}
  ): SubagentToolManifestItem[] {
    return toolNames.map(name => {
      const config = toolConfigMap.get(name);
      const governance = this.mergeGovernance(this.getGovernanceExtra(config), agentPerTool[name]);
      return buildToolManifestItem(name, config, governance);
    });
  }

  private getCoreToolNames(allTools: Array<{ name: string; isCore?: number }>): string[] {
    return allTools
      .filter(item => item.isCore === 1)
      .map(item => item.name);
  }

  private getSessionDelegationRunner(
    context: SessionSubagentDelegationContext
  ): SessionDelegationRunner | null {
    if (typeof context.runSingle === 'function' && typeof context.runBatch === 'function') {
      return {
        runSingle: context.runSingle,
        runBatch: context.runBatch,
      };
    }

    if (
      context.subagentService
      && typeof context.subagentService.runSingle === 'function'
      && typeof context.subagentService.runBatch === 'function'
    ) {
      return context.subagentService;
    }

    return null;
  }

  private isSessionSubagentDelegationContext(value: unknown): value is SessionSubagentDelegationContext {
    if (!value || typeof value !== 'object') {
      return false;
    }

    const context = value as SessionSubagentDelegationContext;
    return context.kind === 'session-subagent'
      && Array.isArray(context.memberAgents)
      && this.getSessionDelegationRunner(context) !== null;
  }

  private createSessionDelegateToolContext(
    context: SessionSubagentDelegationContext
  ): SessionDelegateToolContext {
    const runner = this.getSessionDelegationRunner(context);
    if (!runner) {
      throw new Error('Invalid session subagent delegation context');
    }

    return {
      ...context,
      kind: 'session-subagent',
      memberAgents: context.memberAgents,
      runSingle: runner.runSingle,
      runBatch: runner.runBatch,
    };
  }

  private getDelegateToolContext(
    params: ResolveToolParams
  ): CrewRunContext | CrewDelegateToolContext | SessionDelegateToolContext | null {
    if (params.crewRole === 'master' && params.crewContext) {
      return params.crewContext;
    }

    if (
      params.delegationRole === 'supervisor'
      && this.isSessionSubagentDelegationContext(params.subagentContext)
    ) {
      return this.createSessionDelegateToolContext(params.subagentContext);
    }

    return null;
  }

  private getSupervisorSessionToolNames(params: ResolveToolParams): string[] {
    const configEnabled = params.agent?.subagentConfig?.enabled
      ?? this.subagentDefaults?.enabled
      ?? false;
    const supervisorDelegationEnabled =
      params.delegationRole === 'supervisor'
      && this.isSessionSubagentDelegationContext(params.subagentContext)
      && configEnabled === true;

    if (!supervisorDelegationEnabled) {
      return [];
    }

    return this.normalizeNames(
      getToolNamesByToolsets(['crew']).filter(name =>
        name === 'delegate_task' || name === 'delegate_parallel')
    );
  }

  private buildSessionDelegatePromptHint(context: SessionSubagentDelegationContext): string {
    const presetAgents = context.memberAgents || [];
    if (presetAgents.length === 0) {
      return [
        '## Session Subagent Delegation',
        'Use `delegate_task` or `delegate_parallel` to delegate work.',
        'You must delegate to a preset subagent by explicitly passing `mode: "preset"` together with `agentId` or `agentName`.',
      ].join('\n');
    }

    const presetLines = presetAgents.map(agent => {
      const title = agent.label && agent.label !== agent.name
        ? `${agent.label} (${agent.name})`
        : agent.name;
      return `- id=${agent.id ?? '?'} name=${title}`;
    });

    return [
      '## Session Subagent Delegation',
      'Preset subagents available to you right now:',
      ...presetLines,
      presetAgents.length > 1
        ? 'When delegating to a preset subagent, you must choose the most suitable one yourself and explicitly pass `mode: "preset"` plus `agentId` or `agentName`.'
        : 'A single preset subagent is available. You may still explicitly pass `mode: "preset"` plus `agentId` or `agentName` for clarity.',
      'Temporary clone mode is not available. Delegation is preset-agent only.',
      'When a delegate tool returns structured session-subagent data, treat that payload as authoritative evidence.',
      'For parallel delegation, read `taskResults` sequentially and summarize each entry independently.',
      'Each `taskResults[i]` already binds one delegated task to one delegated result.',
      'Process `taskResults` one by one in order, rather than summarizing the entire payload from memory.',
      'Do not paraphrase, translate, normalize, prettify, infer, or replace delegated filenames, paths, counts, IDs, or list items.',
      'If the delegated payload includes garbled-looking text, keep it exactly as returned and explain that encoding/display may be the cause instead of inventing cleaner names.',
      'If you summarize delegated results, each summarized item must stay value-identical to its paired `taskResults[i].result`.',
    ].join('\n');
  }

  private resolveConfiguredNames(
    agent: NetaClawAgentEntity | null | undefined,
    allTools: Array<{ name: string; isCore?: number }>
  ): { selectedNames: string[]; disabledByAgent: Set<string> } {
    const toolConfig = agent?.tools || {};
    const perTool = this.getAgentPerToolConfig(agent);
    const hasNewShape =
      typeof toolConfig?.inheritCoreTools === 'boolean'
      || Array.isArray(toolConfig?.enabled);

    if (hasNewShape) {
      const inheritCoreTools = toolConfig.inheritCoreTools !== false;
      const explicitlyEnabledByPerTool = Object.entries(perTool)
        .filter(([, value]) => value?.enabled === true)
        .map(([name]) => name);
      const explicitlyDisabledByPerTool = Object.entries(perTool)
        .filter(([, value]) => value?.enabled === false)
        .map(([name]) => name);
      return {
        selectedNames: this.normalizeNames([
          ...(inheritCoreTools ? this.getCoreToolNames(allTools) : []),
          ...(Array.isArray(toolConfig.enabled) ? toolConfig.enabled : []),
          ...explicitlyEnabledByPerTool,
        ]),
        disabledByAgent: new Set(this.normalizeNames([
          ...(toolConfig.disabled || []),
          ...explicitlyDisabledByPerTool,
        ])),
      };
    }

    const rawToolsets = agent?.toolsets;
    const agentToolsets = Array.isArray(rawToolsets)
      ? rawToolsets
      : [...TOOLSET_DEFAULTS];

    return {
      selectedNames: this.normalizeNames(getToolNamesByToolsets(agentToolsets)),
      disabledByAgent: new Set(this.normalizeNames(toolConfig?.disabled || [])),
    };
  }

  async resolve(params: ResolveToolParams): Promise<ResolveToolResult> {
    const allTools = params.catalogOverrides
      ? Array.from(params.catalogOverrides.values()) as any[]
      : await this.toolRegistry.all();
    const toolConfigMap = params.catalogOverrides || new Map(allTools.map(item => [item.name, item]));
    const agentPerTool = this.getAgentPerToolConfig(params.agent);
    const { selectedNames, disabledByAgent } = this.resolveConfiguredNames(params.agent, allTools);
    const supervisorSessionToolNames = this.getSupervisorSessionToolNames(params);
    const sceneNames = this.normalizeNames(getToolNamesByToolsets([
      ...(params.memoryEnabled ? ['memory'] : []),
      ...(params.hasSkills ? ['skill'] : []),
      ...(params.crewRole === 'master' ? ['crew'] : []),
    ]));
    const initialNames = this.normalizeNames([
      ...selectedNames,
      ...sceneNames,
      ...supervisorSessionToolNames,
    ]);
    const disabledReasons: Array<{ name: string; reason: string }> = [];
    const toolPromptHints: Record<string, string> = {};
    const sessionDelegateContext =
      params.delegationRole === 'supervisor' && this.isSessionSubagentDelegationContext(params.subagentContext)
        ? params.subagentContext
        : null;

    const filteredNames = initialNames.filter(name => {
      if (params.delegationRole === 'subagent' && this.subagentBlockedToolNames.has(name)) {
        disabledReasons.push({ name, reason: 'subagent_role_restricted' });
        return false;
      }
      const config = toolConfigMap.get(name);
      const governance = this.mergeGovernance(this.getGovernanceExtra(config), agentPerTool[name]);
      if (params.delegationRole === 'subagent' && governance.allowInSubagent === false) {
        disabledReasons.push({ name, reason: 'tool_governance_subagent_disabled' });
        return false;
      }
      if (this.delegateToolNames.has(name) && !this.getDelegateToolContext(params)) {
        disabledReasons.push({ name, reason: 'delegation_context_required' });
        return false;
      }
      if (name === 'escalate' && !(params.crewRole === 'master' && params.crewContext)) {
        disabledReasons.push({ name, reason: 'crew_context_required' });
        return false;
      }
      if (!config) return false;
      if (config.status === 0) {
        disabledReasons.push({ name, reason: 'globally_disabled' });
        return false;
      }
      if (disabledByAgent.has(name)) {
        disabledReasons.push({ name, reason: 'agent_tool_disabled' });
        return false;
      }
      if (!this.matchCapability(config.capability || 'text', params.modelCapability || 'text')) {
        disabledReasons.push({ name, reason: 'capability_mismatch' });
        return false;
      }
      const promptHint = typeof agentPerTool[name]?.promptHint === 'string' && agentPerTool[name]?.promptHint?.trim()
        ? agentPerTool[name]?.promptHint?.trim()
        : config.promptHint;
      if (promptHint) {
        toolPromptHints[name] = promptHint;
      }
      return true;
    });

    if (sessionDelegateContext) {
      const delegateHint = this.buildSessionDelegatePromptHint(sessionDelegateContext);
      if (filteredNames.includes('delegate_task')) {
        toolPromptHints.delegate_task = delegateHint;
      }
      if (filteredNames.includes('delegate_parallel')) {
        toolPromptHints.delegate_parallel = delegateHint;
      }
    }

    const operations = params.operations ?? getDefaultOperations();
    const baseToolMap = this.getBaseToolMap(operations);
    const runtimeTools: AnyAgentTool[] = [];
    const builtinToolNames: string[] = [];

    const bashEnvProvider = createSkillBashEnvProvider(this.skillLoader, this.skillSecret);
    for (const name of filteredNames) {
      if (name === 'bash') {
        runtimeTools.push(createBashTool({ operations, envProvider: bashEnvProvider }));
        builtinToolNames.push(name);
        continue;
      }
      const baseTool = baseToolMap.get(name);
      if (baseTool) {
        runtimeTools.push(baseTool);
        builtinToolNames.push(name);
      }
    }

    if (params.memoryEnabled) {
      const runtimeConfig: NetaClawAgentRuntimeConfig | undefined = params.agent?.config || undefined;
      const memoryConfig: AgentMemoryConfig | undefined = runtimeConfig?.memory;
      if (memoryConfig?.enabled) {
        const agentName = params.agent?.name ?? 'default';
        const userId = params.userId ?? 'anonymous';
        const { provider } = await this.memoryProviderRegistry.getProvider(agentName);
        runtimeTools.push(createMemorySaveTool(provider, agentName, userId, this.memoryTypeRepo));
        runtimeTools.push(createMemoryRecallTool(provider, agentName, userId));
        runtimeTools.push(createMemoryListTypesTool(this.memoryTypeRepo));
        runtimeTools.push(createMemoryStatsTool(provider, agentName, userId));
      }
    }

    if (params.hasSkills) {
      if (filteredNames.includes('read_skill')) runtimeTools.push(createReadSkillTool(this.skillLoader));
      if (filteredNames.includes('read_skill_file')) runtimeTools.push(createReadSkillFileTool(this.skillLoader));
      if (filteredNames.includes('skill_manage')) runtimeTools.push(createSkillManageTool(this.skillLoader));
    }

    if (params.hasSkills && filteredNames.includes('execute_skill')) {
      const agentSkills = (params.agent as any)?.skills || [];
      const hasComputeEntry = this.skillConfig.hasComputeEntrySkills(agentSkills);
      if (hasComputeEntry) {
        runtimeTools.push(createExecuteSkillTool(this.skillExecutor));
      }
    }

    const delegateToolContext = this.getDelegateToolContext(params);
    if (delegateToolContext) {
      if (filteredNames.includes('delegate_task')) runtimeTools.push(createDelegateTaskTool(delegateToolContext as any));
      if (filteredNames.includes('delegate_parallel')) runtimeTools.push(createDelegateParallelTool(delegateToolContext as any));
    }

    if (params.crewRole === 'master' && params.crewContext) {
      if (filteredNames.includes('escalate')) runtimeTools.push(createEscalateTool(params.crewContext, params.crewContext.crewRunRepo));
    }

    if (filteredNames.includes('image_recognize')) {
      const toolModelConfig = await this.toolRegistry.getToolModelConfig('image_recognize');
      if (toolModelConfig) {
        const channelCreds = await this.modelChannelService.resolveForAgent(toolModelConfig.modelChannelId, toolModelConfig.modelId);
        if (channelCreds) {
          runtimeTools.push(createImageRecognizeTool({
            baseUrl: channelCreds.baseUrl ?? '',
            apiKey: channelCreds.apiKey,
            supplier: channelCreds.channelSupplier,
            modelId: toolModelConfig.modelId,
            promptHint: toolModelConfig.promptHint,
          }));
        } else {
          disabledReasons.push({ name: 'image_recognize', reason: 'model_channel_unavailable' });
        }
      } else {
        disabledReasons.push({ name: 'image_recognize', reason: 'model_not_configured' });
      }
    }

    for (const imgToolName of ['text_to_image', 'image_to_image'] as const) {
      if (filteredNames.includes(imgToolName)) {
        const toolModelConfig = await this.toolRegistry.getToolModelConfig(imgToolName);
        if (toolModelConfig) {
          const channelCreds = await this.modelChannelService.resolveForAgent(toolModelConfig.modelChannelId, toolModelConfig.modelId);
          if (channelCreds) {
            const provider = getImageProvider(channelCreds.channelSupplier, channelCreds.baseUrl ?? '');
            if (provider) {
              const toolEntity = await this.toolRegistry.getToolByName(imgToolName);
              const extra = toolEntity?.extra as import('../tools/manifest.js').ToolGovernanceExtra | null;
              const creds: ImageProviderCredentials = {
                baseUrl: channelCreds.baseUrl ?? '',
                apiKey: channelCreds.apiKey,
                supplier: channelCreds.channelSupplier,
                modelId: toolModelConfig.modelId,
                promptHint: toolModelConfig.promptHint,
                extra,
              };
              if (imgToolName === 'text_to_image') {
                runtimeTools.push(createTextToImageTool(creds, provider, this.imageStorageService));
              } else {
                runtimeTools.push(createImageToImageTool(creds, provider, this.imageStorageService));
              }
            } else {
              disabledReasons.push({ name: imgToolName, reason: 'image_provider_not_found' });
            }
          } else {
            disabledReasons.push({ name: imgToolName, reason: 'model_channel_unavailable' });
          }
        } else {
          disabledReasons.push({ name: imgToolName, reason: 'model_not_configured' });
        }
      }
    }

    const toolManifest = this.buildToolManifest(filteredNames, toolConfigMap as any, agentPerTool);
    const toolRuntimeRoutes = this.buildResolvedToolRuntimeRoutes({
      toolManifest,
      enabledToolNames: filteredNames,
      disabledReasons,
      runtimePolicy: params.runtimePolicy,
    });

    return {
      tools: runtimeTools,
      toolNames: filteredNames,
      toolManifest,
      toolRuntimeRoutes,
      builtinToolNames,
      toolPromptHints,
      disabledReasons,
    };
  }

  async diagnoseRuntime(params: ToolRuntimeDiagnosticParams): Promise<ToolRuntimeDiagnosticResult> {
    const effectiveAgent = params.draftAgentTools
      ? {
        ...(params.agent || {} as NetaClawAgentEntity),
        tools: params.draftAgentTools,
      } as NetaClawAgentEntity
      : params.agent;
    const modelCapability = params.modelCapability ?? 'text';
    const allTools = await this.toolRegistry.all();
    const draftOverride = params.draftToolOverride?.name
      ? params.draftToolOverride
      : null;
    const runtimeTools = allTools.map(tool => {
      if (!draftOverride || tool.name !== draftOverride.name) {
        return tool;
      }
      return {
        ...tool,
        status: typeof draftOverride.status === 'number' ? draftOverride.status : tool.status,
        promptHint: draftOverride.promptHint ?? tool.promptHint,
        extra: {
          ...(tool.extra && typeof tool.extra === 'object' && !Array.isArray(tool.extra) ? tool.extra as any : {}),
          ...(typeof draftOverride.allowInSubagent === 'boolean'
            ? { allowInSubagent: draftOverride.allowInSubagent }
            : {}),
          ...(draftOverride.workerRoutingStrategy
            ? { workerRoutingStrategy: draftOverride.workerRoutingStrategy }
            : {}),
        },
      };
    });
    const toolConfigMap = new Map(runtimeTools.map(item => [item.name, item]));
    const agentPerTool = this.getAgentPerToolConfig(effectiveAgent);
    const mergedAgentPerTool = {
      ...agentPerTool,
      ...(draftOverride?.name
        ? {
          [draftOverride.name]: {
            ...(agentPerTool[draftOverride.name] || {}),
            ...(typeof draftOverride.enabled === 'boolean'
              ? { enabled: draftOverride.enabled }
              : {}),
            ...(typeof draftOverride.promptHint === 'string'
              ? { promptHint: draftOverride.promptHint }
              : {}),
            ...(typeof draftOverride.allowInSubagent === 'boolean'
              ? { allowInSubagent: draftOverride.allowInSubagent }
              : {}),
            ...(draftOverride.workerRoutingStrategy
              ? { workerRoutingStrategy: draftOverride.workerRoutingStrategy }
              : {}),
          },
        }
        : {}),
    };
    const manifestBase = this.buildToolManifest(
      runtimeTools.map(item => item.name),
      toolConfigMap as Map<string, { capability?: string; visibility?: string }>,
      mergedAgentPerTool,
    );
    const manifestByName = new Map(manifestBase.map(item => [item.name, item]));

    const supervisor = await this.resolve({
      agent: effectiveAgent,
      modelCapability,
      memoryEnabled: params.memoryEnabled,
      hasSkills: params.hasSkills,
      catalogOverrides: toolConfigMap,
    });
    const subagent = await this.resolve({
      agent: effectiveAgent,
      modelCapability,
      memoryEnabled: params.memoryEnabled,
      hasSkills: params.hasSkills,
      delegationRole: 'subagent',
      catalogOverrides: toolConfigMap,
    });

    const supervisorDisabledByName = new Map(supervisor.disabledReasons.map(item => [item.name, item.reason]));
    const subagentDisabledByName = new Map(subagent.disabledReasons.map(item => [item.name, item.reason]));
    const subagentManifestByName = new Map(subagent.toolManifest.map(item => [item.name, item]));
    const workerPolicy = this.buildDiagnosticWorkerPolicy({
      sessionCwd: params.sessionCwd,
      workspaceRoots: params.workspaceRoots,
      allowShell: params.allowShell,
      readonly: params.readonly,
      toolManifest: subagent.toolManifest,
    });

    return {
      agentId: effectiveAgent?.id ?? null,
      modelCapability,
      workerPolicy,
      toolRuntimeRoutes: subagent.toolRuntimeRoutes,
      items: runtimeTools.map(tool => {
        const manifest = subagentManifestByName.get(tool.name) ?? manifestByName.get(tool.name);
        const fallbackManifest = buildToolManifestItem(tool.name);
        const subprocessWorker = this.resolveWorkerRuntimeStatus(tool.name, manifest, subagent.toolNames, subagentDisabledByName, workerPolicy);
        const agentOverride = agentPerTool[tool.name] || {};

        return {
          name: tool.name,
          label: tool.label || tool.name,
          description: tool.description ?? null,
          toolset: tool.toolset,
          visibility: tool.visibility ?? 'tool',
          capability: tool.capability ?? 'text',
          status: tool.status,
          isCore: tool.isCore,
          canDisable: tool.canDisable,
          supportsPromptHint: tool.supportsPromptHint,
          promptHint: tool.promptHint ?? null,
          agentOverride: {
            enabled: agentOverride.enabled,
            promptHint: agentOverride.promptHint?.trim() || undefined,
            allowInSubagent: agentOverride.allowInSubagent,
            workerRoutingStrategy: agentOverride.workerRoutingStrategy,
            hasOverride:
              typeof agentOverride.enabled === 'boolean'
              || !!agentOverride.promptHint?.trim()
              || typeof agentOverride.allowInSubagent === 'boolean'
              || (agentOverride.workerRoutingStrategy != null && agentOverride.workerRoutingStrategy !== 'auto'),
          },
          manifest: {
            kind: manifest?.kind ?? fallbackManifest.kind,
            executionMode: manifest?.executionMode ?? fallbackManifest.executionMode,
            supportedInWorker: manifest?.supportedInWorker ?? (fallbackManifest.supportedInWorker === true),
            workerRoutingHint: manifest?.workerRoutingHint
              ?? getBaseWorkerRoutingHint(tool.name),
            requiresShell: manifest?.requiresShell === true,
            requiresWrite: manifest?.requiresWrite === true,
            requiresSkillScope: manifest?.requiresSkillScope === true,
          },
          runtime: {
            supervisorInProcess: {
              enabled: supervisor.toolNames.includes(tool.name),
              blockedReason: supervisorDisabledByName.get(tool.name) ?? null,
              policySources: this.buildRuntimePolicySources({
                tool,
                runtime: 'supervisor',
                blockedReason: supervisorDisabledByName.get(tool.name) ?? null,
              }),
            },
            subagentInProcess: {
              enabled: subagent.toolNames.includes(tool.name),
              blockedReason: subagentDisabledByName.get(tool.name) ?? null,
              policySources: this.buildRuntimePolicySources({
                tool,
                runtime: 'subagent',
                blockedReason: subagentDisabledByName.get(tool.name) ?? null,
              }),
            },
            subprocessWorker,
          },
          renderer: {
            key: tool.name,
            ...buildToolRendererCapabilityProjection(tool.name),
          },
        };
      }),
    };
  }

  private buildDiagnosticWorkerPolicy(params: {
    sessionCwd?: string | null;
    workspaceRoots?: string[];
    allowShell?: boolean;
    readonly?: boolean;
    toolManifest: SubagentToolManifestItem[];
  }): SubagentWorkerPolicy {
    return buildSubprocessWorkerPolicy(params);
  }

  private buildResolvedToolRuntimeRoutes(params: {
    toolManifest: SubagentToolManifestItem[];
    enabledToolNames: string[];
    disabledReasons: Array<{ name: string; reason: string }>;
    runtimePolicy?: ResolveToolParams['runtimePolicy'];
  }): ResolvedToolRuntimeRoute[] {
    const disabledByName = new Map(params.disabledReasons.map(item => [item.name, item.reason]));
    const workerPolicy = this.buildDiagnosticWorkerPolicy({
      sessionCwd: params.runtimePolicy?.sessionCwd,
      workspaceRoots: params.runtimePolicy?.workspaceRoots,
      allowShell: params.runtimePolicy?.allowShell,
      readonly: params.runtimePolicy?.readonly,
      toolManifest: params.toolManifest,
    });

    return params.toolManifest.map(manifest => {
      const runtime = this.resolveWorkerRuntimeStatus(
        manifest.name,
        manifest,
        params.enabledToolNames,
        disabledByName,
        workerPolicy,
      );
      return {
        name: manifest.name,
        kind: manifest.kind,
        executionMode: manifest.executionMode,
        supportedInWorker: manifest.supportedInWorker,
        workerRoutingHint: manifest.workerRoutingHint ?? 'disabled',
        runtimeRoute: mapWorkerStatusToRuntimeRoute(runtime.status),
        blockedReason: runtime.blockedReason,
        policySources: runtime.policySources,
        policyDerived: runtime.policyDerived,
      };
    });
  }

  private resolveWorkerRuntimeStatus(
    toolName: string,
    manifest: SubagentToolManifestItem | undefined,
    enabledSubagentToolNames: string[],
    disabledReasons: Map<string, string>,
    policy: SubagentWorkerPolicy,
  ): ToolRuntimeDiagnosticItem['runtime']['subprocessWorker'] {
    return resolveToolWorkerRuntime({
      toolName,
      manifest,
      enabledToolNames: enabledSubagentToolNames,
      disabledReasons,
      policy,
    });
  }

  private buildRuntimePolicySources(params: {
    tool: {
      status?: number;
      capability?: string;
      promptHint?: string | null;
    };
    runtime: 'supervisor' | 'subagent';
    blockedReason: string | null;
  }): string[] {
    const sources = ['tool catalog', 'tool registry'];

    if (params.tool.status === 0) {
      sources.push('db status');
    }
    if (params.tool.capability) {
      sources.push('model capability');
    }
    if (params.runtime === 'subagent') {
      sources.push('subagent policy');
    } else {
      sources.push('agent tool config');
    }
    if (params.tool.promptHint) {
      sources.push('prompt hint');
    }
    if (params.blockedReason) {
      sources.push('blocked reason');
    }

    return [...new Set(sources)];
  }

}
