"""python3 -m http.server, with each file's size and modification time in directory listings.

    python3 serve.py [port] [--bind ADDRESS] [--directory DIR]

Click a column heading to sort by it.
"""

import argparse
import datetime
import html
import http.server
import io
import os
import urllib.parse


def human(size: int) -> str:
    for unit in ("B", "KB", "MB", "GB"):
        if size < 1024 or unit == "GB":
            return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
        size /= 1024


class Handler(http.server.SimpleHTTPRequestHandler):
    def list_directory(self, path):
        try:
            names = os.listdir(path)
        except OSError:
            self.send_error(404, "No permission to list directory")
            return None
        names.sort(key=str.lower)
        shown = html.escape(urllib.parse.unquote(self.path.split("?", 1)[0]))
        rows = []
        if self.path.rstrip("/") not in ("", "/"):
            rows.append('<tr><td><a href="../">../</a></td><td></td><td></td></tr>')
        for name in names:
            full = os.path.join(path, name)
            try:
                st = os.stat(full)
            except OSError:
                continue
            is_dir = os.path.isdir(full)
            label = name + ("/" if is_dir else "")
            when = datetime.datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M")
            size = "" if is_dir else human(st.st_size)
            rows.append(
                f'<tr><td data-v="{html.escape(label.lower())}"><a href="{urllib.parse.quote(label)}">{html.escape(label)}</a></td>'
                f'<td data-v="{-1 if is_dir else st.st_size}" class="n">{size}</td>'
                f'<td data-v="{st.st_mtime}">{when}</td></tr>'
            )
        page = f"""<!doctype html><html><head><meta charset="utf-8"><title>Index of {shown}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>body{{font:14px system-ui,sans-serif;margin:16px}}table{{border-collapse:collapse}}
td,th{{padding:3px 14px 3px 0;text-align:left}}th{{cursor:pointer;border-bottom:1px solid #ccc}}.n{{text-align:right}}
tr:hover td{{background:#f2f2f2}}</style></head><body><h1>Index of {shown}</h1>
<table><thead><tr><th>Name</th><th class="n">Size</th><th>Modified</th></tr></thead><tbody>
{''.join(rows)}</tbody></table>
<script>document.querySelectorAll("th").forEach((th,i)=>th.onclick=()=>{{const tb=th.closest("table").tBodies[0];
const up=th.dataset.dir!=="asc";th.dataset.dir=up?"asc":"desc";const rows=[...tb.rows].filter(r=>r.cells[0].textContent!=="../");
const v=r=>{{const d=r.cells[i].dataset.v;return isNaN(d)?d:+d}};rows.sort((a,b)=>(v(a)>v(b)?1:v(a)<v(b)?-1:0)*(up?1:-1));
rows.forEach(r=>tb.appendChild(r))}});</script></body></html>"""
        data = page.encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        return io.BytesIO(data)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("port", nargs="?", type=int, default=8000)
    parser.add_argument("--bind", default="")
    parser.add_argument("--directory", default=os.getcwd())
    args = parser.parse_args()
    handler = lambda *a, **kw: Handler(*a, directory=args.directory, **kw)  # noqa: E731
    with http.server.ThreadingHTTPServer((args.bind, args.port), handler) as httpd:
        print(f"Serving {args.directory} on port {args.port} (Ctrl+C to stop)")
        httpd.serve_forever()
