"""
File Listing Script - Comprehensive Directory Scanner with CSV and HTML Export
===============================================================================
File      : 01-List-Files-In-Folder-V03.py
Version   : 2.2.0
Author    : Yahya Nazer
Copyright : (c) 2025 Chatbizdb.com - Yahya Nazer
License   : Proprietary
Email     : contact@chatbizdb.com
Status    : Production
Date      : 2026-05-13

Objective
---------
Recursively scan a user-selected folder, collect file metadata (name, path,
size, dates, type), and export the results in two formats:
    1. CSV  -- Excel-compatible with =HYPERLINK() formulas and a terminal
               open-command column so files can be launched from any OS.
    2. HTML -- Browser-friendly report with clickable file links, styled
               table, and a summary header.

Both output files are written to a TIMESTAMPED subfolder created PARALLEL
to the scanned folder (not next to the script), so the report never
pollutes the folder being scanned.

Output Folder Layout (relative to the scanned folder's PARENT)
----------------------------------------------------------------
    <parent of selected folder>/
    |
    +-- C-Reports/
        +-- list-report-YYYY-MM-DD--HH-MM/
            +-- 01-listing-YYYY-MM-DD--HH-MM.csv
            +-- 01-listing-YYYY-MM-DD--HH-MM.html

Example:
    Script location:  /home/user/scripts/01-List-Files-In-Folder-V03.py
    Selected folder:  /home/user/Documents/MyProject/
    Report created:   /home/user/Documents/C-Reports/list-report-2026-05-13--14-30/

Features
---------
- Cross-platform: Windows, macOS, Linux.
- Recursive scan: finds files in all subfolders.
- Skips macOS resource-fork files (names starting with '._').
- OS-aware clickable URLs: file:///... (Windows) or file://... (Unix).
- CSV column H contains the correct terminal open command for the current OS:
      macOS:   open "path"
      Windows: start "" "path"
      Linux:   xdg-open "path"
- HTML auto-opens in the default browser after the report is created.

Dependencies
-------------
All standard library -- no pip install required:
    os, csv, tkinter, datetime, platform, subprocess, sys

Function Summary
-----------------
    Function                Purpose
    ----------------------  --------------------------------------------------
    print_script_info()     Print version banner at startup.
    select_folder()         GUI folder-picker dialog; returns selected path.
    make_report_dir()       Build the timestamped report folder path and create
                            it; returns (report_dir, timestamp).
    get_csv_file_path()     Derive and return the full CSV output path.
    get_html_file_path()    Derive and return the full HTML output path.
    create_file_url()       Convert an absolute path to an OS-correct file:// URL.
    get_file_type()         Extract the file extension as a lowercase string.
    get_file_info()         Read os.stat() metadata: size, modified, created.
    scan_folder()           Walk the folder tree; return a list of file dicts.
    save_to_csv()           Write the file list to CSV with hyperlinks.
    save_to_html()          Write the file list to a styled HTML report.
    open_file()             Open a file with the OS default application.
    main()                  Orchestrate all steps end-to-end.

Step-by-Step Flow (inside main())
------------------------------------
Step 1  : Print the startup version banner.
Step 2  : Open the GUI folder-picker; exit gracefully if cancelled.
Step 3  : Build and print the timestamped report folder path.
Step 4  : Derive CSV and HTML output file paths.
Step 5  : Recursively scan the selected folder; exit if empty.
Step 6  : Save results to CSV and HTML.
Step 7  : Auto-open the HTML file in the default browser.
Step 8  : Print a completion summary with file counts and paths.

CSV Columns
------------
    Index           Row number (1-based).
    File Name       =HYPERLINK() formula -- clickable in Excel/Numbers.
    File Path       Full absolute path to the file.
    File Size(bytes) Integer byte count from os.stat().
    Date Modified   Last-modified timestamp (YYYY-MM-DD HH:MM:SS).
    Date Created    Creation time on Windows; metadata-change time on Unix.
    File Type       Lowercase extension without the dot (e.g. 'pdf', 'py').
    Open Command    Terminal command to open the file on the current OS.

Notes
-----
- print_flag = True controls all console output.  Set to False for silent
  operation when the script is imported as a module.
- os.walk() yields (root, dirs, files) tuples for every directory in the
  tree.  Files in the root folder appear in the first iteration.
- st_ctime on Unix is the METADATA CHANGE time (permissions, owner), NOT
  the creation time.  True file creation time is not available on most Unix
  filesystems without extended attributes.
- The =HYPERLINK() formula in the CSV only works in Excel/Numbers when the
  file:// URL is valid for that OS.  Column H provides a fallback terminal
  command that works on any OS.
- HTML escaping is not applied to file names in this version.  File names
  containing < > & characters could break the HTML table visually.
"""

__version__   = "2.2.0"
__author__    = "Yahya Nazer"
__copyright__ = "Copyright (c) 2025 Chatbizdb.com - Yahya Nazer"
__license__   = "Proprietary"
__maintainer__= "Yahya Nazer"
__email__     = "contact@chatbizdb.com"
__status__    = "Production"

# ===========================================================================
# Step 1 - Imports
#          All modules are from the Python standard library -- no pip install.
# ===========================================================================
import os                           # File system operations and path handling
import csv                          # CSV writer for structured tabular output
import sys                          # stdout reconfiguration and exit
import platform                     # OS detection (Windows / Darwin / Linux)
import subprocess                   # Launch external processes (open / xdg-open)
from datetime import datetime       # Timestamp generation and formatting
import tkinter as tk                # GUI toolkit for the folder-picker dialog
from tkinter import filedialog      # File/folder dialog widgets

# Reconfigure stdout to UTF-8 so file names with special characters print
# correctly on Windows terminals.
if hasattr(sys.stdout, 'reconfigure'):
    sys.stdout.reconfigure(encoding='utf-8')

# ===========================================================================
# Script-level constants
#   Derived once at import time so all functions share the same values.
# ===========================================================================
SCRIPT_NAME    = "File Listing Script"
SCRIPT_VERSION = __version__
SCRIPT_FILE    = os.path.basename(__file__)     # Filename only (no directory)
SCRIPT_PATH    = os.path.abspath(__file__)      # Full absolute path of this script
SCRIPT_DIR     = os.path.dirname(SCRIPT_PATH)  # Folder containing this script

# Global print flag -- set False for silent / headless operation.
print_flag = True


# ===========================================================================
# Function: print_script_info()
# ===========================================================================
def print_script_info() -> None:
    """
    Print a version and environment banner to the console at startup.

    Displays: script name, version, copyright, filename, absolute path,
    Python version, OS platform, and current timestamp.  Controlled by
    the global print_flag.

    Steps:
        1. Check print_flag; return immediately if False.
        2. Print a 70-character separator line.
        3. Print name, version, copyright, file, path, Python, platform,
           and current date/time.
        4. Print a closing separator line.
    """
    if not print_flag:
        return

    print('=' * 70)
    print(f'{SCRIPT_NAME}')
    print('=' * 70)
    print(f'Version   : {SCRIPT_VERSION}')
    print(f'Copyright : {__copyright__}')
    print(f'File      : {SCRIPT_FILE}')
    print(f'Path      : {SCRIPT_PATH}')
    print(f'Python    : {sys.version.split()[0]}')
    print(f'Platform  : {platform.system()} {platform.release()}')
    print(f'Date      : {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    print('=' * 70)


# ===========================================================================
# Function: select_folder()
# ===========================================================================
def select_folder() -> str:
    """
    Open a GUI folder-picker dialog and return the selected folder path.

    Uses tkinter's askdirectory() dialog, which opens the OS-native folder
    browser.  Returns an empty string '' if the user cancels.

    Steps:
        1. Create an invisible tkinter root window (required before any dialog).
        2. Hide the root window immediately with withdraw().
        3. Open the askdirectory() dialog and wait for user selection.
        4. Return the selected path string (or '' if cancelled).

    Returns:
        str: Absolute path of the selected folder, or '' if cancelled.
    """
    if print_flag:
        print('[INFO]  Opening folder selection dialog...')

    root = tk.Tk()
    root.withdraw()             # Hide the blank root window immediately

    folder_path = filedialog.askdirectory(title='Select folder to scan')

    if print_flag:
        print(f'[INFO]  Selected folder: {folder_path}')

    return folder_path


# ===========================================================================
# Function: make_report_dir()
# ===========================================================================
def make_report_dir(folder_path: str) -> tuple[str, str]:
    """
    Build the timestamped report folder path, create it, and return it.

    The report folder is created PARALLEL to the selected folder (as a
    sibling, not inside it), so the scan never writes into the folder
    being examined.

    Folder structure produced:
        <parent of selected folder>/
        +-- C-Reports/
            +-- list-report-YYYY-MM-DD--HH-MM/    <- returned as report_dir

    Steps:
        1. Resolve the absolute parent directory of the selected folder.
        2. Build the C-Reports path and create it if missing.
        3. Generate a timestamp string (YYYY-MM-DD--HH-MM).
        4. Build and create the timestamped report subfolder.
        5. Return the report folder path and the timestamp string.

    Args:
        folder_path (str): Absolute path of the folder to be scanned.

    Returns:
        tuple[str, str]: (report_dir, timestamp)
            report_dir  -- full path of the timestamped output folder.
            timestamp   -- 'YYYY-MM-DD--HH-MM' string used in filenames.
    """
    # Step 1 - Parent of the SELECTED folder (not the script folder)
    parent_dir = os.path.dirname(os.path.abspath(folder_path))

    # Step 2 - C-Reports folder sits beside the selected folder
    main_report_dir = os.path.join(parent_dir, 'C-Reports')
    if not os.path.exists(main_report_dir):
        os.makedirs(main_report_dir)
        if print_flag:
            print(f'[INFO]  Created C-Reports directory: {main_report_dir}')

    # Step 3 - Generate the timestamp (used in both folder and filenames)
    timestamp = datetime.now().strftime('%Y-%m-%d--%H-%M')

    # Step 4 - Timestamped subfolder ensures each run has its own folder
    report_dir = os.path.join(main_report_dir, f'list-report-{timestamp}')
    if not os.path.exists(report_dir):
        os.makedirs(report_dir)
        if print_flag:
            print(f'[INFO]  Created report directory: {report_dir}')
    else:
        if print_flag:
            print(f'[INFO]  Report directory already exists: {report_dir}')

    return report_dir, timestamp


# ===========================================================================
# Function: get_csv_file_path()
# ===========================================================================
def get_csv_file_path(folder_path: str) -> str:
    """
    Derive the full output path for the CSV report file.

    Calls make_report_dir() to build/obtain the timestamped report folder,
    then appends the CSV filename using the same timestamp.

    Filename pattern:  01-listing-YYYY-MM-DD--HH-MM.csv

    Args:
        folder_path (str): Absolute path of the folder being scanned.

    Returns:
        str: Full absolute path where the CSV file will be written.
    """
    if print_flag:
        print('[INFO]  Deriving CSV output path...')

    report_dir, timestamp = make_report_dir(folder_path)
    csv_filename  = f'01-listing-{timestamp}.csv'
    csv_file_path = os.path.join(report_dir, csv_filename)

    if print_flag:
        print(f'[INFO]  CSV will be saved as: {csv_file_path}')

    return csv_file_path


# ===========================================================================
# Function: get_html_file_path()
# ===========================================================================
def get_html_file_path(folder_path: str) -> str:
    """
    Derive the full output path for the HTML report file.

    Calls make_report_dir() to build/obtain the timestamped report folder,
    then appends the HTML filename using the same timestamp.

    Because make_report_dir() uses exist_ok logic, calling it twice
    (once for CSV, once for HTML) produces the same folder path when
    both calls happen within the same minute.

    Filename pattern:  01-listing-YYYY-MM-DD--HH-MM.html

    Args:
        folder_path (str): Absolute path of the folder being scanned.

    Returns:
        str: Full absolute path where the HTML file will be written.
    """
    if print_flag:
        print('[INFO]  Deriving HTML output path...')

    report_dir, timestamp = make_report_dir(folder_path)
    html_filename  = f'01-listing-{timestamp}.html'
    html_file_path = os.path.join(report_dir, html_filename)

    if print_flag:
        print(f'[INFO]  HTML will be saved as: {html_file_path}')

    return html_file_path


# ===========================================================================
# Function: create_file_url()
# ===========================================================================
def create_file_url(file_path: str) -> str:
    """
    Convert an absolute file path to an OS-correct file:// URL.

    File URL format differences by OS:
        Windows :  file:///C:/Users/...   (three slashes; backslashes -> forward)
        macOS   :  file:///Users/...      (three slashes)
        Linux   :  file:///home/...       (three slashes)

    The Windows case uses chr(92) (backslash) in the replace() call instead
    of a literal backslash to avoid any escape-sequence ambiguity.

    Steps:
        1. Resolve the input to an absolute path with os.path.abspath().
        2. Detect the OS via platform.system().
        3. Build and return the appropriate file:// URL string.

    Args:
        file_path (str): Path to the file (relative or absolute).

    Returns:
        str: file:// URL that can be used as an HTML href or CSV hyperlink.
    """
    abs_path = os.path.abspath(file_path)

    if platform.system() == 'Windows':
        # Replace backslashes with forward slashes for URL compatibility.
        # chr(92) is the backslash character; using it avoids a raw-string
        # or double-escape that would make the code harder to read.
        file_url = f'file:///{abs_path.replace(chr(92), "/")}'
    else:
        # macOS and Linux both use forward slashes natively.
        # Two slashes after 'file:' plus the leading slash of abs_path
        # produces the correct three-slash form: file:///path/to/file.
        file_url = f'file://{abs_path}'

    return file_url


# ===========================================================================
# Function: get_file_type()
# ===========================================================================
def get_file_type(filename: str) -> str:
    """
    Extract the file extension and return it as a lowercase string.

    Steps:
        1. os.path.splitext() splits 'report.PDF' into ('report', '.PDF').
        2. .lower() normalises the extension to lowercase: '.pdf'.
        3. .lstrip('.') removes the leading dot: 'pdf'.
        4. If the result is empty (no extension), return 'No Extension'.

    Args:
        filename (str): File name (with or without directory prefix).

    Returns:
        str: Lowercase extension without the dot (e.g. 'py', 'csv', 'pdf'),
             or 'No Extension' if the file has no extension.
    """
    _, ext      = os.path.splitext(filename)    # Split at the last dot
    file_type   = ext.lower().lstrip('.')       # Lowercase, remove leading dot

    return file_type if file_type else 'No Extension'


# ===========================================================================
# Function: get_file_info()
# ===========================================================================
def get_file_info(file_path: str) -> dict:
    """
    Read file system metadata for a single file using os.stat().

    os.stat() returns a stat_result object with these attributes used here:
        st_size   : File size in bytes.
        st_mtime  : Last modification time as a Unix timestamp (float).
        st_ctime  : On Windows -- creation time.
                    On Unix    -- most recent METADATA CHANGE time
                                  (not creation time; Unix filesystems
                                  do not reliably store file creation time).

    Timestamps are converted from Unix float (seconds since 1970-01-01) to
    human-readable strings using datetime.fromtimestamp().

    On OSError (permission denied, broken symlink, etc.) the function
    returns safe fallback values instead of crashing the scan.

    Args:
        file_path (str): Full absolute path to the file.

    Returns:
        dict with keys:
            'size'     (int): File size in bytes (0 on error).
            'modified' (str): 'YYYY-MM-DD HH:MM:SS' or 'Unknown'.
            'created'  (str): 'YYYY-MM-DD HH:MM:SS' or 'Unknown'.
    """
    try:
        stat_info = os.stat(file_path)

        # File size in bytes
        file_size = stat_info.st_size

        # Last modification time
        mod_time = datetime.fromtimestamp(stat_info.st_mtime)

        # Creation / metadata-change time
        # Windows: st_ctime = true file creation time.
        # Unix:    st_ctime = last metadata change (chmod, chown, rename).
        create_time = datetime.fromtimestamp(stat_info.st_ctime)

        return {
            'size'    : file_size,
            'modified': mod_time.strftime('%Y-%m-%d %H:%M:%S'),
            'created' : create_time.strftime('%Y-%m-%d %H:%M:%S'),
        }

    except OSError as e:
        if print_flag:
            print(f'[WARN]  Could not read metadata for {file_path}: {e}')
        return {
            'size'    : 0,
            'modified': 'Unknown',
            'created' : 'Unknown',
        }


# ===========================================================================
# Function: scan_folder()
# ===========================================================================
def scan_folder(folder_path: str) -> list[dict]:
    """
    Recursively walk the folder tree and collect metadata for every file.

    Uses os.walk() which yields (root, dirs, files) for every directory in
    the tree, starting from folder_path and descending into all subfolders.

        root  : Current directory being visited (absolute path string).
        dirs  : List of subdirectory names in root (not used here).
        files : List of filenames (not paths) in root.

    Skipped files:
        Files whose names start with '._' are macOS resource-fork files
        (hidden metadata artifacts created when files are copied to non-HFS+
        volumes).  They are not meaningful to the user and are skipped.

    Each file is represented as a dict with keys:
        filename, url, path, size, modified, created, type

    Steps:
        1. Initialise an empty file_list.
        2. Walk the folder tree with os.walk().
        3. For each file: skip '._' files, build the full path, collect
           metadata (get_file_info), URL (create_file_url), and type
           (get_file_type), then append a dict to file_list.
        4. Print the total count and return file_list.

    Args:
        folder_path (str): Absolute path of the folder to scan.

    Returns:
        list[dict]: One dict per file found (empty list if folder is empty).
    """
    if print_flag:
        print(f'[INFO]  Scanning folder: {folder_path}')

    file_list = []

    for root, dirs, files in os.walk(folder_path):
        if print_flag:
            print(f'[DEBUG] Scanning directory: {root}  ({len(files)} files)')

        for filename in files:
            # Skip macOS resource-fork hidden files
            if filename.startswith('._'):
                if print_flag:
                    print(f'[DEBUG] Skipping resource-fork file: {filename}')
                continue

            file_path = os.path.join(root, filename)   # Full absolute path

            # Collect metadata, URL, and file type for this file
            file_info = get_file_info(file_path)
            file_url  = create_file_url(file_path)
            file_type = get_file_type(filename)

            file_data = {
                'filename': filename,
                'url'     : file_url,
                'path'    : file_path,
                'size'    : file_info['size'],
                'modified': file_info['modified'],
                'created' : file_info['created'],
                'type'    : file_type,
            }

            file_list.append(file_data)

    if print_flag:
        print(f'[INFO]  Total files found: {len(file_list)}')

    return file_list


# ===========================================================================
# Function: save_to_csv()
# ===========================================================================
def save_to_csv(file_list: list[dict], csv_file_path: str) -> None:
    """
    Write the file list to a CSV file with Excel hyperlinks and open commands.

    CSV Columns:
        Index           : 1-based row number.
        File Name       : =HYPERLINK("file://...", "filename") formula.
                          When the CSV is opened in Excel or Numbers, this
                          renders as a clickable cell that opens the file.
        File Path       : Plain absolute path string (always readable).
        File Size(bytes): Integer size from os.stat().
        Date Modified   : Last-modified timestamp string.
        Date Created    : Creation / metadata-change timestamp string.
        File Type       : Lowercase extension without the dot.
        Open Command    : Terminal command to open the file on the current OS.
                          macOS:   open "/path/to/file"
                          Windows: start "" "/path/to/file"
                          Linux:   xdg-open "/path/to/file"
                          This column is the fallback when Excel hyperlinks
                          do not work (common on network drives or cross-OS).

    Steps:
        1. Define the headers list.
        2. Open the CSV file for writing with newline='' (required by the
           csv module to prevent blank rows on Windows).
        3. Write the header row.
        4. For each file dict: build the OS-correct open command, assemble
           the row list, and write it.
        5. Print a success count.

    Args:
        file_list     (list[dict]): Output of scan_folder().
        csv_file_path (str):        Full path where the CSV will be written.

    Raises:
        Exception: Re-raised after logging if the file cannot be written.
    """
    if print_flag:
        print(f'[INFO]  Saving CSV: {csv_file_path}')

    headers = [
        'Index',
        'File Name',
        'File Path',
        'File Size (bytes)',
        'Date Modified',
        'Date Created',
        'File Type',
        'Open Command',     # Terminal command to open file (cross-OS fallback)
    ]

    try:
        # newline='' is required by the csv module on Windows to prevent
        # it inserting an extra blank row after each data row.
        with open(csv_file_path, 'w', newline='', encoding='utf-8') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(headers)

            for i, file_data in enumerate(file_list, 1):

                # Build the OS-appropriate terminal command for Column H
                if platform.system() == 'Darwin':           # macOS
                    open_command = f'open "{file_data["path"]}"'
                elif platform.system() == 'Windows':        # Windows
                    open_command = f'start "" "{file_data["path"]}"'
                else:                                       # Linux / other Unix
                    open_command = f'xdg-open "{file_data["path"]}"'

                row = [
                    i,
                    # =HYPERLINK() is an Excel/Numbers formula.
                    # First arg: the URL to open.  Second arg: display text.
                    f'=HYPERLINK("{file_data["url"]}", "{file_data["filename"]}")',
                    file_data['path'],
                    file_data['size'],
                    file_data['modified'],
                    file_data['created'],
                    file_data['type'],
                    open_command,
                ]
                writer.writerow(row)

        if print_flag:
            print(f'[INFO]  CSV saved: {len(file_list)} rows written.')
            print('[INFO]  Tip: if Excel hyperlinks do not work, '
                  'use the Open Command in column H.')

    except Exception as e:
        if print_flag:
            print(f'[ERROR] Could not save CSV: {e}')
        raise


# ===========================================================================
# Function: save_to_html()
# ===========================================================================
def save_to_html(file_list: list[dict], html_file_path: str) -> None:
    """
    Write the file list to a styled HTML report with clickable file links.

    The HTML file contains:
        - A summary box: total file count, generation timestamp, script name.
        - A styled table: Index, File Name (link), File Path, Size, Modified,
          Created, File Type.
        - Alternating row colours (tr:nth-child(even)) for readability.
        - A CSS class 'file-link' that styles the anchor tags blue.

    The =HYPERLINK() Excel formula used in the CSV does not apply here.
    Instead, each file name cell contains an <a href="file://..."> anchor
    tag using the pre-built file URL from file_data['url'].

    Steps:
        1. Build the full HTML string using an f-string template for the
           header/style/summary section.
        2. Loop over file_list and append one <tr>...</tr> block per file.
        3. Append the closing HTML tags.
        4. Write the complete string to the file with UTF-8 encoding.

    Args:
        file_list      (list[dict]): Output of scan_folder().
        html_file_path (str):        Full path where the HTML will be written.

    Raises:
        Exception: Re-raised after logging if the file cannot be written.
    """
    if print_flag:
        print(f'[INFO]  Saving HTML: {html_file_path}')

    # -----------------------------------------------------------------------
    # HTML header, CSS styles, and summary box.
    # Double braces {{ }} are used inside the f-string to produce literal
    # { } characters in the output (required for CSS rules).
    # -----------------------------------------------------------------------
    html_content = f"""<!DOCTYPE html>
<html>
<head>
    <title>File Listing Report</title>
    <style>
        body {{
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }}
        .container {{
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }}
        h1 {{
            color: #333;
            border-bottom: 2px solid #4CAF50;
            padding-bottom: 10px;
        }}
        table {{
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
        }}
        th, td {{
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }}
        th {{
            background-color: #4CAF50;
            color: white;
        }}
        tr:nth-child(even) {{
            background-color: #f2f2f2;
        }}
        .file-link {{
            color: #2196F3;
            text-decoration: none;
        }}
        .file-link:hover {{
            text-decoration: underline;
        }}
        .file-path {{
            font-size: 0.9em;
            color: #666;
            word-break: break-all;
        }}
        .file-size {{
            text-align: right;
        }}
        .summary {{
            background-color: #e8f5e8;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }}
        .index {{
            text-align: center;
            font-weight: bold;
            color: #666;
        }}
    </style>
</head>
<body>
    <div class="container">
        <h1>File Listing Report</h1>
        <div class="summary">
            <strong>Total Files:</strong> {len(file_list)}<br>
            <strong>Generated:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}<br>
            <strong>Script:</strong> {SCRIPT_NAME} v{SCRIPT_VERSION}
        </div>
        <table>
            <thead>
                <tr>
                    <th>Index</th>
                    <th>File Name</th>
                    <th>File Path</th>
                    <th>Size (bytes)</th>
                    <th>Date Modified</th>
                    <th>Date Created</th>
                    <th>File Type</th>
                </tr>
            </thead>
            <tbody>
"""

    # -----------------------------------------------------------------------
    # One table row per file.
    # file_data['url'] is a file:// URL; href= opens the file in the browser
    # or passes it to the OS default application via the browser.
    # {file_data['size']:,} formats the integer with thousands separators.
    # -----------------------------------------------------------------------
    for i, file_data in enumerate(file_list, 1):
        html_content += f"""
                <tr>
                    <td class="index">{i}</td>
                    <td><a href="{file_data['url']}" class="file-link">{file_data['filename']}</a></td>
                    <td class="file-path">{file_data['path']}</td>
                    <td class="file-size">{file_data['size']:,}</td>
                    <td>{file_data['modified']}</td>
                    <td>{file_data['created']}</td>
                    <td>{file_data['type']}</td>
                </tr>"""

    html_content += """
            </tbody>
        </table>
    </div>
</body>
</html>
"""

    try:
        with open(html_file_path, 'w', encoding='utf-8') as htmlfile:
            htmlfile.write(html_content)

        if print_flag:
            print(f'[INFO]  HTML saved: {len(file_list)} rows written.')

    except Exception as e:
        if print_flag:
            print(f'[ERROR] Could not save HTML: {e}')
        raise


# ===========================================================================
# Function: open_file()
# ===========================================================================
def open_file(file_path: str) -> None:
    """
    Open a file using the OS default application.

    OS-specific methods:
        Windows :  os.startfile(path)
                   Uses the Windows ShellExecute API to open the file with
                   whatever application is associated with its extension.
        macOS   :  subprocess.run(["open", path])
                   The 'open' command is a macOS built-in that launches the
                   default application for the file type.
        Linux   :  subprocess.run(["xdg-open", path])
                   xdg-open is the freedesktop.org standard launcher that
                   delegates to the desktop environment's default handler.

    On failure (e.g. the file does not exist yet, or the OS launcher is not
    available) the error is caught and a manual navigation hint is printed.

    Args:
        file_path (str): Full absolute path of the file to open.
    """
    if print_flag:
        print(f'[INFO]  Opening file: {file_path}')

    try:
        if platform.system() == 'Windows':
            os.startfile(file_path)
        elif platform.system() == 'Darwin':             # macOS
            subprocess.run(['open', file_path])
        else:                                           # Linux / other Unix
            subprocess.run(['xdg-open', file_path])

        if print_flag:
            print('[INFO]  File opened successfully.')

    except Exception as e:
        if print_flag:
            print(f'[WARN]  Could not open file automatically: {e}')
            print('[INFO]  Please navigate to the report folder and open the file manually.')


# ===========================================================================
# Function: main()
# ===========================================================================
def main() -> None:
    """
    Orchestrate the full file-listing workflow end-to-end.

    Steps:
        1. Print the startup version banner.
        2. Open the GUI folder-picker; exit gracefully if cancelled.
        3. Build and print the anticipated report folder location.
        4. Derive the CSV and HTML output file paths.
        5. Recursively scan the selected folder for files.
        6. Exit gracefully if the folder contains no files.
        7. Save the file list to both CSV and HTML.
        8. Auto-open the HTML file in the default browser.
        9. Print a completion summary.
    """
    # Step 1 - Startup banner
    print_script_info()

    if print_flag:
        print('\n[INFO]  File Listing Script started.')
        print('=' * 50)

    try:
        # ------------------------------------------------------------------
        # Step 2 - Folder selection
        # ------------------------------------------------------------------
        if print_flag:
            print('\n[STEP 2] Select the folder to scan...')

        folder_path = select_folder()

        if not folder_path:
            print('[INFO]  No folder selected. Exiting.')
            return

        # ------------------------------------------------------------------
        # Step 3 - Show where the report will be created
        # ------------------------------------------------------------------
        if print_flag:
            parent_dir       = os.path.dirname(os.path.abspath(folder_path))
            timestamp_preview= datetime.now().strftime('%Y-%m-%d--%H-%M')
            main_report_dir  = os.path.join(parent_dir, 'C-Reports')
            report_dir_preview = os.path.join(main_report_dir,
                                               f'list-report-{timestamp_preview}')
            print(f'\n[INFO]  Scanned folder   : {folder_path}')
            print(f'[INFO]  Report will be at: {report_dir_preview}')
            print('[INFO]  (Report is parallel to the selected folder, '
                  'NOT in the script location)')

        # ------------------------------------------------------------------
        # Step 4 - Derive output file paths
        # ------------------------------------------------------------------
        if print_flag:
            print('\n[STEP 4] Setting up output file paths...')

        csv_file_path  = get_csv_file_path(folder_path)
        html_file_path = get_html_file_path(folder_path)

        # ------------------------------------------------------------------
        # Step 5 - Scan the folder recursively
        # ------------------------------------------------------------------
        if print_flag:
            print('\n[STEP 5] Scanning files...')

        file_list = scan_folder(folder_path)

        # ------------------------------------------------------------------
        # Step 6 - Exit gracefully if no files were found
        # ------------------------------------------------------------------
        if not file_list:
            print('[INFO]  No files found in the selected folder. Exiting.')
            return

        # ------------------------------------------------------------------
        # Step 7 - Save to CSV and HTML
        # ------------------------------------------------------------------
        if print_flag:
            print('\n[STEP 7] Saving reports...')

        save_to_csv(file_list, csv_file_path)
        save_to_html(file_list, html_file_path)

        # ------------------------------------------------------------------
        # Step 8 - Auto-open the HTML report
        # ------------------------------------------------------------------
        if print_flag:
            print('\n[STEP 8] Opening HTML report...')

        open_file(html_file_path)

        # ------------------------------------------------------------------
        # Step 9 - Completion summary
        # ------------------------------------------------------------------
        if print_flag:
            print('\n' + '=' * 50)
            print('[DONE]  Script completed successfully.')
            print(f'[INFO]  CSV  : {csv_file_path}')
            print(f'[INFO]  HTML : {html_file_path}')
            print(f'[INFO]  Files processed: {len(file_list)}')
            print('=' * 50)
            print('\nTips:')
            print('  - HTML file: open in any browser for clickable links.')
            print('  - CSV file : open in Excel / Numbers.')
            print('  - Column H in the CSV contains a terminal open command')
            print('    as a fallback if Excel hyperlinks do not work.')

    except Exception as e:
        if print_flag:
            print(f'[ERROR] Unexpected error: {e}')
        raise


# ===========================================================================
# Entry-point guard
#   main() runs ONLY when this file is executed directly.
#   It is NOT called when the file is imported as a module.
# ===========================================================================
if __name__ == '__main__':
    main()
