package com.easyagents.agent.runtime.tool.asynctool;

import com.alibaba.fastjson2.JSON;
import com.easyagents.agent.runtime.AgentRuntimeException;
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
import com.easyagents.agent.runtime.tool.AgentToolCategory;
import com.easyagents.agent.runtime.tool.AgentToolContext;
import com.easyagents.agent.runtime.tool.AgentToolInvoker;
import com.easyagents.agent.runtime.tool.AgentToolResult;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.agent.runtime.tool.AgentToolVisibility;

import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
 * 异步工具声明展开器。
 *
 * <p>该类将一个业务无关的 {@link AsyncToolSpec} 展开为五个普通
 * {@link AgentToolSpec} 与 {@link AgentToolInvoker}，业务方只需要实现
 * {@link AsyncSubTools}。</p>
 */
public class AsyncToolSpecExpander {

    private static final Pattern SAFE_NAME = Pattern.compile("^[a-z][a-z0-9_]*$");
    private static final String PHASE_SUBMIT = "submit";
    private static final String PHASE_OBSERVE = "observe";
    private static final String PHASE_RESULT = "result";
    private static final String PHASE_CANCEL = "cancel";
    private static final String PHASE_LIST = "list";
    private static final String ERROR_TYPE_TIMEOUT = "TIMEOUT";
    private static final String ERROR_TYPE_EXCEPTION = "EXCEPTION";

    private final ExecutorService executor;

    /**
     * 使用公共 ForkJoinPool 创建展开器。
     */
    public AsyncToolSpecExpander() {
        this(ForkJoinPool.commonPool());
    }

    /**
     * 使用指定执行器创建展开器。
     *
     * @param executor 执行器
     */
    public AsyncToolSpecExpander(Executor executor) {
        if (executor instanceof ExecutorService executorService) {
            this.executor = executorService;
        } else {
            this.executor = new DelegatingExecutorService(executor);
        }
    }

    /**
     * 展开工具声明。
     *
     * @param spec 异步工具声明
     * @return 五个普通工具声明
     */
    public List<AgentToolSpec> expandSpecs(AsyncToolSpec spec) {
        AsyncToolSpec safeSpec = validate(spec);
        List<AgentToolSpec> specs = new ArrayList<>(5);
        specs.add(toolSpec(safeSpec, PHASE_SUBMIT, safeSpec.getSubmitParametersSchema(), submitOutputSchema()));
        specs.add(toolSpec(safeSpec, PHASE_OBSERVE, observeSchema(safeSpec), taskViewOutputSchema()));
        specs.add(toolSpec(safeSpec, PHASE_RESULT, observeSchema(safeSpec), taskViewOutputSchema()));
        specs.add(toolSpec(safeSpec, PHASE_CANCEL, cancelSchema(), cancelOutputSchema()));
        specs.add(toolSpec(safeSpec, PHASE_LIST, listSchema(), listOutputSchema()));
        return specs;
    }

    /**
     * 展开工具调用器。
     *
     * @param spec 异步工具声明
     * @return 按工具名索引的调用器
     */
    public Map<String, AgentToolInvoker> expandInvokers(AsyncToolSpec spec) {
        AsyncToolSpec safeSpec = validate(spec);
        Map<String, AgentToolInvoker> invokers = new LinkedHashMap<>();
        invokers.put(toolName(safeSpec, PHASE_SUBMIT), (arguments, context) -> submit(safeSpec, arguments, context));
        invokers.put(toolName(safeSpec, PHASE_OBSERVE), (arguments, context) -> observe(safeSpec, arguments, context));
        invokers.put(toolName(safeSpec, PHASE_RESULT), (arguments, context) -> result(safeSpec, arguments, context));
        invokers.put(toolName(safeSpec, PHASE_CANCEL), (arguments, context) -> cancel(safeSpec, arguments, context));
        invokers.put(toolName(safeSpec, PHASE_LIST), (arguments, context) -> list(safeSpec, arguments, context));
        return invokers;
    }

    private AsyncToolSpec validate(AsyncToolSpec spec) {
        if (spec == null) {
            throw new AgentRuntimeException("Async tool spec is required.");
        }
        if (spec.getName() == null || spec.getName().isBlank()) {
            throw new AgentRuntimeException("Async tool name is required.");
        }
        if (!SAFE_NAME.matcher(spec.getName()).matches()) {
            throw new AgentRuntimeException("Async tool name must be safe snake_case: " + spec.getName());
        }
        if (spec.getSubTools() == null) {
            throw new AgentRuntimeException("Async sub tools are required: " + spec.getName());
        }
        if (spec.getSubmitParametersSchema() == null || spec.getSubmitParametersSchema().isEmpty()) {
            spec.setSubmitParametersSchema(emptyObjectSchema());
        }
        if (spec.getOptions() == null) {
            spec.setOptions(AsyncToolOptions.defaults());
        }
        return spec;
    }

    private AgentToolSpec toolSpec(AsyncToolSpec spec,
                                   String phase,
                                   Map<String, Object> parametersSchema,
                                   Map<String, Object> outputSchema) {
        AgentToolSpec toolSpec = new AgentToolSpec();
        toolSpec.setName(toolName(spec, phase));
        toolSpec.setDescription(description(spec, phase));
        toolSpec.setCategory(AgentToolCategory.CUSTOM);
        toolSpec.setVisibility(AgentToolVisibility.VISIBLE);
        toolSpec.setParametersSchema(parametersSchema);
        toolSpec.setOutputSchema(outputSchema);
        toolSpec.setApprovalRequired(PHASE_SUBMIT.equals(phase) && spec.isApprovalRequired());
        toolSpec.setApprovalRequest(spec.getApprovalRequest());
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.putAll(spec.getMetadata());
        metadata.put("asyncTool", true);
        metadata.put("asyncToolName", spec.getName());
        metadata.put("asyncToolPhase", phase);
        toolSpec.setMetadata(metadata);
        return toolSpec;
    }

    private String description(AsyncToolSpec spec, String phase) {
        String prefix = spec.getDescription() == null || spec.getDescription().isBlank()
                ? "Async tool " + spec.getName()
                : spec.getDescription();
        return switch (phase) {
            case PHASE_SUBMIT -> prefix
                    + " This is the default entry point when the user asks to run this tool. Submit an asynchronous task with the normal tool arguments and return task_id.";
            case PHASE_OBSERVE -> prefix
                    + " Use immediately after submit with the returned task_id to check progress and incremental events. Do not ask the user for task_id immediately after submit.";
            case PHASE_RESULT -> prefix
                    + " Use only when a known task_id should return the final result, or the current observation if the task is still running.";
            case PHASE_CANCEL -> prefix
                    + " Use only when the user explicitly asks to cancel a known asynchronous task by task_id.";
            case PHASE_LIST -> prefix
                    + " Use only when the user explicitly asks to list visible asynchronous tasks in the current context.";
            default -> prefix;
        };
    }

    private AgentToolResult submit(AsyncToolSpec spec, Map<String, Object> arguments, AgentToolContext context) {
        return execute(spec, PHASE_SUBMIT, context, spec.getOptions().getSubmitTimeout(),
                guardedContext -> {
                    AsyncToolSubmitResult result = spec.getSubTools().submit(safeMap(arguments), guardedContext);
                    return wrapSubmit(spec, result, guardedContext);
                });
    }

    private AgentToolResult observe(AsyncToolSpec spec, Map<String, Object> arguments, AgentToolContext context) {
        return execute(spec, PHASE_OBSERVE, context, spec.getOptions().getObserveTimeout(),
                guardedContext -> {
                    AsyncToolObserveRequest request = observeRequest(arguments, spec.getOptions());
                    return wrapTaskView(spec, PHASE_OBSERVE,
                            spec.getSubTools().observe(request, guardedContext), guardedContext);
                });
    }

    private AgentToolResult result(AsyncToolSpec spec, Map<String, Object> arguments, AgentToolContext context) {
        return execute(spec, PHASE_RESULT, context, spec.getOptions().getResultTimeout(),
                guardedContext -> {
                    AsyncToolResultRequest request = resultRequest(arguments, spec.getOptions());
                    return wrapTaskView(spec, PHASE_RESULT,
                            spec.getSubTools().result(request, guardedContext), guardedContext);
                });
    }

    private AgentToolResult cancel(AsyncToolSpec spec, Map<String, Object> arguments, AgentToolContext context) {
        return execute(spec, PHASE_CANCEL, context, spec.getOptions().getCancelTimeout(),
                guardedContext -> {
                    AsyncToolCancelRequest request = cancelRequest(arguments);
                    return wrapCancel(spec, spec.getSubTools().cancel(request, guardedContext), guardedContext);
                });
    }

    private AgentToolResult list(AsyncToolSpec spec, Map<String, Object> arguments, AgentToolContext context) {
        return execute(spec, PHASE_LIST, context, spec.getOptions().getListTimeout(),
                guardedContext -> {
                    AsyncToolListRequest request = listRequest(arguments);
                    return wrapList(spec, spec.getSubTools().list(request, guardedContext), guardedContext);
                });
    }

    private AgentToolResult execute(AsyncToolSpec spec,
                                    String phase,
                                    AgentToolContext context,
                                    Duration timeout,
                                    Function<AgentToolContext, AgentToolResult> supplier) {
        AtomicBoolean active = new AtomicBoolean(true);
        AgentToolContext guardedContext = guardedContext(context, active);
        Future<AgentToolResult> future = executor.submit(() -> supplier.apply(guardedContext));
        try {
            return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
        } catch (TimeoutException error) {
            active.set(false);
            future.cancel(true);
            AgentToolResult result = failureResult(spec, phase, null, AsyncToolTaskStatus.TIMEOUT,
                    ERROR_TYPE_TIMEOUT, "Async tool " + phase + " timed out.");
            emitFailure(spec, phase, context, null, AsyncToolTaskStatus.TIMEOUT, result.getErrorMessage());
            return result;
        } catch (InterruptedException error) {
            active.set(false);
            Thread.currentThread().interrupt();
            AgentToolResult result = failureResult(spec, phase, null, AsyncToolTaskStatus.FAILED,
                    ERROR_TYPE_EXCEPTION, "Async tool " + phase + " interrupted.");
            emitFailure(spec, phase, context, null, AsyncToolTaskStatus.FAILED, result.getErrorMessage());
            return result;
        } catch (ExecutionException error) {
            active.set(false);
            Throwable cause = error.getCause() == null ? error : error.getCause();
            String message = cause.getMessage() == null || cause.getMessage().isBlank()
                    ? "Async tool " + phase + " failed."
                    : cause.getMessage();
            AgentToolResult result = failureResult(spec, phase, null, AsyncToolTaskStatus.FAILED,
                    ERROR_TYPE_EXCEPTION, message);
            emitFailure(spec, phase, context, null, AsyncToolTaskStatus.FAILED, result.getErrorMessage());
            return result;
        }
    }

    private AgentToolContext guardedContext(AgentToolContext source, AtomicBoolean active) {
        if (source == null) {
            return null;
        }
        AgentToolContext context = new AgentToolContext();
        context.setRequestId(source.getRequestId());
        context.setTraceId(source.getTraceId());
        context.setSessionId(source.getSessionId());
        context.setAgentId(source.getAgentId());
        context.setToolCallId(source.getToolCallId());
        context.setRuntimeContext(source.getRuntimeContext());
        context.setMetadata(new LinkedHashMap<>(source.getMetadata()));
        context.setEventEmitter(event -> {
            // 超时后底层业务可能仍在运行，迟到事件不能再覆盖 runtime 已返回的失败语义。
            if (active.get()) {
                source.emitEvent(event);
            }
        });
        return context;
    }

    private AgentToolResult wrapSubmit(AsyncToolSpec spec, AsyncToolSubmitResult result, AgentToolContext context) {
        AsyncToolSubmitResult safe = result == null ? new AsyncToolSubmitResult() : result;
        AsyncToolTaskStatus status = status(safe.getStatus(), AsyncToolTaskStatus.RUNNING);
        safe.setStatus(status);
        if (safe.getTaskId() == null || safe.getTaskId().isBlank()) {
            String message = "Async tool submit must return taskId.";
            AgentToolResult toolResult = failureResult(spec, PHASE_SUBMIT, null, AsyncToolTaskStatus.FAILED,
                    ERROR_TYPE_EXCEPTION, message, safe);
            emitFailure(spec, PHASE_SUBMIT, context, null, AsyncToolTaskStatus.FAILED, message);
            return toolResult;
        }
        if (safe.getNextAction() == null || safe.getNextAction().isBlank()) {
            safe.setNextAction(toolName(spec, PHASE_OBSERVE) + " 查看任务进度。");
        }
        AgentToolResult toolResult = successResult(spec, PHASE_SUBMIT, safe.getTaskId(), status,
                modelContent(safe.getTaskId(), status, safe.getNextAction(), safe.getSummary()), safe);
        emit(spec, PHASE_SUBMIT, AgentRuntimeEventType.ASYNC_TOOL_SUBMITTED, context, safe.getTaskId(), status,
                safe.getCursor(), null, safe.getSummary(), null);
        return toolResult;
    }

    private AgentToolResult wrapTaskView(AsyncToolSpec spec,
                                         String phase,
                                         AsyncToolTaskView view,
                                         AgentToolContext context) {
        AsyncToolTaskView safe = view == null ? new AsyncToolTaskView() : view;
        AsyncToolTaskStatus status = status(safe.getStatus(), AsyncToolTaskStatus.RUNNING);
        safe.setStatus(status);
        if (safe.getTerminal() == null) {
            safe.setTerminal(status.isTerminal());
        }
        if (safe.getResultAvailable() == null) {
            safe.setResultAvailable(status.isSuccess() && safe.getResult() != null);
        }
        if (safe.getNextAction() == null || safe.getNextAction().isBlank()) {
            safe.setNextAction(status.isTerminal()
                    ? "任务已结束。"
                    : toolName(spec, PHASE_OBSERVE) + " 继续查看任务进度。");
        }
        AgentToolResult toolResult = successResult(spec, phase, safe.getTaskId(), status,
                modelContent(safe.getTaskId(), status, safe.getNextAction(), safe.getSummary(),
                        Boolean.TRUE.equals(safe.getResultAvailable()), safe.getResult()), safe);
        emit(spec, phase, PHASE_RESULT.equals(phase)
                        ? AgentRuntimeEventType.ASYNC_TOOL_RESULT
                        : AgentRuntimeEventType.ASYNC_TOOL_OBSERVED,
                context, safe.getTaskId(), status, safe.getCursor(), safe.getNextCursor(), safe.getSummary(),
                safe.getErrorMessage(), safe.getResultAvailable());
        return toolResult;
    }

    private AgentToolResult wrapCancel(AsyncToolSpec spec, AsyncToolCancelResult result, AgentToolContext context) {
        AsyncToolCancelResult safe = result == null ? new AsyncToolCancelResult() : result;
        AsyncToolTaskStatus status = status(safe.getStatus(), AsyncToolTaskStatus.CANCELLING);
        safe.setStatus(status);
        boolean success = safe.getErrorMessage() == null || safe.getErrorMessage().isBlank();
        AgentToolResult toolResult = success
                ? successResult(spec, PHASE_CANCEL, safe.getTaskId(), status,
                        modelContent(safe.getTaskId(), status, "继续使用 " + toolName(spec, PHASE_OBSERVE) + " 查看取消状态。",
                                safe.getMessage()), safe)
                : failureResult(spec, PHASE_CANCEL, safe.getTaskId(), status, ERROR_TYPE_EXCEPTION, safe.getErrorMessage(), safe);
        emit(spec, PHASE_CANCEL, success ? AgentRuntimeEventType.ASYNC_TOOL_CANCELLED : AgentRuntimeEventType.ASYNC_TOOL_FAILED,
                context, safe.getTaskId(), status, null, null, safe.getMessage(), safe.getErrorMessage());
        return toolResult;
    }

    private AgentToolResult wrapList(AsyncToolSpec spec, AsyncToolTaskListResult result, AgentToolContext context) {
        AsyncToolTaskListResult safe = result == null ? new AsyncToolTaskListResult() : result;
        String summary = "共 " + safe.getTasks().size() + " 个任务。";
        AgentToolResult toolResult = successResult(spec, PHASE_LIST, null, null,
                modelContent(null, null, "按 task_id 使用观察或结果工具查看详情。", summary), safe);
        emit(spec, PHASE_LIST, AgentRuntimeEventType.ASYNC_TOOL_LISTED, context, null, null, null, null, summary, null);
        return toolResult;
    }

    private AgentToolResult successResult(AsyncToolSpec spec,
                                          String phase,
                                          String taskId,
                                          AsyncToolTaskStatus status,
                                          String modelContent,
                                          Object displayContent) {
        AgentToolResult result = AgentToolResult.success(truncate(modelContent, spec.getOptions().getMaxModelContentLength()));
        result.setDisplayContent(displayContent);
        result.setMetadata(metadata(spec, phase, taskId, status));
        return result;
    }

    private AgentToolResult failureResult(AsyncToolSpec spec,
                                          String phase,
                                          String taskId,
                                          AsyncToolTaskStatus status,
                                          String errorType,
                                          String errorMessage) {
        return failureResult(spec, phase, taskId, status, errorType, errorMessage, null);
    }

    private AgentToolResult failureResult(AsyncToolSpec spec,
                                          String phase,
                                          String taskId,
                                          AsyncToolTaskStatus status,
                                          String errorType,
                                          String errorMessage,
                                          Object displayContent) {
        String message = errorMessage == null || errorMessage.isBlank() ? "Async tool failed." : errorMessage;
        AgentToolResult result = AgentToolResult.failure(message);
        result.setDisplayContent(displayContent == null ? Map.of("errorType", errorType, "message", message) : displayContent);
        result.setMetadata(metadata(spec, phase, taskId, status));
        result.getMetadata().put("errorType", errorType);
        return result;
    }

    private void emitFailure(AsyncToolSpec spec,
                             String phase,
                             AgentToolContext context,
                             String taskId,
                             AsyncToolTaskStatus status,
                             String errorMessage) {
        emit(spec, phase, AgentRuntimeEventType.ASYNC_TOOL_FAILED, context, taskId, status, null, null, null, errorMessage);
    }

    private void emit(AsyncToolSpec spec,
                      String phase,
                      AgentRuntimeEventType type,
                      AgentToolContext context,
                      String taskId,
                      AsyncToolTaskStatus status,
                      Long cursor,
                      Long nextCursor,
                      String summary,
                      String errorMessage) {
        emit(spec, phase, type, context, taskId, status, cursor, nextCursor, summary, errorMessage, null);
    }

    private void emit(AsyncToolSpec spec,
                      String phase,
                      AgentRuntimeEventType type,
                      AgentToolContext context,
                      String taskId,
                      AsyncToolTaskStatus status,
                      Long cursor,
                      Long nextCursor,
                      String summary,
                      String errorMessage,
                      Boolean resultAvailable) {
        if (context == null) {
            return;
        }
        AgentRuntimeEvent event = AgentRuntimeEvent.of(type);
        event.setTraceId(context.getTraceId());
        event.setSessionId(context.getSessionId());
        event.setAgentId(context.getAgentId());
        event.setToolCallId(context.getToolCallId());
        event.getMetadata().putAll(spec.getMetadata());
        putIfNotNull(event.getMetadata(), "requestId", context.getRequestId());
        event.getPayload().put("asyncToolName", spec.getName());
        event.getPayload().put("phase", phase);
        putIfNotNull(event.getPayload(), "toolDisplayName", spec.getMetadata().get("toolDisplayName"));
        putIfNotNull(event.getPayload(), "taskId", taskId);
        putIfNotNull(event.getPayload(), "status", status == null ? null : status.name());
        putIfNotNull(event.getPayload(), "cursor", cursor);
        putIfNotNull(event.getPayload(), "nextCursor", nextCursor);
        putIfNotNull(event.getPayload(), "summary", truncate(summary, spec.getOptions().getMaxEventTextLength()));
        putIfNotNull(event.getPayload(), "errorMessage", truncate(errorMessage, spec.getOptions().getMaxEventTextLength()));
        putIfNotNull(event.getPayload(), "resultAvailable", resultAvailable);
        event.getMetadata().put("asyncTool", true);
        event.getMetadata().put("asyncToolName", spec.getName());
        event.getMetadata().put("asyncToolPhase", phase);
        context.emitEvent(event);
    }

    private Map<String, Object> metadata(AsyncToolSpec spec, String phase, String taskId, AsyncToolTaskStatus status) {
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.putAll(spec.getMetadata());
        metadata.put("asyncTool", true);
        metadata.put("asyncToolName", spec.getName());
        metadata.put("asyncToolPhase", phase);
        putIfNotNull(metadata, "taskId", taskId);
        putIfNotNull(metadata, "status", status == null ? null : status.name());
        return metadata;
    }

    private AsyncToolObserveRequest observeRequest(Map<String, Object> arguments, AsyncToolOptions options) {
        AsyncToolObserveRequest request = new AsyncToolObserveRequest();
        request.setTaskId(stringValue(arguments, "taskId"));
        request.setCursor(longValue(arguments, "cursor"));
        request.setLimit(limit(arguments, options));
        return request;
    }

    private AsyncToolResultRequest resultRequest(Map<String, Object> arguments, AsyncToolOptions options) {
        AsyncToolResultRequest request = new AsyncToolResultRequest();
        request.setTaskId(stringValue(arguments, "taskId"));
        request.setCursor(longValue(arguments, "cursor"));
        request.setLimit(limit(arguments, options));
        return request;
    }

    private AsyncToolCancelRequest cancelRequest(Map<String, Object> arguments) {
        AsyncToolCancelRequest request = new AsyncToolCancelRequest();
        request.setTaskId(stringValue(arguments, "taskId"));
        request.setReason(stringValue(arguments, "reason"));
        return request;
    }

    private AsyncToolListRequest listRequest(Map<String, Object> arguments) {
        AsyncToolListRequest request = new AsyncToolListRequest();
        String status = stringValue(arguments, "status");
        if (status != null && !status.isBlank()) {
            request.setStatus(AsyncToolTaskStatus.valueOf(status.trim().toUpperCase(Locale.ROOT)));
        }
        return request;
    }

    private Integer limit(Map<String, Object> arguments, AsyncToolOptions options) {
        Integer limit = intValue(arguments, "limit");
        if (limit == null || limit <= 0) {
            return options.getDefaultEventLimit();
        }
        return Math.min(limit, options.getMaxEventLimit());
    }

    private String modelContent(String taskId, AsyncToolTaskStatus status, String nextAction, String summary) {
        return modelContent(taskId, status, nextAction, summary, false, null);
    }

    private String modelContent(String taskId,
                                AsyncToolTaskStatus status,
                                String nextAction,
                                String summary,
                                boolean resultAvailable,
                                Object result) {
        StringBuilder builder = new StringBuilder();
        if (taskId != null && !taskId.isBlank()) {
            builder.append("task_id: ").append(taskId).append('\n');
        }
        if (status != null) {
            builder.append("status: ").append(status.name()).append('\n');
        }
        if (summary != null && !summary.isBlank()) {
            builder.append("summary: ").append(summary).append('\n');
        }
        if (resultAvailable) {
            builder.append("result_available: true").append('\n');
            builder.append("result: ").append(modelResult(result)).append('\n');
        }
        if (nextAction != null && !nextAction.isBlank()) {
            builder.append("next_action: ").append(nextAction);
        }
        return builder.toString();
    }

    private String modelResult(Object result) {
        if (result == null) {
            return "";
        }
        if (result instanceof CharSequence
                || result instanceof Number
                || result instanceof Boolean
                || result instanceof Character
                || result instanceof Enum<?>) {
            return String.valueOf(result);
        }
        try {
            return JSON.toJSONString(result);
        } catch (Exception ignored) {
            return String.valueOf(result);
        }
    }

    private String toolName(AsyncToolSpec spec, String phase) {
        return spec.getName() + "_" + phase;
    }

    private AsyncToolTaskStatus status(AsyncToolTaskStatus status, AsyncToolTaskStatus defaultStatus) {
        return status == null ? defaultStatus : status;
    }

    private Map<String, Object> emptyObjectSchema() {
        Map<String, Object> schema = new LinkedHashMap<>();
        schema.put("type", "object");
        schema.put("properties", new LinkedHashMap<>());
        return schema;
    }

    private Map<String, Object> observeSchema(AsyncToolSpec spec) {
        Map<String, Object> schema = new LinkedHashMap<>();
        schema.put("type", "object");
        Map<String, Object> properties = new LinkedHashMap<>();
        properties.put("taskId", property("string", "Task id returned by submit."));
        properties.put("cursor", property("integer", "Event cursor returned by previous observe/result."));
        properties.put("limit", property("integer", "Maximum number of incremental events."));
        schema.put("properties", properties);
        schema.put("required", List.of("taskId"));
        return schema;
    }

    private Map<String, Object> cancelSchema() {
        Map<String, Object> schema = new LinkedHashMap<>();
        schema.put("type", "object");
        Map<String, Object> properties = new LinkedHashMap<>();
        properties.put("taskId", property("string", "Task id returned by submit."));
        properties.put("reason", property("string", "Optional cancellation reason."));
        schema.put("properties", properties);
        schema.put("required", List.of("taskId"));
        return schema;
    }

    private Map<String, Object> listSchema() {
        Map<String, Object> schema = new LinkedHashMap<>();
        schema.put("type", "object");
        Map<String, Object> properties = new LinkedHashMap<>();
        properties.put("status", property("string", "Optional task status filter."));
        schema.put("properties", properties);
        return schema;
    }

    private Map<String, Object> submitOutputSchema() {
        return outputSchema("AsyncToolSubmitResult");
    }

    private Map<String, Object> taskViewOutputSchema() {
        return outputSchema("AsyncToolTaskView");
    }

    private Map<String, Object> cancelOutputSchema() {
        return outputSchema("AsyncToolCancelResult");
    }

    private Map<String, Object> listOutputSchema() {
        return outputSchema("AsyncToolTaskListResult");
    }

    private Map<String, Object> outputSchema(String title) {
        Map<String, Object> schema = new LinkedHashMap<>();
        schema.put("type", "object");
        schema.put("title", title);
        return schema;
    }

    private Map<String, Object> property(String type, String description) {
        Map<String, Object> property = new LinkedHashMap<>();
        property.put("type", type);
        property.put("description", description);
        return property;
    }

    private Map<String, Object> safeMap(Map<String, Object> arguments) {
        return arguments == null ? new LinkedHashMap<>() : new LinkedHashMap<>(arguments);
    }

    private String stringValue(Map<String, Object> arguments, String key) {
        Object value = arguments == null ? null : arguments.get(key);
        return value == null ? null : String.valueOf(value);
    }

    private Long longValue(Map<String, Object> arguments, String key) {
        Object value = arguments == null ? null : arguments.get(key);
        if (value instanceof Number number) {
            return number.longValue();
        }
        if (value == null || String.valueOf(value).isBlank()) {
            return null;
        }
        return Long.parseLong(String.valueOf(value));
    }

    private Integer intValue(Map<String, Object> arguments, String key) {
        Object value = arguments == null ? null : arguments.get(key);
        if (value instanceof Number number) {
            return number.intValue();
        }
        if (value == null || String.valueOf(value).isBlank()) {
            return null;
        }
        return Integer.parseInt(String.valueOf(value));
    }

    private void putIfNotNull(Map<String, Object> target, String key, Object value) {
        if (value != null) {
            target.put(key, value);
        }
    }

    private String truncate(String value, int maxLength) {
        if (value == null || value.length() <= maxLength) {
            return value;
        }
        return value.substring(0, Math.max(0, maxLength)) + "...";
    }

    private static class DelegatingExecutorService extends AbstractExecutorService {

        private final Executor executor;
        private volatile boolean shutdown;

        private DelegatingExecutorService(Executor executor) {
            this.executor = executor == null ? ForkJoinPool.commonPool() : executor;
        }

        @Override
        public void shutdown() {
            shutdown = true;
        }

        @Override
        public List<Runnable> shutdownNow() {
            shutdown = true;
            return List.of();
        }

        @Override
        public boolean isShutdown() {
            return shutdown;
        }

        @Override
        public boolean isTerminated() {
            return shutdown;
        }

        @Override
        public boolean awaitTermination(long timeout, TimeUnit unit) {
            return shutdown;
        }

        @Override
        public void execute(Runnable command) {
            executor.execute(command);
        }
    }
}
