<?php

/**
 * In-game reactions and quick chat, for every game.
 *
 * Each game used to carry its own emote implementation, and each stored the
 * reaction inside the match's own `game_state`:
 *
 *   - chess kept ONE shared `emote` slot, so two players reacting in the same
 *     second overwrote each other, and neither carried who sent it;
 *   - ludo posted the reaction through the move endpoint, which REPLACES
 *     game_state wholesale — sending an emote wiped `turn_count` and broke the
 *     game's sync loop outright;
 *   - domino had a third variant, again a single shared slot;
 *   - backgammon wrote to a `last_emote` column that no client ever read, so
 *     the opponent never saw anything at all.
 *
 * Reactions are chat, not game state. They now live in `chat_messages` on a
 * `match` channel, addressed by match id — a table built for exactly this, with
 * a sender, a timestamp and an incremental read. Nothing here can touch a
 * board, a clock or a turn, which is the property that matters: a social
 * feature must never be able to corrupt a game in progress.
 *
 * Only preset emotes and preset phrases are accepted. Free text in a live match
 * between strangers is a moderation surface nobody is staffing.
 */

header('Content-Type: application/json');
require_once __DIR__ . '/../includes/cors.php';

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }

require_once __DIR__ . '/../includes/supabase.php';
require_once __DIR__ . '/../includes/auth.php';

$token  = requireAuth();
$userId = getUserId($token);
$input  = getInput();
$action = $input['action'] ?? ($_GET['action'] ?? '');

/** Emote keys the client may send. Anything else is rejected. */
const REACTION_EMOTES = [
    'gg'        => '🤝',
    'good_move' => '👏',
    'think'     => '🤔',
    'hurry'     => '⏱️',
    'wow'       => '😮',
    'laugh'     => '😂',
    'sad'       => '😢',
    'angry'     => '😤',
    'hello'     => '👋',
    'fire'      => '🔥',
    'love'      => '❤️',
    'salute'    => '🫡',
];

/** Preset phrase ids. The client renders the localised text for each. */
const REACTION_PHRASES = [
    'good_game', 'good_luck', 'well_played', 'oops', 'thanks',
    'rematch', 'hurry_please', 'sorry', 'nice_try', 'close_one',
];

/** Minimum gap between one player's reactions in a single match. */
const REACTION_COOLDOWN_SECONDS = 2;

switch ($action) {
    case 'send': handleSend($userId, $input); break;
    case 'poll': handlePoll($userId, $input); break;
    default:     jsonError('Invalid action');
}

// ---------------------------------------------------------------------------

function handleSend(string $userId, array $input): void {
    $matchId = $input['match_id'] ?? '';
    $gameKey = $input['game_key'] ?? 'chess';
    $kind    = $input['kind'] ?? 'emote';
    $key     = (string)($input['key'] ?? '');

    if (!$matchId) jsonError('match_id required');
    if (!isValidUuid($matchId)) jsonError('match_id must be a uuid');

    if ($kind === 'emote') {
        if (!isset(REACTION_EMOTES[$key])) jsonError('Unknown emote');
    } elseif ($kind === 'phrase') {
        if (!in_array($key, REACTION_PHRASES, true)) jsonError('Unknown phrase');
    } else {
        jsonError('kind must be emote or phrase');
    }

    $sdb = supabaseService();
    if (!isMatchParticipant($sdb, $gameKey, $matchId, $userId)) {
        jsonError('You are not in this match', 403);
    }

    // Server-side cooldown. A client-side one is a courtesy; this is the rule.
    $recent = $sdb->get('chat_messages', [
        'channel_type' => 'eq.match',
        'channel_id'   => 'eq.' . $matchId,
        'sender_id'    => 'eq.' . $userId,
        'select'       => 'created_at',
        'order'        => 'created_at.desc',
        'limit'        => 1,
    ]);
    if (is_array($recent) && !empty($recent) && !isset($recent['error'])) {
        $last = strtotime($recent[0]['created_at'] ?? '');
        if ($last && (time() - $last) < REACTION_COOLDOWN_SECONDS) {
            jsonResponse(['ok' => false, 'cooldown' => true]);
        }
    }

    $row = $sdb->insert('chat_messages', [
        'channel_type' => 'match',
        'channel_id'   => $matchId,
        'sender_id'    => $userId,
        'content'      => $key,
        'message_type' => $kind,
        'metadata'     => ['game_key' => $gameKey],
    ]);
    if (isset($row['error'])) jsonError($row['error']);

    jsonResponse(['ok' => true, 'message' => $row[0] ?? $row]);
}

/**
 * Everything said in this match since `after`, newest last, with the sender's
 * name and avatar attached so the client can show *who* reacted without a
 * second request.
 */
function handlePoll(string $userId, array $input): void {
    $matchId = $input['match_id'] ?? ($_GET['match_id'] ?? '');
    $gameKey = $input['game_key'] ?? ($_GET['game_key'] ?? 'chess');
    $after   = $input['after'] ?? ($_GET['after'] ?? '');

    if (!$matchId) jsonError('match_id required');
    if (!isValidUuid($matchId)) jsonError('match_id must be a uuid');

    $sdb = supabaseService();
    if (!isMatchParticipant($sdb, $gameKey, $matchId, $userId)) {
        jsonError('You are not in this match', 403);
    }

    $params = [
        'channel_type' => 'eq.match',
        'channel_id'   => 'eq.' . $matchId,
        'select'       => 'id,sender_id,content,message_type,created_at',
        'order'        => 'created_at.asc',
        'limit'        => 30,
    ];
    // Only ever ask for what we have not seen. A fresh client starts from the
    // last few seconds rather than replaying the whole match.
    $params['created_at'] = $after
        ? 'gt.' . $after
        : 'gte.' . gmdate('c', time() - 10);

    $rows = $sdb->get('chat_messages', $params);
    if (!is_array($rows) || isset($rows['error'])) {
        jsonResponse(['messages' => [], 'now' => gmdate('c')]);
    }

    $senders = array_values(array_unique(array_filter(array_column($rows, 'sender_id'))));
    $profiles = [];
    if ($senders) {
        $p = $sdb->get('profiles', [
            'id' => 'in.(' . implode(',', $senders) . ')',
            'select' => 'id,display_name,username,avatar_url',
        ]);
        if (is_array($p) && !isset($p['error'])) {
            foreach ($p as $row) $profiles[$row['id']] = $row;
        }
    }

    $out = [];
    foreach ($rows as $r) {
        $prof = $profiles[$r['sender_id']] ?? null;
        $out[] = [
            'id'         => $r['id'],
            'sender_id'  => $r['sender_id'],
            'kind'       => $r['message_type'],
            'key'        => $r['content'],
            'emoji'      => REACTION_EMOTES[$r['content']] ?? null,
            'name'       => $prof['display_name'] ?? $prof['username'] ?? null,
            'avatar_url' => $prof['avatar_url'] ?? null,
            'at'         => $r['created_at'],
            'mine'       => $r['sender_id'] === $userId,
        ];
    }

    jsonResponse(['messages' => $out, 'now' => gmdate('c')]);
}

// ---------------------------------------------------------------------------

function isValidUuid(string $v): bool {
    return (bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $v);
}

/**
 * Is this player actually in this match?
 *
 * Chess keeps two explicit colour columns; ludo and domino keep a `players`
 * json array. A game whose table does not exist simply returns false, which
 * denies rather than errors.
 */
function isMatchParticipant($sdb, string $gameKey, string $matchId, string $userId): bool {
    if ($gameKey === 'chess') {
        $rows = $sdb->get('matches', [
            'id' => 'eq.' . $matchId,
            'select' => 'white_player_id,black_player_id',
            'limit' => 1,
        ]);
        if (!is_array($rows) || empty($rows) || isset($rows['error'])) return false;
        return $rows[0]['white_player_id'] === $userId || $rows[0]['black_player_id'] === $userId;
    }

    $table = [
        'ludo'   => 'ludo_matches',
        'domino' => 'domino_matches',
    ][$gameKey] ?? null;
    if (!$table) return false;

    $rows = $sdb->get($table, ['id' => 'eq.' . $matchId, 'select' => 'players', 'limit' => 1]);
    if (!is_array($rows) || empty($rows) || isset($rows['error'])) return false;

    $players = $rows[0]['players'] ?? [];
    if (is_string($players)) $players = json_decode($players, true) ?: [];
    if (!is_array($players)) return false;

    foreach ($players as $p) {
        $id = is_array($p) ? ($p['id'] ?? $p['player_id'] ?? null) : $p;
        if ($id === $userId) return true;
    }
    return false;
}
