// Augustin Foucheres — exemple pédagogique, 5 septembre 2026.
// Node.js 22+. Lecture locale uniquement : aucun réseau, aucune publication.
// Usage : node opportunites-gsc.mjs gsc-exemple-fictif.json
import { readFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';

const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
const dateIsValid = value => typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)
  && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;

export function analyzeGsc(data) {
  const request = data?.request;
  if (!request || request.dataState !== 'final' || request.type !== 'web'
    || JSON.stringify(request.dimensions) !== '["query","page"]') {
    throw new Error('Export attendu : request.dataState=final, type=web, dimensions=[query,page].');
  }
  if (!dateIsValid(request.startDate) || !dateIsValid(request.endDate) || request.endDate < request.startDate) {
    throw new Error('Période invalide : utiliser deux dates ISO, début avant fin.');
  }
  if (!Array.isArray(data.rows)) throw new Error('rows doit être un tableau, même vide.');
  const filterGroups = request.dimensionFilterGroups ?? [];
  if (!Array.isArray(filterGroups) || filterGroups.some(group => !group
    || !Array.isArray(group.filters) || group.filters.some(filter => !filter
      || ['dimension', 'expression'].some(key => typeof filter[key] !== 'string')
      || (filter.operator !== undefined && typeof filter.operator !== 'string')))) {
    throw new Error('Filtres invalides : conserver les dimensionFilterGroups de la requête API.');
  }
  const seen = new Set();
  const pagesPerQuery = new Map();
  for (const row of data.rows) {
    if (!row || !Array.isArray(row.keys) || row.keys.length !== 2
      || row.keys.some(key => typeof key !== 'string' || !key.trim())) {
      throw new Error('Chaque ligne doit contenir keys=[requête,URL].');
    }
    let url;
    try { url = new URL(row.keys[1]); } catch { throw new Error('URL de page invalide.'); }
    if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) {
      throw new Error('URL HTTP(S) sans identifiants requise.');
    }
    if (!Number.isInteger(row.clicks) || !Number.isInteger(row.impressions)
      || row.clicks < 0 || row.impressions < 1 || row.clicks > row.impressions
      || !Number.isFinite(row.position) || row.position < 1) {
      throw new Error('Métriques invalides : clics/impressions entiers, position positive.');
    }
    const key = JSON.stringify(row.keys);
    if (seen.has(key)) throw new Error('Couple requête/page dupliqué : vérifier les exports concaténés.');
    seen.add(key);
    const [query, page] = row.keys;
    if (!pagesPerQuery.has(query)) pagesPerQuery.set(query, new Set());
    pagesPerQuery.get(query).add(page);
  }
  // Seuils pédagogiques, pas une estimation du potentiel ni une règle Google.
  const thresholds = { minImpressions: 20, minPosition: 4, maxPosition: 25 };
  const selected = data.rows.filter(row => row.impressions >= thresholds.minImpressions
    && row.position >= thresholds.minPosition && row.position <= thresholds.maxPosition)
    .sort((a, b) => b.impressions - a.impressions || a.position - b.position
      || compare(a.keys[1], b.keys[1]) || compare(a.keys[0], b.keys[0]));
  const groups = new Map();
  for (const row of selected) {
    const [query, page] = row.keys;
    if (!groups.has(page)) groups.set(page, []);
    groups.get(page).push({ query, clicks: row.clicks, impressions: row.impressions,
      ctr: row.clicks / row.impressions, position: row.position,
      otherPages: [...pagesPerQuery.get(query)].filter(other => other !== page).sort(compare) });
  }
  return {
    period: { startDate: request.startDate, endDate: request.endDate },
    scope: { type: request.type, dataState: request.dataState, dimensions: [...request.dimensions],
      dimensionFilterGroups: filterGroups.map(group => ({
        groupType: group.groupType ?? 'and',
        filters: group.filters.map(({ dimension, operator, expression }) => ({ dimension, operator: operator ?? 'equals', expression })),
      })) },
    thresholds,
    pages: [...groups].map(([page, queries]) => ({ page, queries })),
    note: 'Tri par impressions du premier couple retenu. CTR en ratio. Plusieurs URL ne prouvent pas une cannibalisation. Aucun total de page ou de propriété déduit des requêtes.',
  };
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  try {
    if (process.argv.length !== 3) throw new Error('Usage : node opportunites-gsc.mjs export.json');
    const result = analyzeGsc(JSON.parse(readFileSync(process.argv[2], 'utf8')));
    console.log(JSON.stringify(result, null, 2));
  } catch (error) {
    // Ne pas afficher le contenu du fichier fourni en cas de JSON mal formé.
    console.error(error instanceof SyntaxError ? 'JSON invalide : vérifier le fichier local.' : error.message);
    process.exitCode = 1;
  }
}
