#!/usr/bin/env python3
"""
Manifest Generator Script
Creates a manifest.json file listing folder contents down 2 levels, excluding LAZ/source folders and octree.bin/hierarchy.bin files
"""

import os
import json
import argparse
from pathlib import Path
from datetime import datetime


class ManifestGenerator:
    def __init__(self, target_directory):
        self.target_dir = Path(target_directory)
        
        if not self.target_dir.exists():
            raise ValueError(f"Target directory does not exist: {self.target_dir}")
        
        if not self.target_dir.is_dir():
            raise ValueError(f"Target path is not a directory: {self.target_dir}")
        
        self.excluded_folders = {'LAZ', 'source'}
        self.excluded_files = {'octree.bin', 'hierarchy.bin', 'log.txt'}
    
    def get_file_info(self, file_path):
        """Get detailed information about a file"""
        stats = file_path.stat()
        return {
            'name': file_path.name,
            'type': 'file',
            'size': stats.st_size,
            'size_readable': self.human_readable_size(stats.st_size),
            'modified': datetime.fromtimestamp(stats.st_mtime).isoformat(),
            'extension': file_path.suffix.lower() if file_path.suffix else None
        }
    
    def human_readable_size(self, size_bytes):
        """Convert bytes to human readable format"""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size_bytes < 1024.0:
                return f"{size_bytes:.2f} {unit}"
            size_bytes /= 1024.0
        return f"{size_bytes:.2f} PB"
    
    def scan_directory(self):
        """Scan directory structure down 2 levels"""
        manifest = {
            'directory': str(self.target_dir),
            'directory_name': self.target_dir.name,
            'generated': datetime.now().isoformat(),
            'contents': []
        }
        
        # Get all items in the target directory (Level 0)
        items = sorted(self.target_dir.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
        
        for item in items:
            # Skip excluded folders
            if item.is_dir() and item.name in self.excluded_folders:
                print(f"  Skipping excluded folder: {item.name}")
                continue
            
            if item.is_file():
                # Skip excluded files
                if item.name in self.excluded_files:
                    print(f"  Skipping excluded file: {item.name}")
                    continue
                # Add file info
                manifest['contents'].append(self.get_file_info(item))
                
            elif item.is_dir():
                # Add folder info with its contents (Level 1)
                folder_info = {
                    'name': item.name,
                    'type': 'directory',
                    'contents': []
                }
                
                # Scan contents of this subdirectory (Level 1)
                try:
                    sub_items = sorted(item.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
                    
                    for sub_item in sub_items:
                        if sub_item.is_file():
                            # Skip excluded files
                            if sub_item.name in self.excluded_files:
                                continue
                            folder_info['contents'].append(self.get_file_info(sub_item))
                            
                        elif sub_item.is_dir():
                            # For subdirectories at Level 1, scan their contents (Level 2)
                            sub_folder_info = {
                                'name': sub_item.name,
                                'type': 'directory',
                                'contents': []
                            }
                            
                            try:
                                # Scan Level 2 contents
                                sub_sub_items = sorted(sub_item.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
                                
                                for sub_sub_item in sub_sub_items:
                                    if sub_sub_item.is_file():
                                        # Skip excluded files
                                        if sub_sub_item.name in self.excluded_files:
                                            continue
                                        sub_folder_info['contents'].append(self.get_file_info(sub_sub_item))
                                    elif sub_sub_item.is_dir():
                                        # For Level 2 directories, just add basic info (no deeper scanning)
                                        sub_folder_info['contents'].append({
                                            'name': sub_sub_item.name,
                                            'type': 'directory'
                                        })
                                
                                sub_folder_info['item_count'] = len(sub_folder_info['contents'])
                                
                            except PermissionError:
                                sub_folder_info['error'] = 'Permission denied'
                                print(f"  Warning: Permission denied for {sub_item.name}")
                            
                            folder_info['contents'].append(sub_folder_info)
                    
                    folder_info['item_count'] = len(folder_info['contents'])
                    
                except PermissionError:
                    folder_info['error'] = 'Permission denied'
                    print(f"  Warning: Permission denied for {item.name}")
                
                manifest['contents'].append(folder_info)
        
        # Add summary statistics
        manifest['summary'] = {
            'total_items': len(manifest['contents']),
            'directories': sum(1 for item in manifest['contents'] if item['type'] == 'directory'),
            'files': sum(1 for item in manifest['contents'] if item['type'] == 'file')
        }
        
        return manifest
    
    def generate(self):
        """Generate and save the manifest.json file"""
        print("=" * 70)
        print("Manifest Generator")
        print("=" * 70)
        print(f"\nScanning directory: {self.target_dir}")
        print(f"Excluding folders: {', '.join(self.excluded_folders)}")
        print(f"Excluding files: {', '.join(self.excluded_files)}\n")
        
        try:
            # Scan directory structure
            manifest = self.scan_directory()
            
            # Write manifest.json
            manifest_path = self.target_dir / "manifest.json"
            with open(manifest_path, 'w', encoding='utf-8') as f:
                json.dump(manifest, f, indent=2, ensure_ascii=False)
            
            print(f"\n✓ Manifest generated successfully!")
            print(f"  Location: {manifest_path}")
            print(f"\nSummary:")
            print(f"  Total items: {manifest['summary']['total_items']}")
            print(f"  Directories: {manifest['summary']['directories']}")
            print(f"  Files: {manifest['summary']['files']}")
            
        except Exception as e:
            print(f"\n✗ Error generating manifest: {e}")
            raise


def main():
    parser = argparse.ArgumentParser(
        description='Generate a manifest.json file listing folder contents down 2 levels',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Example usage:
  python generate_manifest.py C:/data/scan_project_001
  python generate_manifest.py .
  
The manifest.json file will be created in the target directory.
Scanning depth: 2 levels
Excluded folders: LAZ, source
Excluded files: octree.bin, hierarchy.bin, log.txt
        """
    )
    
    parser.add_argument(
        'directory',
        nargs='?',
        default='.',
        help='Path to the directory to scan (default: current directory)'
    )
    
    args = parser.parse_args()
    
    generator = ManifestGenerator(args.directory)
    generator.generate()


if __name__ == "__main__":
    main()
