export const PROVIDER = 'autodarts_ws';

const ADDC_EVENT_TYPES = new Set([
  'match_started',
  'score_update',
  'throw_recorded',
  'leg_won',
  'match_completed',
  'error'
]);

export function normalizeAutodartsEvent({ externalMatchId, rawPayload, fallbackTopic, liveMatch }) {
  const payload = objectOrEmpty(rawPayload);
  const topic = topicFrom(payload, fallbackTopic);
  const statePayload = autodartsStatePayload(payload);
  const isStateSnapshot = topic.endsWith('.state') || Boolean(statePayload);
  const parsedThrow = autodartsBoardThrow(payload);
  const eventType = eventTypeFrom(payload, statePayload, isStateSnapshot, parsedThrow);
  const errors = [];

  let scoreChange = {
    ...autodartsScoreChange(payload, liveMatch),
    ...autodartsBoardThrowScoreChange(parsedThrow, topic)
  };
  let legChange = autodartsLegChange(payload);
  let playerMapping = {
    ...autodartsPlayerMapping(payload),
    ...autodartsBoardThrowPlayerMapping(payload, parsedThrow)
  };
  let liveStats = [];

  if (statePayload) {
    scoreChange = {
      ...scoreChange,
      ...autodartsStateScoreChange(statePayload)
    };
    legChange = {
      ...legChange,
      ...autodartsStateLegChange(statePayload)
    };
    playerMapping = {
      ...playerMapping,
      ...autodartsStatePlayerMapping(statePayload)
    };
    statePayload.previousHomeScore = liveMatch?.home_score;
    statePayload.previousAwayScore = liveMatch?.away_score;
    liveStats = autodartsStateLiveStats(statePayload);
  }

  if (!eventType || !ADDC_EVENT_TYPES.has(eventType)) {
    errors.push('autodarts_ws payload did not map to a known ADDC event type.');
  }
  if (parsedThrow?.error) {
    errors.push(parsedThrow.error);
  }
  if (
    Object.keys(scoreChange).length === 0 &&
    Object.keys(legChange).length === 0 &&
    Object.keys(playerMapping).length === 0
  ) {
    errors.push('No score, leg, or player mapping fields were detected.');
  }

  const confidenceStatus = confidenceFor({ eventType, isStateSnapshot, statePayload, errors });
  scoreChange = {
    ...scoreChange,
    ...snapshotMetadata({ isStateSnapshot, statePayload })
  };

  return {
    providerName: PROVIDER,
    externalMatchId,
    rawPayload: payload,
    normalizedEventType: eventType,
    storageEventType: eventType || 'error',
    scoreChange,
    legChange,
    playerMapping,
    confidenceStatus,
    errors,
    normalizedPayload: {
      provider: PROVIDER,
      external_match_id: externalMatchId,
      normalized_event_type: eventType || null,
      score_change: scoreChange,
      leg_change: legChange,
      player_mapping: playerMapping,
      live_stats: liveStats,
      live_stats_source: liveStatsSource(payload, statePayload),
      confidence_status: confidenceStatus,
      errors,
      official_result_submission: false,
      notifications_sent: false,
      bridge_source: 'local_autodarts_bridge'
    },
    debugLog: parsedThrow?.parsed
      ? `${parsedThrow.isMiss ? 'Parsed miss' : 'Parsed throw'}: ${parsedThrow.dartCode} = ${parsedThrow.points}`
      : null,
    canStoreEvent: Boolean(eventType) && !['failed', 'unsupported'].includes(confidenceStatus)
  };
}

function liveStatsSource(payload, statePayload) {
  if (stringValue(statePayload?.source) === 'addc_visible_scoreboard') return 'visible_scoreboard';
  if (stringValue(payload?.channel) === 'addc.page.scoreboard') return 'visible_scoreboard';
  if (stringValue(payload?.channel) === 'autodarts.matches') return 'autodarts_socket';
  return 'autodarts_socket';
}

export function parseSocketMessage(message, fallbackTopic) {
  if (typeof message !== 'string') {
    return { topic: fallbackTopic, message_type: 'binary', byte_length: message?.byteLength ?? 0 };
  }

  const text = message.trim();
  if (!text) {
    return { topic: fallbackTopic, message_type: 'empty' };
  }

  try {
    const parsed = JSON.parse(text);
    if (Array.isArray(parsed)) {
      return { topic: fallbackTopic, items: parsed, message_type: 'array' };
    }
    if (parsed && typeof parsed === 'object') {
      return { topic: fallbackTopic, ...parsed };
    }
  } catch {
    return { topic: fallbackTopic, message: message, message_type: 'text' };
  }

  return { topic: fallbackTopic, message: message, message_type: typeof message };
}

function eventTypeFrom(rawPayload, statePayload, isStateSnapshot, parsedThrow) {
  const rawTypeCandidate = stringValue(
    rawPayload.event_type,
    rawPayload.type,
    rawPayload.event,
    rawPayload.eventName,
    rawPayload.kind,
    rawPayload.name,
    rawPayload.payload?.event,
    rawPayload.payload?.type,
    rawPayload.payload?.body?.event,
    rawPayload.data?.event,
    rawPayload.data?.body?.event,
    rawPayload.data?.type
  )?.toLowerCase() || '';

  if (rawPayload.error || rawTypeCandidate.includes('error') || rawTypeCandidate.includes('fail')) {
    return 'error';
  }

  if (isBoardImagePayload(rawPayload)) {
    return null;
  }

  if (parsedThrow?.parsed) {
    return 'throw_recorded';
  }
  if (parsedThrow?.error) return 'error';

  if (boolValue(statePayload?.gameFinished, rawPayload.gameFinished, rawPayload.payload?.gameFinished)) {
    return isTerminalMatchState(statePayload) ? 'match_completed' : 'leg_won';
  }
  if (isStateSnapshot) {
    return 'score_update';
  }

  const normalized = rawTypeCandidate.replaceAll('-', '_').replaceAll('.', '_').replaceAll(' ', '_');

  if (ADDC_EVENT_TYPES.has(normalized)) return normalized;
  if (normalized.includes('match') && (normalized.includes('start') || normalized.includes('begin'))) return 'match_started';
  if (normalized.includes('score') || normalized.includes('state')) return 'score_update';
  if (normalized.includes('throw') || normalized.includes('visit') || normalized.includes('dart')) return 'throw_recorded';
  if (normalized.includes('leg') && (normalized.includes('win') || normalized.includes('won') || normalized.includes('complete'))) return 'leg_won';
  if (normalized.includes('match') && (normalized.includes('complete') || normalized.includes('finish') || normalized.includes('end'))) return 'match_completed';
  return null;
}

function isTerminalMatchState(statePayload) {
  if (!statePayload || typeof statePayload !== 'object') return false;
  const targetLegs = intValue(statePayload.legs, statePayload.targetLegs, statePayload.bestOf);
  if (!targetLegs || targetLegs <= 0) return true;
  const legScores = Array.isArray(statePayload.scores)
    ? statePayload.scores
      .map(score => intValue(score?.legs, score?.leg, score?.wonLegs, score))
      .filter(value => value !== null)
    : [];
  return legScores.some(value => value >= targetLegs);
}

function autodartsStatePayload(rawPayload) {
  const candidates = [
    rawPayload,
    rawPayload.payload,
    rawPayload.data,
    rawPayload.payload?.body,
    rawPayload.data?.body,
    rawPayload.state,
    rawPayload.payload?.state,
    rawPayload.data?.state
  ];

  return candidates.find(candidate => candidate && typeof candidate === 'object' && (
    candidate.gameScores !== undefined ||
    candidate.scores !== undefined ||
    candidate.players !== undefined ||
    candidate.turns !== undefined ||
    candidate.gameFinished !== undefined
  )) || null;
}

function autodartsScoreChange(rawPayload, liveMatch) {
  const homeScore = intValue(
    rawPayload.home_score,
    rawPayload.homeScore,
    rawPayload.score?.home,
    rawPayload.scores?.home,
    rawPayload.state?.homeScore,
    rawPayload.payload?.home_score,
    rawPayload.data?.home_score
  );
  const awayScore = intValue(
    rawPayload.away_score,
    rawPayload.awayScore,
    rawPayload.score?.away,
    rawPayload.scores?.away,
    rawPayload.state?.awayScore,
    rawPayload.payload?.away_score,
    rawPayload.data?.away_score
  );

  const result = {};
  if (homeScore !== null) result.home_score = homeScore;
  if (awayScore !== null) result.away_score = awayScore;

  const remaining = intValue(rawPayload.remaining, rawPayload.remainingScore, rawPayload.payload?.remaining);
  const side = playerSide(rawPayload);
  if (Object.keys(result).length === 0 && remaining !== null && side) {
    result[`${side}_score`] = remaining;
    result.other_score = side === 'home' ? liveMatch.away_score : liveMatch.home_score;
  }
  return result;
}

function autodartsBoardThrowScoreChange(parsedThrow, topic) {
  if (!parsedThrow?.parsed) return {};

  const result = {
    source: PROVIDER,
    adapter_event_kind: 'board_throw',
    board_event: parsedThrow.boardEvent,
    board_status_topic: topic || null,
    dart_code: parsedThrow.dartCode,
    multiplier: parsedThrow.multiplier,
    segment: parsedThrow.segment,
    points: parsedThrow.points,
    throw_score: parsedThrow.points,
    is_miss: parsedThrow.isMiss,
    display_label: parsedThrow.displayLabel
  };

  if (parsedThrow.throwNumber !== null) result.throw_number = parsedThrow.throwNumber;
  if (parsedThrow.segmentName) result.segment_name = parsedThrow.segmentName;
  if (parsedThrow.segmentBed) result.segment_bed = parsedThrow.segmentBed;
  if (parsedThrow.segmentNumber !== null) result.segment_number = parsedThrow.segmentNumber;
  if (parsedThrow.segmentMultiplier !== null) result.segment_multiplier = parsedThrow.segmentMultiplier;
  if (Number.isFinite(parsedThrow.coordX)) result.coord_x = parsedThrow.coordX;
  if (Number.isFinite(parsedThrow.coordY)) result.coord_y = parsedThrow.coordY;

  return result;
}

function autodartsStateScoreChange(statePayload) {
  const gameScores = statePayload.gameScores;
  if (gameScores === undefined) return {};

  let values = [];
  if (Array.isArray(gameScores)) {
    values = gameScores.map(score => {
      if (intValue(score) !== null) return intValue(score);
      return intValue(score?.score, score?.value, score?.remaining, score?.points);
    }).filter(value => value !== null);
  } else if (gameScores && typeof gameScores === 'object') {
    values = [
      intValue(gameScores.home, gameScores.player1, gameScores.player_1, gameScores['0']),
      intValue(gameScores.away, gameScores.player2, gameScores.player_2, gameScores['1'])
    ].filter(value => value !== null);
  }

  if (values.length < 2) return {};
  return {
    home_score: values[0],
    away_score: values[1],
    source: PROVIDER
  };
}

function autodartsLegChange(rawPayload) {
  const result = {};
  const winner = stringValue(rawPayload.winner_id, rawPayload.winner, rawPayload.winnerId, rawPayload.payload?.winner);
  if (winner) result.external_winner_id = winner;

  const homeLegs = intValue(rawPayload.home_legs, rawPayload.homeLegs, rawPayload.legs?.home);
  const awayLegs = intValue(rawPayload.away_legs, rawPayload.awayLegs, rawPayload.legs?.away);
  if (homeLegs !== null) result.home_legs = homeLegs;
  if (awayLegs !== null) result.away_legs = awayLegs;
  if (Object.keys(result).length > 0) result.source = PROVIDER;
  return result;
}

function autodartsStateLegChange(statePayload) {
  const result = {};
  if (Array.isArray(statePayload.scores)) {
    const legs = statePayload.scores
      .map(score => intValue(score?.legs, score?.leg, score?.wonLegs, score))
      .filter(value => value !== null);
    if (legs.length >= 2) {
      result.home_legs = legs[0];
      result.away_legs = legs[1];
    }
  }

  const winnerIndex = intValue(statePayload.gameWinner, statePayload.winner);
  const winnerSide = sideForPlayerIndex(winnerIndex);
  if (winnerSide) {
    result.winner_index = winnerIndex;
    result.side = winnerSide;
  }
  if (boolValue(statePayload.gameFinished) === true) result.game_finished = true;
  if (stringValue(statePayload.variant)) result.variant = stringValue(statePayload.variant);
  if (intValue(statePayload.round) !== null) result.round = intValue(statePayload.round);
  if (Object.keys(result).length > 0) result.source = PROVIDER;
  return result;
}

function autodartsPlayerMapping(rawPayload) {
  const mapping = {};
  const externalPlayerId = stringValue(
    rawPayload.player_id,
    rawPayload.playerId,
    rawPayload.participantId,
    rawPayload.winner_id,
    rawPayload.winner,
    rawPayload.payload?.player_id,
    rawPayload.data?.playerId
  );
  const side = playerSide(rawPayload);
  if (externalPlayerId) mapping.external_player_id = externalPlayerId;
  if (side) mapping.side = side;
  return mapping;
}

function autodartsBoardThrowPlayerMapping(rawPayload, parsedThrow) {
  const data = objectOrEmpty(rawPayload.data);
  const mapping = {};
  const boardId = stringValue(data.boardId, rawPayload.boardId);
  if (boardId) mapping.board_id = boardId;
  const throwNumber = parsedThrow?.throwNumber ?? intValue(data.throwNumber);
  if (throwNumber !== null) mapping.throw_number = throwNumber;
  return mapping;
}

function autodartsStatePlayerMapping(statePayload) {
  const mapping = {};
  const currentIndex = intValue(statePayload.player);
  const currentSide = sideForPlayerIndex(currentIndex);
  if (currentSide) {
    mapping.current_player_index = currentIndex;
    mapping.current_side = currentSide;
  }
  if (Array.isArray(statePayload.players)) {
    mapping.external_players_count = statePayload.players.length;
    const homeExternal = externalPlayerId(statePayload.players, 0);
    const awayExternal = externalPlayerId(statePayload.players, 1);
    const homeName = externalPlayerName(statePayload.players, 0);
    const awayName = externalPlayerName(statePayload.players, 1);
    if (homeExternal) mapping.home_external_player_id = homeExternal;
    if (awayExternal) mapping.away_external_player_id = awayExternal;
    if (homeName) mapping.home_player_name = homeName;
    if (awayName) mapping.away_player_name = awayName;
  }
  if (Array.isArray(statePayload.turns)) {
    mapping.turns_count = statePayload.turns.length;
  }
  return mapping;
}

function autodartsStateLiveStats(statePayload) {
  const scores = Array.isArray(statePayload.scores) ? statePayload.scores : [];
  if (Array.isArray(statePayload.stats)) {
    return statePayload.stats.map((entry, index) => {
      const matchStats = objectOrEmpty(entry?.matchStats || entry);
      return {
        player_index: index,
        external_player_id: externalPlayerId(statePayload.players, index) || null,
        player_name: externalPlayerName(statePayload.players, index) || null,
        legs_won: intValue(scores[index]?.legs, scores[index]?.leg, scores[index]) ?? 0,
        total_score: intValue(matchStats.score) ?? 0,
        average: numberValue(matchStats.average) ?? 0,
        darts_thrown: intValue(matchStats.dartsThrown, matchStats.darts_thrown, matchStats.total_darts) ?? 0,
        checkout_attempts: intValue(matchStats.checkouts, matchStats.checkout_attempts) ?? 0,
        checkouts_hit: intValue(matchStats.checkoutsHit, matchStats.checkouts_hit) ?? 0,
        checkout_percentage: numberValue(matchStats.checkoutPercent) !== null ? numberValue(matchStats.checkoutPercent) * 100 : numberValue(matchStats.checkout_percentage) ?? 0,
        checkout_points: intValue(matchStats.checkoutPoints, matchStats.checkout_points) ?? 0,
        checkout_points_average: numberValue(matchStats.checkoutPointsAverage, matchStats.checkout_points_average) ?? 0,
        best_checkout: intValue(matchStats.checkoutPoints, matchStats.bestCheckout, matchStats.highestCheckout, matchStats.best_checkout) ?? 0,
        highest_visit: intValue(matchStats.highestVisit, matchStats.highest_visit, matchStats.highest, matchStats.hi) ?? 0,
        less60: intValue(matchStats.less60) ?? 0,
        plus60: intValue(matchStats.plus60) ?? 0,
        plus100: intValue(matchStats.plus100) ?? 0,
        plus140: intValue(matchStats.plus140) ?? 0,
        plus170: intValue(matchStats.plus170) ?? 0,
        first9_score: intValue(matchStats.first9Score, matchStats.first9_score) ?? 0,
        first9_average: numberValue(matchStats.first9Average, matchStats.first9_average) ?? 0,
        darts_until170: intValue(matchStats.dartsUntil170, matchStats.darts_until170) ?? 0,
        score_until170: intValue(matchStats.scoreUntil170, matchStats.score_until170) ?? 0,
        average_until170: numberValue(matchStats.averageUntil170, matchStats.average_until170) ?? 0,
        total180: intValue(matchStats.total180) ?? 0
      };
    });
  }

  const gameScores = Array.isArray(statePayload.gameScores) ? statePayload.gameScores : [];
  return gameScores.slice(0, 2).map((remaining, index) => {
    const matchStats = objectOrEmpty(scores[index]) || {};
    const previousRemaining = index === 0 ? statePayload.previousHomeScore : statePayload.previousAwayScore;
    const visitScore = intValue(previousRemaining) !== null && intValue(remaining) !== null
      ? Math.max(0, Math.min(180, intValue(previousRemaining) - intValue(remaining)))
      : 0;
    return {
      player_index: index,
      external_player_id: externalPlayerId(statePayload.players, index) || null,
      player_name: externalPlayerName(statePayload.players, index) || null,
      legs_won: intValue(scores[index]?.legs, scores[index]?.leg, scores[index]) ?? 0,
      total_score: intValue(matchStats.score) ?? 0,
      average: numberValue(matchStats.average) ?? 0,
      darts_thrown: intValue(matchStats.dartsThrown) ?? 0,
      checkout_attempts: intValue(matchStats.checkouts) ?? 0,
      checkouts_hit: intValue(matchStats.checkoutsHit) ?? 0,
      checkout_percentage: numberValue(matchStats.checkoutPercent) !== null ? numberValue(matchStats.checkoutPercent) * 100 : 0,
      checkout_points: intValue(matchStats.checkoutPoints) ?? 0,
      checkout_points_average: numberValue(matchStats.checkoutPointsAverage) ?? 0,
      best_checkout: intValue(matchStats.checkoutPoints, matchStats.bestCheckout, matchStats.highestCheckout) ?? 0,
      highest_visit: intValue(matchStats.highestVisit, matchStats.highest_visit, matchStats.highest, matchStats.hi) ?? visitScore,
      less60: intValue(matchStats.less60) ?? 0,
      plus60: intValue(matchStats.plus60) ?? 0,
      plus100: intValue(matchStats.plus100) ?? 0,
      plus140: intValue(matchStats.plus140) ?? 0,
      plus170: intValue(matchStats.plus170) ?? 0,
      first9_score: intValue(matchStats.first9Score) ?? 0,
      first9_average: numberValue(matchStats.first9Average) ?? 0,
      darts_until170: intValue(matchStats.dartsUntil170) ?? 0,
      score_until170: intValue(matchStats.scoreUntil170) ?? 0,
      average_until170: numberValue(matchStats.averageUntil170) ?? 0,
      total180: intValue(matchStats.total180) ?? 0
    };
  });
}

function snapshotMetadata({ isStateSnapshot, statePayload }) {
  if (!isStateSnapshot) return {};
  const metadata = {
    adapter_event_kind: 'state_snapshot',
    automatic_ingestion_enabled: true,
    official_result_submission: false,
    notifications_sent: false
  };
  if (!statePayload) return metadata;
  if (boolValue(statePayload.gameFinished) !== null) metadata.game_finished = boolValue(statePayload.gameFinished);
  if (intValue(statePayload.player) !== null) metadata.current_player_index = intValue(statePayload.player);
  if (intValue(statePayload.round) !== null) metadata.round = intValue(statePayload.round);
  if (stringValue(statePayload.variant)) metadata.variant = stringValue(statePayload.variant);
  return metadata;
}

function confidenceFor({ eventType, isStateSnapshot, statePayload, errors }) {
  if (!eventType) return 'failed';
  if (eventType === 'throw_recorded' && errors.length === 0) return 'high';
  if (
    isStateSnapshot &&
    statePayload?.gameScores !== undefined &&
    statePayload?.scores !== undefined &&
    statePayload?.gameFinished !== undefined
  ) {
    return 'high';
  }
  return errors.length === 0 ? 'medium' : 'low';
}

function topicFrom(payload, fallbackTopic) {
  return stringValue(payload.topic, payload.channel, payload.subject, payload.meta?.topic, fallbackTopic)?.toLowerCase() || '';
}

function isBoardThrowPayload(rawPayload) {
  const data = objectOrEmpty(rawPayload.data);
  const event = stringValue(data.event)?.toLowerCase() || '';
  return event.includes('throw detected') && Boolean(data.throw?.segment);
}

function autodartsBoardThrow(rawPayload) {
  if (!isBoardThrowPayload(rawPayload)) return null;

  const data = objectOrEmpty(rawPayload.data);
  const segmentPayload = objectOrEmpty(data.throw?.segment);
  const segmentName = stringValue(segmentPayload.name)?.trim().toUpperCase() || '';
  const parsed = parseDartCode(segmentName);
  const coords = objectOrEmpty(data.throw?.coords);

  if (!parsed) {
    return {
      parsed: false,
      boardEvent: stringValue(data.event),
      dartCode: segmentName || null,
      error: `Unknown Autodarts throw code: ${segmentName || '<empty>'}.`
    };
  }

  return {
    parsed: true,
    boardEvent: stringValue(data.event),
    dartCode: parsed.dartCode,
    multiplier: parsed.multiplier,
    segment: parsed.segment,
    points: parsed.points,
    isMiss: parsed.isMiss,
    displayLabel: parsed.displayLabel,
    throwNumber: intValue(data.throwNumber),
    segmentName,
    segmentBed: stringValue(segmentPayload.bed),
    segmentNumber: intValue(segmentPayload.number),
    segmentMultiplier: intValue(segmentPayload.multiplier),
    coordX: Number.isFinite(coords.x) ? coords.x : null,
    coordY: Number.isFinite(coords.y) ? coords.y : null
  };
}

export function parseDartCode(rawCode) {
  const code = stringValue(rawCode)?.trim().toUpperCase();
  if (!code) return null;

  const bullAliases = {
    SB: { multiplier: 1, segment: 'bull', points: 25, isMiss: false, displayLabel: 'Single Bull' },
    DB: { multiplier: 2, segment: 'bull', points: 50, isMiss: false, displayLabel: 'Double Bull' },
    S25: { multiplier: 1, segment: 'bull', points: 25, isMiss: false, displayLabel: 'Single Bull' },
    D25: { multiplier: 2, segment: 'bull', points: 50, isMiss: false, displayLabel: 'Double Bull' }
  };
  if (bullAliases[code]) return { dartCode: code, ...bullAliases[code] };

  const match = code.match(/^([SDTM])([1-9]|1[0-9]|20|25)$/);
  if (!match) return null;

  const [, prefix, numberText] = match;
  const number = Number(numberText);
  const multipliers = { S: 1, D: 2, T: 3, M: 0 };
  const labels = { S: 'Single', D: 'Double', T: 'Triple', M: 'Miss at' };
  const multiplier = multipliers[prefix];
  const isMiss = prefix === 'M';

  if (number === 25 && prefix === 'T') return null;

  return {
    dartCode: code,
    multiplier,
    segment: String(number),
    points: number * multiplier,
    isMiss,
    displayLabel: `${labels[prefix]} ${number}`
  };
}

function isBoardImagePayload(rawPayload) {
  const channel = stringValue(rawPayload.channel)?.toLowerCase() || '';
  const topic = stringValue(rawPayload.topic)?.toLowerCase() || '';
  return channel === 'autodarts.boards.images' || topic.includes('.live?cam=');
}

function playerSide(rawPayload) {
  const side = stringValue(rawPayload.side, rawPayload.player_side, rawPayload.playerSide, rawPayload.payload?.side)?.toLowerCase();
  if (['home', 'player1', 'player_1', 'left'].includes(side)) return 'home';
  if (['away', 'player2', 'player_2', 'right'].includes(side)) return 'away';
  const index = intValue(rawPayload.playerIndex, rawPayload.player, rawPayload.payload?.player);
  return sideForPlayerIndex(index);
}

function sideForPlayerIndex(index) {
  if (index === 0) return 'home';
  if (index === 1) return 'away';
  return null;
}

function externalPlayerId(players, index) {
  if (!Array.isArray(players)) return null;
  const player = players[index];
  return stringValue(player, player?.id, player?.uid, player?.userId, player?.name, player?.username);
}

function externalPlayerName(players, index) {
  if (!Array.isArray(players)) return null;
  const player = players[index];
  return stringValue(player?.name, player?.user?.name, player?.host?.name, player?.username);
}

function intValue(...values) {
  for (const value of values) {
    if (Number.isInteger(value)) return value;
    if (typeof value === 'number' && Number.isFinite(value)) return Math.trunc(value);
    if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) return Math.trunc(Number(value));
  }
  return null;
}

function numberValue(...values) {
  for (const value of values) {
    if (typeof value === 'number' && Number.isFinite(value)) return value;
    if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) return Number(value);
  }
  return null;
}

function boolValue(...values) {
  for (const value of values) {
    if (typeof value === 'boolean') return value;
    if (typeof value === 'number') return value !== 0;
    if (typeof value === 'string') {
      const normalized = value.trim().toLowerCase();
      if (['true', 'yes', '1'].includes(normalized)) return true;
      if (['false', 'no', '0'].includes(normalized)) return false;
    }
  }
  return null;
}

function stringValue(...values) {
  for (const value of values) {
    if (typeof value === 'string' && value.length > 0) return value;
    if (Number.isFinite(value)) return String(value);
  }
  return null;
}

function objectOrEmpty(value) {
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
}
