import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { existsSync, 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';

/**
 * 解析 skills/ 根目录(模块级,registry + composer 共用)。
 *
 * 路径策略(env 优先 → src → dist):
 *   - env PAC_SCRIPT_ROOT_DIR:eval / 测试切目录(见下方 resolveScriptRoot)
 *   - src/modules/.../skills:dev 模式(SWC 编译产物在 dist/src/...,__dirname 不可靠;
 *                            源文件始终在 src/,直接读最快)
 *   - dist/modules/.../skills:prod docker(image 只含 dist/,nest-cli.json `assets`
 *                              已 copy SKILL.md 到 dist/modules/.../skills/)
 *
 * 这跟 sync.service.ts 用 cwd 而非 __dirname 是同一个原因 — SWC dev 跟 tsc prod 的
 * 编译输出目录结构不同,__dirname 不能跨态稳定;cwd 在 dev/prod 都是 apps/pac-service 根。
 */
export function resolveScriptRoot(): string {
  const override = process.env.PAC_SCRIPT_ROOT_DIR;
  if (override) return override;
  const src = join(process.cwd(), 'src/modules/ai/calls/draft-plan-script');
  if (existsSync(src)) return src;
  return join(process.cwd(), 'dist/modules/ai/calls/draft-plan-script');
}

/** base 共性提示词路径(三档共用) */
export function resolveBaseCommonPath(): string {
  return join(resolveScriptRoot(), 'shared/skills/_base/common.md');
}

/** 某档的输出格式提示词路径(每档不同) */
export function resolveBaseFormatPath(tier: 'stable' | 'standard' | 'deep'): string {
  return join(resolveScriptRoot(), `tiers/${tier}/skills/_base/format.md`);
}

/**
 * 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);
  }

  private resolveSkillsRoot(): string {
    return resolveScriptRoot();
  }

  /** 递归 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,
    };
  }
}
