package com.gzzm.lobster.context;

/**
 * ToolResultWidthPolicy —— 单条 tool result 宽度限制 /
 * Per-tool-result width policy.
 *
 * <p>策略（对齐 Claude Code "soft truncation"）：
 * <ul>
 *   <li>不截断：若 content ≤ widthChars，直接返回</li>
 *   <li>双端截断：保留 head + tail，中间嵌入剩余字符数 + 可选 ContentStore ref，
 *       让模型把标记中的精确 ref 传给 read_externalized_content(ref, offset, limit) 按需读取</li>
 * </ul>
 *
 * <p>双端的目的：错误堆栈、退出码、JSON 末尾的 totalCount 这类关键信息常落在尾部；
 * 仅留头部会丢这部分。
 */
public final class ToolResultWidthPolicy {

    /** 头部占总宽度的比例 / Head fraction of widthChars. */
    private static final double HEAD_RATIO = 0.7;
    /** 截断标记预算字符数（"...<truncated N chars; ref=...>...\n"）. */
    private static final int MARKER_BUDGET = 120;
    /** 尾部至少保留这么多字符才有意义；否则退化为单端. */
    private static final int MIN_TAIL = 200;

    private final int widthChars;

    public ToolResultWidthPolicy(int widthChars) {
        this.widthChars = widthChars;
    }

    public int getWidthChars() { return widthChars; }

    /** 无 ref 的简化入口；行为上等价于 {@code limit(content, null)}. */
    public String limit(String content) {
        return limit(content, null);
    }

    /**
     * 截断 + 双端 + 可选 ref 提示.
     *
     * @param content 待截断字符串（发送视图中应已由 ContextAssembler 恢复为全文）.
     * @param ref ContentStore 引用；真正截断时附加精确 ref 读取指引，让 LLM 自己决定是否读取全文.
     * @return 截断后的发送视图字符串.
     */
    public String limit(String content, String ref) {
        if (content == null) return "";
        if (content.length() <= widthChars) {
            return content;
        }
        // 超 widthChars：双端截断
        int total = content.length();
        int headSize = (int) (widthChars * HEAD_RATIO);
        int tailSize = widthChars - headSize - MARKER_BUDGET;
        if (tailSize < MIN_TAIL) {
            // widthChars 太小，双端无意义，退化为单端
            String marker = "\n...<truncated " + (total - widthChars) + " chars"
                    + (hasRef(ref) ? "; ref=" + ref + "; pass this exact ref to read_externalized_content>" : ">");
            return content.substring(0, widthChars) + marker;
        }
        String head = content.substring(0, headSize);
        String tail = content.substring(total - tailSize);
        StringBuilder marker = new StringBuilder("\n...<truncated ");
        marker.append(total - headSize - tailSize).append(" middle chars");
        if (hasRef(ref)) {
            marker.append("; ref=").append(ref);
            marker.append("; pass this exact ref to read_externalized_content(ref, offset, limit)");
        }
        marker.append(">...\n");
        return head + marker.toString() + tail;
    }

    private static boolean hasRef(String ref) { return ref != null && !ref.isEmpty(); }

}
