<?php
/**
 * 聊天后台 API：负责会话消息、用户信息、备注简介、导入导出与表情包清单。
 * 作者: @ccfork
 */
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');

$data_dir = __DIR__ . '/data/';
$messages_dir = $data_dir . 'messages/';
$watch_dir = $data_dir . 'watch/';
$sessions_file = $data_dir . 'sessions.json';

if (!file_exists($messages_dir)) mkdir($messages_dir, 0777, true);
if (!file_exists($watch_dir)) mkdir($watch_dir, 0777, true);

$raw_input = file_get_contents('php://input');
$json_input = json_decode($raw_input, true);
if (!is_array($json_input)) $json_input = [];

$action = $_GET['action'] ?? $_POST['action'] ?? ($json_input['action'] ?? '');
$admin_api_key = 'admin123';

function respond_json($payload) {
    echo json_encode($payload, JSON_UNESCAPED_UNICODE);
    exit;
}

function sanitize_session_id($session_id) {
    return preg_replace('/[^A-Za-z0-9_-]/', '', $session_id ?? '');
}

function read_json_file($file, $fallback = []) {
    if (!file_exists($file)) return $fallback;
    $data = json_decode(file_get_contents($file), true);
    return is_array($data) ? $data : $fallback;
}

function write_json_file($file, $data) {
    file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

function session_file_path($messages_dir, $session_id) {
    return $messages_dir . sanitize_session_id($session_id) . '.json';
}

function session_dir_path($messages_dir, $session_id) {
    return $messages_dir . sanitize_session_id($session_id) . '/';
}

function message_file_path($messages_dir, $session_id) {
    return session_dir_path($messages_dir, $session_id) . 'messages.json';
}

function image_dir_path($messages_dir, $session_id) {
    return session_dir_path($messages_dir, $session_id) . 'images/';
}

function ensure_session_dir($messages_dir, $session_id) {
    $dir = session_dir_path($messages_dir, $session_id);
    $image_dir = image_dir_path($messages_dir, $session_id);
    if (!file_exists($dir)) mkdir($dir, 0777, true);
    if (!file_exists($image_dir)) mkdir($image_dir, 0777, true);
}

function read_messages_file($messages_dir, $session_id) {
    $messages = read_json_file(message_file_path($messages_dir, $session_id), null);
    if (!is_array($messages)) {
        $messages = read_json_file(session_file_path($messages_dir, $session_id), []);
    }
    if (!is_array($messages)) $messages = [];
    foreach ($messages as &$message) {
        if (is_array($message)) migrate_reply_from_message($message);
    }
    unset($message);
    return $messages;
}

function write_messages_file($messages_dir, $session_id, $messages) {
    ensure_session_dir($messages_dir, $session_id);
    write_json_file(message_file_path($messages_dir, $session_id), array_values($messages));
}

function delete_directory($dir) {
    if (!is_dir($dir)) return;
    $items = scandir($dir);
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        $path = $dir . DIRECTORY_SEPARATOR . $item;
        if (is_dir($path)) {
            delete_directory($path);
        } elseif (is_file($path)) {
            unlink($path);
        }
    }
    rmdir($dir);
}

function watch_file_path($watch_dir, $session_id) {
    return $watch_dir . sanitize_session_id($session_id) . '.json';
}

function client_ip() {
    $keys = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
    foreach ($keys as $key) {
        if (!empty($_SERVER[$key])) {
            $value = trim(explode(',', $_SERVER[$key])[0]);
            if ($value !== '') return $value;
        }
    }
    return 'unknown';
}

function message_preview($message) {
    if (is_array($message)) {
        if (($message['type'] ?? '') === 'image') return '[图片]';
        $message = $message['message'] ?? '';
    }
    $message = trim($message ?? '');
    if (preg_match('/^\[bq:([a-z0-9_-]+):([^\]\s]+)\]$/i', $message, $matches)) {
        $labels = ['douyin' => '抖音表情', 'qq' => 'QQ表情', 'tieba' => '贴吧表情'];
        return '[' . ($labels[$matches[1]] ?? '表情') . ']';
    }
    $preview = preg_replace('/\[bq:([a-z0-9_-]+):([^\]\s]+)\]/i', '[表情]', $message);
    return mb_substr($preview, 0, 40);
}

function normalize_reply_payload($reply) {
    if (!is_array($reply)) return null;
    $id = intval($reply['id'] ?? $reply['message_id'] ?? $reply['reply_to_id'] ?? 0);
    $sender = ($reply['sender'] ?? '') === 'admin' ? 'admin' : 'user';
    $type = ($reply['type'] ?? '') === 'image' ? 'image' : 'text';
    $preview = trim($reply['preview'] ?? $reply['message'] ?? '');
    if ($preview === '' && $type === 'image') $preview = '[图片]';
    if ($id <= 0 && $preview === '') return null;
    return [
        'id' => $id,
        'sender' => $sender,
        'type' => $type,
        'preview' => mb_substr($preview, 0, 120)
    ];
}

function attach_reply_data(&$message, $reply) {
    $reply = normalize_reply_payload($reply);
    if (!$reply) return;
    $message['reply'] = $reply;
    $message['reply_to_id'] = $reply['id'];
    $message['reply_to_sender'] = $reply['sender'];
    $message['reply_to_type'] = $reply['type'];
    $message['reply_to_preview'] = $reply['preview'];
}

function filter_reply_for_messages($reply, $messages) {
    $reply = normalize_reply_payload($reply);
    if (!$reply || empty($reply['id'])) return $reply;
    foreach ($messages as $message) {
        if (intval($message['id'] ?? 0) !== intval($reply['id'])) continue;
        return !empty($message['recalled']) ? null : $reply;
    }
    return null;
}

function migrate_reply_from_message(&$message) {
    if (!empty($message['reply']) && is_array($message['reply'])) {
        $message['reply'] = normalize_reply_payload($message['reply']);
        if (!$message['reply']) unset($message['reply']);
        return;
    }
    if (!isset($message['reply_to_id']) && !isset($message['reply_to_preview']) && !isset($message['reply_to_sender'])) return;
    $reply = normalize_reply_payload([
        'id' => $message['reply_to_id'] ?? 0,
        'sender' => $message['reply_to_sender'] ?? 'user',
        'type' => $message['reply_to_type'] ?? 'text',
        'preview' => $message['reply_to_preview'] ?? ''
    ]);
    if ($reply) $message['reply'] = $reply;
}

function load_sessions($sessions_file) {
    return read_json_file($sessions_file, []);
}

function save_sessions($sessions_file, $sessions) {
    write_json_file($sessions_file, array_values($sessions));
}

function find_session_index($sessions, $session_id) {
    foreach ($sessions as $index => $session) {
        if (($session['id'] ?? '') === $session_id) return $index;
    }
    return -1;
}

function ensure_session($sessions_file, $session_id, $updates = []) {
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') return null;

    $sessions = load_sessions($sessions_file);
    $now = time();
    $index = find_session_index($sessions, $session_id);
    if ($index < 0) {
        $sessions[] = [
            'id' => $session_id,
            'created' => $now,
            'last_time' => $now,
            'last_message' => '新会话',
            'ip' => client_ip(),
            'user_id' => $session_id,
            'remark' => '',
            'intro' => '',
            'unread' => 0,
            'last_read_id' => 0,
            'visited_questionnaire' => false
        ];
        $index = count($sessions) - 1;
    }

    $sessions[$index] = array_merge($sessions[$index], $updates);
    if (empty($sessions[$index]['ip']) || $sessions[$index]['ip'] === 'unknown') {
        $sessions[$index]['ip'] = client_ip();
    }
    if (empty($sessions[$index]['user_id'])) $sessions[$index]['user_id'] = $session_id;
    if (!isset($sessions[$index]['remark'])) $sessions[$index]['remark'] = '';
    if (!isset($sessions[$index]['intro'])) $sessions[$index]['intro'] = '';
    if (!isset($sessions[$index]['unread'])) $sessions[$index]['unread'] = 0;
    if (!isset($sessions[$index]['last_read_id'])) $sessions[$index]['last_read_id'] = 0;
    if (!isset($sessions[$index]['message_seq'])) $sessions[$index]['message_seq'] = 0;
    if (!isset($sessions[$index]['clear_seq'])) $sessions[$index]['clear_seq'] = 0;
    if (!isset($sessions[$index]['recall_seq'])) $sessions[$index]['recall_seq'] = 0;
    if (!isset($sessions[$index]['admin_clear_after_id'])) $sessions[$index]['admin_clear_after_id'] = 0;
    if (!isset($sessions[$index]['chat_clear_after_id'])) $sessions[$index]['chat_clear_after_id'] = 0;
    if (!isset($sessions[$index]['visited_questionnaire'])) $sessions[$index]['visited_questionnaire'] = true;
    if (!isset($sessions[$index]['chat_seen_admin_id'])) $sessions[$index]['chat_seen_admin_id'] = 0;
    if (!isset($sessions[$index]['chat_seen_at'])) $sessions[$index]['chat_seen_at'] = 0;
    if (!isset($sessions[$index]['typing_until'])) $sessions[$index]['typing_until'] = 0;

    save_sessions($sessions_file, $sessions);
    return $sessions[$index];
}

function next_message_id($sessions_file, $session_id) {
    $sessions = load_sessions($sessions_file);
    $index = find_session_index($sessions, $session_id);
    if ($index < 0) {
        ensure_session($sessions_file, $session_id);
        $sessions = load_sessions($sessions_file);
        $index = find_session_index($sessions, $session_id);
    }
    if ($index < 0) return 1;

    // 消息序号独立递增，清空记录后继续往前走，避免前端轮询漏消息。
    $next_id = intval($sessions[$index]['message_seq'] ?? 0) + 1;
    $sessions[$index]['message_seq'] = $next_id;
    save_sessions($sessions_file, $sessions);
    return $next_id;
}

function touch_session_message($sessions_file, $session_id, $message, $sender, $message_id) {
    $sessions = load_sessions($sessions_file);
    $index = find_session_index($sessions, $session_id);
    if ($index < 0) {
        ensure_session($sessions_file, $session_id);
        $sessions = load_sessions($sessions_file);
        $index = find_session_index($sessions, $session_id);
    }
    if ($index < 0) return;

    $sessions[$index]['last_time'] = time();
    $sessions[$index]['last_message'] = message_preview($message);
    $sessions[$index]['ip'] = $sessions[$index]['ip'] ?? client_ip();
    if ($sender === 'user') {
        $sessions[$index]['unread'] = intval($sessions[$index]['unread'] ?? 0) + 1;
        $sessions[$index]['user_last_message_at'] = time();
    } else {
        $sessions[$index]['last_admin_message_id'] = $message_id;
        $sessions[$index]['admin_last_message_at'] = time();
    }
    save_sessions($sessions_file, $sessions);
}

function safe_image_extension($mime) {
    $map = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/gif' => 'gif',
        'image/webp' => 'webp'
    ];
    return $map[$mime] ?? '';
}

function image_has_script_payload($file) {
    $content = @file_get_contents($file);
    if ($content === false) return true;
    // 轻量扫描图片原始内容，拦截常见 PHP/JS/HTML 代码注入片段。
    $needles = [
        '<?php', '<?=', '<script', '</script', 'javascript:', 'onerror=', 'onload=',
        'eval(', 'base64_decode(', 'shell_exec(', 'system(', 'passthru(', 'assert(',
        'document.cookie', 'window.location', '<iframe', '<object', '<embed'
    ];
    $lower = strtolower($content);
    foreach ($needles as $needle) {
        if (strpos($lower, $needle) !== false) return true;
    }
    return false;
}

function save_image_message($messages_dir, $sessions_file, $session_id, $sender) {
    $session_id = sanitize_session_id($session_id);
    $sender = $sender === 'admin' ? 'admin' : 'user';
    if ($session_id === '' || empty($_FILES['image'])) respond_json(['success' => false, 'error' => '参数错误']);

    $file = $_FILES['image'];
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) respond_json(['success' => false, 'error' => '图片上传失败']);
    if (intval($file['size'] ?? 0) <= 0 || intval($file['size'] ?? 0) > 5 * 1024 * 1024) {
        respond_json(['success' => false, 'error' => '图片最大 5MB']);
    }

    $tmp = $file['tmp_name'] ?? '';
    if (!is_uploaded_file($tmp)) respond_json(['success' => false, 'error' => '图片来源异常']);
    $info = @getimagesize($tmp);
    if (!$info || empty($info['mime'])) respond_json(['success' => false, 'error' => '文件不是有效图片']);
    $mime = strtolower($info['mime']);
    $ext = safe_image_extension($mime);
    if ($ext === '') respond_json(['success' => false, 'error' => '仅支持 jpg/png/gif/webp']);
    if (image_has_script_payload($tmp)) respond_json(['success' => false, 'error' => '图片内容包含异常代码']);

    ensure_session($sessions_file, $session_id, ['ip' => client_ip()]);
    ensure_session_dir($messages_dir, $session_id);
    $new_id = next_message_id($sessions_file, $session_id);
    $file_name = 'img_' . $new_id . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
    $target = image_dir_path($messages_dir, $session_id) . $file_name;
    if (!move_uploaded_file($tmp, $target)) respond_json(['success' => false, 'error' => '图片保存失败']);

    $now = time();
    $relative_url = 'data/messages/' . rawurlencode($session_id) . '/images/' . rawurlencode($file_name);
    $messages = read_messages_file($messages_dir, $session_id);
    $reply = filter_reply_for_messages(json_decode($_POST['reply'] ?? 'null', true), $messages);
    $messages[] = [
        'id' => $new_id,
        'session_id' => $session_id,
        'sender' => $sender,
        'type' => 'image',
        'message' => '[图片]',
        'image' => [
            'url' => $relative_url,
            'name' => $file_name,
            'mime' => $mime,
            'size' => intval($file['size'] ?? 0),
            'width' => intval($info[0] ?? 0),
            'height' => intval($info[1] ?? 0)
        ],
        'time' => date('H:i:s', $now),
        'timestamp' => $now,
        'ip' => $sender === 'user' ? client_ip() : ''
    ];
    $last_index = count($messages) - 1;
    attach_reply_data($messages[$last_index], $reply);
    write_messages_file($messages_dir, $session_id, $messages);
    touch_session_message($sessions_file, $session_id, ['type' => 'image'], $sender, $new_id);
    respond_json(['success' => true, 'message_id' => $new_id]);
}

function visible_message_count($messages_dir, $session) {
    $session_id = $session['id'] ?? '';
    if ($session_id === '') return 0;
    $messages = read_messages_file($messages_dir, $session_id);
    $clear_after_id = intval($session['admin_clear_after_id'] ?? 0);
    $count = 0;
    foreach ($messages as $message) {
        if (intval($message['id'] ?? 0) > $clear_after_id) $count++;
    }
    return $count;
}

function enrich_session_list($sessions_file, $messages_dir) {
    $sessions = load_sessions($sessions_file);
    $now = time();
    foreach ($sessions as &$session) {
        $session = ensure_session($sessions_file, $session['id'] ?? '', $session);
        $message_count = visible_message_count($messages_dir, $session);
        $last_admin_id = intval($session['last_admin_message_id'] ?? 0);
        $seen_admin_id = intval($session['chat_seen_admin_id'] ?? 0);
        $session['message_count'] = $message_count;
        $session['has_current_chat'] = $message_count > 0 || intval($session['unread'] ?? 0) > 0;
        $session['chat_is_typing'] = intval($session['typing_until'] ?? 0) >= $now;
        $session['admin_message_read'] = $last_admin_id > 0 && $seen_admin_id >= $last_admin_id;
        $session['read_label'] = $last_admin_id > 0 ? ($session['admin_message_read'] ? '已读' : '未读') : '';
    }
    unset($session);
    usort($sessions, function($a, $b) {
        return intval($b['last_time'] ?? 0) - intval($a['last_time'] ?? 0);
    });
    return $sessions;
}

function save_message($messages_dir, $sessions_file, $session_id, $message, $sender, $reply = null) {
    $session_id = sanitize_session_id($session_id);
    $message = trim($message ?? '');
    $sender = $sender === 'admin' ? 'admin' : 'user';

    if ($session_id === '' || $message === '') {
        respond_json(['success' => false, 'error' => '参数错误']);
    }

    ensure_session($sessions_file, $session_id, ['ip' => client_ip()]);
    $messages = read_messages_file($messages_dir, $session_id);
    $reply = filter_reply_for_messages($reply, $messages);
    $new_id = next_message_id($sessions_file, $session_id);
    $now = time();

    $messages[] = [
        'id' => $new_id,
        'session_id' => $session_id,
        'sender' => $sender,
        'message' => $message,
        'time' => date('H:i:s', $now),
        'timestamp' => $now,
        'ip' => $sender === 'user' ? client_ip() : ''
    ];
    $last_index = count($messages) - 1;
    attach_reply_data($messages[$last_index], $reply);

    write_messages_file($messages_dir, $session_id, $messages);
    touch_session_message($sessions_file, $session_id, $message, $sender, $new_id);

    respond_json(['success' => true, 'message_id' => $new_id]);
}

function recall_message($messages_dir, $sessions_file, $session_id, $message_id, $caller) {
    global $json_input, $admin_api_key;
    $session_id = sanitize_session_id($session_id);
    $message_id = intval($message_id);
    $caller = $caller === 'admin' ? 'admin' : 'user';
    if ($session_id === '' || $message_id <= 0) respond_json(['success' => false, 'error' => '参数错误']);
    if ($caller === 'admin' && ($json_input['admin_auth'] ?? '') !== $admin_api_key) {
        respond_json(['success' => false, 'error' => '没有撤回权限']);
    }

    $messages = read_messages_file($messages_dir, $session_id);
    $found = false;
    foreach ($messages as &$message) {
        if (intval($message['id'] ?? 0) !== $message_id) continue;
        $found = true;
        $sender = ($message['sender'] ?? '') === 'admin' ? 'admin' : 'user';
        if ($sender !== $caller) {
            respond_json(['success' => false, 'error' => '只能撤回自己发送的消息']);
        }
        if (!empty($message['recalled'])) break;
        $message['recalled'] = true;
        $message['recalled_by'] = $caller;
        $message['recalled_at'] = time();
        break;
    }
    unset($message);
    if (!$found) respond_json(['success' => false, 'error' => '消息不存在']);

    write_messages_file($messages_dir, $session_id, $messages);
    $sessions = load_sessions($sessions_file);
    $index = find_session_index($sessions, $session_id);
    if ($index < 0) {
        ensure_session($sessions_file, $session_id);
        $sessions = load_sessions($sessions_file);
        $index = find_session_index($sessions, $session_id);
    }
    if ($index >= 0) {
        $sessions[$index]['recall_seq'] = intval($sessions[$index]['recall_seq'] ?? 0) + 1;
        $sessions[$index]['last_time'] = time();
        $sessions[$index]['last_message'] = $caller === 'admin' ? '小纸条撤回了一条消息' : '对方撤回了一条消息';
        save_sessions($sessions_file, $sessions);
        respond_json(['success' => true, 'session' => $sessions[$index], 'recall_seq' => intval($sessions[$index]['recall_seq'] ?? 0)]);
    }
    respond_json(['success' => true]);
}

function clear_messages($messages_dir, $sessions_file, $session_id, $sync_chat) {
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);

    $sessions = load_sessions($sessions_file);
    $index = find_session_index($sessions, $session_id);
    if ($index < 0) {
        ensure_session($sessions_file, $session_id);
        $sessions = load_sessions($sessions_file);
        $index = find_session_index($sessions, $session_id);
    }
    if ($index < 0) respond_json(['success' => false, 'error' => '会话不存在']);

    $now = time();
    $clear_after_id = intval($sessions[$index]['message_seq'] ?? 0);
    $sessions[$index]['last_time'] = $now;
    $sessions[$index]['last_message'] = '记录已清空';
    $sessions[$index]['unread'] = 0;
    $sessions[$index]['last_read_id'] = $clear_after_id;
    $sessions[$index]['admin_clear_after_id'] = $clear_after_id;
    if ($sync_chat) {
        write_messages_file($messages_dir, $session_id, []);
        $sessions[$index]['clear_seq'] = intval($sessions[$index]['clear_seq'] ?? 0) + 1;
        $sessions[$index]['chat_clear_after_id'] = $clear_after_id;
        $sessions[$index]['last_admin_message_id'] = $clear_after_id;
        $sessions[$index]['chat_seen_admin_id'] = $clear_after_id;
    }
    save_sessions($sessions_file, $sessions);

    respond_json([
        'success' => true,
        'session' => $sessions[$index],
        'sync_chat' => $sync_chat,
        'clear_seq' => intval($sessions[$index]['clear_seq'] ?? 0)
    ]);
}

function delete_session($data_dir, $messages_dir, $sessions_file, $session_id) {
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);

    $sessions = load_sessions($sessions_file);
    $sessions = array_values(array_filter($sessions, function($session) use ($session_id) {
        return ($session['id'] ?? '') !== $session_id;
    }));
    save_sessions($sessions_file, $sessions);

    $message_file = session_file_path($messages_dir, $session_id);
    $message_dir = session_dir_path($messages_dir, $session_id);
    $watch_file = $data_dir . 'watch/' . $session_id . '.json';
    $results_file = $data_dir . 'results/' . $session_id . '.json';
    if (file_exists($message_file)) unlink($message_file);
    if (is_dir($message_dir)) delete_directory($message_dir);
    if (file_exists($watch_file)) unlink($watch_file);
    if (file_exists($results_file)) unlink($results_file);
    respond_json(['success' => true]);
}

function default_watch_state($session_id) {
    return [
        'session_id' => $session_id,
        'active' => false,
        'url' => '',
        'event' => 'idle',
        'current_time' => 0,
        'duration' => 0,
        'paused' => true,
        'control_seq' => 0,
        'last_controller' => '',
        'updated_at' => microtime(true),
        'chat_current_time' => 0,
        'chat_duration' => 0,
        'chat_paused' => true,
        'chat_reported_at' => 0
    ];
}

function read_watch_state($watch_dir, $session_id) {
    $session_id = sanitize_session_id($session_id);
    $state = read_json_file(watch_file_path($watch_dir, $session_id), []);
    return array_merge(default_watch_state($session_id), is_array($state) ? $state : []);
}

function save_watch_state($watch_dir, $session_id, $state) {
    write_json_file(watch_file_path($watch_dir, $session_id), $state);
}

function sanitize_watch_url($url) {
    $url = trim($url ?? '');
    if ($url === '') return '';
    $parts = parse_url($url);
    $scheme = strtolower($parts['scheme'] ?? '');
    if (!in_array($scheme, ['http', 'https'], true)) return '';
    return filter_var($url, FILTER_VALIDATE_URL) ? $url : '';
}

function respond_watch_state($watch_dir, $session_id) {
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);
    respond_json([
        'success' => true,
        'state' => read_watch_state($watch_dir, $session_id),
        'server_time' => microtime(true)
    ]);
}

function admin_update_watch($watch_dir, $sessions_file, $session_id, $input) {
    global $admin_api_key;
    if (($input['admin_auth'] ?? '') !== $admin_api_key) {
        respond_json(['success' => false, 'error' => '没有一起看控制权限']);
    }
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);
    $state = read_watch_state($watch_dir, $session_id);
    $event = trim($input['event'] ?? 'sync');
    $now = microtime(true);

    if ($event === 'close') {
        $state = array_merge($state, [
            'active' => false,
            'event' => 'close',
            'paused' => true,
            'control_seq' => intval($state['control_seq'] ?? 0) + 1,
            'last_controller' => 'admin',
            'updated_at' => $now
        ]);
        save_watch_state($watch_dir, $session_id, $state);
        ensure_session($sessions_file, $session_id, ['last_time' => time(), 'last_message' => '一起看已结束']);
        respond_json(['success' => true, 'state' => $state, 'server_time' => $now]);
    }

    $url = array_key_exists('url', $input) ? sanitize_watch_url($input['url']) : ($state['url'] ?? '');
    if ($url === '') respond_json(['success' => false, 'error' => '播放链接不正确']);

    $state = array_merge($state, [
        'session_id' => $session_id,
        'active' => true,
        'url' => $url,
        'event' => $event,
        'current_time' => max(0, floatval($input['current_time'] ?? 0)),
        'duration' => max(0, floatval($input['duration'] ?? 0)),
        'paused' => !empty($input['paused']),
        'control_seq' => intval($state['control_seq'] ?? 0) + 1,
        'last_controller' => 'admin',
        'updated_at' => $now
    ]);
    save_watch_state($watch_dir, $session_id, $state);
    ensure_session($sessions_file, $session_id, ['last_time' => time(), 'last_message' => '正在一起看']);
    respond_json(['success' => true, 'state' => $state, 'server_time' => $now]);
}

function chat_update_watch($watch_dir, $session_id, $input) {
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);
    $state = read_watch_state($watch_dir, $session_id);
    if (empty($state['active']) || empty($state['url'])) {
        respond_json(['success' => false, 'error' => '一起看还没有开始']);
    }
    $now = microtime(true);
    $state['event'] = trim($input['event'] ?? 'sync');
    $state['current_time'] = max(0, floatval($input['current_time'] ?? 0));
    $state['duration'] = max(0, floatval($input['duration'] ?? 0));
    $state['paused'] = !empty($input['paused']);
    $state['control_seq'] = intval($state['control_seq'] ?? 0) + 1;
    $state['last_controller'] = 'chat';
    $state['updated_at'] = $now;
    $state['chat_current_time'] = $state['current_time'];
    $state['chat_duration'] = $state['duration'];
    $state['chat_paused'] = $state['paused'];
    $state['chat_reported_at'] = $now;
    save_watch_state($watch_dir, $session_id, $state);
    respond_json(['success' => true, 'state' => $state, 'server_time' => $now]);
}

function report_watch_progress($watch_dir, $session_id, $input) {
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);
    $state = read_watch_state($watch_dir, $session_id);
    $state['chat_current_time'] = max(0, floatval($input['current_time'] ?? 0));
    $state['chat_duration'] = max(0, floatval($input['duration'] ?? 0));
    $state['chat_paused'] = !empty($input['paused']);
    $state['chat_reported_at'] = microtime(true);
    save_watch_state($watch_dir, $session_id, $state);
    respond_json(['success' => true, 'state' => $state, 'server_time' => microtime(true)]);
}

function update_presence($sessions_file, $session_id, $seen_admin_id, $typing) {
    $session_id = sanitize_session_id($session_id);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);

    $updates = [
        'chat_seen_at' => time(),
        'typing_until' => $typing ? time() + 5 : 0
    ];
    if ($seen_admin_id !== null) {
        $updates['chat_seen_admin_id'] = max(0, intval($seen_admin_id));
    }
    $session = ensure_session($sessions_file, $session_id, $updates);
    respond_json(['success' => $session !== null, 'session' => $session]);
}

function sticker_file_index($file_name) {
    if (preg_match('/(\d+)/', $file_name, $matches)) return intval($matches[1]);
    return 0;
}

function get_sticker_packs() {
    $base_dir = __DIR__ . '/bq/';
    $pack_labels = ['douyin' => '抖音', 'qq' => 'QQ', 'tieba' => '贴吧'];
    $packs = [];

    foreach (['douyin', 'qq', 'tieba'] as $pack_id) {
        $dir = $base_dir . $pack_id . '/';
        if (!is_dir($dir)) continue;
        $files = array_values(array_filter(scandir($dir), function($file) use ($dir) {
            return is_file($dir . $file) && preg_match('/\.(gif|png|jpg|jpeg|webp)$/i', $file);
        }));
        usort($files, function($a, $b) {
            return sticker_file_index($a) <=> sticker_file_index($b);
        });
        $packs[] = [
            'id' => $pack_id,
            'label' => $pack_labels[$pack_id],
            'files' => array_map(function($file) {
                return ['name' => $file, 'index' => sticker_file_index($file)];
            }, $files)
        ];
    }

    respond_json(['success' => true, 'packs' => $packs]);
}

function export_messages($messages, $format) {
    if ($format === 'md') {
        $lines = ["# 聊天记录", ""];
        foreach ($messages as $message) {
            $sender = ($message['sender'] ?? '') === 'admin' ? 'Admin' : 'Chat';
            $time = $message['time'] ?? '';
            $reply = normalize_reply_payload($message['reply'] ?? null);
            $reply_line = '';
            if ($reply && trim($reply['preview'] ?? '') !== '') {
                $reply_sender = ($reply['sender'] ?? '') === 'admin' ? '小纸条' : '我';
                $reply_line = '> 回复 ' . $reply_sender . '：' . trim($reply['preview']);
            }
            if (($message['type'] ?? '') === 'image') {
                $url = $message['image']['url'] ?? '';
                $body = $url ? "![图片]({$url})" : '[图片]';
            } else {
                $body = str_replace("\r\n", "\n", $message['message'] ?? '');
            }
            $lines[] = "## {$sender} {$time}";
            $lines[] = "";
            if ($reply_line !== '') {
                $lines[] = $reply_line;
                $lines[] = "";
            }
            $lines[] = $body;
            $lines[] = "";
        }
        return implode("\n", $lines);
    }
    return json_encode($messages, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}

if ($action === 'register_session') {
    $session_id = sanitize_session_id($json_input['session_id'] ?? ($_GET['session_id'] ?? ''));
    $source = $json_input['source'] ?? ($_GET['source'] ?? '');
    $updates = ['ip' => client_ip()];
    if ($source === 'questionnaire') $updates['visited_questionnaire'] = true;
    $session = ensure_session($sessions_file, $session_id, $updates);
    respond_json(['success' => $session !== null, 'session' => $session]);
} elseif ($action === 'get_messages') {
    $session_id = sanitize_session_id($_GET['session_id'] ?? '');
    $last_id = intval($_GET['last_id'] ?? 0);
    $scope = ($_GET['scope'] ?? 'chat') === 'admin' ? 'admin' : 'chat';
    $messages = $session_id !== '' ? read_messages_file($messages_dir, $session_id) : [];
    $session = $session_id !== '' ? ensure_session($sessions_file, $session_id) : null;
    $clear_after_id = $session ? intval($session[$scope === 'admin' ? 'admin_clear_after_id' : 'chat_clear_after_id'] ?? 0) : 0;
    $visible_after_id = max($last_id, $clear_after_id);
    $new_messages = array_filter($messages, function($message) use ($visible_after_id) {
        return intval($message['id'] ?? 0) > $visible_after_id;
    });
    respond_json([
        'success' => true,
        'messages' => array_values($new_messages),
        'session' => $session,
        'clear_seq' => $session ? intval($session['clear_seq'] ?? 0) : 0,
        'recall_seq' => $session ? intval($session['recall_seq'] ?? 0) : 0,
        'clear_after_id' => $clear_after_id
    ]);
} elseif ($action === 'send_message') {
    save_message($messages_dir, $sessions_file, $json_input['session_id'] ?? '', $json_input['message'] ?? '', $json_input['sender'] ?? 'user', $json_input['reply'] ?? null);
} elseif ($action === 'recall_message') {
    recall_message($messages_dir, $sessions_file, $json_input['session_id'] ?? '', $json_input['message_id'] ?? 0, $json_input['sender'] ?? 'user');
} elseif ($action === 'send_image') {
    $session_id = $_POST['session_id'] ?? '';
    $sender = $_POST['sender'] ?? 'user';
    if ($sender === 'admin' && ($_POST['admin_auth'] ?? '') !== $admin_api_key) {
        respond_json(['success' => false, 'error' => '没有图片发送权限']);
    }
    save_image_message($messages_dir, $sessions_file, $session_id, $sender);
} elseif ($action === 'get_sessions') {
    $sessions = enrich_session_list($sessions_file, $messages_dir);
    respond_json(['success' => true, 'sessions' => $sessions]);
} elseif ($action === 'update_session') {
    $session_id = sanitize_session_id($json_input['session_id'] ?? '');
    $updates = [
        'remark' => mb_substr(trim($json_input['remark'] ?? ''), 0, 60),
        'intro' => mb_substr(trim($json_input['intro'] ?? ''), 0, 300)
    ];
    $session = ensure_session($sessions_file, $session_id, $updates);
    respond_json(['success' => $session !== null, 'session' => $session]);
} elseif ($action === 'mark_read') {
    $session_id = sanitize_session_id($json_input['session_id'] ?? '');
    $session = ensure_session($sessions_file, $session_id, ['unread' => 0, 'last_read_id' => intval($json_input['last_read_id'] ?? 0)]);
    respond_json(['success' => $session !== null, 'session' => $session]);
} elseif ($action === 'admin_send') {
    save_message($messages_dir, $sessions_file, $json_input['session_id'] ?? '', $json_input['message'] ?? '', 'admin', $json_input['reply'] ?? null);
} elseif ($action === 'update_presence') {
    $seen_admin_id = array_key_exists('seen_admin_id', $json_input) ? $json_input['seen_admin_id'] : null;
    update_presence($sessions_file, $json_input['session_id'] ?? '', $seen_admin_id, !empty($json_input['typing']));
} elseif ($action === 'get_watch_state') {
    respond_watch_state($watch_dir, $_GET['session_id'] ?? ($json_input['session_id'] ?? ''));
} elseif ($action === 'admin_update_watch') {
    admin_update_watch($watch_dir, $sessions_file, $json_input['session_id'] ?? '', $json_input);
} elseif ($action === 'chat_update_watch') {
    chat_update_watch($watch_dir, $json_input['session_id'] ?? '', $json_input);
} elseif ($action === 'report_watch_progress') {
    report_watch_progress($watch_dir, $json_input['session_id'] ?? '', $json_input);
} elseif ($action === 'clear_messages') {
    clear_messages($messages_dir, $sessions_file, $json_input['session_id'] ?? '', !empty($json_input['sync_chat']));
} elseif ($action === 'delete_session') {
    delete_session($data_dir, $messages_dir, $sessions_file, $json_input['session_id'] ?? '');
} elseif ($action === 'get_user_results') {
    $session_id = sanitize_session_id($_GET['session_id'] ?? '');
    $results_file = $data_dir . 'results/' . $session_id . '.json';
    $results = $session_id !== '' && file_exists($results_file) ? read_json_file($results_file, null) : null;
    respond_json(['success' => true, 'results' => $results]);
} elseif ($action === 'save_results') {
    $session_id = sanitize_session_id($json_input['session_id'] ?? '');
    $results = $json_input['results'] ?? [];
    $results_dir = $data_dir . 'results/';
    if (!file_exists($results_dir)) mkdir($results_dir, 0777, true);
    if ($session_id === '') respond_json(['success' => false, 'error' => '参数错误']);
    ensure_session($sessions_file, $session_id, ['ip' => client_ip()]);
    write_json_file($results_dir . $session_id . '.json', $results);
    respond_json(['success' => true]);
} elseif ($action === 'get_sticker_packs') {
    get_sticker_packs();
} elseif ($action === 'export_messages') {
    $session_id = sanitize_session_id($_GET['session_id'] ?? '');
    $format = ($_GET['format'] ?? 'json') === 'md' ? 'md' : 'json';
    $scope = ($_GET['scope'] ?? 'admin') === 'chat' ? 'chat' : 'admin';
    $messages = $session_id !== '' ? read_messages_file($messages_dir, $session_id) : [];
    $session = $session_id !== '' ? ensure_session($sessions_file, $session_id) : null;
    $clear_after_id = $session ? intval($session[$scope === 'admin' ? 'admin_clear_after_id' : 'chat_clear_after_id'] ?? 0) : 0;
    $messages = array_values(array_filter($messages, function($message) use ($clear_after_id) {
        return intval($message['id'] ?? 0) > $clear_after_id;
    }));
    respond_json(['success' => true, 'format' => $format, 'content' => export_messages($messages, $format)]);
} elseif ($action === 'import_messages') {
    $session_id = sanitize_session_id($json_input['session_id'] ?? '');
    $format = ($json_input['format'] ?? 'json') === 'md' ? 'md' : 'json';
    $content = $json_input['content'] ?? '';
    if ($session_id === '' || $content === '') respond_json(['success' => false, 'error' => '参数错误']);
    if ($format === 'json') {
        $messages = json_decode($content, true);
        if (!is_array($messages)) respond_json(['success' => false, 'error' => 'JSON格式不正确']);
    } else {
        $messages = [[
            'id' => 1,
            'session_id' => $session_id,
            'sender' => 'admin',
            'message' => trim($content),
            'time' => date('H:i:s'),
            'timestamp' => time(),
            'ip' => ''
        ]];
    }
    $id_map = [];
    foreach ($messages as $index => &$message) {
        $old_id = intval($message['id'] ?? 0);
        $new_id = next_message_id($sessions_file, $session_id);
        if ($old_id > 0) $id_map[$old_id] = $new_id;
        $message['id'] = $new_id;
        $message['session_id'] = $session_id;
        $message['sender'] = ($message['sender'] ?? '') === 'admin' ? 'admin' : 'user';
        $message['message'] = trim($message['message'] ?? '');
        $message['time'] = $message['time'] ?? date('H:i:s');
        $message['timestamp'] = intval($message['timestamp'] ?? time());
        migrate_reply_from_message($message);
        if (!empty($message['reply']['id']) && isset($id_map[intval($message['reply']['id'])])) {
            $message['reply']['id'] = $id_map[intval($message['reply']['id'])];
            attach_reply_data($message, $message['reply']);
        }
    }
    unset($message);
    write_messages_file($messages_dir, $session_id, array_values($messages));
    $last = end($messages);
    ensure_session($sessions_file, $session_id, [
        'last_time' => intval($last['timestamp'] ?? time()),
        'last_message' => message_preview($last['message'] ?? ''),
        'unread' => 0
    ]);
    respond_json(['success' => true, 'count' => count($messages)]);
}

respond_json(['success' => false, 'error' => '未知操作']);
?>
