<?php

declare(strict_types=1);

namespace Butschster\ContextGenerator\Lib\TreeBuilder\TreeRenderer;

use Butschster\ContextGenerator\Lib\TokenCounter\CharTokenCounter;
use Butschster\ContextGenerator\Lib\TokenCounter\TokenCounterInterface;
use Butschster\ContextGenerator\Lib\TreeBuilder\TreeRendererInterface;

/**
 * Renderer for ASCII tree format
 */
final readonly class AsciiTreeRenderer implements TreeRendererInterface
{
    public function __construct(
        private TokenCounterInterface $tokenCounter = new CharTokenCounter(),
    ) {}

    public function render(array $tree, array $options = []): string
    {
        $showSize = $options['showSize'] ?? false;
        $showLastModified = $options['showLastModified'] ?? false;
        $showCharCount = $options['showCharCount'] ?? false;
        $includeFiles = $options['includeFiles'] ?? true;
        $dirContext = $options['dirContext'] ?? [];

        return $this->renderNode(
            node: $tree,
            prefix: '',
            isLast: true,
            showSize: $showSize,
            showLastModified: $showLastModified,
            showCharCount: $showCharCount,
            includeFiles: $includeFiles,
            dirContext: $dirContext,
        );
    }

    private function renderNode(
        array $node,
        string $prefix = '',
        bool $isLast = true,
        bool $showSize = false,
        bool $showLastModified = false,
        bool $showCharCount = false,
        bool $includeFiles = true,
        array $dirContext = [],
        string $currentPath = '',
    ): string {
        if (empty($node)) {
            return '';
        }

        $result = '';
        $count = \count($node);
        $i = 0;

        foreach ($node as $name => $children) {
            $i++;
            $isLastItem = ($i === $count);
            $isDirectory = \is_array($children);

            // Skip files if includeFiles is false
            if (!$isDirectory && !$includeFiles) {
                continue;
            }

            $fullPath = $isDirectory ? $name : $children;

            // Build the relative path for directory context lookups
            $relativePath = $currentPath === '' ? $name : "$currentPath/$name";

            // Add name with directory indicator if applicable
            $displayName = $name . ($isDirectory ? '/' : '');

            // Add metadata if requested
            $metadata = '';
            if ($showSize || $showLastModified || $showCharCount) {
                if ($showSize) {
                    $size = $isDirectory
                        ? $this->calculateDirectorySize($children)
                        : (\file_exists($fullPath) ? \filesize($fullPath) : 0);
                    $metadata .= $this->formatSize($size);
                }

                if ($showLastModified) {
                    $mtime = $isDirectory
                        ? $this->getLatestModificationTime($children)
                        : (\file_exists($fullPath) ? \filemtime($fullPath) : 0);
                    if ($showSize) {
                        $metadata .= ', ';
                    }
                    $metadata .= \date('Y-m-d', $mtime ?: \time());
                }

                // Add character count if requested
                if ($showCharCount) {
                    $charCount = $isDirectory
                        ? $this->tokenCounter->calculateDirectoryCount($children)
                        : (\file_exists($fullPath) ? $this->tokenCounter->countFile($fullPath) : 0);
                    if ($showSize || $showLastModified) {
                        $metadata .= ', ';
                    }
                    if ($charCount > 0) {
                        $metadata .= \number_format($charCount) . ' chars';
                    }
                }
            }

            if ($metadata !== '') {
                $metadata = ' [' . $metadata . ']';
            }

            // Add current node
            $result .= $prefix . ($isLast ? '└── ' : '├── ') . $displayName . $metadata;

            // Add context if available for directories using the full relative path
            if ($isDirectory && isset($dirContext[$relativePath])) {
                $result .= ' # ' . $dirContext[$relativePath];
            }

            // Add new line for the current node
            $result .= PHP_EOL;

            // Add children for directories
            if ($isDirectory) {
                $newPrefix = $prefix . ($isLast ? '    ' : '│   ');
                $result .= $this->renderNode(
                    $children,
                    $newPrefix,
                    $isLastItem,
                    $showSize,
                    $showLastModified,
                    $showCharCount,
                    $includeFiles,
                    $dirContext,
                    $relativePath,  // Pass the updated relative path
                );
            }
        }

        return $result;
    }

    /**
     * Calculate the total size of a directory
     *
     * @param array<string, mixed> $directory Directory structure
     * @return int Total size in bytes
     */
    private function calculateDirectorySize(array $directory): int
    {
        $totalSize = 0;

        foreach ($directory as $children) {
            if (\is_array($children)) {
                $totalSize += $this->calculateDirectorySize($children);
            } else {
                $totalSize += \file_exists($children) ? \filesize($children) : 0;
            }
        }

        return $totalSize;
    }

    /**
     * Find the latest modification time in a directory
     *
     * @param array<string, mixed> $directory Directory structure
     * @return int Latest modification timestamp
     */
    private function getLatestModificationTime(array $directory): int
    {
        $latestTime = 0;

        foreach ($directory as $children) {
            $time = \is_array($children)
                ? $this->getLatestModificationTime($children)
                : (\file_exists($children) ? (int) \filemtime($children) : 0);

            if ($time > $latestTime) {
                $latestTime = $time;
            }
        }

        return $latestTime;
    }

    /**
     * Format file size in human-readable format
     *
     * @param int $bytes Size in bytes
     * @return string Formatted size (e.g., "4.2 MB")
     */
    private function formatSize(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];

        $bytes = \max($bytes, 0);
        /**
         * @psalm-suppress InvalidOperand
         */
        $pow = \floor(($bytes ? \log($bytes) : 0) / \log(1024));
        $pow = \min($pow, \count($units) - 1);

        /**
         * @psalm-suppress InvalidOperand
         */
        $bytes /= (1 << (10 * $pow));

        return \sprintf('%.1f %s', $bytes, $units[$pow]);
    }
}
