const SCORE_WEIGHTS = {
  title: 20,
  description: 15,
  url: 15,
  firstParagraph: 15,
  headings: 15,
  density: 15,
  imageAlt: 5,
};

const decodeEntities = (value = '') => {
  if (typeof document === 'undefined') {
    return value;
  }

  const textarea = document.createElement('textarea');
  textarea.innerHTML = value;
  return textarea.value;
};

const stripHtml = (value = '') =>
  decodeEntities(
    String(value)
      .replace(/<script[\s\S]*?<\/script>/gi, ' ')
      .replace(/<style[\s\S]*?<\/style>/gi, ' ')
      .replace(/<[^>]+>/g, ' ')
  );

const normalizeText = (value = '') =>
  stripHtml(value)
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();

const escapeRegex = (value = '') => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

const tokenize = (value = '') =>
  normalizeText(value)
    .split(' ')
    .filter(Boolean);

const containsKeyword = (value, normalizedKeyword, keywordTerms) => {
  const normalizedValue = normalizeText(value);
  if (!normalizedValue || !normalizedKeyword) {
    return false;
  }

  return (
    normalizedValue.includes(normalizedKeyword) ||
    keywordTerms.every((term) => normalizedValue.includes(term))
  );
};

const extractFirstParagraph = (content = '') => {
  const paragraphMatch = String(content).match(/<p\b[^>]*>([\s\S]*?)<\/p>/i);
  if (paragraphMatch?.[1]) {
    return stripHtml(paragraphMatch[1]);
  }

  const plain = stripHtml(content);
  const [firstParagraph = ''] = plain.split(/\n\s*\n/);
  return firstParagraph || plain.slice(0, 320);
};

const extractHeadingTexts = (content = '') => {
  const matches = String(content).match(/<h[1-6]\b[^>]*>[\s\S]*?<\/h[1-6]>/gi) || [];
  return matches.map((heading) => stripHtml(heading)).filter(Boolean);
};

const extractImageAltTexts = (content = '') => {
  const alts = [];
  const pattern = /<img\b[^>]*\balt=(["'])(.*?)\1/gi;
  let match = pattern.exec(String(content));

  while (match) {
    alts.push(decodeEntities(match[2] || ''));
    match = pattern.exec(String(content));
  }

  return alts.filter(Boolean);
};

const getUrlMatchText = (value = '') => {
  if (!value) {
    return '';
  }

  try {
    const parsed = new URL(value, typeof window !== 'undefined' ? window.location.origin : 'https://example.com');
    return `${parsed.pathname} ${parsed.search}`.replace(/[-_/=?&]+/g, ' ');
  } catch (error) {
    return String(value).replace(/[-_/=?&]+/g, ' ');
  }
};

const countPhraseOccurrences = (content = '', normalizedKeyword = '') => {
  if (!normalizedKeyword) {
    return 0;
  }

  const normalizedContent = normalizeText(content);
  if (!normalizedContent) {
    return 0;
  }

  const regex = new RegExp(`\\b${escapeRegex(normalizedKeyword).replace(/\s+/g, '\\s+')}\\b`, 'g');
  return normalizedContent.match(regex)?.length || 0;
};

/**
 * Analyze all configured focus keywords and produce an aggregate score.
 *
 * Scoring weights:
 *   - Primary keyword: 70% of the keyword score component
 *   - Secondary keywords: share the remaining 30% equally
 *   - Single keyword: 100% (backward compatible)
 *
 * @param {Array<{keyword: string, primary: boolean}>} keywords
 * @param {object} context  { title, seoTitle, description, url, content }
 * @returns {{ aggregate: number, primary: object|null, secondary: object[], perKeyword: object[] }}
 */
export const analyzeAllKeywords = (keywords, context) => {
  if (!Array.isArray(keywords) || keywords.length === 0) {
    return { aggregate: 0, primary: null, secondary: [], perKeyword: [] };
  }

  const perKeyword = keywords.map((kw) =>
    analyzeFocusKeyword({ keyword: kw.keyword, ...context })
  );

  const primaryIndex = keywords.findIndex((kw) => kw.primary);
  const primaryResult = primaryIndex >= 0 ? perKeyword[primaryIndex] : perKeyword[0];
  const secondaryResults = perKeyword.filter((_, i) => i !== (primaryIndex >= 0 ? primaryIndex : 0));

  let aggregate;
  if (secondaryResults.length === 0) {
    aggregate = primaryResult.score;
  } else {
    const secondaryAvg =
      secondaryResults.reduce((sum, r) => sum + r.score, 0) / secondaryResults.length;
    aggregate = Math.round(primaryResult.score * 0.7 + secondaryAvg * 0.3);
  }

  return { aggregate, primary: primaryResult, secondary: secondaryResults, perKeyword };
};

export const analyzeFocusKeyword = ({
  keyword = '',
  title = '',
  seoTitle = '',
  description = '',
  url = '',
  content = '',
}) => {
  const normalizedKeyword = normalizeText(keyword);
  const keywordTerms = tokenize(keyword);
  const bodyText = stripHtml(content);
  const totalWords = tokenize(bodyText).length;
  const effectiveTitle = seoTitle || title;
  const firstParagraph = extractFirstParagraph(content);
  const headings = extractHeadingTexts(content);
  const imageAlts = extractImageAltTexts(content);
  const occurrenceCount = countPhraseOccurrences(bodyText, normalizedKeyword);
  const density = totalWords > 0 && keywordTerms.length > 0
    ? Number((((occurrenceCount * keywordTerms.length) / totalWords) * 100).toFixed(2))
    : 0;

  const hasKeyword = Boolean(normalizedKeyword);
  const densityPassed = occurrenceCount > 0 && density >= 0.5 && density <= 2.5;

  const checks = {
    title: {
      passed: hasKeyword && containsKeyword(effectiveTitle, normalizedKeyword, keywordTerms),
      applicable: Boolean(hasKeyword && effectiveTitle),
      detail: effectiveTitle,
    },
    description: {
      passed: hasKeyword && containsKeyword(description, normalizedKeyword, keywordTerms),
      applicable: Boolean(hasKeyword && description),
      detail: description,
    },
    url: {
      passed: hasKeyword && containsKeyword(getUrlMatchText(url), normalizedKeyword, keywordTerms),
      applicable: Boolean(hasKeyword && url),
      detail: url,
    },
    firstParagraph: {
      passed: hasKeyword && containsKeyword(firstParagraph, normalizedKeyword, keywordTerms),
      applicable: Boolean(hasKeyword && firstParagraph),
      detail: firstParagraph,
    },
    headings: {
      passed: hasKeyword && headings.some((heading) => containsKeyword(heading, normalizedKeyword, keywordTerms)),
      applicable: Boolean(hasKeyword && headings.length > 0),
      detail: headings,
    },
    density: {
      passed: hasKeyword && densityPassed,
      applicable: Boolean(hasKeyword && totalWords > 0),
      detail: {
        density,
        occurrenceCount,
        totalWords,
      },
    },
    imageAlt: {
      passed: hasKeyword && imageAlts.some((alt) => containsKeyword(alt, normalizedKeyword, keywordTerms)),
      applicable: Boolean(hasKeyword && imageAlts.length > 0),
      detail: imageAlts,
    },
  };

  const totalPossible = hasKeyword
    ? Object.entries(checks).reduce((sum, [key, check]) => (
        check.applicable ? sum + SCORE_WEIGHTS[key] : sum
      ), 0)
    : 0;

  const totalEarned = hasKeyword
    ? Object.entries(checks).reduce((sum, [key, check]) => (
        check.applicable && check.passed ? sum + SCORE_WEIGHTS[key] : sum
      ), 0)
    : 0;

  return {
    keyword,
    normalizedKeyword,
    totalWords,
    occurrenceCount,
    density,
    score: totalPossible > 0 ? Math.round((totalEarned / totalPossible) * 100) : 0,
    checks,
  };
};
