import os
import http.server
import socketserver
import urllib.parse
import zipfile
import io
import shutil
import json
import re
from datetime import datetime

# --- Funções auxiliares ---
def format_file_size(size_bytes):
    if size_bytes < 1024: return f"{size_bytes} B"
    elif size_bytes < 1024**2: return f"{size_bytes/1024:.1f} KB"
    elif size_bytes < 1024**3: return f"{size_bytes/1024**2:.1f} MB"
    else: return f"{size_bytes/1024**3:.1f} GB"

def load_file_content(filename):
    try:
        folder = "oplaboptionvols"
        file_path = os.path.join(folder, f"{filename}.json")
        if os.path.exists(file_path):
            with open(file_path, 'r', encoding='utf-8') as file: return file.read()
        return None
    except Exception: return None

def log(tag, msg, ok=True):
    ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    status = "OK  " if ok else "ERRO"
    print(f"[{ts}] {status}  {tag:<8}  {msg}")

# --- Handler do Servidor ---
class MyAdvancedHttpRequestHandler(http.server.SimpleHTTPRequestHandler):

    def send_cors_headers(self):
        """Adiciona headers CORS em todas as respostas."""
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type, Content-Disposition")

    def do_OPTIONS(self):
        """Responde ao preflight CORS do navegador/app."""
        self.send_response(204)
        self.send_cors_headers()
        self.end_headers()

    def end_headers(self):
        """Injeta CORS em todas as respostas automaticamente."""
        self.send_cors_headers()
        super().end_headers()

    def do_GET(self):
        parsed_url = urllib.parse.urlparse(self.path)
        query_params = urllib.parse.parse_qs(parsed_url.query)

        # 1. Rota JSON para o Delphi
        if 'list' in query_params and query_params['list'][0] == 'json':
            path_to_list = self.translate_path(parsed_url.path)
            if os.path.isdir(path_to_list):
                self.serve_json_directory_list(path_to_list)
            else:
                self.send_error(404, "Diretorio nao encontrado")
            return

        # 2. Rota original de JSON por nome
        if 'filename' in query_params:
            fname = query_params['filename'][0]
            log("JSON", f"{fname}  ←  {self.client_address[0]}")
            self.handle_json_request(fname)
            return

        # 3. Download em ZIP
        if 'download' in query_params and query_params['download'][0] == 'zip':
            path_to_zip = self.translate_path(parsed_url.path)
            if os.path.isdir(path_to_zip):
                log("ZIP", f"{parsed_url.path}  ←  {self.client_address[0]}")
                self.serve_directory_as_zip(path_to_zip)
            else:
                log("ZIP", f"{parsed_url.path}  ←  {self.client_address[0]}  (diretório não encontrado)", ok=False)
                self.send_error(404, "Diretório não encontrado")
            return

        super().do_GET()

    def serve_json_directory_list(self, path):
        try:
            parsed_url = urllib.parse.urlparse(self.path)
            clean_base_path = parsed_url.path
            raw_list = os.listdir(path)
            items = []
            for name in raw_list:
                fullname = os.path.join(path, name)
                is_dir = os.path.isdir(fullname)
                relative_path = clean_base_path.rstrip('/') + '/' + name
                info = {
                    "name": name,
                    "is_dir": is_dir,
                    "path": relative_path,
                    "size_bytes": os.path.getsize(fullname) if not is_dir else 0,
                    "mtime": datetime.fromtimestamp(os.path.getmtime(fullname)).isoformat()
                }
                items.append(info)
            items.sort(key=lambda x: (not x['is_dir'], x['name'].lower()))
            response_json = json.dumps(items, indent=4).encode('utf-8')
            self.send_response(200)
            self.send_header("Content-type", "application/json; charset=utf-8")
            self.send_header("Content-Length", str(len(response_json)))
            self.end_headers()
            self.wfile.write(response_json)
        except Exception as e:
            self.send_error(500, f"Erro ao listar: {e}")

    def do_POST(self):
        content_type = self.headers.get('Content-Type', '')

        # LÓGICA DE UPLOAD
        if 'multipart/form-data' in content_type:
            result, message = self.handle_upload()
            if result:
                log("UPLOAD", f"{message}  ←  {self.client_address[0]}")
                self.send_response(201)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(json.dumps({"status": "success", "message": message}).encode())
            else:
                log("UPLOAD", f"{message}  ←  {self.client_address[0]}", ok=False)
                self.send_error(400, message)
            return

        # LÓGICA DE DELETE
        try:
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length)
            params = urllib.parse.parse_qs(post_data.decode('utf-8'))

            if params.get('action', [None])[0] == 'delete':
                path_to_delete_str = params.get('path', [None])[0]
                if not path_to_delete_str: raise ValueError("Caminho não especificado.")

                path_to_delete = self.translate_path(path_to_delete_str)

                if os.path.isdir(path_to_delete):
                    log("DELETE", f"pasta  {path_to_delete_str}  ←  {self.client_address[0]}")
                    shutil.rmtree(path_to_delete)
                elif os.path.isfile(path_to_delete):
                    size = format_file_size(os.path.getsize(path_to_delete))
                    log("DELETE", f"arquivo  {path_to_delete_str}  ({size})  ←  {self.client_address[0]}")
                    os.remove(path_to_delete)
                else:
                    log("DELETE", f"{path_to_delete_str}  não encontrado  ←  {self.client_address[0]}", ok=False)
                    self.send_error(404, "Arquivo ou diretório não encontrado para deletar.")
                    return

                parent_dir = os.path.dirname(path_to_delete_str) or '/'
                if not parent_dir.endswith('/'): parent_dir += '/'
                self.send_response(303)
                self.send_header('Location', parent_dir)
                self.end_headers()
            else:
                self.send_error(400, "Ação POST inválida.")
        except Exception as e:
            log("POST", str(e), ok=False)
            self.send_error(500, str(e))

    def handle_upload(self):
        """Processa o upload multipart/form-data usando cgi.FieldStorage (eficiente para arquivos grandes)."""
        import cgi
        try:
            form = cgi.FieldStorage(
                fp=self.rfile,
                headers=self.headers,
                environ={
                    'REQUEST_METHOD': 'POST',
                    'CONTENT_TYPE': self.headers['Content-Type'],
                    'CONTENT_LENGTH': self.headers.get('Content-Length', '0'),
                }
            )

            # Determina o diretório destino
            dest_dir = self.translate_path(".")  # Default: raiz
            if 'directory' in form:
                val = form['directory'].value.strip()
                if val:
                    dest_dir = self.translate_path(val)

            # Processa o arquivo
            if 'file' not in form:
                return False, "Campo 'file' não encontrado no formulário"

            file_item = form['file']
            if not file_item.filename:
                return False, "Nome do arquivo não enviado"

            filename = os.path.basename(file_item.filename)
            if not os.path.exists(dest_dir):
                os.makedirs(dest_dir)
            out_path = os.path.join(dest_dir, filename)

            # Grava em chunks de 1MB — eficiente para arquivos grandes
            CHUNK = 1024 * 1024
            with open(out_path, 'wb') as out_file:
                while True:
                    chunk = file_item.file.read(CHUNK)
                    if not chunk:
                        break
                    out_file.write(chunk)

            return True, f"Salvo em: {os.path.relpath(out_path, os.getcwd())}"
        except Exception as e:
            return False, f"Erro no processamento: {str(e)}"

    def handle_json_request(self, filename):
        json_content = load_file_content(filename)
        if json_content:
            response_bytes = json_content.encode('utf-8')
            self.send_response(200)
            self.send_header("Content-type", "application/json")
        else:
            error_msg = '{"error": "File not found"}'
            response_bytes = error_msg.encode('utf-8')
            self.send_response(404)
            self.send_header("Content-type", "application/json")
        self.send_header("Content-Length", str(len(response_bytes)))
        self.end_headers()
        self.wfile.write(response_bytes)

    def serve_directory_as_zip(self, dir_path):
        dir_name = os.path.basename(os.path.normpath(dir_path))
        zip_filename = f"{dir_name}.zip"
        memory_file = io.BytesIO()
        log("ZIP", f"compactando '{dir_name}'...")
        with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
            for root, dirs, files in os.walk(dir_path):
                for file in files:
                    fp = os.path.join(root, file)
                    archive_name = os.path.relpath(fp, start=dir_path)
                    zf.write(fp, archive_name)
                if not files and not dirs:
                    archive_name = os.path.relpath(root, start=dir_path)
                    zf.writestr(archive_name + '/', '')
        memory_file.seek(0)
        size = memory_file.getbuffer().nbytes
        log("ZIP", f"{zip_filename}  ({format_file_size(size)})")
        self.send_response(200)
        self.send_header('Content-Type', 'application/zip')
        self.send_header('Content-Disposition', f'attachment; filename="{zip_filename}"')
        self.send_header('Content-Length', str(size))
        self.end_headers()
        self.wfile.write(memory_file.getvalue())

    # --- INTERFACE PRINCIPAL ---
    def list_directory(self, path):
        try:
            raw_list = os.listdir(path)
        except OSError:
            self.send_error(404, "Permissão negada para listar o diretório")
            return None

        display_path = urllib.parse.unquote(self.path, errors='surrogatepass').split('?')[0]

        directories, files = [], []
        for name in raw_list:
            if os.path.isdir(os.path.join(path, name)): directories.append(name)
            else: files.append(name)
        directories.sort(key=str.lower)
        files.sort(key=str.lower)
        sorted_list = directories + files

        # --- SVG Icons ---
        ico_download = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>'
        ico_delete   = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>'
        ico_folder   = '<svg viewBox="0 0 24 24" stroke="#e09b3d" stroke-width="1.5" fill="#fbbf24" fill-opacity="0.85"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>'
        ico_file     = '<svg viewBox="0 0 24 24" stroke="#94a3b8" stroke-width="1.5" fill="#f8fafc"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9" fill="#e2e8f0"/></svg>'
        ico_upload   = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>'
        ico_zip      = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18M12 8h3M12 12h3M12 16h3"/></svg>'

        rows_html = []
        if display_path != '/':
            rows_html.append('''
            <tr class="parent-row">
                <td></td><td></td>
                <td class="icon-cell"><span class="type-icon">⬆</span></td>
                <td class="name-cell"><a href="..">..</a></td>
                <td>—</td><td>—</td>
            </tr>''')

        for name in sorted_list:
            fullname = os.path.join(path, name)
            link_name = urllib.parse.quote(name, errors='surrogatepass')
            current_path = (display_path.rstrip('/') + '/' + link_name)

            try:
                mod_time_str = datetime.fromtimestamp(os.path.getmtime(fullname)).strftime('%Y-%m-%d %H:%M')
                file_size_str = format_file_size(os.path.getsize(fullname)) if os.path.isfile(fullname) else '—'
            except OSError:
                mod_time_str, file_size_str = '—', '—'

            if os.path.isdir(fullname):
                type_icon  = ico_folder
                name_cell  = f'<td class="name-cell"><a href="{link_name}/">{name}/</a></td>'
                dl_link    = f'<a href="{link_name}/?download=zip" title="Baixar como .zip" class="act-btn dl-btn">{ico_zip}</a>'
            else:
                type_icon  = ico_file
                name_cell  = f'<td class="name-cell"><a href="{link_name}" title="Abrir {name}">{name}</a></td>'
                dl_link    = f'<a href="{link_name}" download="{name}" title="Baixar {name}" class="act-btn dl-btn">{ico_download}</a>'

            del_form = f'''<form method="POST" action="{current_path}" class="inline-form"
                onsubmit="return confirm('Deletar \\'{name}\\'?');">
                <input type="hidden" name="action" value="delete">
                <input type="hidden" name="path" value="{current_path}">
                <button type="submit" class="act-btn del-btn" title="Deletar {name}">{ico_delete}</button>
            </form>'''

            rows_html.append(f'''
            <tr>
                <td class="icon-cell">{dl_link}</td>
                <td class="icon-cell">{del_form}</td>
                <td class="icon-cell type-icon-cell">{type_icon}</td>
                {name_cell}
                <td class="size-cell">{file_size_str}</td>
                <td class="date-cell">{mod_time_str}</td>
            </tr>''')

        # Constrói o HTML completo da página
        html = f'''<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{display_path}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
  /* ── Reset & Base ── */
  *, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }}

  :root {{
    --bg:          #0d1117;
    --surface:     #161b22;
    --surface2:    #1c2330;
    --border:      #30363d;
    --border-soft: #21262d;
    --text:        #e6edf3;
    --text-muted:  #7d8590;
    --accent:      #58a6ff;
    --accent-dim:  #1f6feb33;
    --danger:      #f85149;
    --danger-dim:  #f8514920;
    --success:     #3fb950;
    --warning:     #d29922;
    --mono:        'IBM Plex Mono', monospace;
    --sans:        'IBM Plex Sans', sans-serif;
    --radius:      6px;
    --shadow:      0 8px 24px rgba(1,4,9,.6);
  }}

  html {{ scroll-behavior: smooth; }}

  body {{
    font-family: var(--sans);
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;
    padding: 0;
    /* Subtle grid pattern */
    background-image:
      linear-gradient(var(--border-soft) 1px, transparent 1px),
      linear-gradient(90deg, var(--border-soft) 1px, transparent 1px);
    background-size: 32px 32px;
  }}

  /* ── Layout ── */
  .page-wrapper {{
    max-width: 1080px;
    margin: 0 auto;
    padding: 32px 24px 80px;
  }}

  /* ── Header ── */
  .header {{
    display: flex;
    align-items: flex-end;
    gap: 16px;
    margin-bottom: 28px;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--border);
  }}
  .header-icon {{
    width: 36px; height: 36px;
    background: var(--accent-dim);
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
  }}
  .header-icon svg {{ width: 18px; height: 18px; stroke: var(--accent); }}
  .header-text {{ flex: 1; min-width: 0; }}
  .header-label {{
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-muted);
    letter-spacing: .08em;
    text-transform: uppercase;
    margin-bottom: 4px;
  }}
  .header-path {{
    font-family: var(--mono);
    font-size: 17px;
    font-weight: 500;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }}
  .header-path .sep {{ color: var(--text-muted); }}
  .header-path .seg {{ color: var(--accent); }}
  .header-path .seg:last-child {{ color: var(--text); }}

  /* ── Breadcrumb ── */
  .breadcrumb {{
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text-muted);
    margin-bottom: 20px;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: center;
  }}
  .breadcrumb a {{ color: var(--accent); text-decoration: none; }}
  .breadcrumb a:hover {{ text-decoration: underline; }}
  .breadcrumb .bc-sep {{ color: var(--border); }}

  /* ── Upload panel ── */
  .upload-panel {{
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 20px;
    margin-bottom: 20px;
    display: flex;
    gap: 12px;
    align-items: center;
    flex-wrap: wrap;
    transition: border-color .2s;
  }}
  .upload-panel:focus-within {{ border-color: var(--accent); }}
  .upload-panel label.upload-label {{
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
    font-family: var(--mono);
  }}
  .upload-panel input[type="file"] {{
    flex: 1;
    min-width: 180px;
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text-muted);
    background: transparent;
    border: none;
    outline: none;
  }}
  .upload-panel input[type="file"]::file-selector-button {{
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    font-family: var(--sans);
    font-size: 12px;
    padding: 4px 10px;
    cursor: pointer;
    transition: background .15s;
  }}
  .upload-panel input[type="file"]::file-selector-button:hover {{
    background: var(--accent-dim);
    border-color: var(--accent);
  }}
  .upload-panel input[type="hidden"] {{ display: none; }}
  .btn-upload {{
    display: flex; align-items: center; gap: 6px;
    background: var(--accent);
    color: #000;
    font-family: var(--sans);
    font-size: 13px;
    font-weight: 600;
    border: none;
    border-radius: var(--radius);
    padding: 6px 16px;
    cursor: pointer;
    white-space: nowrap;
    transition: opacity .15s, transform .1s;
  }}
  .btn-upload:hover {{ opacity: .88; transform: translateY(-1px); }}
  .btn-upload:active {{ transform: translateY(0); }}
  .btn-upload svg {{ width: 14px; height: 14px; stroke: #000; }}
  #upload-status {{
    width: 100%;
    font-family: var(--mono);
    font-size: 12px;
    padding: 4px 0 0;
    display: none;
  }}
  #upload-status.ok  {{ color: var(--success); }}
  #upload-status.err {{ color: var(--danger); }}

  /* ── File Table ── */
  .table-wrap {{
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow);
  }}
  table {{
    width: 100%;
    border-collapse: collapse;
  }}
  thead th {{
    background: var(--surface2);
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-muted);
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
    text-align: left;
  }}
  thead th.r {{ text-align: right; }}
  thead th.c {{ text-align: center; }}

  tbody tr {{
    border-bottom: 1px solid var(--border-soft);
    transition: background .12s;
  }}
  tbody tr:last-child {{ border-bottom: none; }}
  tbody tr:hover {{ background: var(--surface2); }}
  tbody tr.parent-row:hover {{ background: transparent; }}

  td {{
    padding: 9px 14px;
    vertical-align: middle;
    font-size: 13.5px;
  }}

  .icon-cell {{
    width: 44px;
    text-align: center;
    padding: 8px 6px;
  }}
  .type-icon-cell svg {{
    width: 20px; height: 20px;
    display: block; margin: 0 auto;
  }}

  .name-cell a {{
    color: var(--text);
    text-decoration: none;
    font-family: var(--mono);
    font-size: 13px;
    transition: color .12s;
  }}
  .name-cell a:hover {{ color: var(--accent); }}

  .size-cell {{
    text-align: right;
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
  }}
  .date-cell {{
    text-align: right;
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
  }}

  /* ── Action buttons ── */
  .act-btn {{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px; height: 28px;
    border-radius: var(--radius);
    border: 1px solid transparent;
    cursor: pointer;
    background: none;
    color: var(--text-muted);
    transition: color .15s, background .15s, border-color .15s, transform .1s;
    text-decoration: none;
    padding: 0;
  }}
  .act-btn svg {{ width: 15px; height: 15px; stroke: currentColor; }}
  .act-btn:hover {{ transform: scale(1.1); }}

  .dl-btn:hover {{
    color: var(--accent);
    background: var(--accent-dim);
    border-color: var(--accent);
  }}
  .del-btn:hover {{
    color: var(--danger);
    background: var(--danger-dim);
    border-color: var(--danger);
  }}

  .inline-form {{ display: inline-flex; margin: 0; padding: 0; }}

  /* ── Footer stats ── */
  .footer {{
    margin-top: 16px;
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-muted);
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
  }}
  .footer span {{ display: flex; align-items: center; gap: 5px; }}

  /* ── Toast ── */
  #toast {{
    position: fixed;
    bottom: 24px; right: 24px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 16px;
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text);
    box-shadow: var(--shadow);
    opacity: 0;
    transform: translateY(8px);
    transition: opacity .25s, transform .25s;
    pointer-events: none;
    max-width: 320px;
  }}
  #toast.show {{ opacity: 1; transform: translateY(0); }}
  #toast.ok  {{ border-color: var(--success); color: var(--success); }}
  #toast.err {{ border-color: var(--danger);  color: var(--danger);  }}
</style>
</head>
<body>
<div class="page-wrapper">

  <!-- Header -->
  <div class="header">
    <div class="header-icon">
      <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
        <polyline points="9 22 9 12 15 12 15 22"/>
      </svg>
    </div>
    <div class="header-text">
      <div class="header-label">File Server · porta {PORT}</div>
      <div class="header-path">{self._render_path_html(display_path)}</div>
    </div>
  </div>

  <!-- Breadcrumb -->
  <div class="breadcrumb">{self._render_breadcrumb(display_path)}</div>

  <!-- Upload Panel -->
  <form class="upload-panel" id="upload-form" enctype="multipart/form-data">
    <input type="hidden" name="directory" value="{display_path}">
    <label class="upload-label" for="file-input">{ico_upload}&nbsp;&nbsp;Enviar arquivo:</label>
    <input type="file" id="file-input" name="file" required>
    <button type="button" class="btn-upload" onclick="doUpload()">
      {ico_upload} Enviar
    </button>
    <span id="upload-status"></span>
  </form>

  <!-- File Table -->
  <div class="table-wrap">
    <table>
      <thead>
        <tr>
          <th class="c" style="width:44px">DL</th>
          <th class="c" style="width:44px">Del</th>
          <th class="c" style="width:44px">Tipo</th>
          <th>Nome</th>
          <th class="r" style="width:90px">Tamanho</th>
          <th class="r" style="width:140px">Modificado em</th>
        </tr>
      </thead>
      <tbody>
        {''.join(rows_html)}
      </tbody>
    </table>
  </div>

  <!-- Footer stats -->
  <div class="footer">
    <span>📁 {len(directories)} pasta{'s' if len(directories) != 1 else ''}</span>
    <span>📄 {len(files)} arquivo{'s' if len(files) != 1 else ''}</span>
  </div>

</div>

<!-- Toast -->
<div id="toast"></div>

<script>
  function showToast(msg, type) {{
    const t = document.getElementById('toast');
    t.textContent = msg;
    t.className = 'show ' + (type || '');
    clearTimeout(t._tid);
    t._tid = setTimeout(() => {{ t.className = ''; }}, 3500);
  }}

  async function doUpload() {{
    const form = document.getElementById('upload-form');
    const fileInput = document.getElementById('file-input');
    const status = document.getElementById('upload-status');

    if (!fileInput.files.length) {{
      showToast('Nenhum arquivo selecionado.', 'err');
      return;
    }}

    const fd = new FormData(form);
    status.style.display = 'block';
    status.className = '';
    status.textContent = '⏳ Enviando...';

    try {{
      const resp = await fetch(window.location.pathname, {{
        method: 'POST',
        body: fd
      }});
      if (resp.ok) {{
        const data = await resp.json();
        status.className = 'ok';
        status.textContent = '✔ ' + data.message;
        showToast('✔ ' + data.message, 'ok');
        setTimeout(() => location.reload(), 1000);
      }} else {{
        throw new Error('HTTP ' + resp.status);
      }}
    }} catch(e) {{
      status.className = 'err';
      status.textContent = '✖ Erro: ' + e.message;
      showToast('✖ Falha no envio: ' + e.message, 'err');
    }}
  }}
</script>
</body>
</html>'''

        encoded_html = html.encode('utf-8')
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(encoded_html)))
        self.end_headers()
        self.wfile.write(encoded_html)

    def _render_path_html(self, path):
        """Renderiza o path com spans coloridos."""
        parts = [p for p in path.split('/') if p]
        if not parts:
            return '<span class="seg">/</span>'
        out = '<span class="sep">/</span>'
        for i, seg in enumerate(parts):
            out += f'<span class="seg">{seg}</span>'
            if i < len(parts) - 1:
                out += '<span class="sep">/</span>'
        return out

    def _render_breadcrumb(self, path):
        """Gera links de navegação em breadcrumb."""
        parts = [p for p in path.split('/') if p]
        crumbs = ['<a href="/">raiz</a>']
        for i, seg in enumerate(parts):
            href = '/' + '/'.join(parts[:i+1]) + '/'
            if i == len(parts) - 1:
                crumbs.append(f'<span>{seg}</span>')
            else:
                crumbs.append(f'<a href="{href}">{seg}</a>')
        return ' <span class="bc-sep">›</span> '.join(crumbs)

    def translate_path(self, path):
        """Proteção contra Directory Traversal."""
        path = path.split('?', 1)[0].split('#', 1)[0]
        path = os.path.normpath(urllib.parse.unquote(path))
        words = path.split(os.sep)
        result_path = os.getcwd()
        for word in words:
            if word in (os.curdir, os.pardir, ''): continue
            result_path = os.path.join(result_path, word)
        # Garante que o caminho resultante está dentro do cwd
        base_dir = os.path.abspath(os.getcwd())
        requested = os.path.abspath(result_path)
        if os.path.commonpath([base_dir, requested]) != base_dir:
            raise PermissionError("Tentativa de acesso fora do diretório raiz.")
        return result_path

# --- Configuração e Inicialização ---
PORT = 8000
with socketserver.ThreadingTCPServer(("", PORT), MyAdvancedHttpRequestHandler) as httpd:
    print(f"╔══════════════════════════════════╗")
    print(f"║  Servidor rodando na porta {PORT}  ║")
    print(f"║  http://localhost:{PORT}            ║")
    print(f"╚══════════════════════════════════╝")
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print("\nDesligando...")
        httpd.shutdown()