#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
懒大猫工具箱文件浏览器 - 无密钥版
用法: python server.py -p 9800
"""

import os
import argparse
import urllib.parse
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler

# 美观的 HTML 模板（太空主题，匹配工具箱风格）
DIR_LISTING_TEMPLATE = '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title>🧰 工具箱文件浏览器 · {dir_name}</title>
    <style>
        * {{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }}

        body {{
            background: #0f172a;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans', sans-serif;
            padding: 2rem 1rem;
            min-height: 100vh;
        }}

        .stars {{
            position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0;
        }}
        .star {{
            position: absolute; border-radius: 50%; background: white;
            animation: twinkle var(--dur) ease-in-out infinite alternate;
        }}
        @keyframes twinkle {{ from {{ opacity: var(--min); }} to {{ opacity: var(--max); }} }}

        .container {{
            max-width: 1100px;
            margin: 0 auto;
            background: rgba(30, 41, 59, 0.85);
            border-radius: 20px;
            box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.4);
            overflow: hidden;
            border: 1px solid rgba(148, 163, 184, 0.12);
            position: relative;
            z-index: 1;
        }}

        .header {{
            background: linear-gradient(135deg, #1e293b, #334155);
            padding: 1.5rem 2rem;
            color: #e2e8f0;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 1rem;
        }}

        .title {{
            display: flex;
            align-items: center;
            gap: 0.75rem;
            font-size: 1.4rem;
            font-weight: 700;
        }}

        .title .badge {{
            background: linear-gradient(135deg, #60a5fa, #a78bfa);
            color: white;
            font-size: 0.7rem;
            padding: 0.2rem 0.8rem;
            border-radius: 40px;
            font-weight: 600;
        }}

        .back-link {{
            color: #94a3b8;
            text-decoration: none;
            font-size: 0.85rem;
            transition: color 0.2s;
        }}
        .back-link:hover {{ color: #60a5fa; }}

        .breadcrumb {{
            padding: 1rem 2rem;
            background: rgba(15, 23, 42, 0.6);
            border-bottom: 1px solid rgba(148, 163, 184, 0.08);
            font-size: 0.85rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
            flex-wrap: wrap;
            color: #94a3b8;
        }}

        .breadcrumb a {{
            color: #60a5fa;
            text-decoration: none;
            font-weight: 500;
        }}

        .breadcrumb a:hover {{
            text-decoration: underline;
        }}

        .file-list {{
            padding: 1rem 1.5rem 2rem;
        }}

        .file-grid {{
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
            gap: 0.75rem;
        }}

        .file-item {{
            background: rgba(30, 41, 59, 0.7);
            border-radius: 14px;
            transition: all 0.2s ease;
            border: 1px solid rgba(148, 163, 184, 0.1);
            overflow: hidden;
        }}

        .file-item:hover {{
            transform: translateY(-2px);
            box-shadow: 0 8px 24px rgba(96, 165, 250, 0.12);
            border-color: rgba(96, 165, 250, 0.4);
        }}

        .file-link {{
            display: flex;
            align-items: center;
            gap: 0.8rem;
            padding: 0.9rem 1.2rem;
            text-decoration: none;
            color: #e2e8f0;
            font-weight: 500;
            transition: background 0.1s;
        }}

        .file-link:hover {{
            background: rgba(96, 165, 250, 0.06);
        }}

        .file-icon {{
            font-size: 1.6rem;
            min-width: 2rem;
            text-align: center;
        }}

        .file-name {{
            word-break: break-word;
            flex: 1;
        }}

        .file-size {{
            font-size: 0.7rem;
            color: #64748b;
            font-weight: normal;
            margin-top: 0.2rem;
        }}

        .footer {{
            background: rgba(15, 23, 42, 0.5);
            padding: 1rem 2rem;
            text-align: center;
            font-size: 0.7rem;
            color: #475569;
            border-top: 1px solid rgba(148, 163, 184, 0.08);
        }}

        .footer a {{
            color: #64748b;
            text-decoration: none;
        }}
        .footer a:hover {{ color: #94a3b8; }}

        @media (max-width: 640px) {{
            body {{ padding: 1rem 0.5rem; }}
            .header {{ padding: 1rem 1.2rem; flex-direction: column; align-items: flex-start; }}
            .title {{ font-size: 1.2rem; }}
            .file-grid {{ grid-template-columns: 1fr; }}
            .file-link {{ padding: 0.7rem 1rem; }}
        }}

        .empty-folder {{
            text-align: center;
            padding: 3rem;
            color: #64748b;
        }}
    </style>
</head>
<body>

<div class="stars" id="stars"></div>

<div class="container">
    <div class="header">
        <div class="title">
            🧰 工具箱文件浏览器
            <span class="badge">公开访问</span>
        </div>
        <a href="/" class="back-link">← 返回工具箱主页</a>
    </div>
    <div class="breadcrumb">
        <span>📂</span>
        {breadcrumb_html}
    </div>
    <div class="file-list">
        <div class="file-grid">
            {file_list_html}
        </div>
    </div>
    <div class="footer">
        <a href="/">懒大猫的工具箱</a> · 文件浏览器
    </div>
</div>

<script>
  const stars = document.getElementById('stars');
  for (let i = 0; i < 60; i++) {{
    const s = document.createElement('div');
    s.className = 'star';
    const size = Math.random() * 2 + 0.5;
    s.style.cssText = `
      width:${{size}}px;height:${{size}}px;
      left:${{Math.random()*100}}%;top:${{Math.random()*100}}%;
      --dur:${{Math.random()*3+2}}s;--min:${{Math.random()*0.3}};--max:${{Math.random()*0.5+0.5}};
      animation-delay:${{Math.random()*3}}s;
    `;
    stars.appendChild(s);
  }}
</script>
</body>
</html>'''


class ToolsFileBrowser(SimpleHTTPRequestHandler):
    """工具箱文件浏览器 - 无密钥，隐藏 .git 等隐藏文件"""

    HIDDEN = {'.', '__pycache__'}

    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        path = urllib.parse.unquote(parsed.path)
        rel_path = path.lstrip('/')
        fs_path = os.path.join(os.getcwd(), rel_path) if rel_path else os.getcwd()

        # 根路径：如果有 index.html 就正常 serve
        if not rel_path or path == '/':
            index = os.path.join(os.getcwd(), 'index.html')
            if os.path.isfile(index):
                self.path = '/index.html'
                super().do_GET()
                return

        # /browse/ 虚拟路径：显示根目录文件列表
        if rel_path == 'browse' or rel_path.startswith('browse/'):
            sub = rel_path[len('browse'):].strip('/')
            if sub:
                real_fs = os.path.join(os.getcwd(), sub)
                real_rel = sub
            else:
                real_fs = os.getcwd()
                real_rel = ''
            self.show_pretty_directory_listing(real_fs, real_rel)
            return

        is_dir = os.path.isdir(fs_path) or path.endswith('/')

        if is_dir:
            # 子目录也检查 index.html
            index_in_dir = os.path.join(fs_path, 'index.html') if os.path.isdir(fs_path) else None
            if index_in_dir and os.path.isfile(index_in_dir):
                self.path = path.rstrip('/') + '/index.html'
                super().do_GET()
                return
            self.show_pretty_directory_listing(fs_path, rel_path)
        else:
            # 文件请求直接返回
            self.path = path
            super().do_GET()

    def end_headers(self):
        """为文本类 Content-Type 自动追加 charset=utf-8"""
        new_buffer = []
        for line in self._headers_buffer:
            if isinstance(line, bytes) and line.lower().startswith(b'content-type:') and b'charset' not in line.lower():
                if b'text/' in line.lower() or b'application/json' in line.lower():
                    line = line.rstrip(b'\r\n') + b'; charset=utf-8\r\n'
            new_buffer.append(line)
        self._headers_buffer = new_buffer
        super().end_headers()

    def show_pretty_directory_listing(self, fs_path, rel_path):
        """生成美观的目录列表页面"""
        try:
            entries = os.listdir(fs_path)
        except OSError:
            self.send_error(404, "无法列出目录")
            return

        # 分离目录和文件，隐藏 . 开头的文件/目录
        dirs = []
        files = []
        for name in entries:
            if name.startswith('.') or name in self.HIDDEN:
                continue
            full = os.path.join(fs_path, name)
            if os.path.isdir(full):
                dirs.append(name)
            else:
                files.append(name)
        dirs.sort(key=str.lower)
        files.sort(key=str.lower)

        items_html = []

        # 上级目录
        if rel_path and rel_path != '':
            parent_path = os.path.dirname(rel_path)
            if parent_path:
                parent_url = f"/browse/{parent_path}/"
            else:
                parent_url = "/browse/"
            items_html.append(f'''
                <div class="file-item">
                    <a href="{parent_url}" class="file-link">
                        <div class="file-icon">📁</div>
                        <div class="file-name">../ (上级目录)</div>
                    </a>
                </div>
            ''')

        # 目录
        for name in dirs:
            if rel_path:
                href = f"/browse/{urllib.parse.quote(rel_path)}/{urllib.parse.quote(name)}/"
            else:
                href = f"/browse/{urllib.parse.quote(name)}/"
            items_html.append(f'''
                <div class="file-item">
                    <a href="{href}" class="file-link">
                        <div class="file-icon">📁</div>
                        <div class="file-name">{self.escape_html(name)}/</div>
                    </a>
                </div>
            ''')

        # 文件
        for name in files:
            if rel_path:
                href = f"/{urllib.parse.quote(rel_path)}/{urllib.parse.quote(name)}"
            else:
                href = f"/{urllib.parse.quote(name)}"
            full_path = os.path.join(fs_path, name)
            size = os.path.getsize(full_path)
            size_str = self.format_size(size)
            ext = os.path.splitext(name)[1].lower()
            icon = self.get_file_icon(ext)
            items_html.append(f'''
                <div class="file-item">
                    <a href="{href}" class="file-link">
                        <div class="file-icon">{icon}</div>
                        <div class="file-name">
                            {self.escape_html(name)}
                            <div class="file-size">{size_str}</div>
                        </div>
                    </a>
                </div>
            ''')

        if not items_html:
            items_html = ['<div class="empty-folder">✨ 此目录为空 ✨</div>']

        breadcrumb = self.generate_breadcrumb(rel_path)

        html = DIR_LISTING_TEMPLATE.format(
            dir_name=self.escape_html(rel_path if rel_path else '/'),
            breadcrumb_html=breadcrumb,
            file_list_html=''.join(items_html)
        )
        self.send_response(200)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.end_headers()
        self.wfile.write(html.encode('utf-8'))

    def generate_breadcrumb(self, rel_path):
        if not rel_path:
            return '<span style="color:#60a5fa;">/</span>'
        parts = rel_path.split('/')
        bread = []
        current = ''
        for i, part in enumerate(parts):
            if not part:
                continue
            current += part + '/'
            if i == len(parts) - 1:
                bread.append(f'<span style="color:#60a5fa;">{self.escape_html(part)}</span>')
            else:
                url = f"/browse/{current}"
                bread.append(f'<a href="{url}">{self.escape_html(part)}</a>')
                bread.append('<span>/</span>')
        return '<span>/</span>'.join(bread)

    def format_size(self, size):
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024.0:
                return f"{size:.1f} {unit}" if unit != 'B' else f"{size} B"
            size /= 1024.0
        return f"{size:.1f} TB"

    def get_file_icon(self, ext):
        icons = {
            '.html': '🌐', '.htm': '🌐',
            '.css': '🎨', '.js': '⚡', '.json': '📦',
            '.jpg': '🖼️', '.jpeg': '🖼️', '.png': '🖼️', '.gif': '🖼️', '.svg': '🎨',
            '.pdf': '📕', '.doc': '📘', '.docx': '📘', '.xls': '📗', '.xlsx': '📗',
            '.zip': '🗜️', '.tar': '🗜️', '.gz': '🗜️', '.7z': '🗜️',
            '.mp3': '🎵', '.wav': '🎵', '.flac': '🎵',
            '.mp4': '🎬', '.mkv': '🎬', '.avi': '🎬',
            '.txt': '📄', '.md': '📝', '.py': '🐍', '.c': '⚙️', '.cpp': '⚙️',
            '.sh': '🐚', '.bat': '🪟',
        }
        return icons.get(ext, '📄')

    def escape_html(self, text):
        return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')


def run(port):
    server = ThreadingHTTPServer(('', port), ToolsFileBrowser)
    print(f"🧰 工具箱文件浏览器已启动")
    print(f"📍 端口: {port}")
    print(f"📁 访问: http://localhost:{port}/")
    print("按 Ctrl+C 停止服务器")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n👋 服务器已停止")
        server.server_close()


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='懒大猫工具箱文件浏览器')
    parser.add_argument('-p', '--port', type=int, default=9800, help='端口 (默认9800)')
    args = parser.parse_args()
    run(args.port)
