<?php

/**
 * @file
 * CC Soccer league management module.
 *
 * Provides complete league management functionality including:
 * - Season and Tournament management
 * - Player registration with Commerce integration
 * - Waitlist management with override system
 * - Team generation and balancing
 * - Schedule generation with time slot distribution
 * - Credits system
 * - Notification system (email/SMS)
 * - Jersey management
 * - Group invitations
 */

use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Site\Settings;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\node\NodeInterface;

/**
 * Implements hook_help().
 */
function ccsoccer_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.ccsoccer':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('CC Soccer provides complete league management for soccer leagues and tournaments.') . '</p>';
      $output .= '<h3>' . t('Features') . '</h3>';
      $output .= '<ul>';
      $output .= '<li>' . t('Season and Tournament management') . '</li>';
      $output .= '<li>' . t('Player registration with payment processing') . '</li>';
      $output .= '<li>' . t('Waitlist management with override system') . '</li>';
      $output .= '<li>' . t('Automated team generation and balancing') . '</li>';
      $output .= '<li>' . t('Schedule generation with round-robin matchups') . '</li>';
      $output .= '<li>' . t('Credits system for cancellations') . '</li>';
      $output .= '<li>' . t('Email and SMS notifications') . '</li>';
      $output .= '</ul>';
      return $output;
  }
}


/**
 * Implements hook_theme().
 */
function ccsoccer_theme($existing, $type, $theme, $path) {
  return [
    'registration_confirmation' => [
      'variables' => ['registration' => NULL, 'season' => NULL],
      'template' => 'registration-confirmation',
    ],
    'team_roster' => [
      'variables' => ['team' => NULL, 'players' => []],
      'template' => 'team-roster',
    ],
    'game_schedule' => [
      'variables' => ['games' => [], 'season' => NULL],
      'template' => 'game-schedule',
    ],
    'waitlist_notification' => [
      'variables' => ['user' => NULL, 'season' => NULL, 'expires' => NULL],
      'template' => 'waitlist-notification',
    ],
    'ccsoccer_my_registrations' => [
      'variables' => [
        'season_registrations' => [],
        'tournament_registrations' => [],
        'pending_invitations' => [],
      ],
      'template' => 'ccsoccer-my-registrations',
    ],
    'ccsoccer_group_manage' => [
      'variables' => [
        'registration' => NULL,
        'season' => NULL,
        'tournament' => NULL,
        'team' => NULL,
        'context_label' => '',
        'max_group_size' => 0,
        'accepted_count' => 0,
        'pending_count' => 0,
        'spots_left' => 0,
        'is_manager' => FALSE,
        'is_tournament' => FALSE,
        'groups_locked' => FALSE,
        'group_roster' => [],
        'sent_invitations' => [],
        'invitee_has_registration' => [],
        'invitee_names' => [],
        'invitee_display_emails' => [],
        'pending_invitations_to_me' => [],
      ],
      'template' => 'ccsoccer-group-manage',
    ],
    'ccsoccer_game_status_banner' => [
      'variables' => [
        'status' => NULL,
        'updated_time' => NULL,
        'show' => FALSE,
      ],
      'template' => 'ccsoccer-game-status-banner',
    ],
    // Note: Player next-game banner is now handled by the theme
    // (ccsoccer_theme_preprocess_page + page.html.twig template).
    'ccsoccer_dev_banner' => [
      'variables' => [
        'username' => NULL,
        'user_id' => NULL,
        'role' => NULL,
      ],
      'template' => 'ccsoccer-dev-banner',
    ],
    'ccsoccer_masquerade_block' => [
      'variables' => [
        'is_masquerading' => FALSE,
        'masquerade_user' => NULL,
        'switch_back_url' => NULL,
        'masquerade_form' => NULL,
        'dashboard_links' => [],
      ],
      'template' => 'ccsoccer-masquerade-block',
    ],
    'ccsoccer_player_credits' => [
      'variables' => [
        'balance' => NULL,
        'totals' => [],
        'credits' => [],
        'empty_message' => NULL,
      ],
      'template' => 'ccsoccer-player-credits',
    ],
    'ccsoccer_admin_user_credits' => [
      'variables' => [
        'user' => NULL,
        'balance' => NULL,
        'totals' => [],
        'credits' => [],
        'empty_message' => NULL,
      ],
      'template' => 'ccsoccer-admin-user-credits',
    ],
    'ccsoccer_tournament_series_rules' => [
      'variables' => [
        'series_name' => NULL,
        'rules' => NULL,
      ],
      'template' => 'ccsoccer-tournament-series-rules',
    ],
    'ccsoccer_footer_logo' => [
      'variables' => [
        'logo_svg' => NULL,
        'logo_png' => NULL,
      ],
      'template' => 'ccsoccer-footer-logo',
    ],
  ];
}

/**
 * Implements hook_preprocess_block().
 *
 * Add user info to site branding block in dev mode.
 */
function ccsoccer_preprocess_block(&$variables) {
  // Only modify site branding block on non-production instances.
  if ($variables['plugin_id'] !== 'system_branding_block') {
    return;
  }

  $instance = Settings::get('site_instance', 'production');
  if ($instance === 'production') {
    return;
  }

  $current_user = \Drupal::currentUser();
  $role = _ccsoccer_get_highest_role($current_user);

  // Label and color per instance.
  $instance_label = strtoupper($instance); // 'LOCAL' or 'TEST'
  $instance_color = ($instance === 'local') ? '#e67e00' : '#0073aa'; // orange for local, blue for test

  $user_info = $current_user->getAccountName() . ' (' . $role . ') #' . $current_user->id();

  if (!empty($variables['content']['site_name'])) {
    $markup = new FormattableMarkup(
      '<div class="site-name-primary">CCSoccer D11 <span class="site-instance-badge" style="background:@color;color:#fff;font-size:11px;font-weight:700;padding:1px 6px;border-radius:3px;vertical-align:middle;">@instance</span></div><div class="site-name-dev-info">@user_info</div>',
      [
        '@color'    => $instance_color,
        '@instance' => $instance_label,
        '@user_info' => $user_info,
      ]
    );
    $variables['content']['site_name']['#markup'] = $markup;
  }

  // Cache per user so banner updates when user changes.
  $variables['#cache']['contexts'][] = 'user';
  $variables['#cache']['max-age'] = 0;

  // Attach CSS library.
  $variables['#attached']['library'][] = 'ccsoccer/site-branding-dev';
}

/**
 * Implements hook_preprocess_page().
 *
 * Injects site_tagline variable - normal tagline on production,
 * instance badge + logged-in user info on local/test.
 *
 * Also injects a back-link variable on Commerce order detail pages.
 */
function ccsoccer_preprocess_page(&$variables) {
  // Inject a "Back to My Orders" link on the Commerce order detail page.
  $route_name = \Drupal::routeMatch()->getRouteName();
  if ($route_name === 'entity.commerce_order.user_view') {
    $variables['back_to_orders_url'] = \Drupal\Core\Url::fromRoute('ccsoccer.my_orders')->toString();
  }
  $instance = Settings::get('site_instance', 'production');

  if ($instance === 'production' || $instance === '') {
    $variables['site_tagline'] = 'The Recreational Soccer League of the Central Coast';
    return;
  }

  $current_user = \Drupal::currentUser();
  $role = _ccsoccer_get_highest_role($current_user);
  $user_info = $current_user->getAccountName() . ' (' . $role . ') #' . $current_user->id();

  $label = strtoupper($instance);
  $color = ($instance === 'local') ? '#e67e00' : '#0073aa';

  $variables['site_tagline'] = '<span style="background:' . $color . ';color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:3px;margin-right:8px;">' . $label . '</span>' . htmlspecialchars($user_info);

  // Don't cache this per-page since it changes per user.
  $variables['#cache']['contexts'][] = 'user';
  $variables['#cache']['max-age'] = 0;
}

/**
 * Implements hook_page_top().
 *
 * Display game status banner (always visible).
 */
function ccsoccer_page_top(array &$page_top) {
  try {
    $game_storage = \Drupal::entityTypeManager()->getStorage('game');
  }
  catch (\Exception $e) {
    // Game entity not installed yet - just don't show banner
    return;
  }
  
  $current_hour = (int) date('G');
  $is_after_3pm = $current_hour >= 15;
  $today = date('Y-m-d');
  $tomorrow = date('Y-m-d', strtotime('+1 day'));
  
  // Check for games today
  $games_today = $game_storage->getQuery()
    ->condition('game_date', $today)
    ->accessCheck(FALSE)
    ->execute();
  
  $has_games_today = !empty($games_today);
  $games_today_cancelled = FALSE;
  
  if ($has_games_today) {
    // Check if today's games are cancelled
    $games = $game_storage->loadMultiple($games_today);
    $cancelled_count = 0;
    $total_count = count($games);
    
    foreach ($games as $game) {
      if ($game->get('status')->value === 'cancelled') {
        $cancelled_count++;
      }
    }
    
    // If ALL games today are cancelled
    $games_today_cancelled = ($cancelled_count === $total_count && $total_count > 0);
  }
  
  // Determine message
  if ($has_games_today) {
    if ($is_after_3pm) {
      // After 3pm with games today
      if ($games_today_cancelled) {
        $status = 'cancelled_tonight';
        $message = t('Games are CANCELLED tonight');
      }
      else {
        $status = 'on_tonight';
        $message = t('Games are ON tonight');
      }
    }
    else {
      // Before 3pm with games today
      $status = 'before_3pm_tonight';
      $message = t("Games tonight - we'll email if cancelled");
    }
  }
  else {
    // No games today, check tomorrow
    $games_tomorrow = $game_storage->getQuery()
      ->condition('game_date', $tomorrow)
      ->accessCheck(FALSE)
      ->execute();
    
    if (!empty($games_tomorrow)) {
      $games = $game_storage->loadMultiple($games_tomorrow);
      $cancelled_count = 0;
      $total_count = count($games);
      
      foreach ($games as $game) {
        if ($game->get('status')->value === 'cancelled') {
          $cancelled_count++;
        }
      }
      
      $games_tomorrow_cancelled = ($cancelled_count === $total_count && $total_count > 0);
      
      if ($games_tomorrow_cancelled) {
        $status = 'cancelled_tomorrow';
        $message = t('Games CANCELLED for tomorrow');
      }
      else {
        $status = 'before_3pm_tomorrow';
        $message = t("Games tomorrow - we'll email if cancelled");
      }
    }
    else {
      // No games today or tomorrow
      $status = 'no_games';
      $message = t('No games scheduled');
    }
  }
  
  $page_top['game_status_banner'] = [
    '#theme' => 'ccsoccer_game_status_banner',
    '#status' => $status,
    '#message' => $message,
    '#show' => TRUE, // Always show
    '#cache' => [
      'max-age' => 300, // 5 minutes
      'contexts' => ['url.path'],
      'tags' => ['game_list'],
    ],
  ];

  // Note: Personalized player next-game banner is now handled by the theme
  // (ccsoccer_theme_preprocess_page + page.html.twig), not hook_page_top.
}

// _ccsoccer_build_player_next_game_banner() removed.
// Personalized banner is now rendered by the theme layer:
//   - ccsoccer_theme.theme: _ccsoccer_build_player_next_game_data()
//   - page.html.twig: {% if player_next_game %} block

/**
 * Helper: Determine user's highest role for dev banner.
 *
 * Priority order:
 * 1. Administrator
 * 2. Board Member
 * 3. Slofriendly
 * 4. Tournament Team Captain (has registrations where is_captain = TRUE)
 * 5. Player (has any registrations)
 * 6. Authenticated User
 * 7. Anonymous
 */
function _ccsoccer_get_highest_role($current_user) {
  // Anonymous
  if ($current_user->isAnonymous()) {
    return 'Anonymous';
  }
  
  $uid = $current_user->id();
  
  // Load full user account
  $account = \Drupal::entityTypeManager()->getStorage('user')->load($uid);
  if (!$account) {
    return 'Authenticated User';
  }
  
  // Check Drupal roles (highest priority first)
  $roles = $account->getRoles(TRUE); // Exclude authenticated
  
  if (in_array('administrator', $roles)) {
    return 'Administrator';
  }
  
  if (in_array('board_member', $roles)) {
    return 'Board Member';
  }
  
  if (in_array('slofriendly', $roles)) {
    return 'Slofriendly';
  }
  
  // Check for Tournament Team Captain (has registration with is_captain = TRUE)
  try {
    $registration_storage = \Drupal::entityTypeManager()->getStorage('ccsoccer_registration');
    $captain_query = $registration_storage->getQuery()
      ->condition('player', $uid)
      ->condition('is_captain', TRUE)
      ->condition('status', ['paid', 'active'], 'IN')
      ->accessCheck(FALSE)
      ->range(0, 1);
    
    if (!empty($captain_query->execute())) {
      return 'Tournament Team Captain';
    }
  }
  catch (\Exception $e) {
    // Entity might not exist yet
  }
  
  // Check for Player (has any registrations)
  try {
    $registration_storage = \Drupal::entityTypeManager()->getStorage('ccsoccer_registration');
    $player_query = $registration_storage->getQuery()
      ->condition('player', $uid)
      ->condition('status', ['paid', 'active'], 'IN')
      ->accessCheck(FALSE)
      ->range(0, 1);
    
    if (!empty($player_query->execute())) {
      return 'Player';
    }
  }
  catch (\Exception $e) {
    // Entity might not exist yet
  }
  
  // Default: just authenticated
  return 'Authenticated User';
}

/**
 * Implements hook_cron().
 *
 * Handles:
 * - 3pm game cancellation reminders
 * - Override expiration checking (every hour)
 * - Credit expiration (1 year) - TODO
 * - Notification queue processing
 */
function ccsoccer_cron() {
  $current_hour = (int) date('G');
  $current_minute = (int) date('i');
  
  // Send 3pm reminders for cancelled games (run between 3:00-3:05 PM)
  if ($current_hour === 15 && $current_minute < 5) {
    $today = date('Y-m-d');
    $game_storage = \Drupal::entityTypeManager()->getStorage('game');
    
    // Get today's games
    $games_today = $game_storage->getQuery()
      ->condition('game_date', $today)
      ->condition('status', 'cancelled')
      ->accessCheck(FALSE)
      ->execute();
    
    if (!empty($games_today)) {
      $games = $game_storage->loadMultiple($games_today);
      
      // Group by date (should all be today, but keeping structure consistent)
      $date_info = [
        'date' => $today,
        'games' => $games,
        'leagues' => [],
      ];
      
      // Get league names
      foreach ($games as $game) {
        if (!$game->get('season')->isEmpty()) {
          $season = $game->get('season')->entity;
          if ($season && !$season->get('league')->isEmpty()) {
            $league = $season->get('league')->entity;
            if ($league) {
              $league_name = $league->label();
              if (!in_array($league_name, $date_info['leagues'])) {
                $date_info['leagues'][] = $league_name;
              }
            }
          }
        }
      }
      
      // Check if we've already sent initial notification
      $config = \Drupal::config('ccsoccer.game_status');
      $log = $config->get('notification_log') ?? [];
      
      $initial_sent = FALSE;
      $reminder_sent = FALSE;
      
      foreach ($log as $entry) {
        if ($entry['date'] === $today) {
          $initial_sent = !empty($entry['initial_sent']);
          $reminder_sent = !empty($entry['reminder_sent']);
          break;
        }
      }
      
      // Only send reminder if initial was sent but reminder wasn't
      if ($initial_sent && !$reminder_sent) {
        $notification_service = \Drupal::service('ccsoccer.notification');
        $count = $notification_service->sendGameCancellationReminder($date_info);
        
        // Log reminder sent
        $config_editable = \Drupal::configFactory()->getEditable('ccsoccer.game_status');
        $log = $config_editable->get('notification_log') ?? [];
        
        foreach ($log as $key => $entry) {
          if ($entry['date'] === $today) {
            $log[$key]['reminder_sent'] = time();
            break;
          }
        }
        
        $config_editable->set('notification_log', $log)->save();
        
        \Drupal::logger('ccsoccer')->notice('Sent 3pm reminder for @count cancelled games today.', [
          '@count' => count($games),
        ]);
      }
    }
  }
  
  // Check for override expirations (send reminder 12 hours before expiry)
  $notification_service = \Drupal::service('ccsoccer.notification');
  $entity_type_manager = \Drupal::entityTypeManager();
  $registration_storage = $entity_type_manager->getStorage('ccsoccer_registration');
  
  // Find registrations with overrides expiring in the next 12 hours
  // that haven't been notified yet
  $twelve_hours_from_now = time() + (12 * 60 * 60);
  $now = time();
  
  $query = $registration_storage->getQuery()
    ->condition('has_override', TRUE)
    ->condition('override_expires', $now, '>')
    ->condition('override_expires', $twelve_hours_from_now, '<=')
    ->accessCheck(FALSE);
  
  $registration_ids = $query->execute();
  
  if (!empty($registration_ids)) {
    $registrations = $registration_storage->loadMultiple($registration_ids);
    
    foreach ($registrations as $registration) {
      // Check if we've already sent notification for this override
      if (!$registration->get('override_notified')->isEmpty()) {
        continue; // Already notified
      }
      
      $user = $registration->get('user_id')->entity;
      $season = $registration->get('season')->entity;
      
      if ($user && $season) {
        $expires = $registration->get('override_expires')->value;
        $notification_service->sendOverrideExpirationReminder($user, $season, $expires);
        
        // Mark as notified
        $registration->set('override_notified', time());
        $registration->save();
        
        \Drupal::logger('ccsoccer')->notice('Override expiration reminder sent to @user for @season',
          [
            '@user' => $user->getDisplayName(),
            '@season' => $season->label(),
          ]
        );
      }
    }
  }
  
  // TODO: Implement credit expiration
  // \Drupal::service('ccsoccer.credit_manager')->expireCredits();
}

/**
 * Implements hook_ENTITY_TYPE_presave() for registration entity.
 *
 * Handles registration logic before saving.
 */
function ccsoccer_registration_presave(EntityInterface $registration) {
  // TODO: Add any pre-save logic for registrations
  // - Validate capacity
  // - Check for conflicts
  // - Apply business rules
}

/**
 * Implements hook_ENTITY_TYPE_delete() for registration entity.
 *
 * Handles cancellation logic when registration is deleted.
 */
function ccsoccer_registration_delete(EntityInterface $registration) {
  // Handle cancellation
  $registration_service = \Drupal::service('ccsoccer.registration');
  $registration_service->cancelRegistration($registration);
}

/**
 * Implements hook_form_alter().
 */
function ccsoccer_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Style the Commerce cart view form buttons.
  // The form ID includes the order ID suffix (e.g. views_form_commerce_cart_form_default_64)
  // so we match by prefix rather than exact string.
  // Uses #after_build so the 'checkout' button (added by commerce_checkout
  // module's own hook_form_alter) is guaranteed to exist when we apply classes.
  if (str_starts_with($form_id, 'views_form_commerce_cart_form_default')) {
    $form['#after_build'][] = 'ccsoccer_cart_form_after_build';
  }

  // Attach CSS to clean up notification form.
  if ($form_id == 'node_notification_form' || $form_id == 'node_notification_edit_form') {
    $form['#attached']['library'][] = 'ccsoccer/notification-form-cleanup';
    
    // Default test email/phone from current user
    $current_user = \Drupal::currentUser();
    $user = \Drupal::entityTypeManager()->getStorage('user')->load($current_user->id());
    
    if ($user && isset($form['field_test_email'])) {
      $form['field_test_email']['widget'][0]['value']['#default_value'] = $user->getEmail();
    }
    if ($user && isset($form['field_test_sms']) && $user->hasField('field_phone') && !$user->get('field_phone')->isEmpty()) {
      $form['field_test_sms']['widget'][0]['value']['#default_value'] = $user->get('field_phone')->value;
    }
    
    // Change submit button label to "Send Notifications"
    $form['actions']['submit']['#value'] = t('Send Notifications');
    
    // Hide Preview button if it exists
    if (isset($form['actions']['preview'])) {
      $form['actions']['preview']['#access'] = FALSE;
    }
    
    // Add "Test" button with secondary styling (left of Send)
    $form['actions']['send_test'] = [
      '#type' => 'submit',
      '#value' => t('Test'),
      '#submit' => ['ccsoccer_notification_send_test_submit'],
      '#limit_validation_errors' => [],
      '#weight' => -10,
      '#attributes' => [
        'class' => ['button--test-notification'],
      ],
    ];
    
    // Add JS for send confirmation
    $form['#attached']['library'][] = 'ccsoccer/notification-confirm';
    
    // Store field names for JS to find
    $form['#attributes']['data-notification-form'] = 'true';
    
    // Make the Seasons multi-select show 10 rows
    if (isset($form['field_notification_season']['widget'])) {
      $form['field_notification_season']['widget']['#size'] = 10;
    }

    // Live SMS segment counter + "Generate from body".
    //
    // The counter is the safeguard for the field's own failure mode: SMS body
    // is optional, so the natural mistake is to write a good email and tab
    // straight past it, which silently sends nothing to text-only players. The
    // readout names that consequence with a number rather than just showing a
    // zero. It also surfaces encoding, because the characters that cost the
    // most are invisible — a pasted curly apostrophe looks identical to an
    // ASCII one but drops per-part capacity from 153 to 67, and the gateway
    // bills per part.
    if (isset($form['field_sms_body']['widget'][0]['value'])) {
      $notification_service = \Drupal::service('ccsoccer.notification');

      $form['#attached']['library'][] = 'ccsoccer/sms-counter';
      // smsOnlyCount starts NULL deliberately. The real number is scoped to the
      // selected seasons and filters, which the browser fetches from
      // /api/notification/recipient-count on load and on every filter change.
      // Seeding a league-wide figure here would flash a wrong number first, and
      // would mean two different definitions of "who is affected".
      $form['#attached']['drupalSettings']['ccsoccer']['smsCounter'] =
        $notification_service->getSmsCounterSettings() + [
          'smsOnlyCount' => NULL,
          'recipientCount' => NULL,
        ];

      // Marker the JS hooks on, so it does not depend on Drupal's generated
      // field wrapper IDs.
      $form['field_sms_body']['#attributes']['data-ccsoccer-sms-body'] = 'true';

      $form['field_sms_body']['generate'] = [
        '#type' => 'inline_template',
        '#template' => '<div class="ccsoccer-sms-generate"><button type="button" data-ccsoccer-sms-generate>{{ label }}</button></div>',
        '#context' => ['label' => t('Generate from body')],
        '#weight' => -1,
      ];
    }
  }
}

/**
 * Implements hook_commerce_cart_order_item_update().
 * 
 * Prevents duplicate season/tournament registrations from being added to cart.
 */
function ccsoccer_commerce_cart_order_item_update(\Drupal\commerce_order\Entity\OrderItemInterface $order_item, \Drupal\commerce_cart\CartInterface $cart) {
  \Drupal::logger('ccsoccer')->notice('hook_commerce_cart_order_item_update called for order item @id', ['@id' => $order_item->id()]);
  
  $purchased_entity = $order_item->getPurchasedEntity();
  if (!$purchased_entity) {
    return;
  }
  
  $product = $purchased_entity->getProduct();
  if (!$product) {
    return;
  }
  
  $product_type = $product->bundle();
  
  // Only check season/tournament registrations
  if (!in_array($product_type, ['season_registration', 'tournament_registration'])) {
    return;
  }
  
  // Force quantity to 1 for registrations
  if ($order_item->getQuantity() != 1) {
    \Drupal::logger('ccsoccer')->notice('Forcing quantity to 1 for registration product');
    $order_item->setQuantity(1);
  }
}

/**
 * Implements hook_commerce_cart_order_item_add().
 * 
 * Prevents duplicate season/tournament registrations from being added to cart.
 * Since the item is already added when this hook fires, we check for duplicates
 * and immediately remove the new item if it's a duplicate.
 */
function ccsoccer_commerce_cart_order_item_add(\Drupal\commerce_order\Entity\OrderItemInterface $order_item, \Drupal\commerce_cart\CartInterface $cart, $combine, $save_cart) {
  \Drupal::logger('ccsoccer')->notice('hook_commerce_cart_order_item_add called');
  
  $purchased_entity = $order_item->getPurchasedEntity();
  if (!$purchased_entity) {
    return;
  }
  
  $product = $purchased_entity->getProduct();
  if (!$product) {
    return;
  }
  
  $product_type = $product->bundle();
  \Drupal::logger('ccsoccer')->notice('Product type: @type, Product ID: @id', [
    '@type' => $product_type,
    '@id' => $product->id(),
  ]);
  
  // Only check season/tournament registrations
  if (!in_array($product_type, ['season_registration', 'tournament_registration'])) {
    return;
  }
  
  // Check if this product is already in the cart (excluding the one just added)
  $duplicate_found = FALSE;
  foreach ($cart->getItems() as $existing_item) {
    // Skip if this is the item we just added
    if ($existing_item->id() == $order_item->id()) {
      continue;
    }
    
    $existing_purchased = $existing_item->getPurchasedEntity();
    if (!$existing_purchased) {
      continue;
    }
    
    $existing_product = $existing_purchased->getProduct();
    if (!$existing_product) {
      continue;
    }
    
    \Drupal::logger('ccsoccer')->notice('Comparing new product @new with existing @existing', [
      '@new' => $product->id(),
      '@existing' => $existing_product->id(),
    ]);
    
    if ($existing_product->id() == $product->id()) {
      $duplicate_found = TRUE;
      break;
    }
  }
  
  if ($duplicate_found) {
    // Duplicate found - remove the newly added item and show message
    \Drupal::logger('ccsoccer')->notice('DUPLICATE DETECTED! Removing newly added item.');
    $cart->removeItem($order_item);
    $order_item->delete();
    
    \Drupal::messenger()->addWarning(t('You already have "@product" in your cart. You cannot register for the same season/tournament multiple times.', [
      '@product' => $product->label(),
    ]));
  }
  else {
    \Drupal::logger('ccsoccer')->notice('No duplicate found, allowing add');
  }
}

/**
 * AJAX callback to update recipient count.
 */
function ccsoccer_notification_update_recipient_count(array &$form, FormStateInterface $form_state) {
  $notification_service = \Drupal::service('ccsoccer.notification');
  
  // Get field values - now supports multiple seasons
  $season_ids = [];
  $season_values = $form_state->getValue('field_notification_season');
  if (!empty($season_values)) {
    foreach ($season_values as $delta => $value) {
      if (!empty($value['target_id'])) {
        $season_ids[] = (int) $value['target_id'];
      }
    }
  }
  
  $notify_everybody = !empty($form_state->getValue('field_notify_everybody')[0]['value']);
  $exclude_registered = !empty($form_state->getValue('field_exclude_registered')[0]['value']);
  $include_waitlist = !empty($form_state->getValue('field_include_waitlist')[0]['value']);
  $include_new_users = !empty($form_state->getValue('field_include_new_users')[0]['value']);
  
  // Calculate recipient count
  $count = $notification_service->calculateRecipientCount(
    $season_ids,
    $notify_everybody,
    $exclude_registered,
    $include_waitlist,
    $include_new_users
  );
  
  return [
    '#markup' => '<strong>' . $count . ' recipients</strong>',
  ];
}

/**
 * Normalize season IDs from either widget-processed values or raw form input.
 *
 * Entity-reference widgets normalize to [['target_id' => 43]], which is what
 * $form_state->getValue() returns. A raw multi-select POST is a flat list of
 * values instead: ['43', '44']. The Test button reads getUserInput() (because
 * #limit_validation_errors stops getValue() from populating), so it sees the
 * flat shape — and the old ['target_id'] parsing dropped every season
 * silently, which is why Test always reported 0 recipients regardless of what
 * was selected.
 *
 * @param mixed $raw
 *   Season field values in either shape.
 *
 * @return array
 *   Season IDs as integers.
 */
function _ccsoccer_extract_season_ids($raw): array {
  $season_ids = [];

  if (empty($raw) || !is_array($raw)) {
    return $season_ids;
  }

  foreach ($raw as $value) {
    if (is_array($value)) {
      if (!empty($value['target_id']) && is_numeric($value['target_id'])) {
        $season_ids[] = (int) $value['target_id'];
      }
    }
    elseif (is_numeric($value)) {
      // '_none' and other non-numeric sentinels fall through here.
      $season_ids[] = (int) $value;
    }
  }

  return array_values(array_unique(array_filter($season_ids)));
}

/**
 * Submit handler for "Test" button.
 *
 * Sends test notification using form values (not requiring save first).
 */
function ccsoccer_notification_send_test_submit(array &$form, FormStateInterface $form_state) {
  $notification_service = \Drupal::service('ccsoccer.notification');
  
  // Use getUserInput() instead of getValues() since #limit_validation_errors
  // may prevent values from being fully processed
  $input = $form_state->getUserInput();
  
  // Get subject from title field
  $subject = 'Test Notification';
  if (!empty($input['title'][0]['value'])) {
    $subject = $input['title'][0]['value'];
  }
  
  // Get body from body field
  $body = '';
  if (!empty($input['body'][0]['value'])) {
    $body = $input['body'][0]['value'];
  }
  
  // Get test recipients from fields
  $test_email = NULL;
  if (!empty($input['field_test_email'][0]['value'])) {
    $test_email = trim($input['field_test_email'][0]['value']);
  }
  
  $test_sms = NULL;
  if (!empty($input['field_test_sms'][0]['value'])) {
    $test_sms = trim($input['field_test_sms'][0]['value']);
  }
  
  $sent = 0;
  $errors = [];
  
  if ($test_email) {
    if ($notification_service->sendEmail($test_email, $subject, $body)) {
      $sent++;
    }
    else {
      $errors[] = t('Failed to send test email to @email', ['@email' => $test_email]);
    }
  }
  
  // SMS text: exactly what the admin wrote, exactly as the real send uses it.
  // This used to rebuild its own body inline, so Test could deliver completely
  // different text than Send — which defeats the point of testing. An empty
  // field means no SMS here too, so Test demonstrates the real consequence of
  // leaving it blank rather than papering over it with derived text.
  $sms_body = '';
  if (!empty($input['field_sms_body'][0]['value'])) {
    $sms_body = $notification_service->normalizeForSms(trim($input['field_sms_body'][0]['value']));
  }

  if ($test_sms && $sms_body === '') {
    \Drupal::messenger()->addWarning(t('No SMS sent: the SMS message field is empty. A real send would deliver nothing to players whose preference is text only.'));
  }
  elseif ($test_sms) {
    if ($notification_service->sendSms($test_sms, $sms_body)) {
      $sent++;
    }
    else {
      $errors[] = t('Failed to send test SMS to @sms', ['@sms' => $test_sms]);
    }
  }
  
  // Calculate how many would receive this notification.
  $season_ids = _ccsoccer_extract_season_ids($input['field_notification_season'] ?? []);
  $notify_everybody = !empty($input['field_notify_everybody']['value']);
  $exclude_registered = !empty($input['field_exclude_registered']['value']);
  $include_waitlist = !empty($input['field_include_waitlist']['value']);
  $include_new_users = !empty($input['field_include_new_users']['value']);
  
  $recipient_count = $notification_service->calculateRecipientCount(
    $season_ids,
    $notify_everybody,
    $exclude_registered,
    $include_waitlist,
    $include_new_users
  );
  
  if ($sent > 0) {
    \Drupal::messenger()->addStatus(t('Test sent to @sent recipient(s). Full send would go to @count recipient(s).', [
      '@sent' => $sent,
      '@count' => $recipient_count,
    ]));
  }
  elseif (empty($test_email) && empty($test_sms)) {
    \Drupal::messenger()->addWarning(t('No test email or SMS configured. Add values to the Test Email or Test SMS fields.'));
  }
  
  foreach ($errors as $error) {
    \Drupal::messenger()->addError($error);
  }
  
  // Rebuild form instead of redirecting (stay on form)
  $form_state->setRebuild(TRUE);
}

/**
 * Implements hook_ENTITY_TYPE_insert() for node.
 *
 * The single entry point for sending a bulk notification.
 *
 * There used to be a second one: ccsoccer_notification_form_submit() was
 * appended to the node form's #submit array and called
 * ccsoccer_send_notification() when $node->isNew(). It never actually fired —
 * ContentEntityForm runs ::save() before any appended handler, so the node was
 * already saved and isNew() was always FALSE by then. Only handler ordering
 * kept that from double-sending to every recipient; reorder the array and the
 * same notification goes out twice. Removed rather than left as a trap.
 */
function ccsoccer_node_insert(NodeInterface $node) {
  if ($node->bundle() === 'notification') {
    ccsoccer_send_notification($node);
  }
}

/**
 * Helper function to send bulk notification.
 */
function ccsoccer_send_notification(NodeInterface $node) {
  $notification_service = \Drupal::service('ccsoccer.notification');
  
  // Extract field values - now supports multiple seasons
  $season_ids = [];
  if (!$node->get('field_notification_season')->isEmpty()) {
    foreach ($node->get('field_notification_season') as $item) {
      if (!empty($item->target_id)) {
        $season_ids[] = (int) $item->target_id;
      }
    }
  }
  
  $notify_everybody = !$node->get('field_notify_everybody')->isEmpty() && $node->get('field_notify_everybody')->value;
  $exclude_registered = !$node->get('field_exclude_registered')->isEmpty() && $node->get('field_exclude_registered')->value;
  $include_waitlist = !$node->get('field_include_waitlist')->isEmpty() && $node->get('field_include_waitlist')->value;
  $include_new_users = !$node->get('field_include_new_users')->isEmpty() && $node->get('field_include_new_users')->value;
  
  // Send bulk notification
  $count = $notification_service->sendBulkNotification(
    $node,
    $season_ids,
    $notify_everybody,
    $exclude_registered,
    $include_waitlist,
    $include_new_users
  );
  
  if ($count > 0) {
    \Drupal::messenger()->addStatus(t('Notification queued for @count recipients.', ['@count' => $count]));
    \Drupal::logger('ccsoccer')->info('Bulk notification queued for @count recipients from notification node @nid', [
      '@count' => $count,
      '@nid' => $node->id(),
    ]);
  }
  elseif (!$notification_service->isProduction()) {
    // sendBulk() deliberately returns 0 on non-production because the queue is
    // blocked there — nothing is queued, by design. That is NOT the same as
    // "no recipients matched", which is what this used to report: on LOCAL a
    // developer could be holding the [VERIFY] text in their hand while the
    // screen claimed nothing had been sent.
    $would_reach = $notification_service->calculateRecipientCount(
      $season_ids,
      $notify_everybody,
      $exclude_registered,
      $include_waitlist,
      $include_new_users
    );
    \Drupal::messenger()->addWarning(t('Bulk sending is blocked on @instance, so nothing was queued. The [VERIFY] copy was sent to board members. On production this would have reached @count recipient(s).', [
      '@instance' => strtoupper($notification_service->getSiteInstance()),
      '@count' => $would_reach,
    ]));
  }
  else {
    \Drupal::messenger()->addWarning(t('No recipients matched the selected criteria.'));
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for user registration form.
 *
 * Adds required fields to user registration.
 */
function ccsoccer_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Hide fields not needed on the registration form.
  // Admin-only fields are managed elsewhere (All Players page, checkout, etc.).
  // Other fields are collected during season registration or not needed at signup.
  $hidden_fields = [
    'field_skill_level',       // Admin-set via All Players page.
    'field_self_score',        // Collected during first season checkout via PlayerInfoPane.
    'field_discount_percent',  // Admin-managed discount.
    'user_picture',            // Hidden; field_player_picture is the primary picture field.
    'field_email_visible',     // Not needed at registration.
    'field_phone_visible',     // Not needed at registration.
    'field_zip_code',          // Billing info collected at checkout.
    'contact',                 // Contact settings not needed at registration.
  ];

  foreach ($hidden_fields as $field_name) {
    if (isset($form[$field_name])) {
      $form[$field_name]['#access'] = FALSE;
    }
  }

  // Restrict username to alphanumeric characters and @ sign only.
  if (isset($form['account']['name'])) {
    $form['account']['name']['#description'] = t('Only letters, numbers, and the @ sign are allowed.');
  }

  // Make key fields required on the registration form.
  $required_text_fields = [
    'field_first_name',
    'field_last_name',
  ];
  foreach ($required_text_fields as $field_name) {
    if (isset($form[$field_name])) {
      $form[$field_name]['widget'][0]['value']['#required'] = TRUE;
    }
  }

  // Make DOB required.
  if (isset($form['field_dob'])) {
    $form['field_dob']['widget'][0]['value']['#required'] = TRUE;
  }

  // Make Gender required (select field uses different widget path).
  if (isset($form['field_gender'])) {
    $form['field_gender']['widget']['#required'] = TRUE;
  }

  // Show notification preference description above the widget.
  if (isset($form['field_notification_preference'])) {
    $form['field_notification_preference']['widget']['#description_display'] = 'before';
  }

  // Attach client-side face detection for player picture uploads.
  $form['#attached']['library'][] = 'ccsoccer/face-detection';
  $form['#attached']['drupalSettings']['ccsoccer']['faceDetection'] = [
    'modelUrl' => 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model',
  ];

  // Add custom validation for username and 18+ age enforcement.
  $form['#validate'][] = 'ccsoccer_user_register_validate';

  // Add custom submit handler to redirect to confirmation page instead of
  // the front page. This runs after Drupal core's handler creates the account
  // and sends the verification email.
  $form['actions']['submit']['#submit'][] = 'ccsoccer_user_register_submit';
}

/**
 * Custom submit handler for user registration form.
 *
 * Redirects to a dedicated confirmation page so the user gets clear
 * feedback that their account was created and they need to check email.
 */
function ccsoccer_user_register_submit(&$form, FormStateInterface $form_state) {
  $form_state->setRedirect('ccsoccer.register_confirm');
}

/**
 * Implements hook_form_FORM_ID_alter() for user edit form.
 *
 * Hides admin-only fields from non-admin users editing their own profile.
 */
function ccsoccer_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $current_user = \Drupal::currentUser();
  $is_admin = $current_user->hasPermission('manage seasons') || $current_user->hasPermission('administer users');

  // Hide Drupal core user_picture from ALL users; field_player_picture
  // is the primary picture field (used in roster displays and insurance reports).
  if (isset($form['user_picture'])) {
    $form['user_picture']['#access'] = FALSE;
  }

  if (!$is_admin) {
    // Hide admin-only fields from regular players.
    $admin_only_fields = [
      'field_skill_level',
      'field_self_score',
      'field_discount_percent',
    ];

    foreach ($admin_only_fields as $field_name) {
      if (isset($form[$field_name])) {
        $form[$field_name]['#access'] = FALSE;
      }
    }
  }

  // Hide fields not needed on the profile edit form (applies to all users).
  $hidden_fields = [
    'field_email_visible',     // Not needed — small league, not configurable.
    'field_phone_visible',     // Not needed — small league, not configurable.
    'field_credits_balance',   // Legacy field — real balance is calculated live by CreditManagerService from the Credits entity. Slated for removal; hidden in the meantime.
    'timezone',                // Everyone is in Los Angeles timezone.
    'language',                // Single-language site.
  ];
  foreach ($hidden_fields as $field_name) {
    if (isset($form[$field_name])) {
      $form[$field_name]['#access'] = FALSE;
    }
  }

  // Make key fields required on the profile edit form (applies to all users).
  $required_text_fields = [
    'field_first_name',
    'field_last_name',
  ];
  foreach ($required_text_fields as $field_name) {
    if (isset($form[$field_name])) {
      $form[$field_name]['widget'][0]['value']['#required'] = TRUE;
    }
  }

  // Make DOB required.
  if (isset($form['field_dob'])) {
    $form['field_dob']['widget'][0]['value']['#required'] = TRUE;
  }

  // Make Gender required (select field uses different widget path).
  if (isset($form['field_gender'])) {
    $form['field_gender']['widget']['#required'] = TRUE;
  }

  // Attach client-side face detection for player picture uploads.
  $form['#attached']['library'][] = 'ccsoccer/face-detection';
  $form['#attached']['drupalSettings']['ccsoccer']['faceDetection'] = [
    'modelUrl' => 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model',
  ];

  // For admins: add rotate button on the user edit form.
  if ($is_admin) {
    $edited_user = $form_state->getFormObject()->getEntity();
    if ($edited_user->id() && $edited_user->hasField('field_player_picture') && !$edited_user->get('field_player_picture')->isEmpty()) {
      $form['#attached']['library'][] = 'ccsoccer/season-players';
      $file = $edited_user->get('field_player_picture')->entity;
      if ($file) {
        $form['player_picture_rotate'] = [
          '#type' => 'inline_template',
          '#template' => '<div class="player-picture-rotate-form"><button type="button" class="player-picture-rotate-btn" data-user-id="{{ user_id }}">&#x21bb; Rotate Photo 90&deg;</button></div>',
          '#context' => ['user_id' => $edited_user->id()],
          '#weight' => isset($form['field_player_picture']['#weight']) ? $form['field_player_picture']['#weight'] + 0.1 : 5,
        ];
      }
    }
  }

  // Restrict permanent override checkbox to admins only.
  if (isset($form['field_permanent_override'])) {
    if (!$is_admin) {
      $form['field_permanent_override']['#access'] = FALSE;
    }
  }

  // Add custom validation for 18+ age enforcement on profile edit.
  $form['#validate'][] = 'ccsoccer_user_profile_validate';

  // After a new user sets their password via the one-time login link,
  // redirect them to their profile page instead of staying on the edit form.
  $request = \Drupal::request();
  if ($request->query->has('pass-reset-token')) {
    $form['actions']['submit']['#submit'][] = 'ccsoccer_user_first_password_redirect';
  }
}

/**
 * Submit handler: redirects new users to their profile after first password save.
 */
function ccsoccer_user_first_password_redirect(&$form, FormStateInterface $form_state) {
  $account = $form_state->getFormObject()->getEntity();
  $form_state->setRedirectUrl(\Drupal\Core\Url::fromRoute('entity.user.canonical', ['user' => $account->id()]));
}

/**
 * Extracts a DateTime object from a DOB form value.
 *
 * Handles DrupalDateTime objects, date strings, and array values
 * (the datetime widget can return an array with 'date' and 'time' keys).
 *
 * @param mixed $dob_value
 *   The raw DOB value from form state.
 *
 * @return \DateTime|null
 *   A DateTime object, or NULL if the value cannot be parsed.
 */
function _ccsoccer_parse_dob_value($dob_value) {
  if (empty($dob_value)) {
    return NULL;
  }

  if ($dob_value instanceof \Drupal\Core\Datetime\DrupalDateTime) {
    return $dob_value->getPhpDateTime();
  }

  // The datetime widget may return an array with 'date' key.
  if (is_array($dob_value)) {
    if (!empty($dob_value['date'])) {
      $dob_value = $dob_value['date'];
    }
    else {
      return NULL;
    }
  }

  if (is_string($dob_value)) {
    try {
      return new \DateTime($dob_value);
    }
    catch (\Exception $e) {
      return NULL;
    }
  }

  return NULL;
}

/**
 * Validation callback for user registration form.
 *
 * Enforces alphanumeric + @ username and 18+ minimum age.
 */
function ccsoccer_user_register_validate(&$form, FormStateInterface $form_state) {
  // --- Username validation ---
  $username = $form_state->getValue('name');
  if (empty($username)) {
    $form_state->setErrorByName('name', t('Username is required.'));
  }
  elseif (!preg_match('/^[a-zA-Z0-9@]+$/', $username)) {
    $form_state->setErrorByName('name', t('Username may only contain letters, numbers, and the @ sign.'));
  }
  else {
    // Check if the username is already taken.
    $existing = \Drupal::entityTypeManager()
      ->getStorage('user')
      ->loadByProperties(['name' => $username]);
    if (!empty($existing)) {
      $form_state->setErrorByName('name', t('The username %name is already taken. Please choose a different username.', ['%name' => $username]));
    }
  }

  // --- Gender validation ---
  $gender = $form_state->getValue(['field_gender', 0, 'value']);
  if (empty($gender) || $gender === '_none') {
    $form_state->setErrorByName('field_gender', t('Please select your gender. This is required for league placement.'));
  }

  // --- Date of Birth validation ---
  $dob_value = $form_state->getValue(['field_dob', 0, 'value']);
  $dob = _ccsoccer_parse_dob_value($dob_value);
  if (!$dob) {
    $form_state->setErrorByName('field_dob', t('Date of Birth is required.'));
    // Require phone number when text notifications are selected.
    _ccsoccer_validate_phone_for_notifications($form_state);
    return;
  }

  $now = new \DateTime();

  // Check for future dates.
  if ($dob > $now) {
    $form_state->setErrorByName('field_dob', t('Date of Birth cannot be in the future. Please enter your actual date of birth.'));
  }
  else {
    $age = $now->diff($dob)->y;
    if ($age < 18) {
      $form_state->setErrorByName('field_dob', t('You must be at least 18 years old to create an account. This is an adult league and youth cannot play for insurance reasons.'));
    }
  }

  // Require phone number when text notifications are selected.
  _ccsoccer_validate_phone_for_notifications($form_state);
}

/**
 * Validation callback for user profile edit form.
 *
 * Prevents users from setting DOB to an underage value.
 */
function ccsoccer_user_profile_validate(&$form, FormStateInterface $form_state) {
  $dob_value = $form_state->getValue(['field_dob', 0, 'value']);
  $dob = _ccsoccer_parse_dob_value($dob_value);
  if (!$dob) {
    // Require phone number when text notifications are selected.
    _ccsoccer_validate_phone_for_notifications($form_state);
    return;
  }

  $now = new \DateTime();
  $age = $now->diff($dob)->y;

  if ($age < 18) {
    $form_state->setErrorByName('field_dob', t('All players must be at least 18 years old.'));
  }

  // Require phone number when text notifications are selected.
  _ccsoccer_validate_phone_for_notifications($form_state);
}

/**
 * Validates phone number: required for text notifications, and format check.
 *
 * Strips spaces, dashes, parentheses, dots, and leading +1, then checks
 * that the remaining digits are 10 (US standard) or 11 (with leading 1).
 *
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 */
function _ccsoccer_validate_phone_for_notifications(FormStateInterface $form_state) {
  $phone = $form_state->getValue(['field_phone', 0, 'value']);
  $notification_pref = $form_state->getValue(['field_notification_preference', 0, 'value']);

  // Require phone when text notifications are selected.
  if (in_array($notification_pref, ['text', 'both'], TRUE) && empty($phone)) {
    $form_state->setErrorByName('field_phone', t('A phone number is required when text notifications are selected.'));
    return;
  }

  // If a phone number was provided, validate the format.
  if (!empty($phone)) {
    // Strip common formatting characters: spaces, dashes, parens, dots, plus.
    $digits = preg_replace('/[\s\-\(\)\.\+]/', '', $phone);

    // Must be only digits after stripping formatting.
    if (!ctype_digit($digits)) {
      $form_state->setErrorByName('field_phone', t('Phone number can only contain digits, spaces, dashes, parentheses, and dots.'));
      return;
    }

    // Accept 10 digits (e.g. 3105551234) or 11 with leading 1 (e.g. 13105551234).
    $digit_count = strlen($digits);
    if ($digit_count === 11 && $digits[0] !== '1') {
      $form_state->setErrorByName('field_phone', t('Please enter a valid 10-digit US phone number.'));
    }
    elseif ($digit_count < 10 || $digit_count > 11) {
      $form_state->setErrorByName('field_phone', t('Please enter a valid 10-digit US phone number.'));
    }
  }
}

/**
 * Implements hook_mail().
 *
 * Defines email templates (Note: Using Message module primarily).
 */
function ccsoccer_mail($key, &$message, $params) {
  switch ($key) {
    case 'registration_confirmation':
      $message['subject'] = t('Registration Confirmation - @season', ['@season' => $params['season']]);
      $message['body'][] = $params['body'];
      break;
      
    case 'waitlist_notification':
      $message['subject'] = t('Spot Available - @season', ['@season' => $params['season']]);
      $message['body'][] = $params['body'];
      break;
      
    case 'team_assignment':
      $message['subject'] = t('Team Assignment - @season', ['@season' => $params['season']]);
      $message['body'][] = $params['body'];
      break;

    case 'notification':
      $message['subject'] = $params['subject'] ?? 'CCSoccer Notification';
      $message['body'][] = \Drupal\Core\Render\Markup::create($params['body'] ?? '');
      $message['headers']['Content-Type'] = 'text/html; charset=UTF-8';
      if (!empty($params['reply-to'])) {
        $message['reply-to'] = $params['reply-to'];
      }
      break;
  }
}

/**
 * Implements hook_mail_alter().
 *
 * Suppress Commerce's default order receipt email since we send our own
 * branded registration confirmation via OrderCompleteSubscriber.
 */
function ccsoccer_mail_alter(&$message) {
  if ($message['id'] === 'commerce_order_receipt') {
    $message['send'] = FALSE;
    // Prevent Symfony Mailer from surfacing a visible error message for
    // this intentional suppression.
    $message['result'] = TRUE;
  }
}

/**
 * Implements hook_entity_base_field_info().
 *
 * Declares board contact fields on the user entity.
 * These are managed via the Board Contact Preferences admin page and used
 * for board-targeted notifications. Hidden from normal profile editing.
 */
function ccsoccer_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
  $fields = [];

  if ($entity_type->id() === 'user') {
    $fields['field_board_email'] = BaseFieldDefinition::create('email')
      ->setLabel(t('Board Contact Email'))
      ->setDescription(t('Preferred email for board operational notifications. Falls back to personal email if empty.'))
      ->setRequired(FALSE)
      ->setDefaultValue('')
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE);

    $fields['field_board_phone'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Board Contact Phone'))
      ->setDescription(t('Preferred phone for board SMS notifications. Falls back to personal phone if empty.'))
      ->setRequired(FALSE)
      ->setDefaultValue('')
      ->setSetting('max_length', 20)
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE);

    // Permanent league eligibility override.
    // When TRUE, bypasses all age/gender checks on the registration page.
    // Use for players permanently excepted (e.g. women playing in Men's 35+).
    $fields['field_permanent_override'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Permanent League Eligibility Override'))
      ->setDescription(t('When enabled, this player bypasses all age and gender eligibility checks and can register for any visible season.'))
      ->setDefaultValue(FALSE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'weight' => 50,
        'settings' => ['display_label' => TRUE],
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', FALSE);
  }

  return $fields;
}

/**
 * Implements hook_entity_bundle_field_info().
 *
 * Adds custom fields to the Registration entity.
 */
function ccsoccer_entity_bundle_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields = [];

  // Only add fields to registration entity
  if ($entity_type->id() === 'registration' && $bundle === 'ccsoccer_registration') {
    
    // ========================================
    // CORE REFERENCES
    // ========================================
    
    $fields['player'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Player'))
      ->setDescription(t('The player registering.'))
      ->setRequired(TRUE)
      ->setSetting('target_type', 'user')
      ->setDisplayOptions('form', [
        'type' => 'entity_reference_autocomplete',
        'weight' => 1,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['season'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Season'))
      ->setDescription(t('Parent season (XOR with tournament).'))
      ->setSetting('target_type', 'season')
      ->setDisplayOptions('form', [
        'type' => 'entity_reference_autocomplete',
        'weight' => 2,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['tournament'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Tournament'))
      ->setDescription(t('Parent tournament (XOR with season).'))
      ->setSetting('target_type', 'tournament')
      ->setDisplayOptions('form', [
        'type' => 'entity_reference_autocomplete',
        'weight' => 3,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['registration_type'] = BaseFieldDefinition::create('list_string')
      ->setLabel(t('Registration Type'))
      ->setDescription(t('Type of registration flow.'))
      ->setRequired(TRUE)
      ->setSetting('allowed_values', [
        'season' => 'Season Registration',
        'tournament' => 'Tournament Registration',
      ])
      ->setDefaultValue('season')
      ->setDisplayOptions('form', [
        'type' => 'options_select',
        'weight' => 4,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['team'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Team'))
      ->setDescription(t('Assigned or selected team.'))
      ->setSetting('target_type', 'team')
      ->setDisplayOptions('form', [
        'type' => 'entity_reference_autocomplete',
        'weight' => 5,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['commerce_order'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Order'))
      ->setDescription(t('Commerce order for this registration.'))
      ->setRequired(TRUE)
      ->setSetting('target_type', 'commerce_order')
      ->setDisplayConfigurable('view', TRUE);

    $fields['commerce_line_item'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Line Item'))
      ->setDescription(t('Commerce line item for this registration.'))
      ->setRequired(TRUE)
      ->setSetting('target_type', 'commerce_order_item')
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // STATUS TRACKING
    // ========================================

    $fields['status'] = BaseFieldDefinition::create('list_string')
      ->setLabel(t('Status'))
      ->setDescription(t('Registration status.'))
      ->setRequired(TRUE)
      ->setSetting('allowed_values', [
        'pending' => 'Pending Payment',
        'paid' => 'Paid',
        'active' => 'Active',
        'cancelled' => 'Cancelled',
        'waitlist' => 'Waitlist',
        'expired' => 'Override Expired',
      ])
      ->setDefaultValue('pending')
      ->setDisplayOptions('form', [
        'type' => 'options_select',
        'weight' => 10,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['cancellation_date'] = BaseFieldDefinition::create('datetime')
      ->setLabel(t('Cancellation Date'))
      ->setDescription(t('When registration was cancelled.'))
      ->setDisplayConfigurable('view', TRUE);

    $fields['cancellation_reason'] = BaseFieldDefinition::create('text_long')
      ->setLabel(t('Cancellation Reason'))
      ->setDescription(t('Why registration was cancelled.'))
      ->setDisplayOptions('form', [
        'type' => 'text_textarea',
        'weight' => 50,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // WAIVER
    // ========================================

    $fields['waiver_signed'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Waiver Signed'))
      ->setDescription(t('Player accepted waiver terms.'))
      ->setRequired(TRUE)
      ->setDefaultValue(FALSE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'weight' => 15,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['waiver_date'] = BaseFieldDefinition::create('datetime')
      ->setLabel(t('Waiver Date'))
      ->setDescription(t('When waiver was signed.'))
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // SEASON-ONLY: JERSEY
    // ========================================

    $fields['jersey_size'] = BaseFieldDefinition::create('list_string')
      ->setLabel(t('Jersey Size'))
      ->setDescription(t('Jersey size (seasons only).'))
      ->setSetting('allowed_values', [
        's' => 'Small',
        'm' => 'Medium',
        'l' => 'Large',
        'xl' => 'XL',
        'xxl' => 'XXL',
        'none' => 'I don\'t need a jersey',
      ])
      ->setDisplayOptions('form', [
        'type' => 'options_select',
        'weight' => 20,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // SEASON-ONLY: GOALIE
    // ========================================

    $fields['prefers_goalie'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Prefers Goalie'))
      ->setDescription(t('Wants to play goalie this season.'))
      ->setDefaultValue(FALSE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'weight' => 21,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // SEASON-ONLY: GROUP
    // ========================================

    $fields['group_id'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Group ID'))
      ->setDescription(t('Group identifier for friend registrations.'))
      ->setSettings(['max_length' => 255])
      ->setDisplayConfigurable('view', TRUE);

    $fields['invited_by'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Invited By'))
      ->setDescription(t('User who sent group invitation.'))
      ->setSetting('target_type', 'user')
      ->setDisplayConfigurable('view', TRUE);

    $fields['invitation_status'] = BaseFieldDefinition::create('list_string')
      ->setLabel(t('Invitation Status'))
      ->setDescription(t('Group invitation state.'))
      ->setSetting('allowed_values', [
        'none' => 'None',
        'pending' => 'Pending',
        'accepted' => 'Accepted',
        'declined' => 'Declined',
      ])
      ->setDefaultValue('none')
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // SEASON-ONLY: WAITLIST/OVERRIDE
    // ========================================

    $fields['has_override'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Has Override'))
      ->setDescription(t('Priority registration granted (waitlist override).'))
      ->setDefaultValue(FALSE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'weight' => 30,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['override_expires'] = BaseFieldDefinition::create('datetime')
      ->setLabel(t('Override Expires'))
      ->setDescription(t('When override expires.'))
      ->setDisplayConfigurable('view', TRUE);

    $fields['override_notified'] = BaseFieldDefinition::create('datetime')
      ->setLabel(t('Override Notified'))
      ->setDescription(t('When override notification was sent.'))
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // SEASON-ONLY: CREDITS
    // ========================================

    $fields['credits_used'] = BaseFieldDefinition::create('decimal')
      ->setLabel(t('Credits Used'))
      ->setDescription(t('Credit amount applied at checkout.'))
      ->setSettings([
        'precision' => 10,
        'scale' => 2,
      ])
      ->setDefaultValue('0.00')
      ->setDisplayOptions('form', [
        'type' => 'number',
        'weight' => 35,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // TOURNAMENT-ONLY
    // ========================================

    $fields['tournament_deposit_paid'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Deposit Paid'))
      ->setDescription(t('Captain paid tournament deposit.'))
      ->setDefaultValue(FALSE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'weight' => 40,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['is_captain'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Is Captain'))
      ->setDescription(t('Is team captain (tournaments only).'))
      ->setDefaultValue(FALSE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'weight' => 41,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['ccsoccer_pool'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('CCSoccer Pool'))
      ->setDescription(t('Player opted into CCSoccer pool for team assignment (tournaments only).'))
      ->setDefaultValue(FALSE)
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'weight' => 42,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    // ========================================
    // ADMIN
    // ========================================

    $fields['notes'] = BaseFieldDefinition::create('text_long')
      ->setLabel(t('Admin Notes'))
      ->setDescription(t('Internal notes about this registration.'))
      ->setDisplayOptions('form', [
        'type' => 'text_textarea',
        'weight' => 100,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);
  }

  return $fields;
}

/**
 * Implements hook_ENTITY_TYPE_insert() for season entity.
 *
 * Auto-create Commerce product and teams when season is created.
 */
function ccsoccer_season_insert(EntityInterface $season) {
  // Create Commerce product for this season regardless of active status.
  \Drupal::service('ccsoccer.commerce_product')->createSeasonProduct($season);

  // Auto-create teams from taxonomy terms for the season's league.
  // Teams are created regardless of active status so the roster builder
  // is ready as soon as a season exists.
  $team_manager = \Drupal::service('ccsoccer.team_manager');
  $result = $team_manager->createTeamsForSeason($season);

  if ($result['created'] > 0) {
    \Drupal::messenger()->addStatus(t('Created @count teams for this season.', [
      '@count' => $result['created'],
    ]));
  }

  if (!empty($result['errors'])) {
    foreach ($result['errors'] as $error) {
      \Drupal::messenger()->addWarning($error);
    }
  }
  
  // Clear menu cache so new active seasons appear immediately
  \Drupal::cache('menu')->invalidateAll();
  \Drupal::service('router.builder')->rebuild();
  \Drupal::cache('render')->invalidateAll();
}

/**
 * Implements hook_ENTITY_TYPE_update() for season entity.
 *
 * Update Commerce product when season price changes.
 * Clear menu cache when active status changes.
 * Create teams if active is being flipped to TRUE and no teams exist yet.
 */
function ccsoccer_season_update(EntityInterface $season) {
  $product_service = \Drupal::service('ccsoccer.commerce_product');
  $product = $product_service->getSeasonProduct($season);
  
  if ($product) {
    // Update variation price if season price changed
    $variation = $product->getDefaultVariation();
    if ($variation && !$season->get('price')->isEmpty()) {
      $variation->setPrice($season->get('price')->first()->toPrice());
      $variation->save();
    }
    
    // Update product title
    $product->setTitle($season->label());
    $product->save();
  }

  // If active is being flipped to TRUE, create teams if none exist yet.
  // This covers seasons created as inactive before team names were defined.
  $original = $season->original;
  if ($original && !$original->get('active')->value && $season->get('active')->value) {
    $team_manager = \Drupal::service('ccsoccer.team_manager');
    $existing_teams = $team_manager->getTeamsForSeason($season->id());
    if (empty($existing_teams)) {
      $result = $team_manager->createTeamsForSeason($season);
      if ($result['created'] > 0) {
        \Drupal::messenger()->addStatus(t('Created @count teams for this season.', [
          '@count' => $result['created'],
        ]));
      }
      if (!empty($result['errors'])) {
        foreach ($result['errors'] as $error) {
          \Drupal::messenger()->addWarning($error);
        }
      }
    }
  }
  
  // Invalidate entity_list:season so the /register page dynamic page cache
  // rebuilds for authenticated users when any season field is updated.
  // (Drupal only fires entity_list:season automatically on insert/delete,
  // not on update — so we fire it explicitly here.)
  Cache::invalidateTags(['entity_list:season']);
  
  // Clear menu cache so active/inactive changes take effect immediately
  \Drupal::cache('menu')->invalidateAll();
  \Drupal::service('router.builder')->rebuild();
}

/**
 * Implements hook_ENTITY_TYPE_insert() for tournament entity.
 *
 * Auto-create Commerce products when tournament is created.
 */
function ccsoccer_tournament_insert(EntityInterface $tournament) {
  $product_service = \Drupal::service('ccsoccer.commerce_product');
  
  // Create registration product for this tournament.
  $reg_product = $product_service->createTournamentProduct($tournament);
  if ($reg_product) {
    \Drupal::messenger()->addStatus(t('Created registration product for tournament.'));
  }
  
  // Create deposit product for this tournament.
  $deposit_product = $product_service->createTournamentDepositProduct($tournament);
  if ($deposit_product) {
    \Drupal::messenger()->addStatus(t('Created deposit product for tournament.'));
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for tournament entity.
 *
 * Update Commerce products when tournament prices change.
 * Clear menu cache when active status changes.
 */
function ccsoccer_tournament_update(EntityInterface $tournament) {
  $product_service = \Drupal::service('ccsoccer.commerce_product');
  
  // Update registration product
  $reg_product = $product_service->getTournamentProduct($tournament);
  if ($reg_product) {
    $variation = $reg_product->getDefaultVariation();
    if ($variation && !$tournament->get('registration_price')->isEmpty()) {
      $variation->setPrice($tournament->get('registration_price')->first()->toPrice());
      $variation->save();
    }
    $reg_product->setTitle($tournament->label());
    $reg_product->save();
  }
  
  // Update deposit product
  $deposit_product = $product_service->getDepositProduct($tournament);
  if ($deposit_product) {
    $variation = $deposit_product->getDefaultVariation();
    if ($variation && !$tournament->get('deposit_amount')->isEmpty()) {
      $variation->setPrice($tournament->get('deposit_amount')->first()->toPrice());
      $variation->save();
    }
    $deposit_product->setTitle($tournament->label() . ' - Captain Deposit');
    $deposit_product->save();
  }
  
  // Invalidate entity_list:tournament so the /register page dynamic page cache
  // rebuilds for authenticated users when any tournament field is updated.
  Cache::invalidateTags(['entity_list:tournament']);
  
  // Clear menu cache so active/inactive changes take effect immediately
  \Drupal::cache('menu')->invalidateAll();
  \Drupal::service('router.builder')->rebuild();
}

/**
 * Implements hook_ENTITY_TYPE_presave() for commerce_product_variation.
 * 
 * Ensures jersey variations have proper titles based on SKU.
 * Format: JERSEY-{STYLE}-{SIZE} -> "{Style} {Size} Jersey"
 */
function ccsoccer_commerce_product_variation_presave(EntityInterface $entity) {
  $sku = $entity->getSku();
  
  // Only process jersey products
  if (strpos($sku, 'JERSEY-') === 0) {
    // Parse SKU to build proper title
    $parts = explode('-', $sku, 3);
    if (count($parts) >= 3) {
      $style_raw = $parts[1];
      $size_raw = $parts[2];
      
      // Format for display
      $style_display = (stripos($style_raw, 'WOMEN') !== FALSE) ? "Women's" : 'Unisex';
      
      $size_parts = explode('-', $size_raw);
      $size_display = implode('-', array_map(function($part) {
        if (strtoupper($part) === 'XX') {
          return 'XX';
        }
        return ucfirst(strtolower($part));
      }, $size_parts));
      
      $jersey_title = $style_display . ' ' . $size_display . ' Jersey';
      
      // Set the title on the variation entity itself
      $entity->setTitle($jersey_title);
    }
  }
}

/**
 * Implements hook_user_presave().
 *
 * Processes player pictures on upload:
 * 1. Fixes EXIF orientation so phone photos display right-side up.
 * 2. Scales down to a max of 1024px on the longest side.
 */
function ccsoccer_user_presave(EntityInterface $user) {
  if (!$user->hasField('field_player_picture') || $user->get('field_player_picture')->isEmpty()) {
    return;
  }

  /** @var \Drupal\file\FileInterface $file */
  $file = $user->get('field_player_picture')->entity;
  if (!$file) {
    return;
  }

  $uri = $file->getFileUri();
  $real_path = \Drupal::service('file_system')->realpath($uri);
  $changed = FALSE;

  // Step 1: Fix EXIF orientation.
  // Phone cameras store the image data in one orientation and set an EXIF
  // tag to indicate how it should be displayed. Some browsers/contexts
  // ignore this tag, causing upside-down or sideways photos.
  // We physically rotate the pixels to match the EXIF orientation, then
  // strip the EXIF tag so it always displays correctly everywhere.
  if (function_exists('exif_read_data') && preg_match('/\.(jpe?g)$/i', $real_path)) {
    $exif = @exif_read_data($real_path);
    if ($exif && !empty($exif['Orientation'])) {
      $orientation = (int) $exif['Orientation'];
      $rotate = 0;
      $flip = FALSE;

      switch ($orientation) {
        case 2:
          $flip = TRUE;
          break;
        case 3:
          $rotate = 180;
          break;
        case 4:
          $rotate = 180;
          $flip = TRUE;
          break;
        case 5:
          $rotate = 270;
          $flip = TRUE;
          break;
        case 6:
          $rotate = 270;
          break;
        case 7:
          $rotate = 90;
          $flip = TRUE;
          break;
        case 8:
          $rotate = 90;
          break;
      }

      if ($rotate || $flip) {
        $gd = imagecreatefromjpeg($real_path);
        if ($gd) {
          if ($rotate) {
            $gd = imagerotate($gd, $rotate, 0);
          }
          if ($flip) {
            imageflip($gd, IMG_FLIP_HORIZONTAL);
          }
          imagejpeg($gd, $real_path, 90);
          imagedestroy($gd);
          $changed = TRUE;
        }
      }
    }
  }

  // Step 2: Scale down if the image is too large.
  $image_factory = \Drupal::service('image.factory');
  /** @var \Drupal\Core\Image\ImageInterface $image */
  $image = $image_factory->get($uri);

  if (!$image->isValid()) {
    return;
  }

  $max_dimension = 1024;
  $width = $image->getWidth();
  $height = $image->getHeight();

  if ($width > $max_dimension || $height > $max_dimension) {
    $image->scale($max_dimension);
    $image->save();
    $changed = TRUE;
  }

  // Update the stored file size if the image was modified.
  if ($changed) {
    clearstatcache(TRUE, $real_path);
    $file->setSize(filesize($real_path));
    $file->save();
  }
}

/**
 * Implements hook_entity_display_build_alter().
 *
 * Fixes product variation display in cart and checkout.
 * Ensures proper titles are shown instead of "Price" label.
 */
function ccsoccer_entity_display_build_alter(&$build, $context) {
  $entity = $context['entity'];
  
  if ($entity->getEntityTypeId() === 'commerce_product_variation') {
    $sku = $entity->getSku();
    
    // Handle jersey products
    if (strpos($sku, 'JERSEY-') === 0) {
      // Remove price-related fields that show "Price" label
      unset($build['list_price']);
      unset($build['price']);
      unset($build['product_id']);
      unset($build['sku']);
      
      // Add the variation title instead
      $build['title'] = [
        '#markup' => '<div class="product-variation-title"><strong>' . $entity->getTitle() . '</strong></div>',
        '#weight' => -10,
      ];
    }
    // Handle season registration products
    elseif (strpos($sku, 'SEASON-') === 0) {
      unset($build['list_price']);
      unset($build['price']);
      unset($build['product_id']);
      unset($build['sku']);
      
      // Get the season name from the product
      $product = $entity->getProduct();
      $title = $product ? $product->getTitle() : $entity->getTitle();
      
      $build['title'] = [
        '#markup' => '<div class="product-variation-title"><strong>' . $title . ' Registration</strong></div>',
        '#weight' => -10,
      ];
    }
    // Handle tournament registration products
    elseif (strpos($sku, 'TOURN-') === 0) {
      unset($build['list_price']);
      unset($build['price']);
      unset($build['product_id']);
      unset($build['sku']);
      
      // Get the tournament name from the product
      $product = $entity->getProduct();
      $title = $product ? $product->getTitle() : $entity->getTitle();
      
      $build['title'] = [
        '#markup' => '<div class="product-variation-title"><strong>' . $title . ' Registration</strong></div>',
        '#weight' => -10,
      ];
    }
    // Handle tournament deposit products
    elseif (strpos($sku, 'DEPOSIT-') === 0) {
      unset($build['list_price']);
      unset($build['price']);
      unset($build['product_id']);
      unset($build['sku']);
      
      // Get the tournament name from the product
      $product = $entity->getProduct();
      $title = $product ? $product->getTitle() : $entity->getTitle();
      
      $build['title'] = [
        '#markup' => '<div class="product-variation-title"><strong>' . $title . '</strong></div>',
        '#weight' => -10,
      ];
    }
  }
}

/**
 * Implements hook_page_attachments().
 */
function ccsoccer_page_attachments(array &$attachments) {
  // Attach menu styling library globally
  $attachments['#attached']['library'][] = 'ccsoccer/menu-styling';
  // Attach menu fix for BigPipe/navigation issues with Olivero dropdowns
  $attachments['#attached']['library'][] = 'ccsoccer/menu-fix';
  // Attach site branding overrides (header, nav, footer)
  $attachments['#attached']['library'][] = 'ccsoccer/header-branding';
  $attachments['#attached']['library'][] = 'ccsoccer/desktop-nav';
  $attachments['#attached']['library'][] = 'ccsoccer/footer-branding';

  // Attach checkout styles on Commerce cart and checkout pages.
  $route_name = \Drupal::routeMatch()->getRouteName();
  $checkout_routes = [
    'commerce_cart.page',
    'commerce_checkout.form',
  ];
  if (in_array($route_name, $checkout_routes)) {
    $attachments['#attached']['library'][] = 'ccsoccer/checkout';
  }

  // Attach horizontal-scroll wrapper styling on admin routes. Claro
  // (the admin theme) doesn't make wide tables responsive, so admin
  // list pages -- Tournaments, Registrations, Players, Seasons --
  // clip off the right edge whenever the content area is narrower
  // than the table's natural width (iPhone portrait, or desktop with
  // the Manage sidebar open).
  //
  // Detection uses two checks belt-and-braces: the admin context
  // service (which respects entity routes' _admin_route option), and
  // a fallback path check for any admin route that doesn't have the
  // option set explicitly.
  $route = \Drupal::routeMatch()->getRouteObject();
  $current_path = \Drupal::service('path.current')->getPath();
  $is_admin = ($route && \Drupal::service('router.admin_context')->isAdminRoute($route))
    || str_starts_with($current_path, '/admin/');
  if ($is_admin) {
    $attachments['#attached']['library'][] = 'ccsoccer/admin-mobile';
  }
}

/**
 * Implements hook_system_breadcrumb_alter().
 *
 * Fixes Commerce checkout breadcrumb which defaults to
 * "Home / Checkout / Checkout" (section + step both labeled "Checkout").
 * Replaces the last crumb with the actual step name from the page title.
 */
function ccsoccer_system_breadcrumb_alter(\Drupal\Core\Breadcrumb\Breadcrumb &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) {
  $route_name = $route_match->getRouteName();

  if ($route_name !== 'commerce_checkout.form') {
    return;
  }

  $links = $breadcrumb->getLinks();

  // Commerce produces: Home / Checkout / Checkout
  // We want:           Home / Cart
  // The checkout flow H1 already identifies the step — breadcrumb just
  // needs to not be redundant. Strip the last crumb and relabel the
  // second-to-last as "Cart" to give users a useful back target.
  if (count($links) >= 2) {
    // Rebuild: keep Home, replace rest with a single "Cart" link.
    $home_link = $links[0];
    $cart_link = \Drupal\Core\Link::createFromRoute(t('Cart'), 'commerce_cart.page');

    $new_breadcrumb = new \Drupal\Core\Breadcrumb\Breadcrumb();
    $new_breadcrumb->addLink($home_link);
    $new_breadcrumb->addLink($cart_link);
    $new_breadcrumb->addCacheContexts(['route', 'url.path']);

    $breadcrumb = $new_breadcrumb;
  }
}

/**
 * Implements hook_menu_links_discovered_alter().
 *
 * Adds dynamic menu links for active seasons and tournaments.
 * Conditionally shows/hides tournament public menu links based on visibility flags.
 */
function ccsoccer_menu_links_discovered_alter(&$links) {
  // Override "My account" to point to our custom hub page.
  if (isset($links['user.account_menu_link'])) {
    $links['user.account_menu_link']['route_name'] = 'ccsoccer.my_account';
    $links['user.account_menu_link']['route_parameters'] = [];
  }

  $entity_type_manager = \Drupal::entityTypeManager();
  
  // =========================================================================
  // SEASON LINKS (Admin Menu)
  // =========================================================================
  $season_storage = $entity_type_manager->getStorage('season');
  $season_query = $season_storage->getQuery()
    ->condition('active', TRUE)
    ->accessCheck(FALSE)
    ->sort('start_date', 'DESC')
    ->range(0, 20);
  $season_ids = $season_query->execute();
  
  if (!empty($season_ids)) {
    $seasons = $season_storage->loadMultiple($season_ids);
    $weight = 1;
    
    // Iterate in the order returned by the query (preserves sort order)
    foreach ($season_ids as $season_id) {
      $season = $seasons[$season_id] ?? NULL;
      if (!$season) {
        continue;
      }
      $parent_key = "ccsoccer.season_{$season_id}";
      
      // Add main season link
      $links[$parent_key] = [
        'title' => $season->label(),
        'route_name' => 'entity.season.canonical',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => 'ccsoccer.seasons',
        'weight' => $weight++,
      ];
      
      // Add sub-items for this season
      $links["{$parent_key}.edit"] = [
        'title' => 'Edit Season',
        'route_name' => 'entity.season.edit_form',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 1,
      ];
      
      $links["{$parent_key}.roster_builder"] = [
        'title' => 'Roster Builder',
        'route_name' => 'ccsoccer.roster_builder',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 2,
      ];
      
      $links["{$parent_key}.schedule_builder"] = [
        'title' => 'Generate Schedule',
        'route_name' => 'ccsoccer.schedule_builder',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 3,
      ];
      
      $links["{$parent_key}.waitlist"] = [
        'title' => 'Manage Waitlist',
        'route_name' => 'ccsoccer.waitlist',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 4,
      ];

      $links["{$parent_key}.players"] = [
        'title' => 'Players',
        'route_name' => 'ccsoccer.season_players',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 5,
      ];

      $links["{$parent_key}.credits"] = [
        'title' => 'Season Credits',
        'route_name' => 'ccsoccer.season_credits',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 6,
      ];

      $links["{$parent_key}.overrides"] = [
        'title' => 'Overrides',
        'route_name' => 'ccsoccer.season_overrides',
        'route_parameters' => ['season' => $season_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 7,
      ];
    }
  }
  
  // =========================================================================
  // TOURNAMENT LINKS (Admin Menu)
  // =========================================================================
  $tournament_storage = $entity_type_manager->getStorage('tournament');
  $tournament_query = $tournament_storage->getQuery()
    ->condition('active', TRUE)
    ->accessCheck(FALSE)
    ->sort('start_date', 'DESC')
    ->range(0, 20);
  
  $tournament_ids = $tournament_query->execute();
  
  if (!empty($tournament_ids)) {
    $tournaments = $tournament_storage->loadMultiple($tournament_ids);
    $weight = 1;
    
    // Iterate in the order returned by the query (preserves sort order)
    foreach ($tournament_ids as $tournament_id) {
      $tournament = $tournaments[$tournament_id] ?? NULL;
      if (!$tournament) {
        continue;
      }
      $parent_key = "ccsoccer.tournament_{$tournament_id}";
      
      // Add main tournament link
      $links[$parent_key] = [
        'title' => $tournament->label(),
        'route_name' => 'entity.tournament.canonical',
        'route_parameters' => ['tournament' => $tournament_id],
        'menu_name' => 'admin',
        'parent' => 'ccsoccer.tournaments',
        'weight' => $weight++,
      ];
      
      // Add sub-items for this tournament
      $links["{$parent_key}.edit"] = [
        'title' => 'Edit Tournament',
        'route_name' => 'entity.tournament.edit_form',
        'route_parameters' => ['tournament' => $tournament_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 1,
      ];

      $links["{$parent_key}.teams"] = [
        'title' => 'Teams',
        'route_name' => 'ccsoccer.tournament_teams',
        'route_parameters' => ['tournament' => $tournament_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 2,
      ];

      $links["{$parent_key}.roster_builder"] = [
        'title' => 'Roster Builder',
        'route_name' => 'ccsoccer.tournament_roster_builder',
        'route_parameters' => ['tournament' => $tournament_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 3,
      ];

      $links["{$parent_key}.schedule_builder"] = [
        'title' => 'Schedule Builder',
        'route_name' => 'ccsoccer.tournament_schedule_builder',
        'route_parameters' => ['tournament' => $tournament_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 4,
      ];

      $links["{$parent_key}.players"] = [
        'title' => 'Players',
        'route_name' => 'ccsoccer.tournament_players',
        'route_parameters' => ['tournament' => $tournament_id],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 5,
      ];

      $links["{$parent_key}.captain_dashboard"] = [
        'title' => 'Captain Dashboard',
        'route_name' => 'ccsoccer.reports.tournament_deposits',
        'route_parameters' => [],
        'options' => ['query' => ['tournament' => $tournament_id]],
        'menu_name' => 'admin',
        'parent' => $parent_key,
        'weight' => 6,
      ];
    }
  }
  
  // =========================================================================
  // TOURNAMENT PUBLIC MENU LINKS (Main Menu - Conditional Visibility)
  // =========================================================================
  // Tournament public links are defined in ccsoccer.links.menu.yml but should
  // only appear when visibility flags are enabled. Check if any active
  // tournament has roster_visible or schedule_visible = TRUE.
  //
  // Note: This runs at menu cache build time. Changes to visibility flags
  // trigger cache clear via ccsoccer_tournament_update().
  // =========================================================================
  
  try {
    $tournament_storage = $entity_type_manager->getStorage('tournament');
    
    // Check for any tournament with roster_visible = TRUE
    $roster_visible_query = $tournament_storage->getQuery()
      ->condition('active', TRUE)
      ->condition('roster_visible', TRUE)
      ->accessCheck(FALSE)
      ->range(0, 1);
    $has_roster_visible = !empty($roster_visible_query->execute());
    
    // Check for any tournament with schedule_visible = TRUE
    $schedule_visible_query = $tournament_storage->getQuery()
      ->condition('active', TRUE)
      ->condition('schedule_visible', TRUE)
      ->accessCheck(FALSE)
      ->range(0, 1);
    $has_schedule_visible = !empty($schedule_visible_query->execute());
    
    // Hide Tournament Teams link if no tournament has roster_visible = TRUE
    if (!$has_roster_visible && isset($links['ccsoccer.tournament_teams.menu'])) {
      unset($links['ccsoccer.tournament_teams.menu']);
    }
    
    // Hide Tournament Schedule link if no tournament has schedule_visible = TRUE
    if (!$has_schedule_visible && isset($links['ccsoccer.tournament_schedule.menu'])) {
      unset($links['ccsoccer.tournament_schedule.menu']);
    }
    
    // Hide My Tournament Team/Schedule if neither visibility flag is set
    // (no tournament content to show)
    if (!$has_roster_visible && !$has_schedule_visible) {
      if (isset($links['ccsoccer.my_tournament_team.menu'])) {
        unset($links['ccsoccer.my_tournament_team.menu']);
      }
      if (isset($links['ccsoccer.my_tournament_schedule.menu'])) {
        unset($links['ccsoccer.my_tournament_schedule.menu']);
      }
    }
    else {
      // If roster is visible, show My Tournament Team
      // If schedule is visible, show My Tournament Schedule
      if (!$has_roster_visible && isset($links['ccsoccer.my_tournament_team.menu'])) {
        unset($links['ccsoccer.my_tournament_team.menu']);
      }
      if (!$has_schedule_visible && isset($links['ccsoccer.my_tournament_schedule.menu'])) {
        unset($links['ccsoccer.my_tournament_schedule.menu']);
      }
    }
  }
  catch (\Exception $e) {
    // Tournament entity might not be installed yet - hide all tournament public links
    unset($links['ccsoccer.tournament_teams.menu']);
    unset($links['ccsoccer.tournament_schedule.menu']);
    unset($links['ccsoccer.my_tournament_team.menu']);
    unset($links['ccsoccer.my_tournament_schedule.menu']);
  }

  // Override core "My account" link to point to our hub page.
  // The core user.page link (weight -10) always beats our custom entry,
  // so we redirect it in place rather than fighting with a separate link.
  if (isset($links['user.page'])) {
    $links['user.page']['route_name'] = 'ccsoccer.my_account';
    $links['user.page']['route_parameters'] = [];
    $links['user.page']['title'] = 'My Account';
  }

  // Push Log Out to the very bottom of the account menu (core default is 10,
  // which would put it before our tournament items at weight 20/21).
  if (isset($links['user.logout'])) {
    $links['user.logout']['weight'] = 99;
  }
}

/**
 * Implements hook_user_login().
 *
 * Redirect users to My Account hub after login.
 * Sets the destination query param so Drupal's own redirect handling
 * picks it up after the session is fully established.
 *
 * Exception: one-time login links (password reset / new account verification)
 * should go to the user's edit page so they can set their password.
 */
function ccsoccer_user_login($account) {
  $request = \Drupal::request();

  // Don't override the redirect for one-time login links (/user/reset/...).
  // Drupal core sends these users to their profile edit page to set a password,
  // and we should let that happen.
  $path = $request->getPathInfo();
  if (str_starts_with($path, '/user/reset/')) {
    return;
  }

  // Only set destination if nothing else has already claimed it.
  if (!$request->query->has('destination')) {
    $request->query->set('destination', '/my-account');
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 *
 * Controls which tabs appear on user profile pages.
 *
 * Rules:
 * - Everyone (incl. admin): hide View, Shortcuts, Credits (player copy).
 * - Players only: also hide Orders and Credits (Admin).
 * - Admins: see everything else (Edit, Payment methods, Address book,
 *   Orders, Credits (Admin), etc.).
 */
function ccsoccer_menu_local_tasks_alter(&$data, $route_name) {
  if (empty($data['tabs'][0])) {
    return;
  }

  // Only act when user profile tabs are present.
  // Check by looking for the edit tab rather than relying on route name,
  // so this fires correctly on ALL profile sub-pages (edit, address-book, etc.).
  $has_user_edit_tab = FALSE;
  foreach ($data['tabs'][0] as $tab) {
    if (!isset($tab['#link']['url'])) {
      continue;
    }
    if ($tab['#link']['url']->getRouteName() === 'entity.user.edit_form') {
      $has_user_edit_tab = TRUE;
      break;
    }
  }
  if (!$has_user_edit_tab) {
    return;
  }

  $current_user = \Drupal::currentUser();
  $is_admin = $current_user->hasPermission('administer users') ||
              $current_user->hasPermission('administer ccsoccer');

  // Hidden for everyone including admin.
  $always_hidden = [
    'entity.user.canonical',    // "View" tab
    'entity.user.shortcuts',    // Drupal Shortcuts
    'shortcut.set_switch',      // Shortcuts switcher
    'ccsoccer.user_credits',    // Credits player tab (redundant with My Account)
  ];

  // Hidden for players only.
  $player_hidden = [
    'ccsoccer.user_orders_tab', // Our Orders tab (admin only)
    'ccsoccer.admin_user_credits',
  ];

  foreach ($data['tabs'][0] as $key => $tab) {
    if (!isset($tab['#link']['url'])) {
      continue;
    }
    $url = $tab['#link']['url'];
    $tab_route = $url->getRouteName();

    // Always hide.
    if (in_array($tab_route, $always_hidden)) {
      unset($data['tabs'][0][$key]);
      continue;
    }

    // Hide Commerce's Orders tab for everyone — ours replaces it.
    // Catch by route name OR by URL path ending in /orders.
    $is_commerce_orders = in_array($tab_route, [
      'view.commerce_user_orders.page_1',
      'view.commerce_user_orders.page',
      'commerce_order.user_orders',
    ]);
    if (!$is_commerce_orders) {
      try {
        $path = $url->toString();
        // Match /user/123/orders but NOT /user/123/ccsoccer-orders
        $is_commerce_orders = (bool) preg_match('#/user/\d+/orders$#', $path);
      }
      catch (\Exception $e) {}
    }
    if ($is_commerce_orders) {
      unset($data['tabs'][0][$key]);
      continue;
    }

    // Hide player-only tabs from non-admins.
    if (!$is_admin && in_array($tab_route, $player_hidden)) {
      unset($data['tabs'][0][$key]);
      continue;
    }
  }

  // Set tab order.
  $order = [
    'entity.user.edit_form'                 => -30,
    'commerce_payment.user.payment_methods' => -20,
    'entity.user.address_book'              => -15,
    'profile.user_page.single'              => -15,
    'profile.user_page.multiple'            => -15,
    'ccsoccer.user_orders_tab'              => 0,
    'ccsoccer.admin_user_credits'           => 10,
  ];

  foreach ($data['tabs'][0] as $key => &$tab) {
    if (!isset($tab['#link']['url'])) {
      continue;
    }
    $tab_route = $tab['#link']['url']->getRouteName();
    if (isset($order[$tab_route])) {
      $tab['#weight'] = $order[$tab_route];
    }
  }
}

/**
 * Implements hook_masquerade_access().
 *
 * Prevents masquerading as administrator users.
 */
function ccsoccer_masquerade_access($user, $target_account) {
  // Prevent masquerading as administrators (except UID 1 can masquerade as anyone).
  if ($user->id() != 1 && $target_account->hasRole('administrator')) {
    return FALSE;
  }
  // Return NULL to let other access checks decide.
  return NULL;
}

/**
 * Implements hook_toolbar().
 *
 * DISABLED: Dev mode indicator moved to site branding block.
 */
function ccsoccer_toolbar() {
  // Dev mode indicator now shown in site branding block instead
  return [];
}

/**
 * Implements hook_preprocess_views_view_table().
 *
 * Sets column alignment and labels in the cart form view.
 * Uses inline styles on th elements to guarantee alignment beats Olivero.
 */
function ccsoccer_preprocess_views_view_table(&$variables) {
  $view = $variables['view'];
  if ($view->id() !== 'commerce_cart_form') {
    return;
  }

  // Header inline styles: alignment + bottom border divider.
  $header_styles = [
    'purchased_entity'    => 'text-align: left; width: 100%; padding: 8px 20px; border-bottom: 2px solid #e0e0e0;',
    'unit_price__number'  => 'text-align: left; white-space: nowrap; padding: 8px 20px; border-bottom: 2px solid #e0e0e0;',
    'edit_quantity'       => 'text-align: left; padding: 8px 20px; border-bottom: 2px solid #e0e0e0;',
    'remove_button'       => 'text-align: center; padding: 8px 20px; border-bottom: 2px solid #e0e0e0;',
    'total_price__number' => 'text-align: right; white-space: nowrap; padding: 8px 20px; border-bottom: 2px solid #e0e0e0;',
  ];

  foreach ($header_styles as $field => $style) {
    if (isset($variables['header'][$field])) {
      $variables['header'][$field]['attributes']->setAttribute('style', $style);
    }
  }

  // Data cell inline styles: horizontal padding only, vertical-align middle.
  $cell_styles = [
    'purchased_entity'    => 'text-align: left; width: 100%; padding: 12px 20px; vertical-align: middle;',
    'unit_price__number'  => 'text-align: left; white-space: nowrap; padding: 12px 20px; vertical-align: middle;',
    'edit_quantity'       => 'text-align: left; padding: 12px 20px; vertical-align: middle;',
    'remove_button'       => 'text-align: center; padding: 12px 20px; vertical-align: middle;',
    'total_price__number' => 'text-align: right; white-space: nowrap; padding: 12px 20px; vertical-align: middle;',
  ];

  foreach ($variables['rows'] as &$row) {
    foreach ($cell_styles as $field => $style) {
      if (isset($row['columns'][$field])) {
        $row['columns'][$field]['attributes']->setAttribute('style', $style);
      }
    }
  }
}

/**
 * After-build callback for the Commerce cart views form.
 *
 * Applies button classes after all hook_form_alter() implementations have run,
 * ensuring the 'checkout' button added by commerce_checkout is present.
 */
function ccsoccer_cart_form_after_build(array $form, FormStateInterface $form_state) {
  if (isset($form['actions']['submit'])) {
    $form['actions']['submit']['#attributes']['class'][] = 'button--secondary';
  }
  if (isset($form['actions']['checkout'])) {
    $form['actions']['checkout']['#attributes']['class'][] = 'button--primary';
  }
  return $form;
}

/**
 * Implements hook_views_query_alter().
 *
 * Expands the /admin/people search to include first name and last name.
 *
 * The core UserName filter only searches users_field_data.name and .mail.
 * We add LEFT JOINs for the first/last name field tables and replace the
 * LIKE condition with an OR across all four columns — same search box,
 * broader results, everything else unchanged.
 */
function ccsoccer_views_query_alter(\Drupal\views\ViewExecutable $view, \Drupal\views\Plugin\views\query\QueryPluginBase $query) {
  if ($view->id() !== 'user_admin_people') {
    return;
  }

  // The exposed filter key for the UserName filter is 'user'.
  $exposed = $view->getExposedInput();
  $search = isset($exposed['user']) ? trim($exposed['user']) : '';
  if (empty($search)) {
    return;
  }

  // Add LEFT JOINs for first and last name field storage tables.
  $join_manager = \Drupal::service('plugin.manager.views.join');

  $first_join = $join_manager->createInstance('standard', [
    'table'      => 'user__field_first_name',
    'field'      => 'entity_id',
    'left_table' => 'users_field_data',
    'left_field' => 'uid',
    'type'       => 'LEFT',
    'extra'      => [['field' => 'deleted', 'value' => 0, 'numeric' => TRUE]],
  ]);
  $query->addTable('user__field_first_name', NULL, $first_join, 'user__field_first_name');

  $last_join = $join_manager->createInstance('standard', [
    'table'      => 'user__field_last_name',
    'field'      => 'entity_id',
    'left_table' => 'users_field_data',
    'left_field' => 'uid',
    'type'       => 'LEFT',
    'extra'      => [['field' => 'deleted', 'value' => 0, 'numeric' => TRUE]],
  ]);
  $query->addTable('user__field_last_name', NULL, $last_join, 'user__field_last_name');

  // Escape the search term for safe use in LIKE.
  $like = '%' . \Drupal::database()->escapeLike($search) . '%';

  // Find the condition added by the UserName filter and expand it.
  // The filter generates a formula condition referencing users_field_data.name
  // and/or .mail — replace it in place with all four fields ORed together.
  foreach ($query->where as &$condition_group) {
    foreach ($condition_group['conditions'] as &$condition) {
      if (!isset($condition['field']) || !is_string($condition['field'])) {
        continue;
      }
      if (strpos($condition['field'], 'users_field_data.name') !== FALSE
          || strpos($condition['field'], 'users_field_data.mail') !== FALSE) {
        $condition['field'] = '(users_field_data.name LIKE :people_search'
          . ' OR users_field_data.mail LIKE :people_search'
          . ' OR user__field_first_name.field_first_name_value LIKE :people_search'
          . ' OR user__field_last_name.field_last_name_value LIKE :people_search)';
        $condition['value']    = [':people_search' => $like];
        $condition['operator'] = 'formula';
        return;
      }
    }
  }
}

/**
 * Implements hook_views_pre_render().
 *
 * Attach libraries to specific views.
 */
function ccsoccer_views_pre_render(\Drupal\views\ViewExecutable $view) {
  if ($view->id() === 'insurance_report') {
    $view->element['#attached']['library'][] = 'ccsoccer/insurance-report';
  }
}

/**
 * Implements hook_form_alter().
 *
 * Swap the masquerade autocomplete to search by first name, last name, and username.
 */
function ccsoccer_form_masquerade_block_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['autocomplete']['masquerade_as'])) {
    $form['autocomplete']['masquerade_as']['#selection_handler'] = 'ccsoccer_user_by_name';
    $form['autocomplete']['masquerade_as']['#selection_settings'] = [
      'include_anonymous' => FALSE,
      'match_operator' => 'CONTAINS',
    ];
    $form['autocomplete']['masquerade_as']['#placeholder'] = t('Search by name or username...');
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for user_login_form.
 *
 * - Adds autocomplete="username webauthn" to the username field so the browser
 *   knows to surface registered passkeys in the autofill dropdown (conditional UI).
 * - Registers an #after_build callback to attach the passkey conditional library
 *   and add help text once the wa module's fieldset is guaranteed present.
 */
function ccsoccer_form_user_login_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Add the WebAuthn autocomplete hint to the username field.
  // "username webauthn" tells the browser this is the field to attach
  // passkey autofill to — required for mediation: 'conditional' to work.
  if (isset($form['name'])) {
    $form['name']['#attributes']['autocomplete'] = 'username webauthn';
  }

  $form['#after_build'][] = 'ccsoccer_login_form_after_build';
}

/**
 * #after_build callback for the login form.
 *
 * Attaches the passkey conditional UI library and adds passkey help text.
 * Runs after all form alters and processing, so the wa module's
 * 'wa_login_method' fieldset is guaranteed to exist if passkey login is enabled.
 */
function ccsoccer_login_form_after_build(array $form, FormStateInterface $form_state) {
  if (isset($form['wa_login_method'])) {
    // Attach our conditional UI library. The JS will:
    // 1. Check if the browser supports mediation: 'conditional'.
    // 2. If yes: hide this fieldset and register the passkey autofill intent.
    // 3. If no: leave the fieldset visible so the button remains as a fallback.
    $form['#attached']['library'][] = 'ccsoccer/passkey-conditional';

    // Add help text inside the passkey fieldset, above the button.
    // Weight -10 ensures it renders before the passkey_login button.
    // This text is only visible when conditional UI is NOT supported
    // (i.e. the fieldset was not hidden by JS).
    $form['wa_login_method']['passkey_help'] = [
      '#type' => 'markup',
      '#markup' => '<p style="margin: 0 0 0.75em; font-size: 0.9em; color: #555;">'
        . t('Don\'t have a passkey yet? Log in with your username and password, then visit <strong>My Account &gt; My Profile</strong> to set up Touch ID or Face ID.')
        . '</p>',
      '#weight' => -10,
      // Required for elements added in #after_build — without this,
      // FormState->getError() crashes when iterating form children.
      '#parents' => [],
    ];
  }
  return $form;
}

/**
 * Implements hook_preprocess_views_view_field().
 *
 * Format the expiration_date string field consistently in the Override History
 * view. The field is stored as a plain string (e.g. "2026-03-01 23:59:00")
 * and needs to be displayed as "Mar 1, 2026, 11:59 PM".
 */
function ccsoccer_preprocess_views_view_field(&$variables) {
  $view = $variables['view'];
  if ($view->id() === 'ccsoccer_override_history') {
    $field_id = $variables['field']->field;
    if ($field_id === 'expiration_date') {
      $entity = $variables['row']->_entity ?? NULL;
      if ($entity) {
        $raw_value = $entity->get('expiration_date')->value;
        if ($raw_value) {
          $timestamp = strtotime($raw_value);
          if ($timestamp) {
            $variables['output'] = \Drupal\Core\Render\Markup::create(date('M j, Y, g:i A', $timestamp));
          }
        }
      }
    }
  }
}

/**
 * Implements hook_route_alter().
 *
 * Force all /admin/ccsoccer/* routes to use the admin theme (Claro).
 *
 * Without this, routes missing the _admin_route option would render with the
 * frontend theme (ccsoccer_theme), which breaks admin functionality like
 * the roster builder's Shift+drag and Option+drag group management.
 */
function ccsoccer_route_alter(array &$routes) {
  // Force /admin/ccsoccer/* routes to use admin theme (Claro).
  foreach ($routes as $route) {
    $path = $route->getPath();
    if (str_starts_with($path, '/admin/ccsoccer')) {
      $route->setOption('_admin_route', TRUE);
    }
  }

  // Force user profile pages to use the frontend theme.
  // Drupal core marks entity.user.edit_form as an admin route, which would
  // switch to Claro. We override this so the user edit form stays in
  // ccsoccer_theme — consistent with Address book, Payment methods, and Orders.
  $frontend_routes = [
    'entity.user.edit_form',
    'entity.user.canonical',
  ];
  foreach ($frontend_routes as $route_name) {
    if (isset($routes[$route_name])) {
      $routes[$route_name]->setOption('_admin_route', FALSE);
    }
  }
}
