import type { SkillCapabilityBinding } from './types';

export interface SkillCapabilityBindingPayload {
  capabilityType: SkillCapabilityBinding['capabilityType'];
  enabled: boolean;
  executionMode?: SkillCapabilityBinding['executionMode'];
  hitlConfigJson?: Record<string, unknown>;
  hitlEnabled?: boolean;
  optionsJson?: Record<string, unknown>;
  runtimeName?: string;
  selectedToolNamesJson?: string[];
  selectionMode?: SkillCapabilityBinding['selectionMode'];
  sortNo?: number;
  targetId?: number | string;
  targetLogicalRef?: string;
}

/** Build the narrow capability DTO accepted by replace and validation APIs. */
export function buildCapabilityBindingPayload(
  binding: SkillCapabilityBinding,
): SkillCapabilityBindingPayload {
  const isMcp = binding.capabilityType === 'MCP';
  return {
    capabilityType: binding.capabilityType,
    enabled: binding.enabled,
    executionMode: isMcp
      ? undefined
      : normalizeExecutionMode(binding.executionMode),
    hitlConfigJson: sanitizeHitlConfig(binding.hitlConfigJson),
    hitlEnabled: binding.hitlEnabled,
    optionsJson: sanitizeCapabilityOptions(binding.optionsJson),
    runtimeName: binding.runtimeName,
    selectedToolNamesJson: isMcp ? binding.selectedToolNamesJson : undefined,
    selectionMode: isMcp ? binding.selectionMode : undefined,
    sortNo: binding.sortNo,
    targetId: binding.targetId,
    targetLogicalRef: binding.targetLogicalRef,
  };
}

/** Keeps only the non-sensitive HITL strings supported by the backend contract. */
export function sanitizeHitlConfig(source?: Record<string, unknown>) {
  return compactRecord({
    cancelLabel: readString(source?.cancelLabel, 32),
    confirmLabel: readString(source?.confirmLabel, 32),
    description: readString(source?.description, 500),
    prompt: readString(source?.prompt, 1000),
    title: readString(source?.title, 128),
  });
}

/** Keeps only scalar, non-sensitive execution options supported by the contract. */
export function sanitizeCapabilityOptions(source?: Record<string, unknown>) {
  return compactRecord({
    async: readBoolean(source?.async),
    readOnly: readBoolean(source?.readOnly),
    retryCount: readInteger(source?.retryCount),
    timeoutMs: readInteger(source?.timeoutMs),
  });
}

function normalizeExecutionMode(value?: 'ASYNC' | 'SYNC') {
  return value === 'ASYNC' ? 'ASYNC' : 'SYNC';
}

function readString(value: unknown, maxLength: number) {
  if (typeof value !== 'string') return undefined;
  const normalized = value.trim().slice(0, maxLength);
  return normalized || undefined;
}

function readBoolean(value: unknown) {
  return typeof value === 'boolean' ? value : undefined;
}

function readInteger(value: unknown) {
  return typeof value === 'number' && Number.isFinite(value)
    ? Math.trunc(value)
    : undefined;
}

function compactRecord(source: Record<string, unknown>) {
  return Object.fromEntries(
    Object.entries(source).filter(([, value]) => value !== undefined),
  );
}

export function buildCapabilityBindingsPayload(
  bindings?: SkillCapabilityBinding[],
) {
  return bindings?.map(buildCapabilityBindingPayload);
}

/** MCP tools are fetched only when a concrete target uses an explicit allowlist. */
export function shouldLoadMcpTools(
  capabilityType: SkillCapabilityBinding['capabilityType'],
  selectionMode: SkillCapabilityBinding['selectionMode'],
  targetId?: number | string,
): targetId is number | string {
  return (
    capabilityType === 'MCP' &&
    selectionMode === 'SELECTED' &&
    targetId !== undefined &&
    targetId !== null &&
    String(targetId).trim().length > 0
  );
}

/** 从结构化校验路径中解析能力绑定序号。 */
export function resolveCapabilityIssueIndex(path?: string) {
  const match = path?.match(/(?:^|\.)capabilities\[(\d+)\](?:\.|$)/);
  return match?.[1] === undefined ? undefined : Number(match[1]);
}
