<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Analyzer: haalt HTML op (Playwright microservice → fallback cURL),
 * analyseert SEO/AEO factoren en geeft score + details terug.
 * Dit is dezelfde engine als in je eerdere page-template, nu als class.
 */
class MC_SEO_Analyzer {

  const MAX_TOP_KEYWORDS   = 8;
  const MIN_WORD_LEN       = 4;
  const MIN_WORD_FREQ      = 2;
  const MAX_META_KW_ROWS   = 8;
  const MAX_HEADINGS_SHOWN = 8;

  /* ------------ Fetching (browser + cURL) ------------ */

  protected static function fetch_via_browser( $url, &$httpCode=0, &$err='', &$took_ms=0 ) {
    $endpoint = rtrim(MC_RENDER_URL, '/') . '?lite=1&wait=networkidle&timeout=15000&url=' . rawurlencode($url);
    $ch = curl_init($endpoint);
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_CONNECTTIMEOUT => 8,
      CURLOPT_TIMEOUT        => 22,
    ]);
    $raw     = curl_exec($ch);
    $curlErr = curl_error($ch);
    $code    = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($raw === false || $code !== 200) { $httpCode = $code ?: 0; $err = $curlErr ?: 'renderer_http_error'; return false; }

    $json = json_decode($raw, true);
    if (!is_array($json) || empty($json['ok']) || !isset($json['html'])) {
      $httpCode = isset($json['status']) ? (int)$json['status'] : 0;
      $err      = isset($json['error']) ? (string)$json['error'] : 'renderer_bad_payload';
      return false;
    }

    $httpCode = isset($json['status']) ? (int)$json['status'] : 200;
    $took_ms  = isset($json['took_ms']) ? (int)$json['took_ms'] : 0;
    $err      = '';
    return (string)$json['html'];
  }

  protected static function fetch_html( $url, &$httpCode=0, &$curlErr='', &$took_ms=0 ) {
    // 1) Browser-render
    $html = self::fetch_via_browser($url, $httpCode, $curlErr, $took_ms);
    if ($html !== false && $httpCode >= 200 && $httpCode < 400) return $html;

    // 2) Fallback cURL
    $cookieFile = sys_get_temp_dir() . '/mc_seo_cookies.txt';
    $ch = curl_init($url);
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_FOLLOWLOCATION => true,
      CURLOPT_MAXREDIRS      => 5,
      CURLOPT_CONNECTTIMEOUT => 10,
      CURLOPT_TIMEOUT        => 20,
      CURLOPT_SSL_VERIFYPEER => true,
      CURLOPT_SSL_VERIFYHOST => 2,
      CURLOPT_ENCODING       => '',
      CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
      CURLOPT_HTTPHEADER     => [
        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language: nl,en;q=0.9',
        'Cache-Control: no-cache',
        'Pragma: no-cache'
      ],
      CURLOPT_REFERER        => 'https://www.google.com/',
      CURLOPT_COOKIEFILE     => $cookieFile,
      CURLOPT_COOKIEJAR      => $cookieFile,
    ]);
    $start    = microtime(true);
    $html2    = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlErr  = curl_error($ch);
    $took_ms  = (int) round((microtime(true)-$start)*1000);
    curl_close($ch);

    return ($html2 !== false && $httpCode >= 200 && $httpCode < 400) ? $html2 : false;
  }

  /* ---------------- URL helpers ---------------- */
  protected static function resolve_url($base, $href) {
    if (!$href) return null; $href = trim($href);
    if (preg_match('#^(mailto:|tel:|javascript:|data:)#i', $href)) return null;
    if ($href[0] === '#') return null;
    $baseParts = parse_url($base);
    $scheme = $baseParts['scheme'] ?? 'https';
    $host   = $baseParts['host'] ?? '';
    if (preg_match('#^https?://#i', $href)) return $href;
    if (strpos($href, '//') === 0)       return $scheme . ':' . $href;
    if ($href[0] === '/')                return $scheme . '://' . $host . $href;
    $basePath = isset($baseParts['path']) ? rtrim($baseParts['path'], '/') : '';
    return $scheme . '://' . $host . $basePath . '/' . $href;
  }
  protected static function is_same_host($url, $host) {
    $h = parse_url($url, PHP_URL_HOST);
    return $h && (strcasecmp($h, $host) === 0 || preg_match('#\.'.preg_quote($host,'#').'$#i', $h));
  }
  protected static function is_html_like($url) {
    $path = strtolower(parse_url($url, PHP_URL_PATH) ?? '');
    $ext  = pathinfo($path, PATHINFO_EXTENSION);
    if ($ext === '') return true;
    $htmlish = ['html','htm','php','asp','aspx'];
    $non    = ['jpg','jpeg','png','gif','webp','svg','pdf','zip','mp4','mp3','css','js','json','ico','woff','woff2','ttf','webm'];
    if (in_array($ext, $htmlish, true)) return true;
    if (in_array($ext, $non, true))    return false;
    return true;
  }
  protected static function strip_query_fragment($url) {
    $p = parse_url($url);
    $u = ($p['scheme'] ?? 'https') . '://' . ($p['host'] ?? '');
    if (!empty($p['port'])) $u .= ':' . $p['port'];
    $u .= $p['path'] ?? '/';
    return $u;
  }
  protected static function path_depth($url) {
    $p = parse_url($url, PHP_URL_PATH) ?? '/';
    $p = trim($p, '/');
    return $p === '' ? 0 : substr_count($p, '/') + 1;
  }

  protected static function mc_trim($s){ return trim(preg_replace('/\s+/u',' ', (string)$s)); }
  protected static function word_count($text){ $text = trim(preg_replace('/[\n\r\t]+/',' ', strip_tags($text))); return $text ? count(preg_split('/\s+/u',$text)) : 0; }
  protected static function sentence_count($text){
    $text = strip_tags($text);
    $text = preg_replace('/[\n\r]+/',' ', $text);
    $parts = preg_split('/(?<=[\.!?])\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY);
    return max(1, count($parts));
  }
  protected static function avg_words_per_sentence($text){
    $w = self::word_count($text);
    $s = self::sentence_count($text);
    return $s > 0 ? $w / $s : $w;
  }

  /* ---------------- Kernanalyse ---------------- */
  protected static function analyze_html($url, $html, $load_time_ms=null) {
    libxml_use_internal_errors(true);
    $dom = new DOMDocument(); @$dom->loadHTML($html); $xpath = new DOMXPath($dom);

    $extractMeta = function($xpath, $name) {
      $nodes = $xpath->query("//meta[@name='$name']");
      return $nodes->length ? $nodes->item(0)->getAttribute('content') : null;
    };

    $titleNode    = $dom->getElementsByTagName('title');
    $title        = $titleNode->length ? self::mc_trim($titleNode->item(0)->textContent) : null;
    $description  = self::mc_trim($extractMeta($xpath, 'description')) ?: null;
    $keywords_raw = self::mc_trim($extractMeta($xpath, 'keywords')) ?: null;

    // Canonical
    $canonical = null;
    $ln = $xpath->query("//link[@rel='canonical']");
    if ($ln->length) $canonical = self::mc_trim($ln->item(0)->getAttribute('href'));

    // Robots noindex
    $robotsMeta  = $xpath->query("//meta[@name='robots']");
    $has_noindex = false;
    if ($robotsMeta->length) {
      $rv = strtolower($robotsMeta->item(0)->getAttribute('content'));
      $has_noindex = (strpos($rv,'noindex') !== false);
    }

    // Headings
    $headings = []; $h1_count = 0; $h2h3_count = 0;
    foreach (['h1','h2','h3'] as $tag) {
      foreach ($dom->getElementsByTagName($tag) as $node) {
        $txt = self::mc_trim($node->textContent); if ($txt === '') continue;
        $headings[] = $txt; if ($tag==='h1') $h1_count++; else $h2h3_count++;
      }
    }
    $headings_text = strtolower(implode(' ', $headings));

    // Paragraphs
    $paragraphs = []; $first_paragraph = '';
    foreach ($dom->getElementsByTagName('p') as $node) {
      $t = self::mc_trim($node->textContent);
      if ($t !== '') { $paragraphs[] = $t; if ($first_paragraph === '') $first_paragraph = $t; }
    }

    // Word count
    $fullText = strtolower(strip_tags($dom->saveHTML()));
    $wordCount = count(preg_split('/\s+/u', trim($fullText)));

    // Keyword frequency (content only)
    $contentText = strtolower(implode(' ', array_merge($headings, $paragraphs)));
    $contentText = preg_replace('/[\d\W]+/u', ' ', $contentText);
    $words = array_filter(explode(' ', $contentText));

    $stop = ['en','de','het','een','voor','van','op','in','is','te','met','wij','we','je','jij','ik','maar','dat','die','dit','dan','als','ook','niet','geen','zijn','was','the','and','for','with','this','that','your','you','are','from','have','more','our','about','into','over','under','while','been','will','can'];
    $host = parse_url($url, PHP_URL_HOST) ?: '';
    $brand = preg_replace('/^(www\.)/i','',$host);
    $brand = preg_replace('/\.(com|net|org|be|nl|de|fr|eu|co|io|ai|uk)$/i','',$brand);
    $brandTokens = array_filter(preg_split('/[\.-]/', $brand));
    $stop = array_merge($stop, $brandTokens);

    $filtered = array_filter($words, function($w) use ($stop){
      if (strlen($w) < self::MIN_WORD_LEN) return false;
      if (is_numeric($w)) return false;
      return !in_array($w, $stop, true);
    });
    $freq = array_count_values($filtered);
    $freq = array_filter($freq, function($c){ return $c >= self::MIN_WORD_FREQ; });
    arsort($freq);
    $top_keywords = array_slice($freq, 0, self::MAX_TOP_KEYWORDS, true);

    // Images alt
    $imgs_missing_alt = []; $img_total = 0;
    foreach ($xpath->query('//img') as $img) {
      $img_total++;
      if (!$img->hasAttribute('alt') || trim($img->getAttribute('alt')) === '') $imgs_missing_alt[] = $img->getAttribute('src');
    }
    $alt_ratio_ok = ($img_total === 0) ? true : (count($imgs_missing_alt) / max(1,$img_total) <= 0.2);

    // Links
    $internal_links = 0; $external_links = 0;
    foreach ($xpath->query('//a[@href]') as $a) {
      $u = self::resolve_url($url, $a->getAttribute('href')); if (!$u) continue;
      if (self::is_same_host($u, $host)) $internal_links++; else $external_links++;
    }

    // Tech checks
    $viewport         = $xpath->query("//meta[@name='viewport']")->length > 0;
    $favicon          = $xpath->query("//link[contains(@rel, 'icon')]")->length > 0;
    $structured_data  = (strpos($html, 'application/ld+json') !== false);

    $load_time = is_numeric($load_time_ms) ? (int)$load_time_ms : null;

    // Meta keywords usage
    $keywords_list = [];
    if ($keywords_raw) {
      $parsed = array_values(array_filter(array_map('trim', preg_split('/[,;]+/',$keywords_raw))));
      $keywords_list = array_slice($parsed, 0, self::MAX_META_KW_ROWS);
    }
    $first_para_lc = strtolower($first_paragraph);
    $desc_lc       = strtolower($description ?? '');
    $kw_usage = []; $kw_used_any = false;
    foreach ($keywords_list as $kw) {
      $k = strtolower($kw);
      $in_h  = (strpos($headings_text, $k) !== false);
      $in_p  = (strpos($first_para_lc, $k) !== false);
      $in_md = (strpos($desc_lc, $k) !== false);
      $kw_usage[$kw] = ['in_headings'=>$in_h,'in_first_paragraph'=>$in_p,'in_meta_description'=>$in_md];
      if ($in_h || $in_p || $in_md) $kw_used_any = true;
    }

    // Readability / AEO
    $avg_wps = self::avg_words_per_sentence(implode(' ', $paragraphs));
    $readability_ok = ($avg_wps > 0 && $avg_wps <= 22);
    $h1_ok   = ($h1_count === 1);
    $h2h3_ok = ($h2h3_count >= 2);
    $fp_words = self::word_count($first_paragraph);
    $fp_len_ok = ($fp_words >= 40 && $fp_words <= 160);
    $has_faq_schema = (bool) preg_match('/"@type"\s*:\s*"faqpage"/i', $html);
    $has_question_heading = (bool)preg_match('/\b(wat|hoe|waarom|wanneer|wie|what|how|why|when|who)\b/i', $headings_text);
    $aeo_ready = $structured_data && ($has_faq_schema || $has_question_heading) && $fp_len_ok;

    // Score
    $weights = [
      'title'=>4,'title_len'=>3,'description'=>3,'description_len'=>2,'h1'=>3,'h2h3'=>2,
      'word_count'=>3,'readability'=>3,'viewport'=>2,'favicon'=>1,'structured_data'=>2,
      'load_time'=>4,'alt_ratio'=>2,'internal_links'=>2,'canonical'=>1,'noindex'=>4
    ];
    $checks = [
      'title'=>!!$title, 'title_len'=>strlen((string)$title)>=30 && strlen((string)$title)<=60,
      'description'=>!!$description,'description_len'=>strlen((string)$description)>=90 && strlen((string)$description)<=160,
      'h1'=>$h1_ok,'h2h3'=>$h2h3_ok,'word_count'=>$wordCount>=600,'readability'=>$readability_ok,
      'viewport'=>$viewport,'favicon'=>$favicon,'structured_data'=>$structured_data,
      'load_time'=>is_int($load_time)?($load_time<2000):false,'alt_ratio'=>$alt_ratio_ok,
      'internal_links'=>$internal_links>=5,'canonical'=>!!$canonical,'noindex'=>!$has_noindex
    ];
    $max = array_sum($weights); $earned = 0;
    foreach ($checks as $k=>$v) if ($v) $earned += $weights[$k];
    if (count($keywords_list) > 0 && $kw_used_any) { $earned += 1; $max += 1; }
    if (is_int($load_time) && $load_time >= 3000) $earned -= 1;
    if ($wordCount < 300)   $earned -= 1;
    if ($earned < 0) $earned = 0;

    $ratio = $max ? ($earned/$max) : 0;
    $ratio = sqrt(max(0, min(1, $ratio)));
    $score = (int) floor($ratio * 9.2);

    return [
      'url'=>$url,'title'=>$title,'description'=>$description,
      'keywords_meta'=>$keywords_raw,'keywords_meta_list'=>$keywords_list,'keyword_usage'=>$kw_usage,
      'first_paragraph'=>$first_paragraph,'headings'=>$headings,'word_count'=>$wordCount,'top_keywords'=>$top_keywords,
      'images_missing_alt'=>$imgs_missing_alt,'img_total'=>$img_total,
      'has_viewport'=>$viewport,'has_favicon'=>$favicon,'has_structured_data'=>$structured_data,
      'canonical'=>$canonical,'has_noindex'=>$has_noindex,
      'internal_links'=>$internal_links,'external_links'=>$external_links,
      'load_time_ms'=>is_int($load_time)?$load_time:0,'readability_avg_wps'=>round($avg_wps,1),
      'fp_words'=>$fp_words,'aeo_ready'=>$aeo_ready,'has_question_heading'=>$has_question_heading,'has_faq_schema'=>$has_faq_schema,
      'score'=>$score,
      'score_breakdown'=>[
        'title'=>!!$title, 'title_len'=>strlen((string)$title)>=30 && strlen((string)$title)<=60,
        'description'=>!!$description,'description_len'=>strlen((string)$description)>=90 && strlen((string)$description)<=160,
        'headings'=>(count($headings)>0),'h1'=>$h1_ok,'h2h3'=>$h2h3_ok,'word_count'=>$wordCount>=600,
        'readability'=>$readability_ok,'keywords_meta'=>count($keywords_list)>0,'keywords_usage'=>(count($keywords_list)===0)?false:$kw_used_any,
        'viewport'=>$viewport,'favicon'=>$favicon,'structured_data'=>$structured_data,'canonical'=>!!$canonical,
        'noindex'=>!$has_noindex,'load_time'=>is_int($load_time)?($load_time<2000):false,'alt_text'=>$alt_ratio_ok,
        'links'=>($internal_links+$external_links)>0,'robots'=>null,'sitemap'=>null
      ]
    ];
  }

  /* -------------- Publieke API -------------- */
  public static function analyze_domain( $domain_url ) {
    $domain = trim($domain_url);
    if (stripos($domain, 'http') !== 0) $domain = 'https://' . $domain;
    if (! filter_var($domain, FILTER_VALIDATE_URL)) return ['error'=>'Invalid URL'];

    // Home
    $code=0; $err=''; $took=0;
    $homeHtml = self::fetch_html($domain, $code, $err, $took);

    // WWW fallback
    if ((!$homeHtml || $code < 200 || $code >= 400)) {
      $parts = parse_url($domain); $host  = $parts['host'] ?? '';
      if ($host && stripos($host, 'www.') !== 0) {
        $wwwHost = 'www.' . $host;
        $alt = ($parts['scheme'] ?? 'https') . '://' . $wwwHost . (($parts['path'] ?? '/') ?: '/');
        $altCode=0; $altErr=''; $altTook=0;
        $altHtml = self::fetch_html($alt, $altCode, $altErr, $altTook);
        if ($altHtml && $altCode >= 200 && $altCode < 400) { $domain=$alt; $homeHtml=$altHtml; $code=$altCode; $err=$altErr; $took=$altTook; }
      }
    }

    if (!$homeHtml || $code !== 200) return ['error'=>'Error retrieving data.','status'=>$code,'curl'=>$err];

    $homepage = self::analyze_html($domain, $homeHtml, $took);

    // robots/sitemap best-effort
    $robotsCode=0; $smCode=0; $tmpErr=''; $tmpT=0;
    self::fetch_html(rtrim($domain,'/').'/robots.txt', $robotsCode, $tmpErr, $tmpT);
    self::fetch_html(rtrim($domain,'/').'/sitemap.xml',  $smCode,     $tmpErr, $tmpT);
    $homepage['robots_txt']  = ($robotsCode >= 200 && $robotsCode < 400);
    $homepage['sitemap_xml'] = ($smCode    >= 200 && $smCode    < 400);
    if (is_array($homepage['score_breakdown'])) {
      $homepage['score_breakdown']['robots']  = $homepage['robots_txt'];
      $homepage['score_breakdown']['sitemap'] = $homepage['sitemap_xml'];
    }

    // Kandidaten uit homepage
    libxml_use_internal_errors(true);
    $dom = new DOMDocument(); @$dom->loadHTML($homeHtml); $xp = new DOMXPath($dom);
    $host = parse_url($domain, PHP_URL_HOST); $candidates = [];
    foreach ($xp->query('//a[@href]') as $a) {
      $u = self::resolve_url($domain, $a->getAttribute('href')); if (!$u) continue; $u = self::strip_query_fragment($u);
      if (!self::is_same_host($u,$host) || !self::is_html_like($u)) continue;
      if (preg_match('#/(wp-admin|cart|checkout|account|login)#i',$u)) continue;
      $candidates[$u] = ($candidates[$u] ?? 0) + 2;
    }

    // Uit sitemap (best-effort)
    $smUrl = rtrim($domain,'/').'/sitemap.xml';
    $smRawCode=0; $smErr=''; $smT=0;
    $smRaw = self::fetch_html($smUrl, $smRawCode, $smErr, $smT);
    if ($smRaw && $smRawCode >= 200 && $smRawCode < 400) {
      $sm = @simplexml_load_string($smRaw);
      if ($sm && isset($sm->url)) {
        $i=0; foreach ($sm->url as $node) {
          $loc = self::strip_query_fragment((string)$node->loc);
          if (!$loc) continue;
          if (self::is_same_host($loc,$host) && self::is_html_like($loc)) $candidates[$loc] = ($candidates[$loc] ?? 0) + 1;
          if (++$i>50) break;
        }
      }
    }

    // Korte crawl (depth 1)
    foreach (array_slice(array_keys($candidates),0,12) as $u) {
      $c=0; $e=''; $t=0;
      $html = self::fetch_html($u, $c, $e, $t);
      if ($c!==200 || !$html) continue;
      $d = new DOMDocument(); @$d->loadHTML($html); $x = new DOMXPath($d);
      foreach ($x->query('//a[@href]') as $a) {
        $uu = self::resolve_url($u, $a->getAttribute('href')); if (!$uu) continue; $uu = self::strip_query_fragment($uu);
        if (!self::is_same_host($uu,$host) || !self::is_html_like($uu)) continue;
        if (!isset($candidates[$uu])) $candidates[$uu]=0;
        $candidates[$uu]+=0.5;
      }
    }

    // Weging op pad-diepte
    foreach ($candidates as $u=>$w) $candidates[$u] = $w + max(0,3-self::path_depth($u))*0.5;

    unset($candidates[self::strip_query_fragment($domain)]);
    arsort($candidates);
    $topUrls = array_slice(array_keys($candidates), 0, 5);

    // Analyseer top-5
    $top_pages = [];
    foreach ($topUrls as $u) {
      $c=0; $e=''; $t=0;
      $html = self::fetch_html($u, $c, $e, $t);
      if ($c!==200 || !$html) continue;
      $top_pages[] = self::analyze_html($u, $html, $t);
    }

    return ['mode'=>'both','domain'=>$domain,'homepage'=>$homepage,'top_pages'=>$top_pages];
  }
}