<!DOCTYPE html>
<html>
<head>
    <title>TsumugiBoshi|纺星</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    
        
    <!-- PWA 支持 -->
    <link rel="manifest" href="manifest.json">
    <meta name="theme-color" content="#1976d2">
    <link rel="apple-touch-icon" href="icons/icon-192x192.png">
    
    <!-- OpenGraph 标签 -->
    <meta property="og:title" content="TsumugiBoshi|纺星">
    <meta property="og:description" content="神秘API调试工具">
    <meta property="og:image" content="/image/og-image.png">
    <meta property="og:type" content="website">
    
    <title>TsumugiBoshi|纺星</title>
    <!--
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone@7.21.5/babel.min.js"></script>
    <script src="https://unpkg.com/@mui/material@5.14.0/umd/material-ui.production.min.js"></script>
    //-->

    <!-- 使用本地js代替CDN -->


    
    
    <!-- Material Icons -->
    <link rel="stylesheet" href="https://fonts.loli.net/icon?family=Material+Icons">
    
    <!-- React 依赖 -->
    <script src="js\react.production.min.js"></script>
    <script src="js\react-dom.production.min.js"></script>
    
    <!-- Babel -->
    <script src="js\babel.min.js"></script>
    
    <!-- Material UI 组件库 -->
    <script src="js\material-ui.production.min.js"></script>
    
    <link rel="shortcut icon" href="favicon.png">
    <style>
        body { 
            margin: 0; 
            font-family: Arial, sans-serif;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
        }
        #root {
            flex: 1;
            display: flex;
            flex-direction: column;
        }
        pre { 
            background: #f5f5f5; 
            padding: 10px; 
            border-radius: 4px;
            font-size: 14px;
            overflow-x: auto;
        }
        .api-description { 
            margin: 8px 0 16px !important;
            color: #666;
            white-space: pre-line;
            line-height: 1.5;
            background-color: #f8f9fa;
            padding: 12px;
            border-radius: 4px;
            border-left: 4px solid #1976d2;
            font-size: 0.9rem;
        }
        .selected-backend { font-weight: bold; color: #1976d2; }
        .scrollable-tabs {
            max-width: calc(100% - 48px); /* 为菜单按钮留出空间 */
            overflow-x: auto;
            overflow-y: hidden;
        }
        .tabs-container {
            display: flex;
            align-items: center;
            position: relative;
            border-bottom: 1px solid rgba(0, 0, 0, 0.12);
        }
        .menu-button {
            position: absolute !important;
            right: 0;
            top: 50%;
            transform: translateY(-50%);
        }
        footer {
            margin-top: auto;
            padding: 16px;
            background-color: #f5f5f5;
            text-align: center;
            font-size: 0.875rem;
            color: #666;
        }
        .backend-selector {
            margin-top: 2rem;
            padding: 1.5rem;
            background-color: #f8f9fa;
            border-radius: 4px;
        }
        @media (max-width: 600px) {
            .MuiContainer-root {
                padding: 8px !important;
            }
            .MuiPaper-root {
                padding: 12px !important;
            }
            .MuiTypography-h4 {
                font-size: 1.5rem !important;
            }
            .MuiTypography-h6 {
                font-size: 1.1rem !important;
            }
            .MuiTab-root {
                min-height: 48px !important;
                padding: 6px 12px !important;
                font-size: 0.8rem !important;
            }
            .backend-selector {
                margin-top: 1rem;
                padding: 1rem;
            }
            h4.MuiTypography-root {
                font-size: 1.5rem;
            }
            .scrollable-tabs::-webkit-scrollbar {
                display: none;
            }
            .api-description {
                font-size: 0.875rem;
                padding: 8px;
            }
        }
        /* 移动端优化 */
        @media (max-width: 600px) {
            .MuiContainer-root {
                padding: 8px !important;
            }
            .MuiPaper-root {
                padding: 8px !important;
            }
            .MuiTypography-h4 {
                font-size: 1.25rem !important;
                margin-bottom: 0.5rem !important;
            }
            .MuiTypography-h6 {
                font-size: 1rem !important;
            }
            .MuiTab-root {
                min-width: auto !important;
                padding: 6px 8px !important;
                font-size: 0.75rem !important;
            }
            .MuiFormControl-root {
                margin-bottom: 8px !important;
            }
            pre {
                font-size: 0.75rem !important;
                padding: 8px !important;
            }
            .api-description {
                font-size: 0.75rem !important;
                padding: 8px !important;
                margin: 4px 0 8px !important;
            }
        }
        /* 新增日志卡片样式 */
        .log-card {
            margin-top: 1rem;
            transition: all 0.3s ease;
        }
        
        .log-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0.5rem 1rem;
            cursor: pointer;
        }
        
        .log-content {
            padding: 1rem;
            max-height: 300px;
            overflow-y: auto;
        }
        
        .log-item {
            border-left: 3px solid #1976d2;
            margin-bottom: 0.5rem;
            padding: 0.5rem;
            background: #f8f9fa;
        }
        
        /* 优化响应式布局 */
        @media (max-width: 600px) {
            body {
                font-size: 14px;
            }
            
            .MuiContainer-root {
                padding: 0.5rem !important;
            }
            
            .MuiPaper-root {
                padding: 0.75rem !important;
            }
            
            /* 标题样式 */
            .MuiTypography-h4 {
                font-size: 1.5rem !important;
                line-height: 1.3;
            }
            
            .MuiTypography-h6 {
                font-size: 1.1rem !important;
            }
            
            /* 表单样式 */
            .MuiFormControl-root {
                margin-bottom: 0.75rem !important;
            }
            
            /* 标签页样式 */
            .MuiTab-root {
                font-size: 0.85rem !important;
                min-width: unset !important;
                padding: 0.5rem 0.75rem !important;
            }
            
            /* 输入框样式 */
            .MuiInputBase-root {
                font-size: 0.9rem !important;
            }
            
            /* 其他元素缩放 */
            .api-description {
                font-size: 0.85rem !important;
                padding: 0.75rem !important;
            }
            
            pre {
                font-size: 0.8rem !important;
            }
            
            .backend-selector {
                margin-top: 0.75rem;
                padding: 0.75rem;
            }
        }
    </style>
</head>
<body></body>
    <div id="root"></div>
    <footer>
        <Typography variant="body2" color="textSecondary">
            © 2025 Tsumugiboshi. All rights reserved.
            <br />
        </Typography>
    </footer>

    <script type="text/babel">
        // 从全局对象获取Material UI组件
        const {
            Container,
            Tabs,
            Tab,
            TextField,
            Button,
            Paper,
            Typography,
            Grid,
            Box,
            createTheme,
            ThemeProvider,
            Select,
            MenuItem,
            InputLabel,
            FormControl,
            Dialog,
            DialogTitle,
            DialogContent,
            DialogActions,
            Snackbar,
            Alert,
            Menu,
            IconButton,
            Link,
            Collapse
        } = MaterialUI;

        // 创建主题
        const theme = createTheme();

        // 预设的后端地址
        const PRESET_BACKENDS = [
            { label: "localhost", value: "http://127.0.0.1:8080" },
            { label: "测试环境", value: "http://test-api.example.com" },
            { label: "生产环境", value: "http://api.example.com" }
        ];

        // 通用表单组件
        const DynamicForm = React.forwardRef(({ formConfig, onSubmit }, ref) => {
            const [formData, setFormData] = React.useState({});

            // 暴露更新表单数据的方法给父组件
            React.useImperativeHandle(ref, () => ({
                updateField: (fieldId, value) => {
                    handleChange(fieldId, value);
                }
            }));

            const handleChange = (fieldId, value) => {
                const keys = fieldId.split('.');
                setFormData(prev => {
                    let newData = {...prev};
                    let current = newData;
                    for (let i = 0; i < keys.length - 1; i++) {
                        current[keys[i]] = current[keys[i]] || {};
                        current = current[keys[i]];
                    }
                    current[keys[keys.length - 1]] = value;
                    return newData;
                });
            };

            const handleSubmit = async () => {
                if (formConfig.confirmBeforeSubmit) {
                    let confirmMessage = formConfig.confirmTemplate || "确认提交数据?";
                    if (formConfig.confirmTemplate) {
                        confirmMessage = confirmMessage.replace(/\{([^}]+)\}/g, (_, key) => {
                            return key === 'rawData' ? JSON.stringify(formData, null, 2) : 
                                   key.split('.').reduce((obj, k) => obj && obj[k], formData) || '';
                        });
                    }
                    if (!confirm(confirmMessage)) return;
                }
                onSubmit(formData);
            };

            return (
                <div style={{ padding: '1rem' }}>
                    <Typography variant="body1" className="api-description">
                        {formConfig.tab.description}
                    </Typography>
                    <Grid container spacing={2}>
                        {formConfig.fields.map(field => (
                            <Grid item xs={12} key={field.id}>
                                {field.type === 'select' ? (
                                    <FormControl fullWidth>
                                        <InputLabel>{field.label}</InputLabel>
                                        <Select
                                            value={field.id.split('.').reduce((obj, key) => obj && obj[key], formData) ?? ''}
                                            onChange={(e) => handleChange(field.id, e.target.value)}
                                            label={field.label}
                                        >
                                            {field.options.map(opt => (
                                                <MenuItem key={opt.value} value={opt.value}>
                                                    {opt.label}
                                                </MenuItem>
                                            ))}
                                        </Select>
                                    </FormControl>
                                ) : (
                                    <TextField
                                        fullWidth
                                        label={field.label}
                                        type={field.type}
                                        value={field.id.split('.').reduce((obj, key) => obj && obj[key], formData) || ''}
                                        onChange={(e) => handleChange(field.id, e.target.value)}
                                        placeholder={field.placeholder}
                                    />
                                )}
                            </Grid>
                        ))}
                        <Grid item xs={12}>
                            <Button
                                variant="contained"
                                color="primary"
                                onClick={handleSubmit}
                            >
                                提交
                            </Button>
                        </Grid>
                    </Grid>
                </div>
            );
        });

        function App() {
            const [tabValue, setTabValue] = React.useState(0);
            const [qrInput, setQrInput] = React.useState('');
            const [userId, setUserId] = React.useState(getCookie('userId') || '');
            const [response, setResponse] = React.useState('');
            const [musicData, setMusicData] = React.useState({
                userId: parseInt(userId) || 0,
                music: {
                    musicId: 0,
                    level: 0,
                    playCount: 0,
                    //achievement: 0,
                    comboStatus: 0,
                    syncStatus: 0,
                    deluxscoreMax: 0,
                    scoreRank: 0
                }
            });
            const [apiBase, setApiBase] = React.useState(
                localStorage.getItem('apiBase') || PRESET_BACKENDS[0].value
            );
            const [backends, setBackends] = React.useState([
                ...PRESET_BACKENDS,
                ...JSON.parse(localStorage.getItem('customBackends') || '[]')
            ]);
            const [isDialogOpen, setIsDialogOpen] = React.useState(false);
            const [newBackend, setNewBackend] = React.useState({ label: '', value: '' });
            const [snackbarOpen, setSnackbarOpen] = React.useState(false);
            const [snackbarMessage, setSnackbarMessage] = React.useState('');
            const [menuAnchorEl, setMenuAnchorEl] = React.useState(null);
            const [logs, setLogs] = React.useState(
                JSON.parse(localStorage.getItem('apiLogs') || '[]')
            );
            const [isLogExpanded, setIsLogExpanded] = React.useState(false);
            const formRefs = React.useRef({});

            // Cookie操作函数
            function setCookie(name, value, days) {
                const d = new Date();
                d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
                document.cookie = `${name}=${value};expires=${d.toUTCString()};path=/`;
            }

            function getCookie(name) {
                return document.cookie.split('; ')
                    .find(row => row.startsWith(name + '='))
                    ?.split('=')[1];
            }

            React.useEffect(() => {
                if (userId) setCookie('userId', userId, 7);
            }, [userId]);

            const saveLog = (endpoint, request, response) => {
                const newLog = {
                    timestamp: Date.now(),
                    endpoint,
                    request,
                    response,
                    date: new Date().toLocaleString()
                };
                
                const updatedLogs = [newLog, ...logs].slice(0, 50); // 只保留最近50条记录
                setLogs(updatedLogs);
                localStorage.setItem('apiLogs', JSON.stringify(updatedLogs));
            };

            const handleApiCall = async (endpoint, method = 'GET', formData = null, config) => {
                try {
                    const options = {
                        method,
                        headers: { 'Content-Type': 'application/json' }
                    };

                    let url = `${apiBase}${endpoint}`;
                    
                    // 处理GET请求参数
                    if (method === 'GET' && config.queryParams) {
                        const params = new URLSearchParams();
                        config.queryParams.forEach(param => {
                            if (formData[param]) params.append(param, formData[param]);
                        });
                        url += `?${params.toString()}`;
                    }
                    
                    // 处理POST请求体
                    if (method === 'POST') {
                        if (config.requestFormat === 'json') {
                            options.body = JSON.stringify(formData);
                        }
                    }

                    const res = await fetch(url, options);
                    const data = await res.json();
                    setResponse(JSON.stringify(data, null, 2));
                    
                    // 保存日志
                    saveLog(endpoint, formData, data);
                    
                    // 处理登录响应
                    if (config.endpoint === '/qr' && data.userId) {
                        setUserId(data.userId.toString());
                        setCookie('userId', data.userId.toString(), 7);
                        
                        // 遍历所有表单配置,更新包含userid/userId字段的表单
                        Object.entries(formConfigs).forEach(([key, formConfig]) => {
                            const userIdField = formConfig.fields.find(
                                field => field.id.toLowerCase().includes('userid')
                            );
                            if (userIdField && formRefs.current[key]) {
                                formRefs.current[key].updateField(userIdField.id, data.userId.toString());
                            }
                        });
                    }
                    
                    // 根据状态设置提示消息
                    setSnackbarMessage(data.info || '操作成功');
                    setSnackbarOpen(true);
                    
                } catch (error) {
                    setResponse(`Error: ${error.message}`);
                    setSnackbarMessage(`错误: ${error.message}`);
                    setSnackbarOpen(true);
                }
            };

            const handleMusicChange = (e) => {
                const { name, value } = e.target;
                setMusicData(prev => ({
                    ...prev,
                    music: {
                        ...prev.music,
                        [name]: parseInt(value) || 0
                    }
                }));
            };

            const handleApiBaseChange = (event) => {
                const value = event.target.value;
                setApiBase(value);
                localStorage.setItem('apiBase', value);
            };

            const handleAddBackend = () => {
                setIsDialogOpen(true);
            };

            const handleDialogClose = () => {
                setIsDialogOpen(false);
                setNewBackend({ label: '', value: '' });
            };

            const handleSaveBackend = () => {
                if (!newBackend.label || !newBackend.value) {
                    setSnackbarMessage('后端名称和地址不能为空');
                    setSnackbarOpen(true);
                    return;
                }

                const updatedBackends = [...backends, newBackend];
                setBackends(updatedBackends);
                localStorage.setItem('customBackends', JSON.stringify(updatedBackends.filter(b => !PRESET_BACKENDS.includes(b))));
                setIsDialogOpen(false);
                setSnackbarMessage('自定义后端已保存');
                setSnackbarOpen(true);
            };

            const handleSnackbarClose = () => {
                setSnackbarOpen(false);
            };

            const handleDeleteBackend = (backendValue) => {
                const updatedBackends = backends.filter(b => b.value !== backendValue);
                setBackends(updatedBackends);
                
                // 如果删除的是当前选中的后端,切换到第一个默认后端
                if (apiBase === backendValue) {
                    setApiBase(PRESET_BACKENDS[0].value);
                    localStorage.setItem('apiBase', PRESET_BACKENDS[0].value);
                }
                
                // 只保存自定义后端
                localStorage.setItem('customBackends', 
                    JSON.stringify(updatedBackends.filter(b => 
                        !PRESET_BACKENDS.some(preset => preset.value === b.value)
                    ))
                );
                
                setSnackbarMessage('后端已删除');
                setSnackbarOpen(true);
            };

            const handleMenuClick = (event) => {
                setMenuAnchorEl(event.currentTarget);
            };

            const handleMenuClose = () => {
                setMenuAnchorEl(null);
            };

            const handleMenuSelect = (index) => {
                setTabValue(index);
                handleMenuClose();
            };

            // 加载表单配置
            const [formConfigs, setFormConfigs] = React.useState(null);

            React.useEffect(() => {
                fetch('config/label.json')
                    .then(res => res.json())
                    .then(config => setFormConfigs(config.forms));
            }, []);

            return (
                <ThemeProvider theme={theme}>
                    <Container maxWidth="md" style={{ marginTop: '1rem', marginBottom: '2rem' }}>
                        <Typography variant="h4" gutterBottom>
                            💫TsumugiBoshi
                        </Typography>
                        <Typography variant="subtitle1" gutterBottom>
                            神秘API调试工具
                        </Typography>

                        {/* API表单部分 */}
                        <Paper elevation={3} style={{ padding: '1rem', marginBottom: '1rem' }}>
                            <div className="tabs-container">
                                <Box className="scrollable-tabs">
                                    <Tabs 
                                        value={tabValue} 
                                        onChange={(e, v) => setTabValue(v)}
                                        indicatorColor="primary"
                                        textColor="primary"
                                        variant="scrollable"
                                        scrollButtons="auto"
                                        allowScrollButtonsMobile
                                    >
                                        {formConfigs && Object.values(formConfigs)
                                            .sort((a, b) => a.tab.index - b.tab.index)
                                            .map(config => (
                                                <Tab 
                                                    key={config.tab.index} 
                                                    label={config.tab.label}
                                                    style={{ minWidth: 'max-content' }}
                                                />
                                            ))}
                                    </Tabs>
                                </Box>
                                <IconButton 
                                    className="menu-button"
                                    onClick={handleMenuClick}
                                    size="small"
                                >
                                    <span className="material-icons">menu</span>
                                </IconButton>
                                <Menu
                                    anchorEl={menuAnchorEl}
                                    open={Boolean(menuAnchorEl)}
                                    onClose={handleMenuClose}
                                >
                                    {formConfigs && Object.values(formConfigs)
                                        .sort((a, b) => a.tab.index - b.tab.index)
                                        .map(config => (
                                            <MenuItem 
                                                key={config.tab.index}
                                                onClick={() => handleMenuSelect(config.tab.index)}
                                                selected={tabValue === config.tab.index}
                                            >
                                                {config.tab.label}
                                            </MenuItem>
                                        ))}
                                </Menu>
                            </div>

                            {formConfigs && Object.entries(formConfigs).map(([key, config]) => (
                                tabValue === config.tab.index && (
                                    <DynamicForm 
                                        key={key}
                                        ref={el => formRefs.current[key] = el}
                                        formConfig={config}
                                        onSubmit={(data) => handleApiCall(
                                            config.endpoint,
                                            config.method,
                                            data,
                                            config
                                        )}
                                    />
                                )
                            ))}
                        </Paper>

                        {response && (
                            <Paper elevation={3} style={{ padding: '1rem', marginBottom: '1rem' }}>
                                <Typography variant="h6" gutterBottom>响应结果:</Typography>
                                <pre style={{ 
                                    whiteSpace: 'pre-wrap', 
                                    wordWrap: 'break-word',
                                    backgroundColor: '#f5f5f5',
                                    padding: '1rem',
                                    borderRadius: '4px',
                                    fontSize: '0.9rem'
                                }}>
                                    {response}
                                </pre>
                            </Paper>
                        )}

                        {/* 移动后端选择器到底部 */}
                        <Paper elevation={3} className="backend-selector">
                            <Typography variant="h6" gutterBottom>
                                ⚙️ 后端设置
                            </Typography>
                            <FormControl fullWidth>
                                <InputLabel>选择后端地址</InputLabel>
                                <Select
                                    value={apiBase}
                                    onChange={handleApiBaseChange}
                                    label="选择后端地址"
                                >
                                    {/* 默认后端 */}
                                    {PRESET_BACKENDS.map((backend) => (
                                        <MenuItem key={backend.value} value={backend.value}>
                                            {backend.label} - {backend.value}
                                        </MenuItem>
                                    ))}
                                    
                                    {/* 分割线 */}
                                    {backends.some(b => !PRESET_BACKENDS.some(preset => preset.value === b.value)) && (
                                        <MenuItem disabled>
                                            <hr style={{ width: '100%', margin: '4px 0' }} />
                                        </MenuItem>
                                    )}
                                    
                                    {/* 自定义后端 */}
                                    {backends
                                        .filter(b => !PRESET_BACKENDS.some(preset => preset.value === b.value))
                                        .map((backend) => (
                                            <MenuItem key={backend.value} value={backend.value}>
                                                <Box display="flex" justifyContent="space-between" width="100%">
                                                    <span>{backend.label} - {backend.value}</span>
                                                    <Button
                                                        size="small"
                                                        color="error"
                                                        onClick={(e) => {
                                                            e.stopPropagation();
                                                            handleDeleteBackend(backend.value);
                                                        }}
                                                    >
                                                        删除
                                                    </Button>
                                                </Box>
                                            </MenuItem>
                                        ))}
                                </Select>
                                <Button
                                    variant="outlined"
                                    color="primary"
                                    style={{ marginTop: '1rem' }}
                                    onClick={handleAddBackend}
                                >
                                    添加自定义后端
                                </Button>
                            </FormControl>
                        </Paper>

                        {/* 日志卡片 */}
                        <Paper elevation={3} className="log-card">
                            <div 
                                className="log-header"
                                onClick={() => setIsLogExpanded(!isLogExpanded)}
                            >
                                <Typography variant="h6">
                                    📝 API调用日志
                                </Typography>
                                <IconButton size="small">
                                    <span className="material-icons">
                                        {isLogExpanded ? 'expand_less' : 'expand_more'}
                                    </span>
                                </IconButton>
                            </div>
                            <Collapse in={isLogExpanded}>
                                <div className="log-content">
                                    {logs.map((log, index) => (
                                        <div key={index} className="log-item">
                                            <Typography variant="subtitle2" gutterBottom>
                                                {log.date} - {log.endpoint}
                                            </Typography>
                                            <Typography variant="body2" component="pre" style={{margin: 0}}>
                                                请求: {JSON.stringify(log.request, null, 2)}
                                            </Typography>
                                            <Typography variant="body2" component="pre" style={{margin: 0}}>
                                                响应: {JSON.stringify(log.response, null, 2)}
                                            </Typography>
                                        </div>
                                    ))}
                                </div>
                            </Collapse>
                        </Paper>
                    </Container>

                    {/* 添加自定义后端的对话框 */}
                    <Dialog open={isDialogOpen} onClose={handleDialogClose}>
                        <DialogTitle>添加自定义后端</DialogTitle>
                        <DialogContent>
                            <TextField
                                autoFocus
                                margin="dense"
                                label="后端名称"
                                fullWidth
                                value={newBackend.label}
                                onChange={(e) => setNewBackend(prev => ({...prev, label: e.target.value}))}
                            />
                            <TextField
                                margin="dense"
                                label="后端地址"
                                fullWidth
                                value={newBackend.value}
                                onChange={(e) => setNewBackend(prev => ({...prev, value: e.target.value}))}
                            />
                        </DialogContent>
                        <DialogActions>
                            <Button onClick={handleDialogClose}>取消</Button>
                            <Button onClick={handleSaveBackend} color="primary">保存</Button>
                        </DialogActions>
                    </Dialog>

                    {/* 提示消息 */}
                    <Snackbar
                        open={snackbarOpen}
                        autoHideDuration={3000}
                        onClose={handleSnackbarClose}
                    >
                        <Alert onClose={handleSnackbarClose} severity="success">
                            {snackbarMessage}
                        </Alert>
                    </Snackbar>
                </ThemeProvider>
            );
        }

        ReactDOM.render(<App />, document.getElementById('root'));
    </script>
</body>
</html>