import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { load as yamlLoad } from 'js-yaml';
import {
  type Skill,
  SkillFrontmatterSchema,
} from './skill.types';

/**
 * SkillRegistryService — 启动时扫描 SKILL.md,parse frontmatter + body,
 * 校验后索引到 Map<name, Skill>。
 *
 * 设计:
 *   - 路径 = __dirname/skills(swc 编译后 dist/.../draft-plan-script/skills,
 *     nest-cli.json `assets` 配置已设把 modules/ai/calls/**​/skills/**​/*.md 拷贝到 dist)
 *   - 1 个 callKey 1 个 registry — 本 service 专门为 draft_plan_script 服务
 *   - 启动 fail-fast:任何 SKILL.md frontmatter 校验失败抛错(像 yaml assemblers 一样)
 *   - 内存缓存,运行时不重新读盘(dev 改 SKILL.md 需要 nest reload)
 *
 * 跟 PAC 现有"yaml 治理"基建一致 — assembler/canonical-codes 都是启动加载 + 强校验。
 */
@Injectable()
export class DraftPlanScriptSkillRegistry implements OnModuleInit {
  private readonly logger = new Logger(DraftPlanScriptSkillRegistry.name);
  private readonly skills: Map<string, Skill> = new Map();

  onModuleInit(): void {
    const rootDir = this.resolveSkillsRoot();
    const files = this.scanRecursive(rootDir);
    if (files.length === 0) {
      this.logger.warn(`未找到任何 SKILL.md — 路径 ${rootDir}`);
      return;
    }
    let loaded = 0;
    for (const file of files) {
      const skill = this.parseSkillFile(file);
      if (this.skills.has(skill.frontmatter.name)) {
        throw new Error(
          `重复的 skill name "${skill.frontmatter.name}" — ${skill.sourcePath} 与 ` +
            `${this.skills.get(skill.frontmatter.name)!.sourcePath}`,
        );
      }
      this.skills.set(skill.frontmatter.name, skill);
      loaded++;
    }
    this.logger.log(
      `draft-plan-script skills 加载 ${loaded} 个: ${[...this.skills.keys()].sort().join(', ')}`,
    );
  }

  /** 返回所有 skills(顺序无关,composer 自己排序) */
  getAllSkills(): readonly Skill[] {
    return [...this.skills.values()];
  }

  /** 单个 skill 取(测试用) */
  getSkill(name: string): Skill | undefined {
    return this.skills.get(name);
  }

  /**
   * skills/ 目录解析。
   *
   * 路径策略:env PAC_SCRIPT_SKILLS_DIR 优先(eval / 测试切目录用),
   * 否则 __dirname/skills:
   *   - prod 容器:dist/modules/ai/calls/draft-plan-script/skills(nest-cli assets 拷贝)
   *   - dev (swc): 同 prod,assets 已 watchAssets
   *   - ts-jest:  src/modules/.../skills(ts-jest 直接跑 src)
   */
  private resolveSkillsRoot(): string {
    const override = process.env.PAC_SCRIPT_SKILLS_DIR;
    if (override) return override;
    return join(__dirname, 'skills');
  }

  /** 递归 scan,返回所有 SKILL.md 绝对路径 */
  private scanRecursive(dir: string): string[] {
    let entries;
    try {
      entries = readdirSync(dir);
    } catch (err) {
      this.logger.warn(`skills 根目录读取失败 ${dir}: ${(err as Error).message}`);
      return [];
    }
    const out: string[] = [];
    for (const name of entries) {
      const p = join(dir, name);
      const st = statSync(p);
      if (st.isDirectory()) {
        out.push(...this.scanRecursive(p));
      } else if (st.isFile() && name === 'SKILL.md') {
        out.push(p);
      }
    }
    return out;
  }

  /**
   * 解析单个 SKILL.md。
   * 格式:
   *   ---\n
   *   <yaml frontmatter>\n
   *   ---\n
   *   <markdown body>
   *
   * frontmatter 走 zod 校验,失败抛 fail-fast 异常(启动期就崩,不让坏 skill 进 runtime)。
   */
  private parseSkillFile(path: string): Skill {
    const raw = readFileSync(path, 'utf-8');
    const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
    if (!fmMatch) {
      throw new Error(`SKILL.md 缺少 frontmatter 分隔: ${path}`);
    }
    const [, yamlText, body] = fmMatch;
    let rawFm: unknown;
    try {
      rawFm = yamlLoad(yamlText!);
    } catch (err) {
      throw new Error(`SKILL.md frontmatter YAML 解析失败 ${path}: ${(err as Error).message}`);
    }
    const parsed = SkillFrontmatterSchema.safeParse(rawFm);
    if (!parsed.success) {
      throw new Error(
        `SKILL.md frontmatter 校验失败 ${path}:\n${parsed.error.message}`,
      );
    }
    return {
      frontmatter: parsed.data,
      body: (body ?? '').trim(),
      sourcePath: path,
    };
  }
}
