<?php
namespace controls;
class Products
{
  static private function normalize_keyword_source_url( $url )
  {
    $url = trim( (string) $url );
    if ( $url === '' )
    {
      return '';
    }

    $parts = parse_url( $url );
    if ( !is_array( $parts ) || empty( $parts['scheme'] ) || empty( $parts['host'] ) )
    {
      return $url;
    }

    $normalized = strtolower( (string) $parts['scheme'] ) . '://' . strtolower( (string) $parts['host'] );

    if ( isset( $parts['port'] ) )
    {
      $normalized .= ':' . (int) $parts['port'];
    }

    $normalized .= isset( $parts['path'] ) ? (string) $parts['path'] : '/';

    if ( isset( $parts['query'] ) && $parts['query'] !== '' )
    {
      $normalized .= '?' . (string) $parts['query'];
    }

    return $normalized;
  }

  static public function sync_product_fields_to_merchant( $product_id, $changed_fields, $sync_source = 'products_ui' )
  {
    $product_id = (int) $product_id;
    $changed_fields = is_array( $changed_fields ) ? $changed_fields : [];
    $sync_source = trim( (string) $sync_source ) ?: 'products_ui';

    if ( $product_id <= 0 || empty( $changed_fields ) )
    {
      return [ 'status' => 'skipped', 'message' => 'Brak zmian do synchronizacji.' ];
    }

    $supported_fields = [ 'title', 'description', 'google_product_category' ];
    $normalized_changes = [];

    foreach ( $changed_fields as $field => $change )
    {
      if ( !in_array( $field, $supported_fields, true ) )
      {
        continue;
      }

      $old_value = trim( (string) ( $change['old'] ?? '' ) );
      $new_value = trim( (string) ( $change['new'] ?? '' ) );

      if ( $old_value === $new_value )
      {
        continue;
      }

      $normalized_changes[ $field ] = [
        'old' => $old_value,
        'new' => $new_value
      ];
    }

    if ( empty( $normalized_changes ) )
    {
      return [ 'status' => 'skipped', 'message' => 'Brak rzeczywistych zmian pól.' ];
    }

    $merchant_context = \factory\Products::get_product_merchant_context( $product_id );
    $merchant_account_id = trim( (string) ( $merchant_context['google_merchant_account_id'] ?? '' ) );
    $offer_id = trim( (string) ( $merchant_context['offer_id'] ?? '' ) );

    if ( !$merchant_context || $merchant_account_id === '' || $offer_id === '' )
    {
      $reason = 'Brak merchant_account_id lub offer_id dla produktu.';
      foreach ( $normalized_changes as $field => $change )
      {
        \factory\Products::add_product_merchant_sync_log( [
          'product_id' => $product_id,
          'field_name' => $field,
          'old_value' => $change['old'],
          'new_value' => $change['new'],
          'sync_status' => 'skipped',
          'sync_source' => $sync_source,
          'merchant_account_id' => $merchant_account_id !== '' ? $merchant_account_id : null,
          'offer_id' => $offer_id !== '' ? $offer_id : null,
          'error_message' => $reason
        ] );
      }

      return [ 'status' => 'skipped', 'message' => $reason ];
    }

    $merchant_api = new \services\GoogleAdsApi();
    if ( !$merchant_api -> is_merchant_configured() )
    {
      $reason = 'Merchant API nie jest skonfigurowane.';
      foreach ( $normalized_changes as $field => $change )
      {
        \factory\Products::add_product_merchant_sync_log( [
          'product_id' => $product_id,
          'field_name' => $field,
          'old_value' => $change['old'],
          'new_value' => $change['new'],
          'sync_status' => 'skipped',
          'sync_source' => $sync_source,
          'merchant_account_id' => $merchant_account_id,
          'offer_id' => $offer_id,
          'error_message' => $reason
        ] );
      }

      return [ 'status' => 'skipped', 'message' => $reason ];
    }

    $payload_fields = [];
    foreach ( $normalized_changes as $field => $change )
    {
      $payload_fields[ $field ] = $change['new'];
    }

    $sync_result = $merchant_api -> update_merchant_product_fields_by_offer_id( $merchant_account_id, $offer_id, $payload_fields );
    $sync_success = !empty( $sync_result['success'] );
    $sync_status = $sync_success ? 'success' : 'error';
    $sync_error = trim( (string) ( $sync_result['error'] ?? '' ) );
    $merchant_product_id = trim( (string) ( $sync_result['merchant_product_id'] ?? '' ) );
    $api_response = null;

    if ( isset( $sync_result['response'] ) )
    {
      $api_response = is_string( $sync_result['response'] )
        ? $sync_result['response']
        : json_encode( $sync_result['response'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
    }

    foreach ( $normalized_changes as $field => $change )
    {
      \factory\Products::add_product_merchant_sync_log( [
        'product_id' => $product_id,
        'field_name' => $field,
        'old_value' => $change['old'],
        'new_value' => $change['new'],
        'sync_status' => $sync_status,
        'sync_source' => $sync_source,
        'merchant_account_id' => $merchant_account_id,
        'merchant_product_id' => $merchant_product_id !== '' ? $merchant_product_id : null,
        'offer_id' => $offer_id,
        'api_response' => $api_response,
        'error_message' => $sync_error !== '' ? $sync_error : null
      ] );
    }

    return [
      'status' => $sync_status,
      'message' => $sync_success ? 'Synchronizacja Merchant API zakończona sukcesem.' : ( $sync_error !== '' ? $sync_error : 'Błąd synchronizacji Merchant API.' ),
      'result' => $sync_result
    ];
  }

  static public function main_view()
  {
    return \Tpl::view( 'products/main_view', [
      'clients' => \factory\Campaigns::get_clients(),
    ] );
  }

  static public function get_campaigns_list()
  {
    $client_id = (int) \S::get( 'client_id' );
    $campaigns = \factory\Campaigns::get_campaigns_list( $client_id, true );
    $allowed_channel_types = [ 'SHOPPING', 'PERFORMANCE_MAX' ];

    $campaigns = array_values( array_filter( (array) $campaigns, function( $row ) use ( $allowed_channel_types ) {
      $channel_type = strtoupper( trim( (string) ( $row['advertising_channel_type'] ?? '' ) ) );
      return in_array( $channel_type, $allowed_channel_types, true );
    } ) );

    echo json_encode( [ 'campaigns' => $campaigns ] );
    exit;
  }

  static public function get_campaign_ad_groups()
  {
    $campaign_id = (int) \S::get( 'campaign_id' );

    if ( $campaign_id <= 0 )
    {
      echo json_encode( [ 'ad_groups' => [] ] );
      exit;
    }

    echo json_encode( [ 'ad_groups' => \factory\Campaigns::get_campaign_ad_groups( $campaign_id ) ] );
    exit;
  }

  static public function get_client_bestseller_settings()
  {
    $client_id = (int) \S::get( 'client_id' );

    if ( $client_id <= 0 )
    {
      echo json_encode( [
        'status' => 'error',
        'message' => 'Nieprawidlowe ID klienta.'
      ] );
      exit;
    }

    $settings = \factory\Products::get_client_bestseller_settings( $client_id );

    echo json_encode( [
      'status' => 'ok',
      'settings' => $settings
    ] );
    exit;
  }

  static public function save_client_bestseller_settings()
  {
    $client_id = (int) \S::get( 'client_id' );
    if ( $client_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidlowe ID klienta.' ] );
      exit;
    }

    $settings = [
      'bestseller_roas_entry' => \S::get( 'bestseller_roas_entry' ),
      'bestseller_roas_exit' => \S::get( 'bestseller_roas_exit' ),
      'min_conversions' => \S::get( 'min_conversions' ),
      'cooldown_period' => \S::get( 'cooldown_period' )
    ];

    $saved = \factory\Products::save_client_bestseller_settings( $client_id, $settings );

    echo json_encode( [
      'status' => $saved ? 'ok' : 'error',
      'settings' => \factory\Products::get_client_bestseller_settings( $client_id )
    ] );
    exit;
  }

  static public function preview_client_bestseller_settings()
  {
    $client_id = (int) \S::get( 'client_id' );
    if ( $client_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidlowe ID klienta.' ] );
      exit;
    }

    $settings = [
      'bestseller_roas_entry' => \S::get( 'bestseller_roas_entry' ),
      'bestseller_roas_exit' => \S::get( 'bestseller_roas_exit' ),
      'min_conversions' => \S::get( 'min_conversions' ),
      'cooldown_period' => \S::get( 'cooldown_period' )
    ];

    $count = \factory\Products::get_client_bestseller_preview_count( $client_id, $settings );

    echo json_encode( [
      'status' => 'ok',
      'count' => (int) $count
    ] );
    exit;
  }

  static public function get_scope_alerts()
  {
    $client_id = (int) \S::get( 'client_id' );
    $campaign_id = (int) \S::get( 'campaign_id' );
    $ad_group_id = (int) \S::get( 'ad_group_id' );

    $alerts = \factory\Products::get_scope_alerts( $client_id, $campaign_id, $ad_group_id, 80 );

    echo json_encode( [
      'status' => 'ok',
      'alerts' => $alerts,
      'count' => count( $alerts )
    ] );
    exit;
  }

  static public function get_products_without_impressions_30()
  {
    $client_id = (int) \S::get( 'client_id' );
    $campaign_id = (int) \S::get( 'campaign_id' );
    $ad_group_id = (int) \S::get( 'ad_group_id' );

    if ( $client_id <= 0 || $campaign_id <= 0 )
    {
      echo json_encode( [
        'status' => 'ok',
        'products' => [],
        'count' => 0
      ] );
      exit;
    }

    $rows = \factory\Products::get_products_without_impressions_30( $client_id, $campaign_id, $ad_group_id, 1000 );
    $products = [];

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

      $offer_id = trim( (string) ( $row['offer_id'] ?? '' ) );
      $product_name = trim( (string) ( $row['title'] ?? '' ) );
      if ( $product_name === '' )
      {
        $product_name = trim( (string) ( $row['name'] ?? '' ) );
      }
      if ( $product_name === '' )
      {
        $product_name = $offer_id !== '' ? $offer_id : ( 'Produkt #' . $product_id );
      }

      $products[] = [
        'product_id' => $product_id,
        'offer_id' => $offer_id,
        'name' => $product_name
      ];
    }

    echo json_encode( [
      'status' => 'ok',
      'products' => $products,
      'count' => count( $products )
    ] );
    exit;
  }

  static public function delete_campaign_ad_group()
  {
    $campaign_id = (int) \S::get( 'campaign_id' );
    $ad_group_id = (int) \S::get( 'ad_group_id' );
    $delete_scope = trim( (string) \S::get( 'delete_scope' ) );

    if ( $ad_group_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nie wybrano grupy reklam do usuniecia.' ] );
      exit;
    }

    if ( !in_array( $delete_scope, [ 'local', 'google' ], true ) )
    {
      $delete_scope = 'local';
    }

    $context = \factory\Products::get_ad_group_delete_context( $ad_group_id );
    if ( !$context )
    {
      echo json_encode( [ 'status' => 'ok', 'message' => 'Grupa reklam byla juz usunieta lokalnie.' ] );
      exit;
    }

    $local_campaign_id = (int) ( $context['local_campaign_id'] ?? 0 );
    if ( $campaign_id > 0 && $campaign_id !== $local_campaign_id )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Wybrana grupa reklam nie nalezy do wskazanej kampanii.' ] );
      exit;
    }

    $channel_type = strtoupper( trim( (string) ( $context['advertising_channel_type'] ?? '' ) ) );
    if ( $channel_type !== 'SHOPPING' )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Usuwanie grup reklam jest dostepne tylko dla kampanii produktowych (Shopping).' ] );
      exit;
    }

    if ( $delete_scope === 'google' )
    {
      $customer_id = preg_replace( '/\D+/', '', (string) ( $context['google_ads_customer_id'] ?? '' ) );
      $external_ad_group_id = (int) ( $context['external_ad_group_id'] ?? 0 );

      if ( $customer_id === '' || $external_ad_group_id <= 0 )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Brak danych Google Ads (customer_id lub ad_group_id).' ] );
        exit;
      }

      $google_ads_api = new \services\GoogleAdsApi();
      if ( !$google_ads_api -> is_configured() )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Google Ads API nie jest skonfigurowane.' ] );
        exit;
      }

      $google_result = $google_ads_api -> remove_ad_group( $customer_id, $external_ad_group_id );
      if ( empty( $google_result['success'] ) )
      {
        $error_message = trim( (string) ( $google_result['error'] ?? \services\GoogleAdsApi::get_setting( 'google_ads_last_error' ) ) );
        if ( $error_message === '' )
        {
          $error_message = 'Nie udalo sie usunac grupy reklam w Google Ads.';
        }

        echo json_encode( [ 'status' => 'error', 'message' => $error_message ] );
        exit;
      }
    }

    if ( !\factory\Products::delete_ad_group_local( $ad_group_id ) )
    {
      $still_exists_local = \factory\Products::get_ad_group_scope_context( $ad_group_id );
      if ( !$still_exists_local )
      {
        echo json_encode( [ 'status' => 'ok', 'message' => 'Grupa reklam zostala usunieta lokalnie.' ] );
        exit;
      }

      echo json_encode( [ 'status' => 'error', 'message' => 'Nie udalo sie usunac grupy reklam lokalnie.' ] );
      exit;
    }

    if ( $delete_scope === 'google' )
    {
      echo json_encode( [ 'status' => 'ok', 'message' => 'Usunieto grupe reklam lokalnie oraz w Google Ads.' ] );
    }
    else
    {
      echo json_encode( [ 'status' => 'ok', 'message' => 'Usunieto grupe reklam lokalnie.' ] );
    }
    exit;
  }

  static public function assign_product_scope()
  {
    $product_id = (int) \S::get( 'product_id' );
    $campaign_mode = trim( (string) \S::get( 'campaign_mode' ) );
    $campaign_id = (int) \S::get( 'campaign_id' );
    $campaign_name = trim( (string) \S::get( 'campaign_name' ) );

    $ad_group_mode = trim( (string) \S::get( 'ad_group_mode' ) );
    $ad_group_id = (int) \S::get( 'ad_group_id' );
    $ad_group_name = trim( (string) \S::get( 'ad_group_name' ) );

    $campaign_daily_budget = (float) \S::get( 'campaign_daily_budget' );
    $default_cpc = (float) \S::get( 'default_cpc' );

    if ( $campaign_daily_budget <= 0 )
    {
      $campaign_daily_budget = 50.0;
    }

    if ( $default_cpc <= 0 )
    {
      $default_cpc = 1.0;
    }

    if ( $product_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidłowy produkt.' ] );
      exit;
    }

    $product_context = \factory\Products::get_product_scope_context( $product_id );
    if ( !$product_context )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono produktu.' ] );
      exit;
    }

    $client_id = (int) ( $product_context['client_id'] ?? 0 );
    $offer_id = trim( (string) ( $product_context['offer_id'] ?? '' ) );
    $customer_id = preg_replace( '/\D+/', '', (string) ( $product_context['google_ads_customer_id'] ?? '' ) );
    $merchant_account_id = preg_replace( '/\D+/', '', (string) ( $product_context['google_merchant_account_id'] ?? '' ) );

    if ( $offer_id === '' )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Produkt nie ma offer_id (ID oferty).' ] );
      exit;
    }

    if ( $customer_id === '' )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Brak Google Ads Customer ID u klienta.' ] );
      exit;
    }

    $google_ads_api = new \services\GoogleAdsApi();
    if ( !$google_ads_api -> is_configured() )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Google Ads API nie jest skonfigurowane.' ] );
      exit;
    }

    $external_campaign_id = 0;
    $external_ad_group_id = 0;
    $resolved_campaign_name = '';
    $resolved_ad_group_name = '';

    if ( $campaign_mode === 'new' )
    {
      if ( $campaign_name === '' )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Podaj nazwę nowej kampanii.' ] );
        exit;
      }

      if ( $merchant_account_id === '' )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Brak Google Merchant Account ID u klienta (wymagane dla kampanii Shopping).' ] );
        exit;
      }

      $campaign_result = $google_ads_api -> create_standard_shopping_campaign( $customer_id, $merchant_account_id, $campaign_name, $campaign_daily_budget );

      if ( empty( $campaign_result['success'] ) )
      {
        $error_message = trim( (string) ( $campaign_result['error'] ?? \services\GoogleAdsApi::get_setting( 'google_ads_last_error' ) ) );
        echo json_encode( [ 'status' => 'error', 'message' => $error_message !== '' ? $error_message : 'Nie udało się utworzyć kampanii Standard Shopping w Google Ads.' ] );
        exit;
      }

      $external_campaign_id = (int) ( $campaign_result['campaign_id'] ?? 0 );
      $resolved_campaign_name = trim( (string) ( $campaign_result['campaign_name'] ?? $campaign_name ) );
    }
    else
    {
      if ( $campaign_id <= 0 )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Wybierz kampanię.' ] );
        exit;
      }

      $campaign_scope = \factory\Products::get_campaign_scope_context( $campaign_id );
      if ( !$campaign_scope )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono wybranej kampanii.' ] );
        exit;
      }

      if ( (int) ( $campaign_scope['client_id'] ?? 0 ) !== $client_id )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Wybrana kampania nie należy do klienta produktu.' ] );
        exit;
      }

      $external_campaign_id = (int) ( $campaign_scope['campaign_id'] ?? 0 );
      $resolved_campaign_name = trim( (string) ( $campaign_scope['campaign_name'] ?? '' ) );

      if ( $external_campaign_id <= 0 )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Wybrana kampania nie ma ID Google Ads. Wybierz kampanię zsynchronizowaną z Google Ads.' ] );
        exit;
      }
    }

    if ( $ad_group_mode === 'new' )
    {
      if ( $ad_group_name === '' )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Podaj nazwę nowej grupy reklam.' ] );
        exit;
      }

      $ad_group_result = $google_ads_api -> create_standard_shopping_ad_group_with_offer( $customer_id, $external_campaign_id, $ad_group_name, $offer_id, $default_cpc );

      if ( empty( $ad_group_result['success'] ) )
      {
        $error_message = trim( (string) ( $ad_group_result['error'] ?? \services\GoogleAdsApi::get_setting( 'google_ads_last_error' ) ) );
        echo json_encode( [ 'status' => 'error', 'message' => $error_message !== '' ? $error_message : 'Nie udało się utworzyć grupy reklam i przypisać produktu w Google Ads.' ] );
        exit;
      }

      $external_ad_group_id = (int) ( $ad_group_result['ad_group_id'] ?? 0 );
      $resolved_ad_group_name = trim( (string) ( $ad_group_result['ad_group_name'] ?? $ad_group_name ) );
    }
    else
    {
      if ( $ad_group_id <= 0 )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Wybierz grupę reklam.' ] );
        exit;
      }

      $ad_group_scope = \factory\Products::get_ad_group_scope_context( $ad_group_id );
      if ( !$ad_group_scope )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono wybranej grupy reklam.' ] );
        exit;
      }

      if ( (int) ( $ad_group_scope['campaign_id'] ?? 0 ) !== (int) $campaign_id && $campaign_mode !== 'new' )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Wybrana grupa reklam nie należy do wskazanej kampanii.' ] );
        exit;
      }

      $external_ad_group_id = (int) ( $ad_group_scope['ad_group_id'] ?? 0 );
      $resolved_ad_group_name = trim( (string) ( $ad_group_scope['ad_group_name'] ?? '' ) );

      if ( $external_ad_group_id <= 0 )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Wybrana grupa reklam nie ma ID Google Ads. Wybierz grupę zsynchronizowaną z Google Ads.' ] );
        exit;
      }

      $offer_result = $google_ads_api -> ensure_standard_shopping_offer_in_ad_group( $customer_id, $external_ad_group_id, $offer_id, $default_cpc );
      if ( empty( $offer_result['success'] ) )
      {
        $error_message = trim( (string) ( $offer_result['error'] ?? \services\GoogleAdsApi::get_setting( 'google_ads_last_error' ) ) );
        echo json_encode( [ 'status' => 'error', 'message' => $error_message !== '' ? $error_message : 'Nie udało się przypisać produktu do wybranej grupy reklam w Google Ads.' ] );
        exit;
      }
    }

    if ( $external_campaign_id <= 0 || $external_ad_group_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nie udało się przygotować docelowego scope Google Ads.' ] );
      exit;
    }

    $resolved_scope = \controls\Cron::resolve_products_scope_ids(
      $client_id,
      $external_campaign_id,
      $resolved_campaign_name,
      $external_ad_group_id,
      $resolved_ad_group_name,
      date( 'Y-m-d' )
    );

    $local_campaign_id = (int) ( $resolved_scope['campaign_id'] ?? 0 );
    $local_ad_group_id = (int) ( $resolved_scope['ad_group_id'] ?? 0 );

    if ( $local_campaign_id <= 0 || $local_ad_group_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Utworzono scope w Google Ads, ale nie udało się zsynchronizować mapowania lokalnego.' ] );
      exit;
    }

    if ( !\factory\Products::assign_product_scope( $product_id, $local_campaign_id, $local_ad_group_id ) )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Produkt dodano do Google Ads, ale nie udało się zapisać przypisania lokalnie.' ] );
      exit;
    }

    \factory\Products::add_product_comment(
      $product_id,
      'Przypisano produkt do Google Ads: kampania #' . $external_campaign_id . ' (' . $resolved_campaign_name . '), grupa reklam #' . $external_ad_group_id . ' (' . $resolved_ad_group_name . ').'
    );

    echo json_encode( [
      'status' => 'ok',
      'campaign_id' => $local_campaign_id,
      'ad_group_id' => $local_ad_group_id,
      'external_campaign_id' => $external_campaign_id,
      'external_ad_group_id' => $external_ad_group_id
    ] );
    exit;
  }

  static public function comment_add()
  {
    $product_id = \S::get( 'product_id' );
    $date = \S::get( 'date' );
    $comment = \S::get( 'comment' );

    if ( \factory\Products::add_product_comment( $product_id, $comment, $date ) )
    {
      echo json_encode( [ 'status' => 'ok' ] );
    }
    else
      echo json_encode( [ 'status' => 'error' ] );
    exit;
  }

  static public function comment_delete()
  {
    $comment_id = \S::get( 'comment_id' );

    if ( \factory\Products::delete_product_comment( $comment_id ) )
    {
      echo json_encode( [ 'status' => 'ok' ] );
    }
    else
      echo json_encode( [ 'status' => 'error' ] );
    exit;
  }

  static public function get_product_data() {
    $product_id = \S::get( 'product_id' );

    $product_name = \factory\Products::get_product_name( $product_id );
    $product_title = \factory\Products::get_product_data( $product_id, 'title' );
    $product_description = \factory\Products::get_product_data( $product_id, 'description' );
    $google_product_category = \factory\Products::get_product_data( $product_id, 'google_product_category' );
    $product_url = \factory\Products::get_product_data( $product_id, 'product_url' );

    echo json_encode( [ 'status' => 'ok', 'product_details' => [
      'name' => $product_name,
      'title' => $product_title,
      'description' => $product_description,
      'google_product_category' => $google_product_category,
      'product_url' => $product_url
    ] ] );
    exit;
  }

  static public function get_product_merchant_sync_logs()
  {
    $product_id = (int) \S::get( 'product_id' );
    $limit = (int) \S::get( 'limit' );

    if ( $product_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidłowe ID produktu.' ] );
      exit;
    }

    if ( $limit <= 0 )
    {
      $limit = 50;
    }

    $logs = \factory\Products::get_product_merchant_sync_logs( $product_id, $limit );

    echo json_encode( [
      'status' => 'ok',
      'logs' => $logs
    ] );
    exit;
  }

  static public function delete_product_merchant_sync_log()
  {
    $log_id = (int) \S::get( 'log_id' );

    if ( $log_id <= 0 )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidłowe ID logu.' ] );
      exit;
    }

    $result = \factory\Products::delete_product_merchant_sync_log( $log_id );

    echo json_encode( [ 'status' => $result ? 'ok' : 'error' ] );
    exit;
  }

  static public function ai_suggest()
  {
    $product_id = \S::get( 'product_id' );
    $field = \S::get( 'field' );
    $provider = \S::get( 'provider' ) ?: 'openai';

    if ( $provider === 'claude' )
    {
      if ( \services\GoogleAdsApi::get_setting( 'claude_enabled' ) === '0' )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Claude jest wyłączony. Włącz go w Ustawieniach.' ] );
        exit;
      }
      if ( !\services\ClaudeApi::is_configured() )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Klucz API Claude nie jest skonfigurowany. Przejdź do Ustawień.' ] );
        exit;
      }
    }
    else if ( $provider === 'gemini' )
    {
      if ( \services\GoogleAdsApi::get_setting( 'gemini_enabled' ) === '0' )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Gemini jest wyłączony. Włącz go w Ustawieniach.' ] );
        exit;
      }
      if ( !\services\GeminiApi::is_configured() )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Klucz API Gemini nie jest skonfigurowany. Przejdź do Ustawień.' ] );
        exit;
      }
    }
    else
    {
      if ( \services\GoogleAdsApi::get_setting( 'openai_enabled' ) === '0' )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'OpenAI jest wyłączony. Włącz go w Ustawieniach.' ] );
        exit;
      }
      if ( !\services\OpenAiApi::is_configured() )
      {
        echo json_encode( [ 'status' => 'error', 'message' => 'Klucz API OpenAI nie jest skonfigurowany. Przejdź do Ustawień.' ] );
        exit;
      }
    }

    $product = \factory\Products::get_product_full_context( $product_id );
    if ( !$product )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono produktu.' ] );
      exit;
    }

    // Pobierz treść strony produktu jeśli podano URL
    $product_url = \S::get( 'product_url' );
    $keyword_source_url = self::normalize_keyword_source_url( $product_url );
    $page_content = '';
    if ( $product_url && filter_var( $product_url, FILTER_VALIDATE_URL ) )
    {
      $page_content = \services\OpenAiApi::fetch_page_content( $product_url );
    }

    $keyword_terms = [];
    $warnings = [];

    $should_enrich_with_keyword_planner = in_array( $field, [ 'title', 'description' ], true )
      && in_array( $provider, [ 'openai', 'claude', 'gemini' ], true );

    if ( $should_enrich_with_keyword_planner && $keyword_source_url !== '' && filter_var( $keyword_source_url, FILTER_VALIDATE_URL ) )
    {
      $keyword_terms = \factory\Products::get_cached_keyword_planner_terms( $product_id, $keyword_source_url, 15 );

      if ( empty( $keyword_terms ) )
      {
        $ads_context = \factory\Products::get_product_ads_keyword_context( $product_id );
        $customer_id = trim( (string) ( $ads_context['google_ads_customer_id'] ?? '' ) );

        if ( $customer_id !== '' )
        {
          $google_ads_api = new \services\GoogleAdsApi();
          $fetched_terms = $google_ads_api -> generate_keyword_ideas_from_url( $customer_id, $keyword_source_url, 40 );

          if ( $fetched_terms !== false )
          {
            \factory\Products::replace_keyword_planner_terms( $product_id, $keyword_source_url, $customer_id, $fetched_terms );
            $keyword_terms = \factory\Products::get_cached_keyword_planner_terms( $product_id, $keyword_source_url, 15 );
          }
          else
          {
            $last_error = trim( (string) \services\GoogleAdsApi::get_setting( 'google_ads_last_error' ) );
            $warnings[] = 'Nie udało się pobrać fraz z Google Ads Keyword Planner. ' . ( $last_error !== '' ? 'Szczegóły: ' . $last_error : '' );
          }
        }
        else
        {
          $warnings[] = 'Brak Google Ads Customer ID u klienta — pominięto frazy z Keyword Planner.';
        }
      }
    }

    $context = [
      'original_name'       => $product['name'],
      'current_title'       => \factory\Products::get_product_data( $product_id, 'title' ),
      'current_description' => \factory\Products::get_product_data( $product_id, 'description' ),
      'current_category'    => \factory\Products::get_product_data( $product_id, 'google_product_category' ),
      'offer_id'            => $product['offer_id'],
      'impressions_30'      => $product['impressions_30'] ?? 0,
      'clicks_30'           => $product['clicks_30'] ?? 0,
      'ctr'                 => $product['ctr'] ?? 0,
      'cost'                => $product['cost'] ?? 0,
      'conversions'         => $product['conversions'] ?? 0,
      'conversions_value'   => $product['conversions_value'] ?? 0,
      'roas'                => $product['roas'] ?? 0,
      'custom_label_4'      => \factory\Products::get_product_data( $product_id, 'custom_label_4' ),
      'page_content'        => $page_content,
      'keyword_planner_terms' => $keyword_terms,
    ];

    $api_map = [
      'claude' => \services\ClaudeApi::class,
      'gemini' => \services\GeminiApi::class,
    ];
    $api = $api_map[ $provider ] ?? \services\OpenAiApi::class;

    switch ( $field )
    {
      case 'title':
        $result = $api::suggest_title( $context );
        break;
      case 'description':
        $result = $api::suggest_description( $context );
        break;
      case 'category':
        $result = $api::suggest_category( $context );
        break;
      default:
        $result = [ 'status' => 'error', 'message' => 'Nieznane pole: ' . $field ];
    }

    $result['provider'] = $provider;

    if ( $product_url && !$page_content )
      $warnings[] = 'Nie udało się pobrać treści ze strony produktu (strona może blokować dostęp). Sugestia oparta tylko na nazwie produktu.';
    elseif ( $page_content )
      $result['page_fetched'] = true;

    if ( !empty( $warnings ) )
      $result['warning'] = implode( ' ', array_filter( $warnings ) );

    if ( !empty( $keyword_terms ) )
    {
      $result['keyword_planner_terms_used'] = true;
      $result['keyword_planner_terms_count'] = count( $keyword_terms );
    }

    echo json_encode( $result );
    exit;
  }


  static public function get_products()
  {
    $client_id = \S::get( 'client_id' );
    $campaign_id = (int) \S::get( 'campaign_id' );
    $ad_group_id = (int) \S::get( 'ad_group_id' );
    $limit = \S::get( 'length' ) ? \S::get( 'length' ) : 10;
    $start = \S::get( 'start' ) ? \S::get( 'start' ) : 0;

    $order_dir = $_POST['order'][0]['dir'] ? strtoupper( $_POST['order'][0]['dir'] ) : 'DESC';
    $order_name = $_POST['order'][0]['name'] ? $_POST['order'][0]['name'] : 'clicks';
    $search = trim( (string) \S::get( 'search_text' ) );
    $filter_cl4 = trim( (string) \S::get( 'filter_cl4' ) );

    // ➊ MIN/MAX ROAS dla kontekstu klienta (opcjonalnie z filtrem search)
    $bounds    = \factory\Products::get_roas_bounds( (int) $client_id, $search, $campaign_id, $ad_group_id, $filter_cl4 );
    $roas_min  = (float)$bounds['min'];
    $roas_max  = (float)$bounds['max'];
    // zabezpieczenie przed dzieleniem przez 0
    if ($roas_min === $roas_max) {
        $roas_max = $roas_min + 0.000001;
    }

    // ➋ Helper do paska performance (lokalna funkcja)
    $renderPerfBar = function (float $value, float $min, float $max): string
    {
        // normalizacja 0..1
        $t = ($value - $min) / ($max - $min);
        if ($t < 0) $t = 0;
        if ($t > 1) $t = 1;

        // szerokości
        $minPx = 20;        // minimalna długość paska
        $maxPx = 120;       // szerokość „toru”
        $fill  = (int)round($minPx + $t * ($maxPx - $minPx));

        // kolor od #E74C3C (czerwony) do #2ECC71 (zielony)
        $from = [231, 76, 60];
        $to   = [ 46,204,113];
        $r = (int)round($from[0] + ($to[0] - $from[0]) * $t);
        $g = (int)round($from[1] + ($to[1] - $from[1]) * $t);
        $b = (int)round($from[2] + ($to[2] - $from[2]) * $t);
        $hex = sprintf('#%02X%02X%02X', $r, $g, $b);

        // prosty pasek (tor + wypełnienie)
        return '<div class="roas-perf-wrap" title="Min: '.number_format($min,2).' | Max: '.number_format($max,2).'" style="margin-top:4px;width:'.$maxPx.'px;height:8px;background:#eee;border-radius:4px;overflow:hidden;">
                  <div class="roas-perf-fill" style="height:100%;width:'.$fill.'px;background:'.$hex.';"></div>
                </div>';
    };

    $db_results = \factory\Products::get_products( $client_id, $search, $limit, $start, $order_name, $order_dir, $campaign_id, $ad_group_id, $filter_cl4 );
    $recordsTotal = \factory\Products::get_records_total_products( $client_id, $search, $campaign_id, $ad_group_id, $filter_cl4 );

    // Sredni CR konta — do obliczenia progu klikniec
    $account_cr = \factory\Products::get_account_conversion_rate( (int) $client_id );
    if ( $account_cr <= 0 ) $account_cr = 0.02; // fallback 2%

    $data['draw'] = \S::get( 'draw' );
    $data['recordsTotal'] = $recordsTotal;
    $data['recordsFiltered'] = $recordsTotal;
    $data['data'] = [];

    foreach ( $db_results as $row )
    {
      $custom_class = '';
      $custom_label_4 = \factory\Products::get_product_data( $row['product_id'], 'custom_label_4' );
      $custom_name = \factory\Products::get_product_data( $row['product_id'], 'title' );
      $product_url = trim( (string) \factory\Products::get_product_data( $row['product_id'], 'product_url' ) );

      if ( $custom_name )
      {
        $row['name'] = $custom_name;
        $custom_class = 'custom_name';
      }

      if ( $custom_label_4 == 'deleted' )
        $custom_class = 'text-danger';

      $custom_label_4_color = '';
      if ( $custom_label_4 == 'bestseller' )
        $custom_label_4_color = 'background-color:rgb(96, 119, 102); color: #FFF;';
      else if ( $custom_label_4 == 'deleted' )
        $custom_label_4_color = 'background-color:rgb(255, 0, 0); color: #FFF;';
      else if ( $custom_label_4 == 'zombie' )
        $custom_label_4_color = 'background-color:rgb(58, 58, 58); color: #FFF;';
      else if ( $custom_label_4 == 'pla_single' )
        $custom_label_4_color = 'background-color:rgb(49, 184, 9); color: #FFF;';
      else if ( $custom_label_4 == 'pla' )
        $custom_label_4_color = 'background-color:rgb(74, 63, 136); color: #FFF;';
      else if ( $custom_label_4 == 'paused' )
        $custom_label_4_color = 'background-color:rgb(143, 143, 143); color: #FFF;';

      // ➌ ROAS – liczba + pasek performance
      $roasValue     = (float)$row['roas'];
        $roasDisplay   = (int) round( $roasValue, 0 );
      $roasNumeric   = ($roasValue <= (float)$row['min_roas'])
          ? '<span class="text-danger text-bold">' . $roasDisplay . '</span>'
          : $roasDisplay;

      $roasPerfBar   = $renderPerfBar($roasValue, $roas_min, $roas_max);
      $roasCellHtml  = '<div class="roas-cell">'.$roasNumeric.$roasPerfBar.'</div>';
      $product_url_html = $product_url !== ''
        ? '<a href="' . htmlspecialchars( $product_url ) . '" target="_blank" rel="noopener noreferrer" title="' . htmlspecialchars( $product_url ) . '"><i class="fa-solid fa-up-right-from-square"></i> Otworz</a>'
        : '';

      // Algorytm ostrzezen produktowych
      $warnings = [];
      $clicks = (int) $row['clicks'];
      $conversions = (float) $row['conversions'];
      $ctr = (float) $row['ctr'];
      $min_roas_val = (float) $row['min_roas'];
      $click_threshold = (int) ceil( 3 * ( 1 / $account_cr ) );

      // 1. Niski CTR po 30+ klikniec
      if ( $clicks >= 30 && $ctr < 0.5 )
      {
        $warnings[] = 'Niski CTR (' . round( $ctr, 2 ) . '%) po ' . $clicks . ' kliknieciach — prawdopodobny problem z tytulem, zdjeciem lub cena.';
      }

      // 2. Zero konwersji po progu klikniec
      if ( $clicks >= $click_threshold && $conversions == 0 )
      {
        $warnings[] = 'Brak konwersji po ' . $clicks . ' kliknieciach (prog: ' . $click_threshold . '). Produkt prawdopodobnie nie sprzedaje sie z reklam.';
      }

      // 3. ROAS ponizej progu rentownosci
      if ( $clicks >= $click_threshold && $conversions > 0 && $min_roas_val > 0 && $roasValue < $min_roas_val )
      {
        $warnings[] = 'ROAS ' . $roasDisplay . '% ponizej progu rentownosci (' . (int) $min_roas_val . '%). Rozważ optymalizacje lub wylaczenie.';
      }

      // 4. Strefa zolta — ROAS bliski progu (80-100% min_roas)
      if ( $clicks >= $click_threshold && $conversions > 0 && $min_roas_val > 0 && $roasValue >= $min_roas_val && $roasValue < $min_roas_val * 1.5 )
      {
        $warnings[] = 'ROAS ' . $roasDisplay . '% — blisko progu rentownosci (' . (int) $min_roas_val . '%). Daj jeszcze 50-100 klikniec lub zoptymalizuj listing.';
      }

      $warningHtml = '';
      if ( !empty( $warnings ) )
      {
        $warningTitle = htmlspecialchars( implode( "\n", $warnings ), ENT_QUOTES );
        $warningHtml = '<span class="product-warning-icon" data-warnings="' . $warningTitle . '" title="Produkt ma problemy"><i class="fa-solid fa-triangle-exclamation"></i></span>';
      }

      $data['data'][] = [
        '', // checkbox column
        $row['product_id'],
        $row['offer_id'],
        htmlspecialchars( (string) ( $row['campaign_name'] ?? '' ) ),
        htmlspecialchars( (string) ( $row['ad_group_name'] ?? '' ) ),
        $product_url_html,
        '<div class="table-product-title" product_id="' . $row['product_id'] . '">
          <a href="/products/product_history/client_id=' . $client_id . '&product_id=' . $row['product_id'] . '&campaign_id=' . (int) ( $row['campaign_id'] ?? 0 ) . '&ad_group_id=' . (int) ( $row['ad_group_id'] ?? 0 ) . '" target="_blank" class="' . $custom_class . '">
            ' . $row['name'] . '
          </a>
          <span class="edit-product-title" product_id="' . $row['product_id'] . '">
            <i class="fa fa-pencil"></i>
          </span>
        </div>',
        $warningHtml,
        $row['impressions'],
        $row['impressions_30'],
        $row['clicks'],
        $row['clicks_30'],
        round( $row['ctr'], 2 ) . '%',
        \S::number_display( $row['cost'] ),
        \S::number_display( $row['cpc'] ),
        round( $row['conversions'], 2 ),
        \S::number_display( $row['conversions_value'] ),
        $roasCellHtml,
        '<input type="text" class="form-control min_roas" product_id="' . $row['product_id'] . '" value="' . $row['min_roas'] . '" style="width: 100px;">',
        '',
        '<input type="text" class="form-control custom_label_4" product_id="' . $row['product_id'] . '" value="' . $custom_label_4 . '" style="' . $custom_label_4_color . '">',
        '<div class="btn-group btn-group-sm products-row-actions" role="group">'
          . '<button type="button" class="btn btn-primary assign-product-scope" product_id="' . $row['product_id'] . '" title="Dodaj produkt do kampanii/grupy"><i class="fa-solid fa-diagram-project"></i></button>'
          . '<button type="button" class="btn btn-secondary view-merchant-logs" product_id="' . $row['product_id'] . '" title="Pokaż logi synchronizacji Merchant"><i class="fa-solid fa-clock-rotate-left"></i></button>'
          . '<button type="button" class="btn btn-danger delete-product" product_id="' . $row['product_id'] . '" title="Usuń produkt"><i class="fa-solid fa-trash"></i></button>'
        . '</div>'
      ];
    }

    echo json_encode( $data );
    exit;
  }

  static public function delete_product() {
    $product_id = \S::get( 'product_id' );

    if ( \factory\Products::delete_product( $product_id ) )
      echo json_encode( [ 'status' => 'ok' ] );
    else
      echo json_encode( [ 'status' => 'error' ] );
    exit;
  }

  static public function delete_products() {
    $product_ids = \S::get( 'product_ids' );

    if ( !is_array( $product_ids ) || empty( $product_ids ) ) {
      echo json_encode( [ 'status' => 'error', 'message' => 'Brak produktów do usunięcia' ] );
      exit;
    }

    if ( \factory\Products::delete_products( $product_ids ) )
      echo json_encode( [ 'status' => 'ok' ] );
    else
      echo json_encode( [ 'status' => 'error', 'message' => 'Błąd podczas usuwania produktów' ] );
    exit;
  }

  static public function save_min_roas()
  {
    $product_id = \S::get( 'product_id' );
    $min_roas = \S::get( 'min_roas' );

    if ( \factory\Products::save_min_roas( $product_id, $min_roas ) )
    {
      echo json_encode( [ 'status' => 'ok' ] );
    }
    else
      echo json_encode( [ 'status' => 'error' ] );
    exit;
  }

  static public function get_distinct_cl4()
  {
    $client_id = (int) \S::get( 'client_id' );
    $values = \factory\Products::get_distinct_custom_label_4( $client_id );
    echo json_encode( [ 'status' => 'ok', 'values' => $values ] );
    exit;
  }

  static public function save_custom_label_4()
  {
    $product_id = \S::get( 'product_id' );
    $custom_label_4 = \S::get( 'custom_label_4' );

    if ( \factory\Products::set_product_data( $product_id, 'custom_label_4', $custom_label_4 ) )
    {
      \factory\Products::add_product_comment( $product_id, 'Zmiana etykiety 4 na: ' . $custom_label_4 );
      echo json_encode( [ 'status' => 'ok' ] );
    }
    else
      echo json_encode( [ 'status' => 'error' ] );
    exit;
  }

  static public function product_history()
  {
    $client_id = \S::get( 'client_id' );
    $product_id = \S::get( 'product_id' );
    $campaign_id = (int) \S::get( 'campaign_id' );
    $ad_group_id = (int) \S::get( 'ad_group_id' );

    return \Tpl::view( 'products/product_history', [
      'client_id' => $client_id,
      'product_id' => $product_id,
      'campaign_id' => $campaign_id,
      'ad_group_id' => $ad_group_id,
      'min_roas' => \factory\Products::get_min_roas( $product_id )
    ] );
  }

  static public function get_product_history_table()
  {
    $client_id= \S::get( 'client_id' );
    $product_id = \S::get( 'product_id' );
    $campaign_id = (int) \S::get( 'campaign_id' );
    $ad_group_id = (int) \S::get( 'ad_group_id' );
    $start = \S::get( 'start' ) ? \S::get( 'start' ) : 0;
    $limit = \S::get( 'length' ) ? \S::get( 'length' ) : 10;

    $db_results = \factory\Products::get_product_history( $client_id, $product_id, $start, $limit, $campaign_id, $ad_group_id );
    $recordsTotal = \factory\Products::get_records_total_product_history( $client_id, $product_id, $campaign_id, $ad_group_id );

    $data['draw'] = \S::get( 'draw' );
    $data['recordsTotal'] = $recordsTotal;
    $data['recordsFiltered'] = $recordsTotal;
    $data['data'] = [];

    foreach ( $db_results as $row )
    {
      $roas_value = ( $row['cost'] > 0) ? ( $row['conversions_value'] / $row['cost'] ) * 100 : 0;
      $roas = number_format( $roas_value, 0, '.', '' ) . '%';

      $comment_data = \factory\Products::get_product_comment_by_date( $product_id, $row['date_add'] );
      $comment_html = '';
      if ( $comment_data )
      {
        $comment_html = '<div class="comment-cell">
          <span class="comment-text">' . htmlspecialchars( $comment_data['comment'] ) . '</span>
          <a href="#" class="delete-comment" data-comment-id="' . $comment_data['id'] . '">Usuń</a>
        </div>';
      }

      $data['data'][] = [
        $row['id'],
        $row['impressions'],
        $row['clicks'],
        round( $row['ctr'], 2 ) . '%',
        \S::number_display( $row['cost'] ),
        $row['conversions'],
        \S::number_display( $row['conversions_value'] ),
        $roas,
        $row['date_add'],
        $comment_html,
      ];
    }

    echo json_encode( $data );
    exit;
  }

  static public function get_product_history_table_chart()
  {
    $client_id = \S::get( 'client_id' );
    $product_id = \S::get( 'product_id' );
    $campaign_id = (int) \S::get( 'campaign_id' );
    $ad_group_id = (int) \S::get( 'ad_group_id' );
    $limit = \S::get( 'length' ) ? \S::get( 'length' ) : 360;
    $start = \S::get( 'start' ) ? \S::get( 'start' ) : 0;

    $db_results = \factory\Products::get_product_history_30( $client_id, $product_id, $start, $limit, $campaign_id, $ad_group_id );

    $impressions = [];
    $clicks = [];
    $ctr = [];
    $cost = [];
    $conversions = [];
    $conversions_value = [];
    $roas = [];
    $dates = [];

    foreach ( $db_results as $row )
    {
      $impressions[] = (int)$row['impressions'];
      $clicks[] = (int)$row['clicks'];
      $ctr[] = (float)$row['ctr'];
      $cost[] = (float)$row['cost'];
      $conversions[] = (int)$row['conversions'];
      $conversions_value[] = (float)$row['conversions_value'];
      $roas[] = (float)$row['roas_all_time'];
      $dates[] = $row['date_add'];
    }

    $chart_data = [
      [
        'name' => 'Wyświetlenia',
        'data' => $impressions,
        'visible' => false
      ], [
        'name' => 'Kliknięcia',
        'data' => $clicks,
        'visible' => false
      ], [
        'name' => 'CTR',
        'data' => $ctr,
        'visible' => false
      ], [
        'name' => 'Koszt',
        'data' => $cost,
        'visible' => false
      ], [
        'name' => 'Konwersje',
        'data' => $conversions,
        'visible' => false
      ], [
        'name' => 'Wartość konwersji',
        'data' => $conversions_value,
        'visible' => false
      ], [
        'name' => 'ROAS',
        'data' => $roas
      ]
    ];

    echo json_encode([
      'chart_data' => $chart_data,
      'dates' => $dates,
      'comments' => \factory\Products::get_product_comments( $product_id ),
    ]);
    exit;
  }

  static public function save_product_data()
  {
    $product_id = \S::get( 'product_id' );
    $custom_title = \S::get( 'custom_title' );
    $custom_description = \S::get( 'custom_description' );
    $google_product_category = \S::get( 'google_product_category' );
    $product_url = \S::get( 'product_url' );

    $old_title = (string) \factory\Products::get_product_data( $product_id, 'title' );
    $old_description = (string) \factory\Products::get_product_data( $product_id, 'description' );
    $old_category = (string) \factory\Products::get_product_data( $product_id, 'google_product_category' );

    $changed_for_merchant = [];

    if ( $product_id )
    {
      if ( $custom_title )
      {
        \factory\Products::set_product_data( $product_id, 'title', $custom_title );
        $changed_for_merchant['title'] = [ 'old' => $old_title, 'new' => (string) $custom_title ];
      }

      if ( $custom_description )
      {
        \factory\Products::set_product_data( $product_id, 'description', $custom_description );
        $changed_for_merchant['description'] = [ 'old' => $old_description, 'new' => (string) $custom_description ];
      }

      if ( $google_product_category )
      {
        \factory\Products::set_product_data( $product_id, 'google_product_category', $google_product_category );
        $changed_for_merchant['google_product_category'] = [ 'old' => $old_category, 'new' => (string) $google_product_category ];
      }

      \factory\Products::set_product_data( $product_id, 'product_url', $product_url ?: '' );

      foreach ( $changed_for_merchant as $field => $change )
      {
        if ( trim( $change['old'] ) === trim( $change['new'] ) )
        {
          continue;
        }

        $log_old = $change['old'];
        $log_new = $change['new'];

        if ( $field === 'description' )
        {
          $log_old = $log_old !== '' ? '(zmieniono)' : '';
          $log_new = '(zmieniono)';
        }

        \factory\Products::add_product_merchant_sync_log( [
          'product_id'  => $product_id,
          'field_name'  => $field,
          'old_value'   => $log_old,
          'new_value'   => $log_new,
          'sync_status' => 'local',
          'sync_source' => 'products_ui'
        ] );
      }
    }

    \factory\Products::add_product_comment( $product_id, 'Zmiana tytułu i opisu produktu.' );

    echo json_encode( [ 'status' => 'ok' ] );
    exit;
  }
}
