<?php

namespace Drupal\dfg_3dviewer\Service;

use Symfony\Component\Process\Process;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;

class ConvertProcessService {

    protected $logger;

    public function __construct(LoggerChannelFactoryInterface $logger_factory) {
        $this->logger = $logger_factory->get('dfg_3dviewer');
    }

    private function boolToString($value): string {
        return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false';
    }

    private function normalizePath(string $path): string {
        return rtrim(str_replace('\\', '/', $path), '/');
    }

    private function resolveConvertedOutputPath(string $inputPath, array $options): string {
        $isBinary = filter_var($options['b'] ?? true, FILTER_VALIDATE_BOOLEAN);
        $outputExt = $isBinary ? 'glb' : 'gltf';
        $inputExt = strtolower((string) pathinfo($inputPath, PATHINFO_EXTENSION));

        if (!empty($options['o'])) {
            $outputBase = $this->normalizePath((string) $options['o']);
            $inputBase = (string) pathinfo($inputPath, PATHINFO_FILENAME);
            return $outputBase . '/gltf/' . $inputBase . '.' . $outputExt;
        }

        if ($inputExt === 'glb' && $outputExt === 'glb') {
            return $inputPath;
        }

        $dirname = $this->normalizePath((string) pathinfo($inputPath, PATHINFO_DIRNAME));
        $filename = (string) pathinfo($inputPath, PATHINFO_FILENAME);
        return $dirname . '/gltf/' . $filename . '.' . $outputExt;
    }

    private function resolveThumbnailBasePath(string $inputPath): string {
        $dirname = $this->normalizePath((string) pathinfo($inputPath, PATHINFO_DIRNAME));
        $filename = (string) pathinfo($inputPath, PATHINFO_FILENAME);
        $extension = strtolower((string) pathinfo($inputPath, PATHINFO_EXTENSION));

        return $dirname . '/views/' . $filename . '.' . $extension;
    }

    private function thumbnailsAlreadyExist(string $inputPath): bool {
        $basePath = $this->resolveThumbnailBasePath($inputPath);
        $requiredFiles = [
            $basePath . '_side45.png',
            $basePath . '_side90.png',
            $basePath . '_side135.png',
            $basePath . '_side180.png',
            $basePath . '_side225.png',
            $basePath . '_side270.png',
            $basePath . '_side315.png',
            $basePath . '_top.png',
        ];

        foreach ($requiredFiles as $file) {
            if (!file_exists($file)) {
                return FALSE;
            }
        }

        if (file_exists($basePath . '_side0.png') || file_exists($basePath . '_RENDER.png')) {
            return TRUE;
        }

        return FALSE;
    }

    private function emitProgress(?callable $onProgress, int $percent, string $state, string $message): void {
        if ($onProgress === NULL) {
            return;
        }

        try {
            $onProgress($percent, $state, $message);
        }
        catch (\Throwable $e) {
            $this->logger->warning(
                'Progress callback failed at @percent% (@state): @message',
                [
                    '@percent' => $percent,
                    '@state' => $state,
                    '@message' => $e->getMessage(),
                ]
            );
        }
    }

    /**
     * Run convert.sh process.
     *
     * @param string $spath
     * @param string $inputPath
     * @param int $lightweight
     * @param array $options
     *
     * @return array
     */
    public function run(
        string $spath,
        string $inputPath,
        int $lightweight = 0,
        array $options = [],
        ?callable $onProgress = NULL
        ) : array {

        $script = $spath . '/scripts/convert.sh';

        if (!file_exists($script)) {
            return [
            'success' => FALSE,
            'exit_code' => NULL,
            'output' => '',
            'error' => 'Script not found',
            ];
        }

        $args = [
            $script,
            '-t', $this->boolToString($lightweight),
            '-c', $this->boolToString($options['c'] ?? true),
            '-l', $options['l'] ?? '3',
            '-b', $this->boolToString($options['b'] ?? true),
            '-i', $inputPath,
        ];

        // optional
        if (!empty($options['o'])) {
            $args[] = '-o';
            $args[] = $options['o'];
        }

        $args[] = '-f';
        $args[] = $this->boolToString($options['f'] ?? true);

        if (isset($options['a'])) {
            $args[] = '-a';
            $args[] = $options['a'];
        }

        $process = new \Symfony\Component\Process\Process($args);
        $process->setTimeout($options['timeout'] ?? 600);
        $process->setWorkingDirectory($spath);

        $this->emitProgress($onProgress, 35, 'processing', 'Converting to GLTF...');
        $process->run();

        $success = $process->isSuccessful();
        $exitCode = $process->getExitCode();
        $output = $process->getOutput();
        $error = $process->getErrorOutput();
        $renderResult = NULL;

        if ($success) {
            $this->emitProgress($onProgress, 55, 'converted', 'GLTF conversion finished.');
        }

        if ($success && !filter_var($lightweight, FILTER_VALIDATE_BOOLEAN)) {
            if ($this->thumbnailsAlreadyExist($inputPath)) {
                $this->emitProgress($onProgress, 75, 'rendering', 'Skipping thumbnail generation, files already exist.');
            }
            else {
                $this->emitProgress($onProgress, 65, 'rendering', 'Generating thumbnails...');
                $renderResult = $this->render(
                    $spath,
                    $inputPath,
                    [
                        'a' => $this->boolToString($options['a'] ?? false),
                        'g' => $this->resolveConvertedOutputPath($inputPath, $options),
                        'timeout' => $options['render_timeout'] ?? $options['timeout'] ?? 600,
                    ]
                );

                $output .= $renderResult['output'] ?? '';
                $error .= $renderResult['error'] ?? '';

                if (!($renderResult['success'] ?? FALSE)) {
                    $success = FALSE;
                    $exitCode = $renderResult['exit_code'] ?? 1;
                }
                else {
                    $this->emitProgress($onProgress, 75, 'rendering', 'Thumbnails generated.');
                }
            }
        }

        return [
            'success' => $success,
            'exit_code' => $exitCode,
            'output' => $output,
            'error' => $error,
            'command' => $process->getCommandLine(),
            'render' => $renderResult,
        ];
    }

    /**
     * Run render.sh process.
     *
     * @param string $spath
     * @param string $inputPath
     * @param array $options
     *
     * @return array
     */
    public function render(
        string $spath,
        string $inputPath,
        array $options = []
    ) : array {
        $script = $spath . '/scripts/render.sh';

        if (!file_exists($script)) {
            return [
                'success' => FALSE,
                'exit_code' => NULL,
                'output' => '',
                'error' => 'Script not found',
            ];
        }

        $args = [
            $script,
            '-i', $inputPath,
            '-a', $this->boolToString($options['a'] ?? false),
        ];

        if (!empty($options['g'])) {
            $args[] = '-g';
            $args[] = $options['g'];
        }

        $process = new \Symfony\Component\Process\Process($args);
        $process->setTimeout($options['timeout'] ?? 600);
        $process->setWorkingDirectory($spath);
        $process->run();

        return [
            'success' => $process->isSuccessful(),
            'exit_code' => $process->getExitCode(),
            'output' => $process->getOutput(),
            'error' => $process->getErrorOutput(),
            'command' => $process->getCommandLine(),
        ];
    }

    public function uncompress(
        string $spath,
        string $type,
        string $inputPath,
        string $outputPath,
        string $name,
        array $options = []
        ) : array {

        $script = $spath . '/scripts/uncompress.sh';

        if (!file_exists($script)) {
            return [
            'success' => FALSE,
            'exit_code' => NULL,
            'output' => '',
            'error' => 'Script not found',
            ];
        }

        $args = [
            $script,
            '-t', $type,
            '-i', $inputPath,
            '-o', $outputPath,
            '-n', $name,
        ];

        $process = new \Symfony\Component\Process\Process($args);
        $process->setTimeout($options['timeout'] ?? 600);
        $process->setWorkingDirectory($spath);

        $process->run();

        return [
            'success' => $process->isSuccessful(),
            'exit_code' => $process->getExitCode(),
            'output' => $process->getOutput(),
            'error' => $process->getErrorOutput(),
            'command' => $process->getCommandLine(),
        ];
    }



}
