<?php
namespace boru\boruai\OCR\Tesseract;

use boru\boruai\OCR\Contract\PageImageProviderInterface;

// If your PageImageProviderInterface is in a different namespace, add a use statement.
// use boru\boruai\OCR\Contract\PageImageProviderInterface;

class VipsPdfPageImageProvider implements PageImageProviderInterface
{
    private $pdfPath;
    private $imageDir;

    /** DPI for mutool rendering */
    private $tileDpi = 600;

    /** If 0, never tile. Otherwise tile when width*height > threshold */
    private $tilePixelThreshold = 0;

    private $tileCols = 2;
    private $tileRows = 2;
    private $tileOverlap = 20;

    private $mutoolBandHeight = 0;

    /** NEW: whether to use vips dzsave auto-tiling */
    private $vipsAutoTile = false;

    /** NEW: base tile size for dzsave (also used for offset calculations) */
    private $vipsTileSize = 1024;

    /**
     * Cached result:
     *  - string: single full-page PNG
     *  - array:  tiles [ ['path'=>..., 'offset_x'=>..., 'offset_y'=>...], ... ]
     *
     * @var array<int,string|array<int,array{path:string,offset_x:int,offset_y:int}>>|null
     */
    private $images = null;

    /**
     * @param string $pdfPath
     * @param string $imageDir
     * @param array<string,mixed> $options
     */
    public function __construct($pdfPath, $imageDir, array $options = [])
    {
        $this->pdfPath  = $pdfPath;
        $this->imageDir = rtrim($imageDir, '/');

        if (!is_dir($this->imageDir)) {
            @mkdir($this->imageDir, 0777, true);
        }

        if (isset($options['tileDpi'])) {
            $this->tileDpi = max(72, (int)$options['tileDpi']);
        }
        if (isset($options['tilePixelThreshold'])) {
            $this->tilePixelThreshold = (int)$options['tilePixelThreshold'];
        }
        if (isset($options['tileCols'])) {
            $this->tileCols = max(1, (int)$options['tileCols']);
        }
        if (isset($options['tileRows'])) {
            $this->tileRows = max(1, (int)$options['tileRows']);
        }
        if (isset($options['tileOverlap'])) {
            $this->tileOverlap = max(0, (int)$options['tileOverlap']);
        }
        if (isset($options['mutoolBandHeight'])) {
            $this->mutoolBandHeight = max(0, (int)$options['mutoolBandHeight']);
        }

        // NEW: VIPS auto-tiling options
        if (isset($options['vipsAutoTile'])) {
            $this->vipsAutoTile = (bool)$options['vipsAutoTile'];
        }
        if (isset($options['vipsTileSize'])) {
            $this->vipsTileSize = max(64, (int)$options['vipsTileSize']);
        }
    }

    public static function isAvailable()
    {
        $vips   = @shell_exec('command -v vips 2>/dev/null');
        $mutool = @shell_exec('command -v mutool 2>/dev/null');

        return is_string($vips) && trim($vips) !== ''
            && is_string($mutool) && trim($mutool) !== '';
    }

    /**
     * @return array<int,string|array<int,array{path:string,offset_x:int,offset_y:int}>>
     */
    public function getPageImages()
    {
        if ($this->images !== null) {
            return $this->images;
        }

        if (!self::isAvailable()) {
            throw new \RuntimeException("VIPS or mutool not found in PATH; cannot use VipsPdfPageImageProvider.");
        }

        $pageCount = $this->getPageCount();
        if ($pageCount < 1) {
            $pageCount = 1;
        }

        $result = [];

        for ($page = 1; $page <= $pageCount; $page++) {
            $pngPath = $this->renderPageWithMutool($page);

            if (!file_exists($pngPath)) {
                $result[$page - 1] = "[MUPDF+VIPS RENDER FAILED]";
                continue;
            }

            $size = @getimagesize($pngPath);
            if (!$size) {
                $result[$page - 1] = $pngPath;
                continue;
            }

            $width  = (int)$size[0];
            $height = (int)$size[1];
            $pixels = $width * $height;

            if ($this->tilePixelThreshold > 0 && $pixels > $this->tilePixelThreshold) {
                if ($this->vipsAutoTile) {
                    // Preferred: VIPS DeepZoom auto-tiling
                    $tiles = $this->createTilesWithVipsDzsave($pngPath, $page - 1);
                    if (!empty($tiles)) {
                        $result[$page - 1] = $tiles;
                        continue;
                    }
                    // If dzsave fails for some reason, fall back to manual cropping:
                    $result[$page - 1] = $this->createTilesWithVipsCrop($pngPath, $page - 1, $width, $height);
                } else {
                    // Existing behavior: manual crop loop via vips crop
                    $result[$page - 1] = $this->createTilesWithVipsCrop($pngPath, $page - 1, $width, $height);
                }
            } else {
                $result[$page - 1] = $pngPath;
            }
        }

        $this->images = $result;
        return $this->images;
    }

    private function getPageCount()
    {
        $cmd    = 'mutool info ' . escapeshellarg($this->pdfPath) . ' 2>/dev/null';
        $output = shell_exec($cmd);
        if (!is_string($output) || $output === '') {
            return 1;
        }

        if (preg_match('/Pages:\s*([0-9]+)/', $output, $m)) {
            return max(1, (int)$m[1]);
        }

        if (preg_match_all('/^Page\s+([0-9]+)/mi', $output, $mm)) {
            $count = count($mm[1]);
            if ($count > 0) {
                return $count;
            }
        }

        return 1;
    }

    private function renderPageWithMutool($page)
    {
        $pattern = $this->imageDir . '/vips_page_%d.png';
        $outPath = $this->imageDir . '/vips_page_' . $page . '.png';
        @unlink($outPath);

        $cmdParts = [
            'mutool', 'draw',
            '-q',
            '-F', 'png',
            '-r', (string)$this->tileDpi,
        ];

        if ($this->mutoolBandHeight > 0) {
            $cmdParts[] = '-B';
            $cmdParts[] = (string)$this->mutoolBandHeight;
        }

        $cmdParts[] = '-o';
        $cmdParts[] = $pattern;
        $cmdParts[] = escapeshellarg($this->pdfPath);
        $cmdParts[] = (string)$page;

        $cmd = implode(' ', $cmdParts) . ' 2>/dev/null';
        // echo "[VIPS provider mutool] $cmd\n";
        shell_exec($cmd);

        return $outPath;
    }

    /**
     * EXISTING behavior: manual tiling via vips crop.
     *
     * @param string $pngPath
     * @param int    $pageIndex
     * @param int    $width
     * @param int    $height
     * @return array<int,array{path:string,offset_x:int,offset_y:int}>
     */
    private function createTilesWithVipsCrop($pngPath, $pageIndex, $width, $height)
    {
        $tiles = [];

        $cols    = max(1, $this->tileCols);
        $rows    = max(1, $this->tileRows);
        $overlap = $this->tileOverlap;

        $tileWidth  = (int)ceil($width  / $cols);
        $tileHeight = (int)ceil($height / $rows);

        for ($ty = 0; $ty < $rows; $ty++) {
            for ($tx = 0; $tx < $cols; $tx++) {
                $x = $tx * $tileWidth;
                $y = $ty * $tileHeight;

                $x = max(0, $x - $overlap);
                $y = max(0, $y - $overlap);

                $wTile = min($tileWidth + $overlap * 2, $width  - $x);
                $hTile = min($tileHeight + $overlap * 2, $height - $y);

                if ($wTile <= 0 || $hTile <= 0) {
                    continue;
                }

                $tilePath = sprintf(
                    '%s/page_%d_tile_%d_%d.png',
                    $this->imageDir,
                    $pageIndex,
                    $tx,
                    $ty
                );
                @unlink($tilePath);

                $cmdParts = [
                    'vips', 'crop',
                    escapeshellarg($pngPath),
                    escapeshellarg($tilePath),
                    (string)$x,
                    (string)$y,
                    (string)$wTile,
                    (string)$hTile,
                ];

                $cmd = implode(' ', $cmdParts) . ' 2>/dev/null';
                // echo "[VIPS crop tile] $cmd\n";
                shell_exec($cmd);

                if (!file_exists($tilePath)) {
                    continue;
                }

                $tiles[] = [
                    'path'     => $tilePath,
                    'offset_x' => $x,
                    'offset_y' => $y,
                ];
            }
        }

        if (!empty($tiles)) {
            @copy(
                $tiles[count($tiles) - 1]['path'],
                '/var/work/libs/boruai/tests/pdfs/last_ocr_page.png'
            );
        }

        return $tiles;
    }

    /**
     * NEW: VIPS DeepZoom auto-tiling via dzsave.
     *
     * Layout (default DeepZoom):
     *   root.dzi
     *   root_files/<level>/<col>_<row>.png
     *
     * We:
     *  - run dzsave with --tile-size and --overlap
     *  - find the highest zoom level
     *  - treat each <col>_<row>.png as a tile
     *  - offsets = col * vipsTileSize, row * vipsTileSize
     *
     * @param string $pngPath  full-page image
     * @param int    $pageIndex zero-based page index
     * @return array<int,array{path:string,offset_x:int,offset_y:int}>
     */
    private function createTilesWithVipsDzsave($pngPath, $pageIndex)
    {
        $tiles = [];

        $root = sprintf('%s/page_%d_dz', $this->imageDir, $pageIndex);
        $dzi  = $root . '.dzi';
        $dir  = $root . '_files';

        // Clean old outputs if any
        if (file_exists($dzi)) {
            @unlink($dzi);
        }
        if (is_dir($dir)) {
            foreach (glob($dir . '/*', GLOB_ONLYDIR) as $sub) {
                foreach (glob($sub . '/*') as $file) {
                    @unlink($file);
                }
                @rmdir($sub);
            }
            @rmdir($dir);
        }

        $cmdParts = [
            'vips', 'dzsave',
            escapeshellarg($pngPath),
            escapeshellarg($root),
            '--tile-size=' . (int)$this->vipsTileSize,
            '--overlap='   . (int)$this->tileOverlap,
            '--depth=one',             // only highest zoom level
            '--suffix=.png',
        ];

        $cmd = implode(' ', $cmdParts) . ' 2>/dev/null';
        // echo "[VIPS dzsave] $cmd\n";
        shell_exec($cmd);

        if (!is_dir($dir)) {
            // dzsave failed or produced something unexpected
            return [];
        }

        // Find the highest level directory (DeepZoom levels are numeric)
        $levelDirs = glob($dir . '/*', GLOB_ONLYDIR);
        if (!$levelDirs) {
            return [];
        }

        $maxLevel   = -1;
        $maxLevelDir = null;

        foreach ($levelDirs as $levelDir) {
            $base  = basename($levelDir);
            $level = (int)$base;
            if ($level > $maxLevel) {
                $maxLevel    = $level;
                $maxLevelDir = $levelDir;
            }
        }

        if ($maxLevelDir === null) {
            return [];
        }

        // Each tile: "<col>_<row>.png"
        $tileFiles = glob($maxLevelDir . '/*.png');
        if (!$tileFiles) {
            return [];
        }

        foreach ($tileFiles as $path) {
            $base = basename($path, '.png');
            $parts = explode('_', $base);
            if (count($parts) !== 2) {
                continue;
            }
            //echo "Tile: $base\n";

            $col = (int)$parts[0];
            $row = (int)$parts[1];

            $offsetX = $col * $this->vipsTileSize;
            $offsetY = $row * $this->vipsTileSize;

            $tiles[] = [
                'path'     => $path,
                'offset_x' => $offsetX,
                'offset_y' => $offsetY,
            ];
        }

        if (!empty($tiles)) {
            @copy(
                $tiles[count($tiles) - 1]['path'],
                '/var/work/libs/boruai/tests/pdfs/last_ocr_page.png'
            );
        }

        return $tiles;
    }

    /**
     * Cleanup any temporary files created by this provider.
     * Safe to call multiple times.
     *
     * @return void
     */    public function cleanup()
    {
        $files = glob($this->imageDir . '/vips_page_*.png');
        if (is_array($files)) {
            foreach ($files as $file) {
                @unlink($file); 
            }
        }
        @rmdir($this->imageDir);
    }
}