<?php
// ============================================================================
// HANBYEOL Server - 파일 배포 (clean rewrite, 폴더 카드 + 파일 버튼)  2026-06-26
//  - 다운로드(?code=), 보안 잠금(secret), 검색, generator/5per 연동, JSON 폴더 유지
// ============================================================================

$cfg = __DIR__ . '/config.php';
if (!file_exists($cfg)) {
    http_response_code(500);
    die('<div style="font-family:sans-serif;text-align:center;margin-top:40px;"><h3>시스템 오류</h3><p>config.php 설정 파일이 누락되었습니다.</p></div>');
}
require_once $cfg;

$FILES_DIR  = __DIR__ . '/files';
$JSON_DIR   = __DIR__ . '/createzip/json';
$JSON_LABEL = '설정파일 (JSON)';
$IGNORE     = ['.', '..', 'descriptions.txt', '.DS_Store', 'Thumbs.db'];

$authed = isset($_COOKIE['admin_auth']) && $_COOKIE['admin_auth'] === ADMIN_PASSWORD;
$GLOBALS['is_authenticated'] = $authed;

// ----------------------------------------------------------------------------
// 다운로드 핸들러
// ----------------------------------------------------------------------------
$code = isset($_GET['code']) ? trim($_GET['code'], '/') : '';
if ($code !== '' && strtolower($code) !== 'index.php') {
    $path = hb_resolve_download($code, $FILES_DIR, $JSON_DIR, $JSON_LABEL);
    if ($path !== null) { hb_stream_file($path); exit; }
    // 매칭 실패: 목록으로 fall-through
}

function hb_resolve_download($code, $FILES_DIR, $JSON_DIR, $JSON_LABEL) {
    $base  = realpath($FILES_DIR);
    $jbase = realpath($JSON_DIR);
    // 1) 가상 JSON 폴더
    if ($jbase && strpos($code, $JSON_LABEL . '/') === 0) {
        $name = basename(substr($code, strlen($JSON_LABEL) + 1));
        $p = realpath($jbase . '/' . $name);
        if ($p && is_file($p) && strpos($p, $jbase) === 0) return $p;
    }
    // 2) files/ 아래 정확한 상대경로
    if ($base) {
        $p = realpath($base . '/' . $code);
        if ($p && is_file($p) && strpos($p, $base) === 0) return $p;
    }
    // 3) json 디렉터리 basename
    if ($jbase) {
        $p = realpath($jbase . '/' . basename($code));
        if ($p && is_file($p) && strpos($p, $jbase) === 0) return $p;
    }
    // 4) files/ 트리에서 파일명 검색 (확장자 유무 모두) - /OJSETUP 단축링크 대응
    if ($base && is_dir($base)) {
        $target = strtolower(str_replace(' ', '', $code));
        $it = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS));
        foreach ($it as $f) {
            if (!$f->isFile()) continue;
            $fn = $f->getFilename();
            $n1 = strtolower(str_replace(' ', '', $fn));
            $n2 = strtolower(str_replace(' ', '', pathinfo($fn, PATHINFO_FILENAME)));
            if ($n1 === $target || $n2 === $target) {
                $p = $f->getRealPath();
                if ($p && strpos($p, $base) === 0) return $p;
            }
        }
    }
    return null;
}

function hb_stream_file($path) {
    while (ob_get_level()) { ob_end_clean(); }
    $name = basename($path);
    $size = filesize($path);
    header('Content-Type: application/octet-stream');
    header("Content-Disposition: attachment; filename=\"" . $name . "\"; filename*=UTF-8''" . rawurlencode($name));
    if ($size !== false) header('Content-Length: ' . $size);
    header('Cache-Control: private');
    @set_time_limit(0);
    $fp = fopen($path, 'rb');
    if ($fp === false) { http_response_code(500); echo 'file open error'; return; }
    while (!feof($fp)) { echo fread($fp, 262144); flush(); }
    fclose($fp);
}

// ----------------------------------------------------------------------------
// 목록 데이터
// ----------------------------------------------------------------------------
function hb_load_desc($dir) {
    $map = [];
    $f = $dir . '/descriptions.txt';
    if (is_file($f)) {
        foreach (file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
            $parts = explode(',', $line, 2);
            if (count($parts) === 2) $map[strtolower(trim($parts[0]))] = trim($parts[1]);
        }
    }
    return $map;
}
function hb_size($b) {
    if ($b >= 1073741824) return number_format($b / 1073741824, 2) . ' GB';
    if ($b >= 1048576)    return number_format($b / 1048576, 2) . ' MB';
    if ($b >= 1024)       return number_format($b / 1024, 2) . ' KB';
    return $b . ' B';
}
// 한 파일 버튼의 데이터 묶음 생성
function hb_file_meta($fullpath, $descMap) {
    $name = basename($fullpath);
    $desc = isset($descMap[strtolower($name)]) ? $descMap[strtolower($name)] : '';
    return [
        'name' => $name,
        'desc' => $desc,
        'size' => hb_size(filesize($fullpath)),
        'date' => date('Y.m.d', filemtime($fullpath)),
    ];
}

$rootDesc  = hb_load_desc($FILES_DIR);
$rootFiles = [];
$folders   = [];
if (is_dir($FILES_DIR)) {
    foreach (scandir($FILES_DIR) as $e) {
        if ($e === '' || $e[0] === '.' || $e[0] === '@' || in_array($e, $IGNORE, true)) continue;
        $full = $FILES_DIR . '/' . $e;
        if (is_dir($full))       $folders[]   = $e;
        elseif (is_file($full))  $rootFiles[] = $e;
    }
}

// 루트 파일 정리 (OJSETUP을 맨 앞 featured 로)
$rootItems = [];
foreach ($rootFiles as $rf) {
    $m = hb_file_meta($FILES_DIR . '/' . $rf, $rootDesc);
    $m['code']     = $rf;
    $m['featured'] = (stripos($rf, 'OJSETUP') === 0);
    $rootItems[] = $m;
}
usort($rootItems, function ($a, $b) {
    if ($a['featured'] !== $b['featured']) return $a['featured'] ? -1 : 1;
    return strcasecmp($a['name'], $b['name']);
});

// 폴더 카드 (files/ 하위폴더 = 보안 항목)
$folderCards = [];
foreach ($folders as $fd) {
    $dir = $FILES_DIR . '/' . $fd;
    $dmap = hb_load_desc($dir);
    $items = [];
    foreach (scandir($dir) as $e) {
        if ($e === '' || $e[0] === '.' || $e[0] === '@' || in_array($e, $IGNORE, true)) continue;
        $full = $dir . '/' . $e;
        if (!is_file($full)) continue;
        $m = hb_file_meta($full, $dmap);
        $m['code'] = $fd . '/' . $e;
        $items[] = $m;
    }
    usort($items, function ($a, $b) { return strcasecmp($a['name'], $b['name']); });
    if ($items) $folderCards[] = ['title' => $fd, 'secret' => false, 'items' => $items];
}

// JSON 설정 폴더 (공개)
if (is_dir($JSON_DIR)) {
    $jitems = [];
    foreach (scandir($JSON_DIR) as $e) {
        if ($e === '' || $e[0] === '.' || $e[0] === '@' || in_array($e, $IGNORE, true)) continue;
        $full = $JSON_DIR . '/' . $e;
        if (!is_file($full)) continue;
        $m = hb_file_meta($full, []);
        $m['code'] = $JSON_LABEL . '/' . $e;
        $jitems[] = $m;
    }
    usort($jitems, function ($a, $b) { return strcasecmp($a['name'], $b['name']); });
    if ($jitems) $folderCards[] = ['title' => $JSON_LABEL, 'secret' => false, 'items' => $jitems];
}

// 버튼 렌더 헬퍼
function hb_render_btn($m, $featured = false) {
    $href  = '/?code=' . rawurlencode($m['code']);
    $name  = htmlspecialchars($m['name']);
    $desc  = $m['desc'] !== '' ? htmlspecialchars($m['desc']) : '';
    $sub   = trim(($desc !== '' ? $desc . ' · ' : '') . $m['size'] . ' · ' . $m['date']);
    $search = htmlspecialchars(strtolower($m['name'] . ' ' . $m['desc'] . ' ' . $m['code']));
    $cls   = 'file-btn' . ($featured ? ' featured' : '');
    $star  = $featured ? '<span class="star">★</span>' : '';
    return "<a class=\"$cls\" href=\"$href\" data-search=\"$search\">"
         . "<span class=\"fb-main\">$star<span class=\"fb-name\">$name</span></span>"
         . "<span class=\"fb-sub\">$sub</span>"
         . "<span class=\"fb-dl\">&#8595;</span></a>";
}

header('Content-Type: text/html; charset=UTF-8');
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= htmlspecialchars(SITE_NAME) ?> | Print Driver Resources</title>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
    <style>
        :root{
            --primary:#1a73e8; --bg:#f4f6f9; --text:#202124; --sub:#5f6368;
            --border:#dee2e6; --card:#ffffff;
        }
        *{box-sizing:border-box;}
        body{font-family:'Noto Sans KR',sans-serif;background:var(--bg);margin:0;color:var(--text);display:flex;flex-direction:column;min-height:100vh;}
        a{text-decoration:none;color:inherit;}

        header{background:#fff;border-bottom:1px solid var(--border);padding:0 20px;box-shadow:0 2px 8px rgba(0,0,0,.05);position:sticky;top:0;z-index:100;}
        .nav-container{max-width:1050px;margin:0 auto;height:65px;display:flex;align-items:center;justify-content:space-between;}
        .logo{font-size:21px;font-weight:700;color:var(--primary);display:flex;align-items:center;gap:8px;letter-spacing:-.5px;}
        .nav-links{display:flex;align-items:center;gap:18px;font-size:14.5px;font-weight:500;}
        .nav-links a{color:var(--sub);transition:color .2s;}
        .nav-links a:hover{color:var(--primary);}
        .link-highlight{color:#0f9d58 !important;font-weight:700;}
        .link-highlight:hover{color:#0b8043 !important;}
        .btn-create{background:#f36c60;color:#fff !important;padding:9px 18px;border-radius:20px;font-size:14px;font-weight:700;transition:all .2s;box-shadow:0 2px 4px rgba(243,108,96,.3);}
        .btn-create:hover{background:#e53935;transform:translateY(-1px);box-shadow:0 4px 8px rgba(229,57,53,.4);}

        .hero{background:linear-gradient(135deg,#1976d2,#0d47a1);color:#fff;padding:45px 20px;text-align:center;border-radius:0 0 16px 16px;margin-bottom:25px;box-shadow:inset 0 -3px 10px rgba(0,0,0,.1);}
        .hero h1{margin:0 0 12px;font-size:26px;font-weight:700;letter-spacing:-.5px;}
        .hero p{margin:0;font-size:15px;color:#e3f2fd;line-height:1.5;}

        .container{max-width:1000px;margin:-30px auto 40px;background:var(--card);padding:32px;border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.08);flex:1;width:calc(100% - 40px);position:relative;z-index:10;border:1px solid var(--border);}

        .search-container{position:relative;margin-bottom:28px;}
        .search-input{width:100%;padding:15px 20px 15px 45px;border:2px solid #ced4da;border-radius:8px;font-size:16px;font-weight:500;outline:none;transition:border-color .2s;color:var(--text);}
        .search-input:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(26,115,232,.15);}
        .search-input::placeholder{color:#adb5bd;font-weight:400;}
        .search-icon{position:absolute;left:16px;top:16px;color:#6c757d;}

        .section-title{font-size:15px;font-weight:700;color:var(--sub);margin:6px 0 14px;display:flex;align-items:center;gap:8px;}
        .section-title .count{font-size:13px;color:#adb5bd;font-weight:500;}

        /* 파일 버튼 */
        .btn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px;}
        .file-btn{display:flex;flex-direction:column;gap:3px;position:relative;background:#fff;border:1.5px solid var(--border);border-radius:10px;padding:14px 44px 14px 16px;transition:all .15s;}
        .file-btn:hover{border-color:var(--primary);box-shadow:0 4px 14px rgba(26,115,232,.15);transform:translateY(-2px);}
        .fb-main{display:flex;align-items:center;gap:6px;}
        .fb-name{font-size:14.5px;font-weight:700;color:var(--text);word-break:break-all;}
        .fb-sub{font-size:12px;color:var(--sub);}
        .fb-dl{position:absolute;right:14px;top:50%;transform:translateY(-50%);font-size:18px;color:var(--primary);font-weight:700;}
        .file-btn:hover .fb-dl{animation:bob .6s infinite;}
        @keyframes bob{0%,100%{transform:translateY(-50%);}50%{transform:translateY(-30%);}}
        .file-btn.featured{border-color:#1a73e8;background:linear-gradient(135deg,#e8f0fe,#fff);box-shadow:0 2px 10px rgba(26,115,232,.18);}
        .file-btn.featured .fb-name{color:#0d47a1;}
        .star{color:#f9ab00;font-size:15px;}

        /* 폴더 카드 */
        .folder-card{border:1px solid var(--border);border-radius:12px;margin-top:22px;overflow:hidden;background:#fdfdfe;}
        .folder-card-head{background:#fff3cd;color:#856404;padding:13px 18px;font-size:15.5px;font-weight:700;display:flex;align-items:center;gap:8px;border-bottom:1px solid #ffe8a1;}
        .folder-card-head .fc-count{font-size:13px;color:#b58900;font-weight:500;}
        .folder-card-head .lock{margin-left:auto;font-size:12px;color:#b58900;font-weight:500;}
        .folder-card-body{padding:16px;}

        footer{padding:30px 20px;text-align:center;font-size:13px;color:#6c757d;margin-top:auto;}

        /* 검색/보안 */
        .search-hide{display:none !important;}
        body:not(.show-secrets) .secret-item{display:none !important;}
        #secret-dot{position:fixed;top:10px;left:10px;font-size:20px;font-weight:bold;color:#1a73e8;opacity:.15;cursor:pointer;z-index:9999;user-select:none;padding:8px;}
        #secret-dot:hover{opacity:1;}
        #pwd-modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:10000;align-items:center;justify-content:center;backdrop-filter:blur(3px);}
        .modal-content{background:#fff;padding:30px;border-radius:12px;width:320px;box-shadow:0 10px 30px rgba(0,0,0,.2);text-align:center;}
        .modal-content h3{margin:0 0 15px;font-size:18px;color:#333;}
        .modal-content input{width:100%;padding:12px;border:2px solid #ced4da;border-radius:6px;font-size:20px;margin-bottom:20px;text-align:center;letter-spacing:3px;}
        .modal-content input:focus{border-color:var(--primary);outline:none;box-shadow:0 0 0 3px rgba(26,115,232,.15);}
        .modal-buttons{display:flex;gap:10px;}
        .modal-buttons button{flex:1;padding:12px;border:none;border-radius:6px;font-size:15px;font-weight:bold;cursor:pointer;transition:.2s;}
        .btn-cancel{background:#e9ecef;color:#495057;}
        .btn-cancel:hover{background:#dde2e6;}
        .btn-confirm{background:var(--primary);color:#fff;}
        .btn-confirm:hover{background:#1557b0;}

        @media(max-width:600px){
            .container{padding:20px 16px;}
            .btn-grid{grid-template-columns:1fr;}
            .nav-links{gap:12px;font-size:13px;}
            .nav-links a:not(.btn-create):not(.link-highlight){display:none;}
        }
    </style>
</head>
<body<?= $authed ? ' class="show-secrets"' : '' ?>>

<div id="secret-dot">.</div>

<header>
    <div class="nav-container">
        <a href="/" class="logo">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                <path d="M12 2L2 7l10 5 10-5-10-5z"></path><path d="M2 17l10 5 10-5"></path><path d="M2 12l10 5 10-5"></path>
            </svg>
            <?= htmlspecialchars(SITE_NAME) ?>
        </a>
        <div class="nav-links">
            <a href="/">Home</a>
            <?php if (defined('LINK_SHOP') && LINK_SHOP !== ''): ?>
                <a href="<?= htmlspecialchars(LINK_SHOP) ?>">Shop</a>
            <?php endif; ?>
            <a href="/createzip/5per.php" class="link-highlight">📊 5%밀도 확인</a>
            <a href="/createzip/generator.php" class="btn-create">바로설치 생성</a>
        </div>
    </div>
</header>

<section class="hero">
    <h1>프린터 드라이버 다운로드 센터</h1>
    <p>임대처 통합 드라이버 및 소프트웨어 배포 시스템<br>문의사항: <?= htmlspecialchars(CONTACT_NO) ?></p>
</section>

<div class="container">
    <div class="search-container">
        <svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>
        </svg>
        <input type="text" id="searchInput" class="search-input" placeholder="파일명이나 프로그램을 검색해 보세요 (예: 교세라, 251, 드라이버)">
    </div>

    <?php if ($rootItems): ?>
    <div class="root-section filter-card">
        <div class="section-title">주요 프로그램 <span class="count">(<?= count($rootItems) ?>)</span></div>
        <div class="btn-grid">
            <?php foreach ($rootItems as $m) echo hb_render_btn($m, $m['featured']); ?>
        </div>
    </div>
    <?php endif; ?>

    <?php foreach ($folderCards as $fc): ?>
    <div class="folder-card filter-card<?= $fc['secret'] ? ' secret-item' : '' ?>">
        <div class="folder-card-head">
            📁 <?= htmlspecialchars($fc['title']) ?>
            <span class="fc-count">(<?= count($fc['items']) ?>개)</span>
            <?php if ($fc['secret']): ?><span class="lock">🔒 보안</span><?php endif; ?>
        </div>
        <div class="folder-card-body">
            <div class="btn-grid">
                <?php foreach ($fc['items'] as $m) echo hb_render_btn($m); ?>
            </div>
        </div>
    </div>
    <?php endforeach; ?>
</div>

<footer>
    <p><strong><?= htmlspecialchars(SITE_NAME) ?></strong> | 기술지원팀</p>
    <p>Copyright &copy; <?= date('Y') ?> <?= htmlspecialchars(COPY_RIGHT) ?> All rights reserved.</p>
</footer>

<div id="pwd-modal">
    <div class="modal-content">
        <h3>🔒 보안 폴더 접근</h3>
        <input type="password" id="modal-pwd-input" placeholder="비밀번호 입력">
        <div class="modal-buttons">
            <button class="btn-cancel" id="btn-modal-cancel">취소</button>
            <button class="btn-confirm" id="btn-modal-confirm">확인</button>
        </div>
    </div>
</div>

<script>
document.getElementById('secret-dot').addEventListener('click', function () {
    const modal = document.getElementById('pwd-modal');
    const input = document.getElementById('modal-pwd-input');
    modal.style.display = 'flex';
    input.value = '';
    setTimeout(() => input.focus(), 100);
    const close = () => modal.style.display = 'none';
    const submit = () => {
        const pwd = input.value;
        if (pwd) {
            document.cookie = "admin_auth=" + encodeURIComponent(pwd) + "; path=/; max-age=1800";
            location.reload();
        } else { close(); }
    };
    document.getElementById('btn-modal-cancel').onclick = close;
    document.getElementById('btn-modal-confirm').onclick = submit;
    input.onkeyup = (e) => { if (e.key === 'Enter') submit(); };
});

document.getElementById('searchInput').addEventListener('keyup', function () {
    const q = this.value.toLowerCase().trim();
    document.querySelectorAll('.file-btn').forEach(b => {
        const hit = !q || (b.dataset.search || '').includes(q);
        b.classList.toggle('search-hide', !hit);
    });
    document.querySelectorAll('.filter-card').forEach(card => {
        const visible = [...card.querySelectorAll('.file-btn')]
            .some(b => !b.classList.contains('search-hide') && getComputedStyle(b).display !== 'none');
        card.classList.toggle('search-hide', q.length > 0 && !visible);
    });
});
</script>
</body>
</html>
