import { readFile, readdir } from "node:fs/promises";
import path from "node:path";
import type {
  SkillCatalogAvailability,
  SkillCatalogItem,
  SkillCatalogSource,
  SystemSummary,
  WorkspaceSkillSummary
} from "@qjclaw/shared-types";
import {
  CURATED_GENERIC_SKILLS,
  CURATED_GENERIC_SKILL_IDS,
  getCuratedGenericSkillDefinition
} from "./curated-skills.js";
import type { ProjectStoreService } from "./project-store.js";

interface SkillCatalogServiceOptions {
  systemSummary: SystemSummary;
  projectStore: ProjectStoreService;
  qSkillsRoot?: string;
}

interface ParsedSkillDocument {
  name?: string;
  description?: string;
  body: string;
}

const TOKEN_ZH_MAP: Record<string, string> = {
  xhs: "小红书",
  pipeline: "全流程",
  workflow: "工作流",
  writer: "写作",
  search: "搜索",
  doc: "文档",
  docs: "文档",
  file: "文件",
  market: "市场",
  researcher: "调研",
  note: "笔记",
  organizer: "整理",
  email: "邮件",
  pdf: "PDF",
  pptx: "演示",
  xlsx: "表格",
  docx: "文档"
};

const SKILL_ZH_OVERRIDES: Record<string, { name: string; description: string; aliases?: string[] }> = {
  xhs: {
    name: "小红书内容助手",
    description: "适合生成小红书文案、整理发布内容和处理相关对话。",
    aliases: ["小红书", "笔记", "种草", "发帖", "发布"]
  },
  "xhs-pipeline": {
    name: "小红书全流程助手",
    description: "适合把选题、文案、配图和发布流程串起来执行。",
    aliases: ["小红书", "全流程", "自动发布", "发帖", "工作流"]
  },
  xlsx: {
    name: "表格助手",
    description: "适合处理 Excel、表格计算、数据汇总和清单整理。"
  },
  docx: {
    name: "文档助手",
    description: "适合起草 Word 文档、报告、通知和正式材料。"
  },
  pdf: {
    name: "PDF 助手",
    description: "适合查看、提取、合并和整理 PDF 文件内容。"
  },
  pptx: {
    name: "演示助手",
    description: "适合整理 PPT、大纲、汇报页和演示材料。"
  },
  "file-skill": {
    name: "文件整理助手",
    description: "适合做文件分类、批量整理、归档和目录收纳。"
  },
  "note-organizer": {
    name: "笔记整理助手",
    description: "适合梳理会议纪要、学习笔记、标签分类和内容归并。"
  },
  "email-skill": {
    name: "邮件助手",
    description: "适合写邮件、查邮件、整理收件箱和邮件沟通。"
  },
  "market-researcher": {
    name: "调研助手",
    description: "适合收集市场信息、竞品动态和趋势摘要。"
  }
};

function normalizeSearch(value: string): string {
  return value
    .normalize("NFKC")
    .toLowerCase()
    .replace(/[^\p{L}\p{N}\s-]+/gu, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function cleanCopy(value: string | undefined): string {
  return (value ?? "")
    .replace(/\r/g, "")
    .replace(/[`*_>#-]+/g, " ")
    .replace(/\[(.*?)\]\((.*?)\)/g, "$1")
    .replace(/\s+/g, " ")
    .trim();
}

function readScalar(lines: string[], startIndex: number): { value?: string; nextIndex: number } {
  const currentLine = lines[startIndex] ?? "";
  const [, rawValue = ""] = currentLine.split(/:\s*/, 2);
  const directValue = rawValue.trim();
  if (!directValue || directValue === "|" || directValue === ">") {
    const block: string[] = [];
    let cursor = startIndex + 1;
    while (cursor < lines.length) {
      const nextLine = lines[cursor];
      if (!nextLine.startsWith("  ") && nextLine.trim()) {
        break;
      }
      block.push(nextLine.replace(/^  /, ""));
      cursor += 1;
    }
    return {
      value: cleanCopy(block.join(" ").trim()),
      nextIndex: cursor - 1
    };
  }

  return {
    value: cleanCopy(directValue.replace(/^['"]|['"]$/g, "")),
    nextIndex: startIndex
  };
}

function parseSkillDocument(content: string): ParsedSkillDocument {
  const normalized = content.replace(/^\uFEFF/, "");
  const match = normalized.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n)?/);
  if (!match) {
    return { body: normalized };
  }

  const lines = match[1].split(/\r?\n/);
  let name: string | undefined;
  let description: string | undefined;
  for (let index = 0; index < lines.length; index += 1) {
    const trimmed = lines[index].trimStart();
    if (trimmed.startsWith("name:")) {
      const parsed = readScalar(lines, index);
      name = parsed.value;
      index = parsed.nextIndex;
      continue;
    }
    if (trimmed.startsWith("description:")) {
      const parsed = readScalar(lines, index);
      description = parsed.value;
      index = parsed.nextIndex;
    }
  }

  return {
    name,
    description,
    body: normalized.slice(match[0].length)
  };
}

function extractBodySummary(body: string): string {
  const lines = body
    .split(/\r?\n/)
    .map((line) => cleanCopy(line))
    .filter(Boolean);
  return lines.find((line) => !line.startsWith("##") && !line.startsWith("#")) ?? lines[0] ?? "";
}

function translateSkillId(skillId: string): string {
  const override = SKILL_ZH_OVERRIDES[skillId];
  if (override) {
    return override.name;
  }

  return skillId
    .split("-")
    .filter(Boolean)
    .map((part) => TOKEN_ZH_MAP[part] ?? part.toUpperCase())
    .join("");
}

function summarizeSkill(skillId: string, description: string): string {
  const override = SKILL_ZH_OVERRIDES[skillId];
  if (override) {
    return override.description;
  }

  const cleaned = cleanCopy(description);
  if (!cleaned) {
    return `适合处理${translateSkillId(skillId)}相关任务。`;
  }

  const firstSentence = cleaned.split(/[。！？!?]/)[0]?.trim() ?? cleaned;
  return firstSentence.length > 42 ? `${firstSentence.slice(0, 42)}…` : firstSentence;
}

function buildSelectionHint(selectable: boolean, isProjectSkill: boolean): string | undefined {
  if (selectable) {
    return isProjectSkill ? "当前项目已接入，首页输入框里点 @ 就能直接调用。" : "当前已经接入，可以直接在首页对话里调用。";
  }
  return "当前先作为能力说明展示，暂时不能直接调用。";
}

function compareCatalogItems(left: SkillCatalogItem, right: SkillCatalogItem): number {
  const priority = (item: SkillCatalogItem) => {
    if (item.selectable && item.isProjectSkill) {
      return 0;
    }
    if (item.selectable) {
      return 1;
    }
    return 2;
  };

  return priority(left) - priority(right) || left.zhName.localeCompare(right.zhName, "zh-CN");
}

export class SkillCatalogService {
  private readonly projectStore: ProjectStoreService;
  private readonly qSkillsRoot: string;

  constructor(options: SkillCatalogServiceOptions) {
    this.projectStore = options.projectStore;
    this.qSkillsRoot = options.qSkillsRoot
      ?? process.env.QJCLAW_SKILL_CATALOG_ROOT?.trim()
      ?? path.resolve(options.systemSummary.appPath, "..", "..", "q-skills", "skills");
  }

  async listForActiveProject(): Promise<SkillCatalogItem[]> {
    const [repoItems, projectItems] = await Promise.all([
      this.listCuratedRepoSkills(),
      this.listActiveProjectSkills()
    ]);
    const merged = new Map<string, SkillCatalogItem>();

    for (const item of repoItems) {
      merged.set(item.id, item);
    }

    for (const item of projectItems) {
      const current = merged.get(item.id);
      if (!current) {
        merged.set(item.id, item);
        continue;
      }

      const source: SkillCatalogSource = current.source === "project" ? "project" : "hybrid";
      merged.set(item.id, {
        ...current,
        name: item.name || current.name,
        zhName: item.zhName || current.zhName,
        description: item.description || current.description,
        zhDescription: item.zhDescription || current.zhDescription,
        source,
        selectable: true,
        isProjectSkill: item.isProjectSkill,
        showInSkillsPage: current.showInSkillsPage || item.showInSkillsPage,
        selectionHint: buildSelectionHint(true, item.isProjectSkill),
        searchText: normalizeSearch([current.searchText, item.searchText].join(" "))
      });
    }

    return [...merged.values()].sort(compareCatalogItems);
  }

  private async listCuratedRepoSkills(): Promise<SkillCatalogItem[]> {
    const idsInRoot = new Set(
      (await readdir(this.qSkillsRoot, { withFileTypes: true }).catch(() => []))
        .filter((entry) => entry.isDirectory())
        .map((entry) => entry.name)
    );

    const items: SkillCatalogItem[] = [];
    for (const skill of CURATED_GENERIC_SKILLS) {
      if (!idsInRoot.has(skill.id)) {
        continue;
      }

      const parsed = await this.readSkillCatalogDocument(skill.id);
      const description = skill.description || parsed.description || extractBodySummary(parsed.body) || skill.zhDescription;
      items.push({
        id: skill.id,
        name: parsed.name || skill.id,
        zhName: skill.zhName,
        description,
        zhDescription: skill.zhDescription,
        category: "通用办公技能",
        source: "q-skills",
        availability: "usable",
        selectable: true,
        isProjectSkill: false,
        showInSkillsPage: true,
        searchText: normalizeSearch([
          skill.id,
          parsed.name || "",
          skill.zhName,
          skill.zhDescription,
          description,
          ...skill.searchAliases
        ].join(" ")),
        selectionHint: buildSelectionHint(true, false)
      });
    }

    return items;
  }

  private async listActiveProjectSkills(): Promise<SkillCatalogItem[]> {
    const projects = await this.projectStore.listProjects().catch(() => []);
    if (!projects.length) {
      return [];
    }

    const activeProject = await this.projectStore.getActiveProject().catch(() => null);
    if (!activeProject) {
      return [];
    }

    const projectSkills = (await this.projectStore.listProjectSkills(activeProject.id)).filter((skill) => skill.ready);
    const items = await Promise.all(projectSkills.map(async (skill) => this.toProjectCatalogItem(skill)));
    return items.filter((item): item is SkillCatalogItem => Boolean(item));
  }

  private async toProjectCatalogItem(skill: WorkspaceSkillSummary): Promise<SkillCatalogItem | null> {
    const target = await this.projectStore.getCurrentProjectSkillTarget(skill.id).catch(() => undefined);
    const raw = target?.localPath ? await readFile(target.localPath, "utf8").catch(() => "") : "";
    const parsed = parseSkillDocument(raw);
    const curated = getCuratedGenericSkillDefinition(skill.id);
    const description = curated?.description || parsed.description || extractBodySummary(parsed.body) || skill.description;
    const override = SKILL_ZH_OVERRIDES[skill.id];
    const zhName = override?.name || curated?.zhName || translateSkillId(skill.id);
    const zhDescription = override?.description || curated?.zhDescription || summarizeSkill(skill.id, description);
    const source: SkillCatalogSource = CURATED_GENERIC_SKILL_IDS.has(skill.id)
      ? "hybrid"
      : skill.category === "project"
        ? "project"
        : "hybrid";

    return {
      id: skill.id,
      name: parsed.name || skill.name,
      zhName,
      description,
      zhDescription,
      category: skill.category === "project" ? "项目技能" : "通用办公技能",
      source,
      availability: "usable" as SkillCatalogAvailability,
      selectable: true,
      isProjectSkill: skill.category === "project",
      showInSkillsPage: CURATED_GENERIC_SKILL_IDS.has(skill.id),
      searchText: normalizeSearch([
        skill.id,
        skill.name,
        parsed.name || "",
        zhName,
        zhDescription,
        description,
        ...(override?.aliases ?? []),
        ...(curated?.searchAliases ?? [])
      ].join(" ")),
      selectionHint: buildSelectionHint(true, skill.category === "project")
    };
  }

  private async readSkillCatalogDocument(skillId: string): Promise<ParsedSkillDocument> {
    const skillRoot = path.join(this.qSkillsRoot, skillId);
    const skillDocPath = path.join(skillRoot, "SKILL.md");
    const raw = await readFile(skillDocPath, "utf8").catch(() => "");
    return parseSkillDocument(raw);
  }
}
