package com.gzzm.lobster.skill;

import com.gzzm.lobster.common.SkillScope;
import com.gzzm.platform.commons.Tools;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SkillService —— Skill 目录服务 / Skill catalog service.
 *
 * <p>同时维护 thread 级「已激活 skill 集合」，供 ContextAssembler 按需注入 skill guidance。
 * Also tracks per-thread activated-skill set for ContextAssembler.
 */
@Service
public class SkillService {

    @Inject
    private SkillDefinitionDao skillDao;

    /**
     * threadId -> activated skillIds.
     * <p>static：nest 对 @Inject SkillService 可能返回多实例，用 static 保证所有注入
     * 点共享同一份激活态（参考 ToolRegistry 的修复思路）。
     *
     * <p><b>TODO(lobster)</b>：无 TTL / 无 eviction。thread 被删除或长期闲置后 entry
     * 残留。单 thread 平均开销 ~几十字节，短期无感；lobster 跑数周后需要上 Caffeine
     * （带 expireAfterAccess）或在 ThreadService 删除 thread 时主动清理.
     */
    private static final ConcurrentHashMap<String, Set<String>> activatedSkills = new ConcurrentHashMap<>();

    /** thunwind DAO 跨线程保护 —— 详见 feedback_thunwind_dao_thread_binding */
    private SkillDefinitionDao skillDao() {
        try {
            SkillDefinitionDao d = Tools.getBean(SkillDefinitionDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* fallback */ }
        return skillDao;
    }

    public SkillDefinition get(String skillId) throws Exception {
        return skillDao().getSkill(skillId);
    }

    public List<SkillDefinition> listAll() throws Exception {
        return skillDao().listEnabled();
    }

    public List<SkillDefinition> listByScope(SkillScope scope) throws Exception {
        return skillDao().listByScope(scope);
    }

    /**
     * 标记 thread 已调用过 use_skill。
     *
     * <p>历史上这个方法是"让 guidance 在下一轮 system prompt 里出现"的开关；
     * 对齐 Claude Code 的 progressive disclosure 后 guidance 改由 use_skill 工具
     * 以 tool result 形式返回给模型，不再进 system。这里保留仅作：
     * <ul>
     *   <li>审计：本 thread 触发过哪些 skill（便于统计/调试）</li>
     *   <li>门禁：CodeExecTool 允许挂 asset bundle 前校验"模型读过 guidance"</li>
     * </ul>
     */
    public void activateForThread(String threadId, String skillId) {
        activatedSkills.computeIfAbsent(threadId, new java.util.function.Function<String, Set<String>>() {
            @Override public Set<String> apply(String s) { return ConcurrentHashMap.newKeySet(); }
        }).add(skillId);
    }

    public Set<String> activatedForThread(String threadId) {
        Set<String> s = activatedSkills.get(threadId);
        return s == null ? new HashSet<String>() : s;
    }

    /** 构建极薄 skill index / Build the thin skill index (id + 一句话触发条件). */
    public List<SkillSummary> listThinIndex() throws Exception {
        List<SkillDefinition> defs = skillDao().listEnabled();
        List<SkillSummary> out = new ArrayList<>();
        for (SkillDefinition d : defs) {
            out.add(new SkillSummary(d.getSkillId(), d.getName(),
                    d.getScope() == null ? "system" : d.getScope().name(),
                    d.getTriggerCondition(),
                    d.getRuntimeKind() == null ? "single_shot" : d.getRuntimeKind().name()));
        }
        return out;
    }

    public static final class SkillSummary {
        public final String skillId;
        public final String name;
        public final String scope;
        public final String triggerCondition;
        public final String runtimeKind;

        public SkillSummary(String skillId, String name, String scope, String triggerCondition, String runtimeKind) {
            this.skillId = skillId; this.name = name; this.scope = scope;
            this.triggerCondition = triggerCondition; this.runtimeKind = runtimeKind;
        }
    }
}
