package com.gzzm.lobster.runtime;

import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.thread.ThreadRoom;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * RunRequest —— 一次执行的输入 / Input to a single run.
 *
 * <p>输入不一定来自键盘，也可以来自 pending_resolve / schedule / external_event。
 *
 * <p>知识库（KB）相关字段：
 * <ul>
 *   <li>{@link #kbEnabled} —— 本轮是否允许 LLM 使用知识库；false 时 OA KB 工具会被
 *       工具过滤层从工具列表里剥掉，模型完全看不到</li>
 *   <li>{@link #kbMode} —— {@code "auto"}（默认）= LLM 自己决定调不调；
 *       {@code "forced"} = 在送上下文前先做一次 KB search，把命中以 prelude 形式
 *       注入到 user 输入前</li>
 *   <li>{@link #kbScopeIds} —— 检索范围；空 = 全库</li>
 * </ul>
 */
public final class RunRequest {

    private final ThreadRoom thread;
    private final UserContext user;
    private final String runId;
    private final String userInput;
    /** user_input / confirm / schedule / external_event / pending_resolve */
    private final String triggerSource;
    /** trigger 的关联 id，例如 pendingRequestId / External 事件 id */
    private final String triggerRef;
    private final String clientRequestId;
    /** 附件（mediaId 列表，用于多模态）*/
    private final List<String> attachmentMediaIds;
    /**
     * 本轮用户显式关联的 workspace resourceId 列表（主要是对话框刚上传的 USER_UPLOAD）。
     * AgentRuntime 会把这些资源的 summary/outline 作为 prelude 拼到 userInput 前面，
     * 让 LLM 本轮就"看见"附件，而不是等 workspace 索引段下一轮生效.
     */
    private final List<String> attachedResourceIds;

    /** 知识库总开关 / Whether KB lookup is enabled this turn. */
    private final boolean kbEnabled;
    /** 知识库模式：{@code "auto"} | {@code "forced"}. null/空 默认 "auto". */
    private final String kbMode;
    /** 知识库范围 scope id 列表；空 = 全库. */
    private final List<String> kbScopeIds;
    private final boolean appendUserInput;
    private final boolean resumeContinuation;
    private final String continuationSourceRunId;

    public RunRequest(ThreadRoom thread, UserContext user, String userInput,
                      String triggerSource, String triggerRef,
                      List<String> attachmentMediaIds) {
        this(thread, user, userInput, triggerSource, triggerRef, attachmentMediaIds, null);
    }

    public RunRequest(ThreadRoom thread, UserContext user, String userInput,
                      String triggerSource, String triggerRef,
                      List<String> attachmentMediaIds,
                      List<String> attachedResourceIds) {
        this(thread, user, userInput, triggerSource, triggerRef,
                attachmentMediaIds, attachedResourceIds, false, null, null,
                null, null, true, false, null);
    }

    public RunRequest(ThreadRoom thread, UserContext user, String userInput,
                      String triggerSource, String triggerRef,
                      List<String> attachmentMediaIds,
                      List<String> attachedResourceIds,
                      boolean kbEnabled, String kbMode, List<String> kbScopeIds) {
        this(thread, user, userInput, triggerSource, triggerRef,
                attachmentMediaIds, attachedResourceIds, kbEnabled, kbMode, kbScopeIds,
                null, null, true, false, null);
    }

    private RunRequest(ThreadRoom thread, UserContext user, String userInput,
                       String triggerSource, String triggerRef,
                       List<String> attachmentMediaIds,
                       List<String> attachedResourceIds,
                       boolean kbEnabled, String kbMode, List<String> kbScopeIds,
                       String runId, String clientRequestId,
                       boolean appendUserInput, boolean resumeContinuation,
                       String continuationSourceRunId) {
        this.thread = thread;
        this.user = user;
        this.runId = runId;
        this.userInput = userInput;
        this.triggerSource = triggerSource == null ? "user_input" : triggerSource;
        this.triggerRef = triggerRef;
        this.clientRequestId = clientRequestId;
        this.attachmentMediaIds = attachmentMediaIds == null ? Collections.<String>emptyList() : new ArrayList<>(attachmentMediaIds);
        this.attachedResourceIds = attachedResourceIds == null ? Collections.<String>emptyList() : new ArrayList<>(attachedResourceIds);
        this.kbEnabled = kbEnabled;
        this.kbMode = (kbMode == null || kbMode.isEmpty()) ? "auto" : kbMode;
        this.kbScopeIds = kbScopeIds == null ? Collections.<String>emptyList() : new ArrayList<>(kbScopeIds);
        this.appendUserInput = appendUserInput;
        this.resumeContinuation = resumeContinuation;
        this.continuationSourceRunId = continuationSourceRunId;
    }

    public RunRequest withRunId(String runId) {
        return new RunRequest(thread, user, userInput, triggerSource, triggerRef,
                attachmentMediaIds, attachedResourceIds, kbEnabled, kbMode, kbScopeIds,
                runId, clientRequestId, appendUserInput, resumeContinuation, continuationSourceRunId);
    }

    public RunRequest withClientRequestId(String clientRequestId) {
        return new RunRequest(thread, user, userInput, triggerSource, triggerRef,
                attachmentMediaIds, attachedResourceIds, kbEnabled, kbMode, kbScopeIds,
                runId, clientRequestId, appendUserInput, resumeContinuation, continuationSourceRunId);
    }

    public RunRequest asResumeContinuation() {
        return asResumeContinuation(null);
    }

    public RunRequest asResumeContinuation(String sourceRunId) {
        return asResumeContinuation(sourceRunId, false);
    }

    public RunRequest asResumeContinuation(String sourceRunId, boolean appendUserInput) {
        return new RunRequest(thread, user, userInput, triggerSource, triggerRef,
                attachmentMediaIds, attachedResourceIds, kbEnabled, kbMode, kbScopeIds,
                runId, clientRequestId, appendUserInput, true, sourceRunId);
    }

    public ThreadRoom getThread() { return thread; }
    public UserContext getUser() { return user; }
    public String getRunId() { return runId; }
    public String getUserInput() { return userInput; }
    public String getTriggerSource() { return triggerSource; }
    public String getTriggerRef() { return triggerRef; }
    public String getClientRequestId() { return clientRequestId; }
    public List<String> getAttachmentMediaIds() { return attachmentMediaIds; }
    public List<String> getAttachedResourceIds() { return attachedResourceIds; }
    public boolean isKbEnabled() { return kbEnabled; }
    public String getKbMode() { return kbMode; }
    public List<String> getKbScopeIds() { return kbScopeIds; }
    public boolean shouldAppendUserInput() { return appendUserInput; }
    public boolean isResumeContinuation() { return resumeContinuation; }
    public String getContinuationSourceRunId() { return continuationSourceRunId; }
}
