import { readFile } from "node:fs/promises";
import type { ProjectPackageEntry, WorkspaceSkillSummary } from "@qjclaw/shared-types";
import type { ProjectStoreService } from "./project-store.js";
import {
  detectProjectPromptSignals,
  normalizeProjectPrompt,
  type ProjectPromptSignals
} from "./project-prompt-signals.js";

const MAX_SKILL_BYTES = 4096;
const MIN_ROUTE_SCORE = 5;
const MIN_ROUTE_MARGIN = 2;

const PLATFORM_ALIAS_MAP: Record<string, string[]> = {
  xiaohongshu: ["小红书"],
  rednote: ["小红书"],
  xhs: ["小红书"],
  douyin: ["抖音"],
  tiktok: ["抖音"]
};

export interface ProjectSkillRoute {
  skillId: string;
  reason: string;
  score: number;
}

interface SkillCandidate {
  skill: WorkspaceSkillSummary;
  aliases: string[];
  role: "pipeline" | "publisher" | "writer" | "strategy" | "reviewer" | "operations" | "generic";
  entry?: ProjectPackageEntry;
  isDefaultEntry: boolean;
}

function normalizeAlias(value: string): string {
  return normalizeProjectPrompt(value).replace(/\s+/g, "");
}

function uniqueStrings(values: Iterable<string>): string[] {
  return [...new Set([...values].map((value) => value.trim()).filter(Boolean))];
}

function scoreRole(signals: ProjectPromptSignals, role: SkillCandidate["role"]): number {
  switch (role) {
    case "pipeline":
      return signals.publishCount * 4 + signals.writingCount * 2 + signals.draftCount + signals.operationsCount * 2;
    case "publisher":
      return signals.publishCount * 5 + signals.draftCount * 4 - signals.writingCount;
    case "writer":
      return signals.writingCount * 6 + signals.draftCount * 2 - signals.publishCount * 3;
    case "strategy":
      return signals.strategyCount * 5 + signals.writingCount;
    case "reviewer":
      return signals.reviewCount * 5;
    case "operations":
      return signals.operationsCount * 4 + signals.publishCount + signals.writingCount;
    default:
      return signals.writingCount + signals.publishCount + signals.draftCount + signals.strategyCount + signals.reviewCount + signals.operationsCount;
  }
}

function scoreCapabilities(signals: ProjectPromptSignals, capabilities: string[]): number {
  let score = 0;
  for (const capability of capabilities) {
    const normalized = normalizeAlias(capability);
    if (!normalized) {
      continue;
    }
    if (normalized.includes("publish") || normalized.includes("发") || normalized.includes("post")) {
      score += signals.publishCount * 2;
    }
    if (normalized.includes("write") || normalized.includes("文案") || normalized.includes("draft")) {
      score += signals.writingCount * 2 + signals.draftCount;
    }
    if (normalized.includes("review") || normalized.includes("审核")) {
      score += signals.reviewCount * 2;
    }
    if (normalized.includes("plan") || normalized.includes("strategy") || normalized.includes("选题")) {
      score += signals.strategyCount * 2;
    }
    if (normalized.includes("workflow") || normalized.includes("orchestrator") || normalized.includes("pipeline") || normalized.includes("执行")) {
      score += signals.operationsCount * 2 + signals.publishCount;
    }
  }
  return score;
}

function detectRole(skillId: string, aliases: string[], entry?: ProjectPackageEntry): SkillCandidate["role"] {
  const normalized = [skillId, ...aliases, ...(entry?.capabilities ?? [])]
    .map((value) => normalizeAlias(value))
    .join(" ");
  if (normalized.includes("pipeline") || normalized.includes("orchestrator") || normalized.includes("workflow") || normalized.includes("全链路")) {
    return "pipeline";
  }
  if (normalized.includes("writer") || normalized.includes("write") || normalized.includes("文案") || normalized.includes("帖子") || normalized.includes("笔记")) {
    return "writer";
  }
  if (normalized.includes("publisher") || normalized.includes("publish") || normalized.includes("发帖") || normalized.includes("发布")) {
    return "publisher";
  }
  if (normalized.includes("strategy") || normalized.includes("plan") || normalized.includes("选题")) {
    return "strategy";
  }
  if (normalized.includes("review") || normalized.includes("审核") || normalized.includes("校对")) {
    return "reviewer";
  }
  if (normalized.includes("operations") || normalized.includes("assistant") || normalized.includes("运营")) {
    return "operations";
  }
  return "generic";
}

function extractAliases(...values: Array<string | null | undefined>): string[] {
  const aliases = new Set<string>();
  for (const value of values) {
    const raw = value?.trim();
    if (!raw) {
      continue;
    }

    const compact = normalizeAlias(raw);
    if (compact.length >= 2) {
      aliases.add(compact);
    }

    const lowered = raw.toLowerCase();
    for (const [token, expansions] of Object.entries(PLATFORM_ALIAS_MAP)) {
      if (!lowered.includes(token)) {
        continue;
      }
      for (const expansion of expansions) {
        aliases.add(normalizeAlias(expansion));
      }
    }
  }

  return [...aliases];
}

async function readSkillPreview(projectStore: ProjectStoreService, projectId: string, skillId: string): Promise<string> {
  try {
    const target = await projectStore.getProjectSkillTarget(projectId, skillId);
    if (!target) {
      return "";
    }
    const raw = await readFile(target.localPath, "utf8");
    return raw.slice(0, MAX_SKILL_BYTES);
  } catch {
    return "";
  }
}

function extractFrontmatterField(content: string, field: string): string | undefined {
  const trimmed = content.replace(/^\uFEFF/, "");
  if (!trimmed.startsWith("---")) {
    return undefined;
  }
  const endIndex = trimmed.indexOf("\n---", 3);
  if (endIndex < 0) {
    return undefined;
  }
  const header = trimmed.slice(3, endIndex);
  const match = header.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
  return match?.[1]?.trim().replace(/^[\"']|[\"']$/g, "") || undefined;
}

function chooseByRole(candidates: SkillCandidate[], role: SkillCandidate["role"], reason: string): ProjectSkillRoute | null {
  const matched = candidates.find((candidate) => candidate.role === role);
  return matched
    ? {
        skillId: matched.skill.id,
        reason,
        score: MIN_ROUTE_SCORE + 1
      }
    : null;
}

export class ProjectSkillRouterService {
  private readonly projectStore: ProjectStoreService;

  constructor(projectStore: ProjectStoreService) {
    this.projectStore = projectStore;
  }

  async resolve(projectId: string, prompt: string): Promise<ProjectSkillRoute | null> {
    const normalizedPrompt = normalizeAlias(prompt);
    if (!normalizedPrompt) {
      return null;
    }

    const skills = (await this.projectStore.listProjectSkills(projectId)).filter((skill) => skill.ready);
    const projectConfig = await this.projectStore.getProjectPackageConfig(projectId);
    if (skills.length === 0) {
      const defaultEntry = projectConfig?.defaultEntry;
      if (defaultEntry?.type === "skill") {
        const target = await this.projectStore.getProjectSkillTarget(projectId, defaultEntry.id);
        if (target) {
          return {
            skillId: defaultEntry.id,
            reason: `project default entry ${defaultEntry.id}`,
            score: MIN_ROUTE_SCORE
          };
        }
      }
      return null;
    }

    const defaultSkillEntry = projectConfig?.defaultEntry?.type === "skill"
      ? projectConfig.defaultEntry
      : null;
    if (skills.length === 1 && !projectConfig?.entries.length) {
      return {
        skillId: skills[0].id,
        reason: `single ready skill ${skills[0].name}`,
        score: MIN_ROUTE_SCORE
      };
    }

    const entryMap = new Map<string, ProjectPackageEntry>();
    for (const entry of projectConfig?.entries ?? []) {
      if (entry.type !== "skill") {
        continue;
      }
      entryMap.set(entry.id, entry);
    }

    const candidates = await Promise.all(skills.map(async (skill) => {
      const entry = entryMap.get(skill.id);
      const preview = await readSkillPreview(this.projectStore, projectId, skill.id);
      const frontmatterName = extractFrontmatterField(preview, "name");
      const frontmatterDescription = extractFrontmatterField(preview, "description");
      const aliases = uniqueStrings([
        ...extractAliases(skill.id, skill.name, frontmatterName, frontmatterDescription),
        ...extractAliases(...(entry?.intentAliases ?? [])),
        ...extractAliases(...(entry?.capabilities ?? []))
      ]);
      return {
        skill,
        aliases,
        entry,
        role: detectRole(skill.id, aliases, entry),
        isDefaultEntry: defaultSkillEntry?.id === skill.id
      } satisfies SkillCandidate;
    }));

    const signals = detectProjectPromptSignals(normalizedPrompt);
    if (signals.writingCount > 0 && signals.publishCount === 0) {
      const route = chooseByRole(candidates, "writer", "matched writer intent");
      if (route) {
        return route;
      }
    }
    if (signals.publishCount > 0 && signals.draftCount > 0 && signals.writingCount <= 1) {
      const route = chooseByRole(candidates, "publisher", "matched publisher intent");
      if (route) {
        return route;
      }
    }
    if (signals.publishCount > 0 && (signals.writingCount > 0 || signals.operationsCount > 0)) {
      const route = chooseByRole(candidates, "pipeline", defaultSkillEntry ? `project default entry ${defaultSkillEntry.id}` : "matched pipeline intent");
      if (route) {
        return route;
      }
    }

    const ranked = candidates
      .map((candidate) => {
        const matchedAliases = new Set<string>();
        let score = scoreRole(signals, candidate.role);
        score += scoreCapabilities(signals, candidate.entry?.capabilities ?? []);
        for (const alias of candidate.aliases) {
          if (!alias || !normalizedPrompt.includes(alias)) {
            continue;
          }
          matchedAliases.add(alias);
          score += 3;
        }
        if (candidate.isDefaultEntry) {
          score += 1;
        }
        return {
          candidate,
          score,
          matchedAliases: uniqueStrings(matchedAliases)
        };
      })
      .sort((left, right) => right.score - left.score || left.candidate.skill.name.localeCompare(right.candidate.skill.name, "zh-CN"));

    const best = ranked[0];
    const second = ranked[1];
    if (best && best.score >= MIN_ROUTE_SCORE && (!second || best.score >= second.score + MIN_ROUTE_MARGIN)) {
      return {
        skillId: best.candidate.skill.id,
        reason: best.matchedAliases.length > 0
          ? `matched ${best.matchedAliases.join(", ")}`
          : best.candidate.isDefaultEntry
            ? `project default entry ${best.candidate.skill.id}`
            : `matched ${best.candidate.role}`,
        score: best.score
      };
    }

    if (defaultSkillEntry) {
      const defaultSkill = candidates.find((candidate) => candidate.skill.id === defaultSkillEntry.id);
      if (defaultSkill) {
        return {
          skillId: defaultSkill.skill.id,
          reason: `project default entry ${defaultSkill.skill.id}`,
          score: best?.score ?? MIN_ROUTE_SCORE
        };
      }
    }

    return null;
  }
}


