<?php  
// Include database connection settings
include 'db.php';

// Get the IDs from the URL parameters
$repository_id = isset($_GET['repository_id']) ? intval($_GET['repository_id']) : 0;
$project_id = isset($_GET['project_id']) ? intval($_GET['project_id']) : 0;
$component_id = isset($_GET['component_id']) ? intval($_GET['component_id']) : 0;

// Handle form submission for new file
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["filename"]) && isset($_POST["code_content"])) {
    $filename = trim($_POST["filename"]);
    $code_content = $_POST["code_content"]; // 前端已加密为 v1$... 文本

    if (!empty($filename) && !empty($code_content) && $component_id > 0) {
        try {
            $stmt = $conn->prepare('
                INSERT INTO "FILE"
                    ("COMPONENT_ID", "FILE_NAME", "CODE")
                VALUES (?, ?, ?)
            ');
            $stmt->bindParam(1, $component_id, PDO::PARAM_INT);
            $stmt->bindParam(2, $filename, PDO::PARAM_STR);
            $stmt->bindParam(3, $code_content, PDO::PARAM_LOB); // Use LOB for bytea
            $stmt->execute();
            
            // Redirect to prevent form resubmission
            header("Location: FILE.php?repository_id=" . $repository_id . "&project_id=" . $project_id . "&component_id=" . $component_id);
            exit();
        } catch (PDOException $e) {
            echo "Error: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
        }
    }
}

// Search functionality
$search = "";
if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET["search"])) {
    $search = trim($_GET["search"]);
}

// Fetch files
try {
    $base_query = '
        SELECT f.*, c."COMPONENT_NAME", p."PROJECT_NAME", p."PROJECT_ID", r."REPOSITORY_NAME", r."REPOSITORY_ID"
        FROM "FILE" f
        LEFT JOIN "COMPONENT" c ON f."COMPONENT_ID" = c."COMPONENT_ID"
        LEFT JOIN "PROJECT" p ON c."PROJECT_ID" = p."PROJECT_ID"
        LEFT JOIN "REPOSITORY" r ON p."REPOSITORY_ID" = r."REPOSITORY_ID"
    ';

    if ($search !== "") {
        $searchParam = "%{$search}%";
        // Corrected Search: Only search FILE_NAME using ILIKE for case-insensitivity
        $stmt = $conn->prepare($base_query . ' WHERE f."FILE_NAME" ILIKE ? ORDER BY f."CREATED_AT" DESC');
        $stmt->bindParam(1, $searchParam, PDO::PARAM_STR);
    } else {
        $stmt = $conn->prepare($base_query . ' WHERE f."COMPONENT_ID" = ? ORDER BY f."CREATED_AT" DESC');
        $stmt->bindParam(1, $component_id, PDO::PARAM_INT);
    }
    $stmt->execute();
    $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    echo "Error: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
    $results = [];
}

$conn = null; // Close PDO connection
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Files</title>
    <style>
        :root {
            --bg-color: #090a0f;
            --panel-bg-color: rgba(26, 26, 26, 0.6);
            --border-color: rgba(255, 255, 255, 0.1);
            --text-primary: #f0f0f0;
            --text-secondary: #a0a0a0;
            --accent-color: #00aaff;
            --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            --border-radius: 16px;
            --danger-color: #E74C3C;
        }

        *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

        body {
            background-color: transparent;
            color: var(--text-primary);
            font-family: var(--font-family);
            padding: 1rem;
        }

        .top-section {
            display: flex; gap: 1rem; margin-bottom: 2rem; align-items: flex-start;
        }

        .form-container { flex-grow: 1; display: flex; flex-direction: column; gap: 0.75rem; }

        input[type="text"], textarea {
            width: 100%; padding: 0.75rem 1rem; font-size: 1em;
            background-color: rgba(0, 0, 0, 0.3); color: var(--text-primary);
            border: 1px solid var(--border-color); border-radius: 12px;
            transition: border-color 0.3s, box-shadow 0.3s;
        }
        input[type="text"]:focus, textarea:focus {
            outline: none; border-color: var(--accent-color);
            box-shadow: 0 0 10px rgba(0, 170, 255, 0.5);
        }

        textarea { height: 120px; resize: vertical; font-family: 'SF Mono', 'Fira Code', 'monospace'; }

        .action-group { min-width: 350px; display: flex; flex-direction: column; gap: 0.75rem; }

        button {
            padding: 0.75rem 1.5rem; font-size: 1em; font-weight: 600; cursor: pointer;
            background: var(--accent-color); color: white; border: none; border-radius: 12px;
            transition: background-color 0.3s, transform 0.2s; width: 100%;
        }
        button:hover { background-color: #0088cc; transform: translateY(-2px); }

        .search-form { display: flex; gap: 0.75rem; }
        .search-form input { flex-grow: 1; }

        .file-list-container {
            background-color: var(--panel-bg-color); border: 1px solid var(--border-color);
            border-radius: var(--border-radius); padding: 1rem;
            backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
            position: relative; /* 右侧容器作为定位上下文 */
        }

        .file-list-header, .file-item {
            display: grid; grid-template-columns: 40px 1.5fr 3fr 160px;
            gap: 1rem; align-items: center; padding: 0.75rem 1rem;
            border-bottom: 1px solid var(--border-color);
        }

        .file-list-header {
            font-weight: 600; color: var(--text-secondary); text-transform: uppercase;
            font-size: 0.85em; letter-spacing: 0.5px;
        }
        .file-item:last-child { border-bottom: none; }
        .file-item:hover { background-color: rgba(0, 170, 255, 0.08); }

        .file-path { font-size: 0.9em; word-break: break-all; }
        .file-path a { text-decoration: none; transition: opacity 0.2s; }
        .file-path a:hover { text-decoration: underline; }
        .file-path .repo { color: #ff8282; }
        .file-path .proj { color: #ffc966; }
        .file-path .comp { color: #66d9ff; }
        .file-path .name { color: var(--text-primary); font-weight: 500; }
        .file-path .sep { color: var(--text-secondary); margin: 0 2px; }

        .code-content {
            white-space: pre-wrap; word-break: break-all; cursor: pointer;
            max-height: 200px; overflow-y: auto; padding: 0.5rem; border-radius: 8px;
            font-family: 'SF Mono', 'Fira Code', 'monospace'; font-size: 0.9em;
        }
        .code-content.highlighted { background-color: rgba(0, 170, 255, 0.2); }
        .code-content::-webkit-scrollbar { width: 8px; }
        .code-content::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.2); border-radius: 4px; }
        .code-content::-webkit-scrollbar-thumb {
            background-color: var(--text-secondary); border-radius: 4px;
            border: 2px solid transparent; background-clip: content-box;
        }
        .code-content::-webkit-scrollbar-thumb:hover { background-color: var(--accent-color); }

        .file-timestamp { font-size: 0.9em; color: var(--text-secondary); cursor: context-menu; }

        .no-results { text-align: center; padding: 3rem; color: var(--text-secondary); font-size: 1.1em; }
        
        .context-menu {
            display: none; position: absolute; z-index: 1000;
            background-color: #1e1e1e; border: 1px solid var(--border-color);
            border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.5);
            padding: 8px; min-width: 150px;
            backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px);
        }
        .context-menu.show { display: block; }
        .context-menu-item {
            padding: 10px 15px; color: var(--text-primary); cursor: pointer;
            font-size: 0.95em; transition: background-color 0.2s; border-radius: 8px;
        }
        .context-menu-item:hover { background-color: rgba(0, 170, 255, 0.15); }
        .context-menu-item.delete { color: var(--danger-color); }
        .context-menu-item.delete:hover { background-color: rgba(231, 76, 60, 0.2); color: #ff8a80; }

        /* --- Password Modal (dark) 在右侧容器内居中 --- */

.pw-modal-backdrop {
    position: absolute; /* 相对 .file-list-container 居中 */
    inset: 0;
    display: none;
    align-items: flex-start; /* <-- 修改这里  */
    justify-content: center;
    padding-top: 10px; /* <-- 增加这一行  */
    z-index: 2000;
    background: rgba(0,0,0,0.65);
    backdrop-filter: blur(6px);
}



        .pw-modal {
            width: 90%; max-width: 420px; background: #111; color: #eaeaea;
            border: 1px solid rgba(255,255,255,0.08); border-radius: 18px; padding: 20px;
            box-shadow: 0 12px 40px rgba(0,0,0,0.6);
        }
        .pw-title { font-size: 1.1rem; font-weight: 700; margin-bottom: 8px; }
        .pw-desc { font-size: 0.9rem; color: #aaa; margin-bottom: 14px; }
        .pw-input {
            width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid #333;
            background: #0c0c0c; color: #eaeaea; outline: none;
        }
        .pw-actions { display: flex; gap: 10px; margin-top: 14px; }
        .pw-btn {
            flex: 1; padding: 10px; border: none; border-radius: 10px; cursor: pointer;
            background: #00aaff; color: white; font-weight: 700;
        }
        .pw-btn.secondary { background: #2a2a2a; color: #ddd; }
        .pw-error { color: #ff7b7b; font-size: 0.85rem; margin-top: 8px; display: none; }
    </style>
</head>
<body>
    <div class="top-section">
        <div class="form-container">
            <form id="file-submit-form" method="post" action="" style="display: contents;">
                <input type="text" name="filename" required placeholder="输入文件名...">
                <textarea name="code_content" required placeholder="在此输入代码内容..."></textarea>
            </form>
        </div>
        <div class="action-group">
            <form class="search-form" method="get" action="">
                <input type="hidden" name="repository_id" value="<?php echo htmlspecialchars($repository_id, ENT_QUOTES, 'UTF-8'); ?>">
                <input type="hidden" name="project_id" value="<?php echo htmlspecialchars($project_id, ENT_QUOTES, 'UTF-8'); ?>">
                <input type="hidden" name="component_id" value="<?php echo htmlspecialchars($component_id, ENT_QUOTES, 'UTF-8'); ?>">
                <input type="text" name="search" placeholder="全文检索..." value="<?php echo htmlspecialchars($search, ENT_QUOTES, 'UTF-8'); ?>">
                <button type="submit">搜索</button>
            </form>
            <button type="submit" form="file-submit-form">创建文件</button>
        </div>
    </div>

    <div class="file-list-container">
        <div class="file-list-header">
            <div>ID</div>
            <div>文件路径</div>
            <div>代码内容</div>
            <div>时间戳</div>
        </div>
        <?php if (!empty($results)): ?>
            <?php foreach ($results as $row): ?>
                <?php
                    $repo_link = "PROJECT.php?repository_id=" . urlencode($row['REPOSITORY_ID']);
                    $proj_link = "COMPONENT.php?repository_id=" . urlencode($row['REPOSITORY_ID']) . "&project_id=" . urlencode($row['PROJECT_ID']);
                    $comp_link = "FILE.php?repository_id=" . urlencode($row['REPOSITORY_ID']) . "&project_id=" . urlencode($row['PROJECT_ID']) . "&component_id=" . urlencode($row['COMPONENT_ID']);
                    // 兼容导出/导入后密文被折行：在输出到 data-enc 前去除 CR/LF
                    $enc_payload = str_replace(array("\r","\n"), '', stream_get_contents($row['CODE']));
                ?>
                <div class="file-item">
                    <div class="file-id"><?php echo htmlspecialchars($row['FILE_ID'], ENT_QUOTES, 'UTF-8'); ?></div>
                    <div class="file-path">
                        <a href="<?php echo $repo_link; ?>" target="contentFrame" class="repo"><?php echo htmlspecialchars($row['REPOSITORY_NAME'], ENT_QUOTES, 'UTF-8'); ?></a><span class="sep">/</span>
                        <a href="<?php echo $proj_link; ?>" target="contentFrame" class="proj"><?php echo htmlspecialchars($row['PROJECT_NAME'], ENT_QUOTES, 'UTF-8'); ?></a><span class="sep">/</span>
                        <a href="<?php echo $comp_link; ?>" target="contentFrame" class="comp"><?php echo htmlspecialchars($row['COMPONENT_NAME'], ENT_QUOTES, 'UTF-8'); ?></a><span class="sep">/</span>
                        <span class="name"><?php echo htmlspecialchars($row['FILE_NAME'], ENT_QUOTES, 'UTF-8'); ?></span>
                    </div>
                    <div class="code-content"
                         data-enc="<?php echo htmlspecialchars($enc_payload, ENT_QUOTES, 'UTF-8'); ?>"
                         onclick="copyToClipboard(this)">Decrypting…</div>
                    <div class="file-timestamp" oncontextmenu="showContextMenu(event, <?php echo $row['FILE_ID']; ?>)"><?php echo date('Y-m-d H:i:s', strtotime($row['CREATED_AT'])); ?></div>
                </div>
            <?php endforeach; ?>
        <?php else: ?>
            <div class="no-results">未找到任何文件记录。</div>
        <?php endif; ?>

        <!-- Password Modal 放在右侧容器内部 -->
        <div id="pwBackdrop" class="pw-modal-backdrop" aria-hidden="true">
            <div class="pw-modal" role="dialog" aria-modal="true" aria-labelledby="pwTitle">
                <div id="pwTitle" class="pw-title">Enter decryption password</div>
                <div class="pw-desc">The password is required to encrypt new code and decrypt stored content for this session.</div>
                <input id="pwInput" class="pw-input" type="password" placeholder="Password" autocomplete="new-password" />
                <div class="pw-actions">
                    <button id="pwConfirm" class="pw-btn">Continue</button>
                    <button id="pwCancel"  class="pw-btn secondary" type="button">Later</button>
                </div>
                <div id="pwError" class="pw-error">Password required.</div>
            </div>
        </div>
    </div>

    <!-- Context Menu HTML Structure -->
    <div id="contextMenu" class="context-menu">
        <div id="deleteMenuItem" class="context-menu-item delete">删除</div>
    </div>

    <script>
        const contextMenu = document.getElementById('contextMenu');
        const deleteMenuItem = document.getElementById('deleteMenuItem');

        function copyToClipboard(element) {
            document.querySelectorAll('.code-content').forEach(el => el.classList.remove('highlighted'));
            element.classList.add('highlighted');
            const range = document.createRange();
            range.selectNodeContents(element);
            const selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            try { document.execCommand('copy'); } catch (err) { console.error('Failed to copy text: ', err); }
            selection.removeAllRanges();
            setTimeout(() => { element.classList.remove('highlighted'); }, 500);
        }

        function showContextMenu(event, fileId) {
            event.preventDefault();
            contextMenu.style.top = `${event.pageY}px`;
            contextMenu.style.left = `${event.pageX}px`;
            contextMenu.classList.add('show');
            deleteMenuItem.onclick = () => deleteFile(fileId);
        }
        
        window.onclick = () => { if (contextMenu.classList.contains('show')) contextMenu.classList.remove('show'); };
        contextMenu.onclick = (e) => e.stopPropagation();

        function deleteFile(fileId) {
            contextMenu.classList.remove('show');
            const repoId = <?php echo json_encode($repository_id); ?>;
            const projId = <?php echo json_encode($project_id); ?>;
            const compId = <?php echo json_encode($component_id); ?>;
            window.location.href = `removefile.php?file_id=${fileId}&repository_id=${repoId}&project_id=${projId}&component_id=${compId}`;
        }

        // ---------- WebCrypto helpers ----------
        const enc = new TextEncoder();
        const dec = new TextDecoder();

        function b64encode(bytes) {
            let bin = '';
            const arr = new Uint8Array(bytes);
            for (let i = 0; i < arr.length; i++) bin += String.fromCharCode(arr[i]);
            return btoa(bin);
        }
        function b64decode(str) {
            // 统一清理任何空白（导入后可能插入换行或空格）
            const clean = (str || '').replace(/[\r\n\s]/g, '');
            const bin = atob(clean);
            const out = new Uint8Array(bin.length);
            for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
            return out.buffer;
        }

        async function deriveKey(password, salt) {
            const keyMaterial = await crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']);
            return crypto.subtle.deriveKey(
                { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
                keyMaterial,
                { name: 'AES-GCM', length: 256 },
                false,
                ['encrypt','decrypt']
            );
        }

        async function aesEncrypt(plaintext, password) {
            const salt = crypto.getRandomValues(new Uint8Array(16));
            const iv   = crypto.getRandomValues(new Uint8Array(12));
            const key = await deriveKey(password, salt);
            const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, enc.encode(plaintext));
            return `v1$${b64encode(salt)}$${b64encode(iv)}$${b64encode(ct)}`;
        }

        async function aesDecrypt(packet, password) {
            if (typeof packet !== 'string' || !packet.startsWith('v1$')) return packet;
            const parts = packet.trim().split('$'); // 分段前 trim
            if (parts.length !== 4) throw new Error('Invalid packet');
            const salt = new Uint8Array(b64decode(parts[1]));
            const iv   = new Uint8Array(b64decode(parts[2]));
            const ct   = b64decode(parts[3]);
            const key = await deriveKey(password, salt);
            const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
            return dec.decode(pt);
        }

        // ---------- Session-level password modal ----------
        const PW_KEY = 'code_pw_session';
        const pwBackdrop = document.getElementById('pwBackdrop');
        const pwInput = document.getElementById('pwInput');
        const pwConfirm = document.getElementById('pwConfirm');
        const pwCancel = document.getElementById('pwCancel');
        const pwError = document.getElementById('pwError');

        function havePassword() { return !!sessionStorage.getItem(PW_KEY); }
        function getPassword() { return sessionStorage.getItem(PW_KEY) || ''; }
        function openPwModal() {
            pwBackdrop.style.display = 'flex';
            pwBackdrop.setAttribute('aria-hidden', 'false');
            pwInput.value = '';
            pwInput.focus();
        }
        function closePwModal() {
            pwBackdrop.style.display = 'none';
            pwBackdrop.setAttribute('aria-hidden', 'true');
        }

        pwConfirm.addEventListener('click', () => {
            const v = pwInput.value;
            if (!v) { pwError.style.display = 'block'; return; }
            pwError.style.display = 'none';
            sessionStorage.setItem(PW_KEY, v);
            closePwModal();
            decryptAll();
        });
        pwCancel.addEventListener('click', () => { closePwModal(); });

        window.addEventListener('load', () => {
            if (!havePassword()) openPwModal();
            attachEncryptOnSubmit();
            if (havePassword()) decryptAll();
        });

        function attachEncryptOnSubmit() {
            const form = document.getElementById('file-submit-form');
            form.addEventListener('submit', async (e) => {
                e.preventDefault();
                const textarea = form.querySelector('textarea[name="code_content"]');
                const plaintext = textarea.value;
                if (!plaintext) { form.submit(); return; }

                if (!havePassword()) {
                    openPwModal();
                    const check = setInterval(async () => {
                        if (havePassword()) {
                            clearInterval(check);
                            try {
                                const cipherText = await aesEncrypt(plaintext, getPassword());
                                textarea.value = cipherText;
                                form.submit();
                            } catch { alert('Encryption failed.'); }
                        }
                    }, 200);
                    return;
                }
                try {
                    const cipherText = await aesEncrypt(plaintext, getPassword());
                    textarea.value = cipherText;
                    form.submit();
                } catch { alert('Encryption failed.'); }
            });
        }

        async function decryptAll() {
            const blocks = document.querySelectorAll('.code-content');
            const pw = getPassword();
            for (const el of blocks) {
                const packet = el.getAttribute('data-enc') || '';
                try {
                    if (!packet) continue;
                    const text = await aesDecrypt(packet, pw);
                    el.textContent = text;
                } catch {
                    el.textContent = '•••• encrypted ••••';
                }
            }
        }
    </script>
</body>
</html>

