import { readFile } from "node:fs/promises";
import path from "node:path";
import type { ProjectSummary } from "@qjclaw/shared-types";
import type { ProjectStoreService } from "./project-store.js";

const MAX_README_BYTES = 4096;
const MIN_CLEAR_ROUTE_SCORE = 4;
const MIN_ROUTE_MARGIN = 2;
const GENERIC_CJK_SUFFIXES = [
  "全链路",
  "自动化",
  "工作流",
  "项目",
  "技能",
  "助手",
  "运营",
  "交付",
  "发布",
  "剪辑"
] as const;
const GENERIC_ASCII_TOKENS = new Set([
  "project",
  "workflow",
  "automation",
  "assistant",
  "skill",
  "skills",
  "delivery",
  "content",
  "campaign",
  "general"
]);

const PLATFORM_ALIAS_MAP: Record<string, string[]> = {
  xiaohongshu: ["\u5c0f\u7ea2\u4e66"],
  rednote: ["\u5c0f\u7ea2\u4e66"],
  xhs: ["\u5c0f\u7ea2\u4e66"],
  douyin: ["\u6296\u97f3"],
  tiktok: ["\u6296\u97f3"]
};

export interface ProjectIntentRoute {
  projectId: string;
  score: number;
  confidence: "low" | "medium" | "high";
  reason: string;
  matchedAliases: string[];
}

interface ProjectCandidate {
  project: ProjectSummary;
  nameAliases: string[];
  descriptionAliases: string[];
  skillAliases: string[];
  readmeAliases: string[];
}

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

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

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

function extractAsciiTokens(value: string): string[] {
  return value
    .split(/[^a-z0-9]+/i)
    .map((token) => token.trim().toLowerCase())
    .filter((token) => token.length >= 3 && !GENERIC_ASCII_TOKENS.has(token));
}

function extractCjkPhrases(value: string): string[] {
  const matches = value.match(/[\p{Script=Han}]{2,16}/gu) ?? [];
  const phrases = new Set<string>();
  for (const match of matches) {
    phrases.add(match);
    for (const suffix of GENERIC_CJK_SUFFIXES) {
      if (match.endsWith(suffix) && match.length > suffix.length + 1) {
        phrases.add(match.slice(0, match.length - suffix.length));
      }
    }
  }
  return [...phrases];
}

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

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

    for (const line of raw.split(/\r?\n/)) {
      const normalizedLine = normalizeAlias(line);
      if (normalizedLine.length >= 2) {
        aliases.add(normalizedLine);
      }
    }

    for (const token of extractAsciiTokens(raw)) {
      aliases.add(normalizeAlias(token));
      const expansions = PLATFORM_ALIAS_MAP[token.toLowerCase()];
      if (expansions) {
        for (const expansion of expansions) {
          aliases.add(normalizeAlias(expansion));
        }
      }
    }
    for (const phrase of extractCjkPhrases(raw)) {
      aliases.add(normalizeAlias(phrase));
    }
  }

  return [...aliases].filter((alias) => alias.length >= 2);
}

async function readProjectReadme(projectRoot: string): Promise<string> {
  try {
    const raw = await readFile(path.join(projectRoot, "README.md"), "utf8");
    return raw.slice(0, MAX_README_BYTES);
  } catch {
    return "";
  }
}

function scoreAliases(
  prompt: string,
  aliases: string[],
  weight: number,
  matchedAliases: Set<string>
): number {
  let score = 0;
  for (const alias of aliases) {
    if (!alias || !prompt.includes(alias)) {
      continue;
    }

    matchedAliases.add(alias);
    score += weight;
  }
  return score;
}

function toConfidence(score: number): "low" | "medium" | "high" {
  if (score >= 14) {
    return "high";
  }
  if (score >= 9) {
    return "medium";
  }
  return "low";
}

export class ProjectIntentRouterService {
  private readonly projectStore: ProjectStoreService;

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

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

    const projects = await this.projectStore.listProjects();
    if (projects.length <= 1) {
      return null;
    }

    const candidates = await Promise.all(projects.map((project) => this.buildCandidate(project)));
    const ranked = candidates
      .map((candidate) => {
        const matchedAliases = new Set<string>();
        let score = 0;
        score += scoreAliases(normalizedPrompt, candidate.nameAliases, 6, matchedAliases);
        score += scoreAliases(normalizedPrompt, buildAliasSet(candidate.project.id), 5, matchedAliases);
        score += scoreAliases(normalizedPrompt, candidate.skillAliases, 4, matchedAliases);
        score += scoreAliases(normalizedPrompt, candidate.descriptionAliases, 3, matchedAliases);
        score += scoreAliases(normalizedPrompt, candidate.readmeAliases, 2, matchedAliases);
        if (candidate.project.id === currentProjectId) {
          score += 1;
        }
        return {
          candidate,
          score,
          matchedAliases: uniqueStrings(matchedAliases)
        };
      })
      .sort((left, right) => right.score - left.score || left.candidate.project.name.localeCompare(right.candidate.project.name, "zh-CN"));

    const best = ranked[0];
    const second = ranked[1];
    if (!best || best.score < MIN_CLEAR_ROUTE_SCORE) {
      return null;
    }
    if (second && best.score < second.score + MIN_ROUTE_MARGIN) {
      return null;
    }

    return {
      projectId: best.candidate.project.id,
      score: best.score,
      confidence: toConfidence(best.score),
      reason: best.matchedAliases.length > 0
        ? `matched ${best.matchedAliases.join(", ")}`
        : `matched ${best.candidate.project.name}`,
      matchedAliases: best.matchedAliases
    };
  }

  private async buildCandidate(project: ProjectSummary): Promise<ProjectCandidate> {
    const [skills, projectRoot] = await Promise.all([
      this.projectStore.listProjectSkills(project.id),
      this.projectStore.getProjectRoot(project.id)
    ]);
    const readme = await readProjectReadme(projectRoot);

    return {
      project,
      nameAliases: buildAliasSet(project.name),
      descriptionAliases: buildAliasSet(project.description),
      skillAliases: buildAliasSet(...skills.map((skill) => skill.name)),
      readmeAliases: buildAliasSet(...readme
        .split(/\r?\n/)
        .map((line) => line.replace(/^#+\s*/, "").trim())
        .filter((line) => line.length >= 2)
        .slice(0, 12))
    };
  }
}
