<?php
namespace services;
class OpenAiApi
{
  static private $api_url = 'https://api.openai.com/v1/chat/completions';

  static private $system_prompt = 'Jesteś ekspertem od optymalizacji feedów produktowych Google Merchant Center i reklam Google Ads (Performance Max, Shopping).

OFICJALNE ZASADY GOOGLE MERCHANT CENTER — BEZWZGLĘDNIE PRZESTRZEGAJ:

TYTUŁY (title) — max 150 znaków:
Zalecana struktura: [Marka] [Typ produktu] [Kluczowe atrybuty: kolor, rozmiar, materiał — TYLKO jeśli wynikają z nazwy]
- Najważniejsze informacje umieść w pierwszych 70 znakach (reszta może być obcięta na mobile)
- Każdy wariant produktu musi mieć unikalny tytuł (różne kolory/rozmiary = różne tytuły)
- Tytuł MUSI być spójny z nazwą produktu na stronie docelowej
- Używaj naturalnego języka — pisz dla ludzi, nie dla algorytmów
ZAKAZANE w tytułach:
- Tekst promocyjny: bestseller, hit, okazja, wyprzedaż, rabat, najlepszy, TOP, #1, idealny, polecamy, nowość
- Wykrzykniki (!) i nadmierna interpunkcja
- WIELKIE LITERY (wyjątek: ustalone skróty jak LED, USB, TV)
- Emotikony, emoji, symbole dekoracyjne (★, ♥, ✓)
- Informacje o cenie, dostawie, promocji
- Wezwania do działania (kup teraz, sprawdź, zamów)
- Upychanie słów kluczowych (keyword stuffing) — np. powtarzanie tych samych fraz
- Kody wewnętrzne, SKU, numery katalogowe
- Cechy produktu których NIE MA w oryginalnej nazwie (nie wymyślaj koloru, materiału, rozmiaru)

OPISY (description) — max 5000 znaków, ale najważniejsze info w pierwszych 160-500 znakach:
- Opisz cechy, specyfikacje techniczne, zastosowanie, grupę docelową
- Wymień atrybuty niewidoczne na zdjęciu (materiał, wzór, przeznaczenie wiekowe)
- Ton neutralny, informacyjny — jak w katalogu produktowym
ZAKAZANE w opisach:
- Tekst promocyjny i reklamowy (te same słowa co w tytułach)
- Nazwa firmy/sklepu (chyba że to marka produktu)
- Informacje o wysyłce, cenach, promocjach
- Wezwania do działania
- Opisy innych produktów, akcesoriów nie wchodzących w skład oferty
- Tekst zastępczy (lorem ipsum, placeholder)
- Powielanie tego samego opisu dla wielu produktów
- HTML, tagi formatowania

OGÓLNE:
- Pisz poprawną polszczyzną (ortografia, gramatyka, interpunkcja)
- Używaj pisowni zdaniowej lub tytułowej — NIE CAPS
- Dane w feedzie MUSZĄ zgadzać się z danymi na stronie produktowej
- Opisuj TYLKO cechy wynikające z nazwy produktu — nie wymyślaj

Twoje odpowiedzi muszą być:
- Ściśle zgodne z polityką Google Merchant Center
- Zgodne z prawdą (opisuj tylko cechy widoczne w nazwie produktu)
- Zoptymalizowane pod kątem trafności wyszukiwań w Google Ads
- W języku polskim';

  static public function is_configured()
  {
    return (bool) GoogleAdsApi::get_setting( 'openai_api_key' );
  }

  static public function fetch_page_content( $url )
  {
    $ch = curl_init( $url );
    curl_setopt_array( $ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_FOLLOWLOCATION => true,
      CURLOPT_TIMEOUT => 10,
      CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
      CURLOPT_HTTPHEADER => [
        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language: pl-PL,pl;q=0.9,en;q=0.8',
      ],
      CURLOPT_SSL_VERIFYPEER => false
    ] );

    $html = curl_exec( $ch );
    $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    curl_close( $ch );

    if ( $http_code !== 200 || !$html ) return '';

    // Usuń skrypty, style, komentarze
    $html = preg_replace( '/<script[^>]*>.*?<\/script>/si', '', $html );
    $html = preg_replace( '/<style[^>]*>.*?<\/style>/si', '', $html );
    $html = preg_replace( '/<!--.*?-->/s', '', $html );
    $html = preg_replace( '/<nav[^>]*>.*?<\/nav>/si', '', $html );
    $html = preg_replace( '/<footer[^>]*>.*?<\/footer>/si', '', $html );
    $html = preg_replace( '/<header[^>]*>.*?<\/header>/si', '', $html );

    // Zamień tagi na spacje i wyciągnij tekst
    $text = strip_tags( $html );
    $text = html_entity_decode( $text, ENT_QUOTES, 'UTF-8' );
    $text = preg_replace( '/\s+/', ' ', $text );
    $text = self::clean_page_content_text( trim( $text ) );

    // Ogranicz do ~1800 znaków
    if ( mb_strlen( $text ) > 1800 )
      $text = mb_substr( $text, 0, 1800 ) . '...';

    return $text;
  }

  static private function clean_page_content_text( $text )
  {
    $text = trim( (string) $text );
    if ( $text === '' )
    {
      return '';
    }

    $noise_phrases = [
      'cookies',
      'polityce prywatności',
      'zaakceptuj wszystkie',
      'odrzuć wszystkie',
      'dostosuj zgody',
      'sklep jest w trybie podglądu',
      'pokaż pełną wersję strony',
      'wersje językowe',
      'strona główna',
      'social media',
      'darmowa dostawa',
      'profesjonalne doradztwo',
      'bezpieczne płatności',
      'szybka wysyłka',
      'produkt miesiąca',
      'niezbędne do działania strony',
      'analityczne',
      'marketingowe',
      'funkcjonalne',
      'shoper'
    ];

    $parts = preg_split( '/(?<=[\.\!\?])\s+|\s{2,}/u', $text );
    $clean_parts = [];
    $seen = [];

    foreach ( (array) $parts as $part )
    {
      $part = trim( (string) $part );
      if ( mb_strlen( $part ) < 25 )
      {
        continue;
      }

      $part_l = mb_strtolower( $part );
      $is_noise = false;
      foreach ( $noise_phrases as $phrase )
      {
        if ( mb_strpos( $part_l, $phrase ) !== false )
        {
          $is_noise = true;
          break;
        }
      }

      if ( $is_noise )
      {
        continue;
      }

      if ( isset( $seen[ $part_l ] ) )
      {
        continue;
      }
      $seen[ $part_l ] = true;
      $clean_parts[] = $part;
    }

    $result = trim( implode( '. ', $clean_parts ) );
    if ( mb_strlen( $result ) < 80 )
    {
      return '';
    }

    return $result;
  }

  static private function call_api( $system_prompt, $user_prompt, $max_tokens = 500, $temperature = 0.7, $extra_payload = [] )
  {
    $api_key = GoogleAdsApi::get_setting( 'openai_api_key' );
    $model = GoogleAdsApi::get_setting( 'openai_model' ) ?: 'gpt-5-mini';
    $is_gpt5_model = ( strpos( $model, 'gpt-5' ) === 0 );

    // GPT-5.x wymaga max_completion_tokens, starsze modele używają max_tokens
    $tokens_key = $is_gpt5_model ? 'max_completion_tokens' : 'max_tokens';

    $payload = [
      'model' => $model,
      'messages' => [
        [ 'role' => 'system', 'content' => $system_prompt ],
        [ 'role' => 'user', 'content' => $user_prompt ]
      ],
      $tokens_key => $max_tokens
    ];

    // Modele GPT-5 (w tym gpt-5-mini) nie wspierają niestandardowej temperatury.
    if ( !$is_gpt5_model )
    {
      $payload['temperature'] = $temperature;
    }

    if ( is_array( $extra_payload ) && !empty( $extra_payload ) )
    {
      $payload = array_merge( $payload, $extra_payload );
    }

    $ch = curl_init( self::$api_url );
    curl_setopt_array( $ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $api_key
      ],
      CURLOPT_POSTFIELDS => json_encode( $payload ),
      CURLOPT_TIMEOUT => 30
    ] );

    $response = curl_exec( $ch );
    $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    curl_close( $ch );

    if ( $http_code !== 200 )
    {
      $error = json_decode( $response, true );
      return [ 'status' => 'error', 'message' => $error['error']['message'] ?? 'Błąd API OpenAI (HTTP ' . $http_code . ')' ];
    }

    $data = json_decode( $response, true );
    $content = trim( $data['choices'][0]['message']['content'] ?? '' );

    return [ 'status' => 'ok', 'suggestion' => $content ];
  }

  static private function build_context_text( $context )
  {
    $lines = [];
    $lines[] = 'Nazwa produktu: ' . ( $context['original_name'] ?? '—' );

    if ( !empty( $context['current_title'] ) )
      $lines[] = 'Obecny tytuł (custom): ' . $context['current_title'];

    if ( !empty( $context['current_description'] ) )
      $lines[] = 'Obecny opis: ' . $context['current_description'];

    if ( !empty( $context['current_category'] ) )
      $lines[] = 'Obecna kategoria Google: ' . $context['current_category'];

    if ( !empty( $context['offer_id'] ) )
      $lines[] = 'ID oferty: ' . $context['offer_id'];

    if ( !empty( $context['custom_label_4'] ) )
      $lines[] = 'Status produktu: ' . $context['custom_label_4'];

    $lines[] = '';
    $lines[] = 'Metryki reklamowe (ostatnie 30 dni):';
    $lines[] = '- Wyświetlenia: ' . ( $context['impressions_30'] ?? 0 );
    $lines[] = '- Kliknięcia: ' . ( $context['clicks_30'] ?? 0 );
    $lines[] = '- CTR: ' . ( $context['ctr'] ?? 0 ) . '%';
    $lines[] = '- Koszt: ' . ( $context['cost'] ?? 0 ) . ' PLN';
    $lines[] = '- Konwersje: ' . ( $context['conversions'] ?? 0 );
    $lines[] = '- Wartość konwersji: ' . ( $context['conversions_value'] ?? 0 ) . ' PLN';
    $lines[] = '- ROAS: ' . ( $context['roas'] ?? 0 ) . '%';

    if ( !empty( $context['page_content'] ) )
    {
      $lines[] = '';
      $lines[] = 'Treść ze strony produktu (użyj tych informacji do stworzenia dokładniejszego opisu):';
      $lines[] = $context['page_content'];
    }

    return implode( "\n", $lines );
  }

  static private function get_prompt_template( $setting_key, $default_template )
  {
    $template = trim( (string) GoogleAdsApi::get_setting( $setting_key ) );
    if ( $template === '' )
    {
      return $default_template;
    }

    return $template;
  }

  static private function expand_prompt_template( $template, $vars )
  {
    $result = (string) $template;
    foreach ( (array) $vars as $key => $value )
    {
      $result = str_replace( '{{' . $key . '}}', (string) $value, $result );
    }

    return $result;
  }

  static private function extract_meaningful_tokens( $text )
  {
    $text = mb_strtolower( (string) $text );
    $text = preg_replace( '/[^\p{L}\p{N}]+/u', ' ', $text );
    $raw_tokens = preg_split( '/\s+/u', trim( (string) $text ) );

    $stopwords = [
      'oraz', 'przez', 'jego', 'jej', 'this', 'that', 'with', 'from', 'jest',
      'dla', 'the', 'and', 'lub', 'czy', 'ten', 'ta', 'to', 'tych', 'taki',
      'takie', 'jako', 'się', 'sie', 'nad', 'pod', 'bez', 'www', 'http', 'https'
    ];
    $stop_map = array_fill_keys( $stopwords, true );

    $tokens = [];
    foreach ( (array) $raw_tokens as $token )
    {
      $token = trim( (string) $token );
      if ( mb_strlen( $token ) < 4 )
      {
        continue;
      }
      if ( isset( $stop_map[ $token ] ) )
      {
        continue;
      }
      $tokens[ $token ] = true;
    }

    return array_keys( $tokens );
  }

  static private function select_relevant_keyword_terms( $terms, $context, $limit = 10 )
  {
    $terms = is_array( $terms ) ? $terms : [];
    if ( empty( $terms ) )
    {
      return [];
    }

    $context_source = trim( (string) ( $context['original_name'] ?? '' ) ) . ' ' . trim( (string) ( $context['page_content'] ?? '' ) );
    $context_tokens = self::extract_meaningful_tokens( $context_source );
    $context_map = array_fill_keys( $context_tokens, true );

    $scored = [];
    foreach ( $terms as $idx => $term )
    {
      $keyword_text = trim( (string) ( $term['keyword_text'] ?? '' ) );
      if ( $keyword_text === '' )
      {
        continue;
      }

      $kw_tokens = self::extract_meaningful_tokens( $keyword_text );
      $overlap = 0;
      foreach ( $kw_tokens as $kw_token )
      {
        if ( isset( $context_map[ $kw_token ] ) )
        {
          $overlap++;
        }
      }

      $avg = (int) ( $term['avg_monthly_searches'] ?? 0 );
      $score = ( $overlap * 1000000 ) + $avg - (int) $idx;
      if ( $overlap <= 0 )
      {
        $score -= 5000000;
      }

      $term['_score'] = $score;
      $term['_overlap'] = $overlap;
      $scored[] = $term;
    }

    usort( $scored, function( $a, $b )
    {
      return (int) ( $b['_score'] ?? 0 ) <=> (int) ( $a['_score'] ?? 0 );
    } );

    $filtered = array_values( array_filter( $scored, function( $row )
    {
      return (int) ( $row['_overlap'] ?? 0 ) > 0;
    } ) );

    if ( empty( $filtered ) )
    {
      $filtered = $scored;
    }

    $filtered = array_slice( $filtered, 0, max( 1, (int) $limit ) );

    foreach ( $filtered as &$row )
    {
      unset( $row['_score'], $row['_overlap'] );
    }
    unset( $row );

    return $filtered;
  }

  static private function build_keyword_planner_text( $context, $usage_line, $limit = 10 )
  {
    $terms = self::select_relevant_keyword_terms( (array) ( $context['keyword_planner_terms'] ?? [] ), $context, $limit );
    if ( empty( $terms ) )
    {
      return '';
    }

    $keyword_lines = [];
    $keyword_lines[] = 'Najpopularniejsze frazy z Google Ads Keyword Planner (na bazie URL produktu, posortowane malejąco po średniej liczbie wyszukiwań):';

    foreach ( $terms as $term )
    {
      $text = trim( (string) ( $term['keyword_text'] ?? '' ) );
      if ( $text === '' )
      {
        continue;
      }

      $avg_monthly = (int) ( $term['avg_monthly_searches'] ?? 0 );
      $keyword_lines[] = '- ' . $text . ' (avg miesięcznie: ' . $avg_monthly . ')';
    }

    if ( count( $keyword_lines ) <= 1 )
    {
      return '';
    }

    $keyword_lines[] = $usage_line;
    return "\n\n" . implode( "\n", $keyword_lines );
  }

  static public function parse_title_candidates( $raw )
  {
    $raw = trim( (string) $raw );
    if ( $raw === '' )
    {
      return [];
    }

    $data = json_decode( $raw, true );
    if ( is_array( $data ) && !empty( $data['titles'] ) && is_array( $data['titles'] ) )
    {
      return array_values( array_filter( array_map( 'trim', $data['titles'] ) ) );
    }

    if ( preg_match( '/\{[\s\S]*\}/u', $raw, $m ) )
    {
      $data = json_decode( (string) $m[0], true );
      if ( is_array( $data ) && !empty( $data['titles'] ) && is_array( $data['titles'] ) )
      {
        return array_values( array_filter( array_map( 'trim', $data['titles'] ) ) );
      }
    }

    $lines = preg_split( '/\r?\n/u', $raw );
    $titles = [];
    foreach ( (array) $lines as $line )
    {
      $line = trim( (string) $line );
      $line = preg_replace( '/^\d+[\)\.\-\s]+/u', '', $line );
      $line = trim( (string) $line, " \t\n\r\0\x0B\"'" );
      if ( $line !== '' )
      {
        $titles[] = $line;
      }
    }

    return array_values( array_slice( array_unique( $titles ), 0, 5 ) );
  }

  static private function score_title_candidate( $title, $context )
  {
    $title = trim( (string) $title );
    if ( $title === '' )
    {
      return -100000;
    }

    $score = 0;
    $len = mb_strlen( $title );

    // Kara za przekroczenie limitu Google Merchant Center
    if ( $len > 150 )
    {
      $score -= 2000;
    }
    // Sweet spot: 60-120 znaków — tytuł wzbogacony, ale nie za długi
    else if ( $len >= 60 && $len <= 120 )
    {
      $score += 180;
    }
    else if ( $len >= 40 && $len <= 140 )
    {
      $score += 100;
    }
    else if ( $len >= 25 )
    {
      $score += 30;
    }
    else
    {
      $score -= 200;
    }

    $bad_words = [ 'bestseller', 'hit', 'okazja', 'tanio', 'gratis', 'promocja', 'najlepszy', 'polecamy', 'nowość' ];
    $title_l = mb_strtolower( $title );
    foreach ( $bad_words as $bad_word )
    {
      if ( mb_strpos( $title_l, $bad_word ) !== false )
      {
        $score -= 300;
      }
    }

    $source_tokens = self::extract_meaningful_tokens( (string) ( $context['original_name'] ?? '' ) );
    $title_tokens = self::extract_meaningful_tokens( $title );
    $source_map = array_fill_keys( $source_tokens, true );

    // Overlap z oryginalną nazwą — zmniejszona waga, żeby nie preferować kopii oryginału
    $overlap = 0;
    foreach ( $title_tokens as $token )
    {
      if ( isset( $source_map[ $token ] ) )
      {
        $overlap++;
      }
    }
    $score += ( $overlap * 15 );

    // Bonus za wzbogacenie tytułu informacjami ze strony produktu
    $page_content = trim( (string) ( $context['page_content'] ?? '' ) );
    if ( $page_content !== '' )
    {
      $page_tokens = self::extract_meaningful_tokens( $page_content );
      $page_map = array_fill_keys( $page_tokens, true );

      $enrichment = 0;
      foreach ( $title_tokens as $token )
      {
        // Token jest ze strony produktu, ale NIE z oryginalnej nazwy = wzbogacenie
        if ( isset( $page_map[ $token ] ) && !isset( $source_map[ $token ] ) )
        {
          $enrichment++;
        }
      }
      $score += min( $enrichment * 20, 100 );
    }

    if ( preg_match( '/\d+\s?(ml|l|g|kg|cm|mm)/iu', (string) ( $context['original_name'] ?? '' ), $m ) )
    {
      if ( preg_match( '/' . preg_quote( $m[0], '/' ) . '/iu', $title ) )
      {
        $score += 30;
      }
    }

    return $score;
  }

  static public function pick_best_title_candidate( $candidates, $context )
  {
    $best_title = '';
    $best_score = -1000000;

    foreach ( (array) $candidates as $candidate )
    {
      $candidate = trim( (string) $candidate );
      if ( $candidate === '' )
      {
        continue;
      }

      $score = self::score_title_candidate( $candidate, $context );
      if ( $score > $best_score )
      {
        $best_score = $score;
        $best_title = $candidate;
      }
    }

    return $best_title;
  }

  static public function get_default_title_prompt_template()
  {
    return 'Zaproponuj zoptymalizowany tytuł produktu dla Google Merchant Center.

WYMAGANIA:
- Max 150 znaków, najważniejsze info w pierwszych 70 znakach
- Struktura: [Marka jeśli jest w nazwie] [Typ produktu] [Kluczowe cechy z nazwy] [Dla kogo/okazja jeśli wynika z nazwy]
- Pisownia zdaniowa (wielka litera tylko na początku i w nazwach własnych/markach) — NIE stosuj Title Case / Camel Case, np. "Preparat do laminacji rzęs 20ml" a NIE "Preparat Do Laminacji Rzęs 20ml"
- Tytuł musi brzmieć jak wpis w katalogu produktowym

BEZWZGLĘDNY ZAKAZ (odrzucenie przez Google):
- Słowa promocyjne: bestseller, hit, idealny, najlepszy, polecamy, okazja, nowość, TOP
- Wykrzykniki (!), emotikony, symbole dekoracyjne
- WIELKIE LITERY (wyjątek: LED, USB, TV)
- Wezwania do działania, informacje o cenie/dostawie
- Cechy wymyślone — opisuj TYLKO to co wynika z oryginalnej nazwy lub treści strony produktu
- Jeśli podano treść ze strony produktu, wykorzystaj ją do wzbogacenia tytułu o rzeczywiste cechy (marka, materiał, kolor, rozmiar itp.)

{{context}}{{keyword_terms}}

Zwróć TYLKO tytuł, bez cudzysłowów, bez wyjaśnień.';
  }

  static public function get_default_description_prompt_template()
  {
    return 'Napisz zoptymalizowany opis produktu dla Google Merchant Center.

WYMAGANIA:
{{length_guide}}
- Najważniejsze info na początku (pierwsze 160 znaków widoczne w wynikach)
- Rzeczowo opisz: co to jest, z czego się składa, do czego służy, dla kogo
- Ton neutralny, informacyjny — jak w specyfikacji produktowej
- Każdy akapit musi zawierać INNĄ informację niż poprzedni — NIGDY nie powtarzaj tych samych elementów

FORMATOWANIE — Google Merchant Center obsługuje podstawowe HTML:
- Używaj <br> do oddzielania akapitów/sekcji
- Używaj <b>pogrubienia</b> dla kluczowych cech (np. nazwy elementów zestawu, materiał)
- Używaj <ul><li>...</li></ul> gdy wymieniasz elementy zestawu lub listę cech
- NIE używaj innych tagów HTML (h1, p, div, span, img, a, table itp.)
- NIE używaj Markdown — tylko dozwolone tagi HTML

BEZWZGLĘDNY ZAKAZ (odrzucenie przez Google):
- Słowa promocyjne: bestseller, hit, okazja, najlepszy, polecamy, nowość, idealny
- Wykrzykniki (!), emotikony, WIELKIE LITERY
- Wezwania do działania: kup teraz, zamów, sprawdź, dodaj do koszyka
- Informacje o cenie, dostawie, promocjach, nazwie sklepu
- Opisy akcesoriów/produktów nie wchodzących w skład oferty
- Cechy wymyślone — opisuj TYLKO to co wynika z nazwy lub treści strony produktu

{{context}}{{keyword_terms}}

Zwróć TYLKO opis w formacie HTML (używając dozwolonych tagów), bez cudzysłowów, bez wyjaśnień.';
  }

  static public function suggest_title( $context )
  {
    $context_text = self::build_context_text( $context );
    $keyword_planner_text = self::build_keyword_planner_text(
      $context,
      'Użyj tych fraz WYBIÓRCZO i naturalnie (bez upychania słów kluczowych), tylko jeśli pasują do produktu.',
      8
    );

    $prompt = self::get_default_title_prompt_template();

    $prompt_template = self::get_prompt_template( 'ai_prompt_title_template', $prompt );

    if ( strpos( $prompt_template, '{{context}}' ) === false )
    {
      $prompt_template .= "\n\n{{context}}";
    }
    if ( strpos( $prompt_template, '{{keyword_terms}}' ) === false )
    {
      $prompt_template .= "{{keyword_terms}}";
    }

    $prompt = self::expand_prompt_template( $prompt_template, [
      'context' => $context_text,
      'keyword_terms' => $keyword_planner_text
    ] );
    $prompt .= "\n\nWygeneruj 3 różne warianty tytułu (A/B/C). Zwróć WYŁĄCZNIE poprawny JSON: {\"titles\":[\"wariant A\",\"wariant B\",\"wariant C\"]}.";

    $result = self::call_api( self::$system_prompt, $prompt, 1200 );
    if ( ( $result['status'] ?? '' ) !== 'ok' )
    {
      return $result;
    }

    $candidates = self::parse_title_candidates( (string) ( $result['suggestion'] ?? '' ) );
    $best_title = self::pick_best_title_candidate( $candidates, $context );
    if ( $best_title !== '' )
    {
      $result['suggestion'] = $best_title;
      $result['title_candidates'] = $candidates;
    }

    return $result;
  }

  static public function suggest_description( $context )
  {
    $context_text = self::build_context_text( $context );
    $has_page = !empty( $context['page_content'] );
    $keyword_planner_text = self::build_keyword_planner_text(
      $context,
      'W opisie wykorzystuj te frazy naturalnie i wyłącznie gdy realnie pasują do produktu (bez keyword stuffing).',
      12
    );

    $length_guide = $has_page
      ? '- Napisz rozbudowany, szczegółowy opis: ok. 1000 znaków (800-1200)
- Wykorzystaj szczegóły ze strony produktu: skład zestawu, materiały, wymiary, kolory, przeznaczenie
- Każdy akapit/punkt powinien wnosić NOWĄ informację — NIE powtarzaj tych samych elementów'
      : '- Napisz opis o długości ok. 1000 znaków (800-1200) — jeśli brak szczegółów, opisz ogólnie zastosowanie i grupę docelową
- Opisuj TYLKO to co wynika z oryginalnej nazwy — nie wymyślaj konkretnych parametrów';

    $prompt = self::get_default_description_prompt_template();

    $prompt_template = self::get_prompt_template( 'ai_prompt_description_template', $prompt );

    if ( strpos( $prompt_template, '{{length_guide}}' ) === false )
    {
      $prompt_template .= "\n\n{{length_guide}}";
    }
    if ( strpos( $prompt_template, '{{context}}' ) === false )
    {
      $prompt_template .= "\n\n{{context}}";
    }
    if ( strpos( $prompt_template, '{{keyword_terms}}' ) === false )
    {
      $prompt_template .= "{{keyword_terms}}";
    }

    $prompt = self::expand_prompt_template( $prompt_template, [
      'length_guide' => $length_guide,
      'context' => $context_text,
      'keyword_terms' => $keyword_planner_text
    ] );

    $tokens = $has_page ? 1500 : 1000;
    return self::call_api( self::$system_prompt, $prompt, $tokens );
  }

  static public function suggest_category( $context, $categories = [] )
  {
    $context_text = self::build_context_text( $context );

    $cats_text = '';
    if ( !empty( $categories ) )
    {
      $cats_list = array_slice( $categories, 0, 50 );
      $cats_text = "\n\nDostępne kategorie Google Product Taxonomy (format: id - tekst):\n";
      foreach ( $cats_list as $cat )
      {
        $cats_text .= $cat['id'] . ' - ' . $cat['text'] . "\n";
      }
    }

    $prompt = 'Dopasuj produkt do najlepszej kategorii Google Product Taxonomy.
Wybierz najbardziej szczegółową (najgłębszą) kategorię pasującą do produktu.

' . $context_text . $cats_text . '

Zwróć TYLKO ID kategorii (liczbę), bez wyjaśnień.';

    return self::call_api( self::$system_prompt, $prompt );
  }

  static public function suggest_negative_keywords_to_exclude( $search_terms_rows, $analysis_context = [] )
  {
    $rows = is_array( $search_terms_rows ) ? $search_terms_rows : [];
    $rows = array_slice( $rows, 0, 150 );

    $campaign_name = trim( (string) ( $analysis_context['campaign_name'] ?? '' ) );
    $campaign_type = trim( (string) ( $analysis_context['campaign_type'] ?? '' ) );
    $ad_group_name = trim( (string) ( $analysis_context['ad_group_name'] ?? '' ) );
    $ad_group_id = (int) ( $analysis_context['ad_group_id'] ?? 0 );

    $context_lines = [];
    $context_lines[] = 'KONTEKST KAMPANII:';
    $context_lines[] = '- Nazwa kampanii: ' . ( $campaign_name !== '' ? $campaign_name : '-' );
    $context_lines[] = '- Rodzaj kampanii: ' . ( $campaign_type !== '' ? $campaign_type : '-' );
    if ( $ad_group_id > 0 || $ad_group_name !== '' )
    {
      $context_lines[] = '- Wybrana grupa reklam: ' . ( $ad_group_name !== '' ? $ad_group_name : ( '#' . $ad_group_id ) );
    }
    else
    {
      $context_lines[] = '- Wybrana grupa reklam: wszystkie';
    }

    $lines = [];
    foreach ( $rows as $row )
    {
      $id = (int) ( $row['id'] ?? 0 );
      if ( $id <= 0 )
      {
        continue;
      }

      $lines[] = json_encode( [
        'id' => $id,
        'phrase' => trim( (string) ( $row['search_term'] ?? '' ) ),
        'ad_group' => trim( (string) ( $row['ad_group_name'] ?? '' ) ),
        'clicks_all' => (float) ( $row['clicks_all_time'] ?? 0 ),
        'cost_all' => (float) ( $row['cost_all_time'] ?? 0 ),
        'value_all' => (float) ( $row['conversion_value_all_time'] ?? 0 ),
        'roas_all' => (float) ( $row['roas_all_time'] ?? 0 ),
        'clicks_30' => (float) ( $row['clicks_30'] ?? 0 ),
        'cost_30' => (float) ( $row['cost_30'] ?? 0 ),
        'value_30' => (float) ( $row['conversion_value_30'] ?? 0 ),
        'roas_30' => (float) ( $row['roas_30'] ?? 0 )
      ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
    }

    $prompt = 'Przeanalizuj frazy wyszukiwane Google Ads i wskaż, które warto wykluczyć jako negatywne słowa kluczowe.

  ' . implode( "\n", $context_lines ) . '

KRYTERIA OCENY:
- Priorytet: frazy z kosztami i kliknięciami bez wartości konwersji, niski/zerowy ROAS, nietrafna intencja.
- Nie zaznaczaj wszystkich na siłę. Jeśli fraza ma potencjał, ustaw akcję "keep".
- Oceniaj zarówno dane all-time, jak i 30d.
- Powód musi być krótki, konkretny i oparty na danych.

FORMAT ODPOWIEDZI (BEZWZGLĘDNIE):
Zwróć WYŁĄCZNIE poprawny JSON (bez markdown i bez komentarzy), zgodny ze schematem:
{
  "items": [
    {
      "id": 123,
      "phrase": "fraza",
      "action": "exclude" lub "keep",
      "reason": "krótki powód"
    }
  ]
}

Zasady formatu:
- Pole id musi być identyczne z wejściowym id.
- action może mieć tylko wartości: "exclude" albo "keep".
- reason max 120 znaków.
- Nie dodawaj żadnych dodatkowych pól.

DANE WEJŚCIOWE (JSONL, 1 rekord na linię):
' . implode( "\n", $lines );

    $schema = [
      'type' => 'json_schema',
      'json_schema' => [
        'name' => 'negative_keyword_recommendations',
        'schema' => [
          'type' => 'object',
          'additionalProperties' => false,
          'properties' => [
            'items' => [
              'type' => 'array',
              'items' => [
                'type' => 'object',
                'additionalProperties' => false,
                'properties' => [
                  'id' => [ 'type' => 'integer' ],
                  'phrase' => [ 'type' => 'string' ],
                  'action' => [ 'type' => 'string', 'enum' => [ 'exclude', 'keep' ] ],
                  'reason' => [ 'type' => 'string' ]
                ],
                'required' => [ 'id', 'phrase', 'action', 'reason' ]
              ]
            ]
          ],
          'required' => [ 'items' ]
        ],
        'strict' => true
      ]
    ];

    $rows_count = count( $rows );
    $max_tokens = min( 6000, max( 2200, $rows_count * 30 ) );

    return self::call_api( self::$system_prompt, $prompt, $max_tokens, 0.2, [ 'response_format' => $schema ] );
  }
}
