/**
 * Lightweight on-page SEO checker for Finzone v2.
 *
 * Inputs available content of the article + focusKeyword. Returns a score 0..100
 * and a list of rule results.
 */
import { stripMarkdown, countWords } from "./slug-vi";

export type SeoCheck = {
  rule: string;
  pass: "pass" | "warn" | "fail";
  message: string;
};

export type SeoResult = {
  score: number; // 0..100
  checks: SeoCheck[];
};

export type SeoInput = {
  title: string;
  metaTitle?: string | null;
  metaDesc?: string | null;
  content: string;
  focusKeyword?: string | null;
  coverImage?: string | null;
  slug?: string | null;
};

const W = {
  title_len: 10,
  meta_title_len: 8,
  meta_desc_len: 10,
  focus_in_title: 10,
  focus_in_h1_first: 6,
  focus_in_meta_desc: 8,
  focus_density: 8,
  word_count: 12,
  has_image: 6,
  alt_on_images: 6,
  has_links: 6,
  slug_short: 6,
  has_subheads: 4,
};

function lc(s: string): string {
  return s.toLowerCase();
}

function countOccurrences(haystack: string, needle: string): number {
  if (!needle) return 0;
  const re = new RegExp(needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
  return (haystack.match(re) ?? []).length;
}

export function scoreSeo(input: SeoInput): SeoResult {
  const checks: SeoCheck[] = [];
  const title = input.title ?? "";
  const metaTitle = input.metaTitle?.trim() ?? "";
  const metaDesc = input.metaDesc?.trim() ?? "";
  const fk = input.focusKeyword?.trim() ?? "";
  const fkLow = lc(fk);
  const content = input.content ?? "";
  const plain = stripMarkdown(content);
  const words = countWords(plain);

  let earned = 0;
  const cap: Record<string, number> = {};

  function award(rule: string, weight: number, pass: "pass" | "warn" | "fail", message: string) {
    cap[rule] = weight;
    const got = pass === "pass" ? weight : pass === "warn" ? Math.round(weight * 0.5) : 0;
    earned += got;
    checks.push({ rule, pass, message });
  }

  // 1. Title length 30..65
  if (title.length === 0) award("title_len", W.title_len, "fail", "Thiếu tiêu đề.");
  else if (title.length < 30)
    award("title_len", W.title_len, "warn", `Tiêu đề ngắn (${title.length}/30 ký tự).`);
  else if (title.length > 65)
    award("title_len", W.title_len, "warn", `Tiêu đề dài (${title.length}/65 ký tự).`);
  else award("title_len", W.title_len, "pass", `Tiêu đề ${title.length} ký tự — chuẩn.`);

  // 2. Meta title 50..60 (use title fallback)
  const effMetaTitle = metaTitle || title;
  if (!effMetaTitle)
    award("meta_title_len", W.meta_title_len, "fail", "Thiếu meta title.");
  else if (effMetaTitle.length < 50 || effMetaTitle.length > 60)
    award(
      "meta_title_len",
      W.meta_title_len,
      "warn",
      `Meta title ${effMetaTitle.length} ký tự — nên 50–60.`,
    );
  else
    award(
      "meta_title_len",
      W.meta_title_len,
      "pass",
      `Meta title ${effMetaTitle.length} ký tự — chuẩn.`,
    );

  // 3. Meta desc 130..160
  if (!metaDesc)
    award("meta_desc_len", W.meta_desc_len, "fail", "Thiếu meta description.");
  else if (metaDesc.length < 130 || metaDesc.length > 160)
    award(
      "meta_desc_len",
      W.meta_desc_len,
      "warn",
      `Meta desc ${metaDesc.length} ký tự — nên 130–160.`,
    );
  else
    award(
      "meta_desc_len",
      W.meta_desc_len,
      "pass",
      `Meta desc ${metaDesc.length} ký tự — chuẩn.`,
    );

  // 4. Focus keyword in title
  if (!fk) {
    award("focus_in_title", W.focus_in_title, "warn", "Chưa khai báo focus keyword.");
  } else if (lc(title).includes(fkLow))
    award("focus_in_title", W.focus_in_title, "pass", "Focus keyword có trong tiêu đề.");
  else
    award("focus_in_title", W.focus_in_title, "fail", "Focus keyword KHÔNG có trong tiêu đề.");

  // 5. Focus keyword in first 100 words
  if (fk) {
    const first100 = plain.split(/\s+/).slice(0, 100).join(" ").toLowerCase();
    if (first100.includes(fkLow))
      award(
        "focus_in_h1_first",
        W.focus_in_h1_first,
        "pass",
        "Focus xuất hiện trong 100 từ đầu.",
      );
    else
      award(
        "focus_in_h1_first",
        W.focus_in_h1_first,
        "warn",
        "Focus chưa xuất hiện trong 100 từ đầu.",
      );
  } else {
    award("focus_in_h1_first", W.focus_in_h1_first, "warn", "Bỏ qua — chưa có focus.");
  }

  // 6. Focus in meta desc
  if (fk && metaDesc && lc(metaDesc).includes(fkLow))
    award("focus_in_meta_desc", W.focus_in_meta_desc, "pass", "Focus có trong meta desc.");
  else if (fk)
    award(
      "focus_in_meta_desc",
      W.focus_in_meta_desc,
      "warn",
      "Focus chưa có trong meta desc.",
    );
  else award("focus_in_meta_desc", W.focus_in_meta_desc, "warn", "Bỏ qua — chưa có focus.");

  // 7. Focus keyword density 0.5..2.5%
  if (fk && words > 0) {
    const occ = countOccurrences(plain, fk);
    const density = (occ / words) * 100;
    if (density < 0.3)
      award(
        "focus_density",
        W.focus_density,
        "warn",
        `Mật độ focus ${density.toFixed(2)}% (thấp).`,
      );
    else if (density > 3)
      award(
        "focus_density",
        W.focus_density,
        "warn",
        `Mật độ focus ${density.toFixed(2)}% (nhồi nhét).`,
      );
    else
      award(
        "focus_density",
        W.focus_density,
        "pass",
        `Mật độ focus ${density.toFixed(2)}% — chuẩn.`,
      );
  } else {
    award("focus_density", W.focus_density, "warn", "Bỏ qua — chưa có focus.");
  }

  // 8. Word count >= 600
  if (words < 300) award("word_count", W.word_count, "fail", `Bài quá ngắn (${words} từ).`);
  else if (words < 600)
    award(
      "word_count",
      W.word_count,
      "warn",
      `Bài ${words} từ — chấp nhận, lý tưởng > 600.`,
    );
  else award("word_count", W.word_count, "pass", `Bài ${words} từ — đủ chiều sâu.`);

  // 9. Has cover image
  if (input.coverImage) award("has_image", W.has_image, "pass", "Có ảnh đại diện.");
  else award("has_image", W.has_image, "warn", "Thiếu ảnh đại diện.");

  // 10. Alt text on inline images: count `![X](url)` where X non-empty
  const imgs = [...content.matchAll(/!\[([^\]]*)\]\(([^)]+)\)/g)];
  if (imgs.length === 0)
    award("alt_on_images", W.alt_on_images, "warn", "Bài chưa có ảnh nội dung.");
  else {
    const missing = imgs.filter((m) => !m[1].trim()).length;
    if (missing === 0)
      award("alt_on_images", W.alt_on_images, "pass", "Tất cả ảnh đã có alt text.");
    else
      award(
        "alt_on_images",
        W.alt_on_images,
        "warn",
        `${missing}/${imgs.length} ảnh thiếu alt.`,
      );
  }

  // 11. Has external/internal link
  const links = [...content.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)].filter(
    (m) => !m[0].startsWith("!"),
  );
  if (links.length >= 2)
    award("has_links", W.has_links, "pass", `Có ${links.length} liên kết.`);
  else if (links.length === 1)
    award("has_links", W.has_links, "warn", "Mới có 1 liên kết.");
  else award("has_links", W.has_links, "warn", "Chưa có liên kết outbound/internal.");

  // 12. Slug short and contains focus
  if (input.slug) {
    if (input.slug.length > 70)
      award("slug_short", W.slug_short, "warn", "Slug dài, nên < 70 ký tự.");
    else if (fk && !input.slug.toLowerCase().includes(fkLow.replace(/\s+/g, "-")))
      award("slug_short", W.slug_short, "warn", "Slug chưa chứa focus keyword.");
    else award("slug_short", W.slug_short, "pass", "Slug ngắn gọn.");
  } else {
    award("slug_short", W.slug_short, "warn", "Chưa có slug.");
  }

  // 13. Has subheadings (## or ###)
  const subheads = (content.match(/^#{2,3}\s+/gm) ?? []).length;
  if (subheads >= 2)
    award("has_subheads", W.has_subheads, "pass", `Có ${subheads} sub-heading.`);
  else if (subheads === 1)
    award("has_subheads", W.has_subheads, "warn", "Mới có 1 sub-heading.");
  else award("has_subheads", W.has_subheads, "warn", "Chưa chia sub-heading.");

  const total = Object.values(cap).reduce((a, b) => a + b, 0);
  const score = Math.round((earned / total) * 100);
  return { score, checks };
}

/** Flesch-style readability (rough VI tuned: penalize long sentences and words). */
export function readabilityScore(content: string): number {
  const plain = stripMarkdown(content);
  const sentences = plain.split(/[.!?…]+/).filter((s) => s.trim().length > 0);
  if (sentences.length === 0) return 0;
  const words = plain.split(/\s+/).filter(Boolean);
  const wordsPerSentence = words.length / sentences.length;
  const longWords = words.filter((w) => w.length > 7).length;
  const longRatio = longWords / Math.max(words.length, 1);
  // Lower wps + lower long-word ratio → higher score
  let s = 100 - wordsPerSentence * 1.8 - longRatio * 100;
  s = Math.max(0, Math.min(100, Math.round(s)));
  return s;
}

/** JSON-LD Article schema. */
export function buildJsonLd(args: {
  title: string;
  description: string;
  url: string;
  image?: string | null;
  authorName: string;
  publishedAt: Date | null;
  updatedAt: Date;
  siteName: string;
}): Record<string, unknown> {
  return {
    "@context": "https://schema.org",
    "@type": "NewsArticle",
    headline: args.title,
    description: args.description,
    mainEntityOfPage: { "@type": "WebPage", "@id": args.url },
    image: args.image ? [args.image] : undefined,
    author: { "@type": "Person", name: args.authorName },
    publisher: {
      "@type": "Organization",
      name: args.siteName,
    },
    datePublished: args.publishedAt?.toISOString(),
    dateModified: args.updatedAt.toISOString(),
  };
}
