<?php

declare(strict_types=1);

namespace Instinctor\Core\Storage;

/**
 * WordPress storage driver.
 *
 * Delegates path and URL resolution to WordPress's `wp_upload_dir()`.
 * Directory creation uses `wp_mkdir_p()`.
 * All other file operations use native PHP filesystem functions.
 */

if (!defined('ABSPATH')) exit;
class WordPressDriver implements DriverInterface
{
    public function basedir(string $sub = ''): string
    {
        $upload = wp_upload_dir();
        $base   = rtrim($upload['basedir'], '/\\');
        return $sub !== '' ? $base . '/' . ltrim($sub, '/\\') : $base;
    }

    public function baseurl(string $sub = ''): string
    {
        $upload = wp_upload_dir();
        $base   = rtrim($upload['baseurl'], '/');
        return $sub !== '' ? $base . '/' . ltrim($sub, '/') : $base;
    }

    public function path(string $rel): string
    {
        return $this->basedir() . '/' . ltrim($rel, '/\\');
    }

    public function url(string $rel): string
    {
        return $this->baseurl() . '/' . ltrim($rel, '/');
    }

    public function mkdir(string $rel): bool
    {
        $absPath = $this->basedir($rel);
        if (is_dir($absPath)) {
            return true;
        }
        return wp_mkdir_p($absPath);
    }

    public function put(string $rel, string $data): bool
    {
        // WP.org storage hardening: $rel is plugin-derived (never raw user
        // input), but enforce at the sink regardless -- refuse path traversal
        // out of the uploads base, refuse executable/code file types, and
        // require a plugin-specific first path segment so writes are always
        // confined to the plugin's own upload subdirectories.
        $rel = ltrim($rel, '/');
        if (strpos($rel, '..') !== false) {
            return false;
        }
        $firstSeg = strstr($rel, '/', true) ?: $rel;
        if (stripos($firstSeg, 'instinctor') !== 0 && stripos($firstSeg, 'impulse-') !== 0) {
            return false;
        }
        $ext = strtolower((string) pathinfo($rel, PATHINFO_EXTENSION));
        if (in_array($ext, array('php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'phps', 'phar', 'pht', 'shtml', 'cgi', 'pl', 'py', 'sh', 'asp', 'aspx', 'jsp', 'exe', 'htaccess'), true)) {
            return false;
        }
        $absPath = $this->path($rel);
        $dir     = dirname($absPath);

        if (!is_dir($dir)) {
            wp_mkdir_p($dir);
        }

        // phpcs:disable WordPress.WP.AlternativeFunctions,PluginCheck.CodeAnalysis.WriteFile.ABSPATHDetected -- native filesystem I/O for the storage driver writing media to the plugin's own storage directory; no WP_Filesystem in this code path and the path is plugin-derived, not user input.
        return file_put_contents($absPath, $data) !== false;
        // phpcs:enable WordPress.WP.AlternativeFunctions,PluginCheck.CodeAnalysis.WriteFile.ABSPATHDetected
    }

    public function get(string $rel): string|false
    {
        $absPath = $this->path($rel);
        if (!file_exists($absPath)) {
            return false;
        }
        // phpcs:disable WordPress.WP.AlternativeFunctions -- native filesystem I/O for the standalone storage driver; no WP_Filesystem in this code path.
        return file_get_contents($absPath);
        // phpcs:enable WordPress.WP.AlternativeFunctions
    }

    public function delete(string $rel): bool
    {
        $absPath = $this->path($rel);
        if (!file_exists($absPath)) {
            return true;
        }
        // phpcs:disable WordPress.WP.AlternativeFunctions -- native filesystem I/O for the standalone storage driver; no WP_Filesystem in this code path.
        return @unlink($absPath);
        // phpcs:enable WordPress.WP.AlternativeFunctions
    }

    public function exists(string $rel): bool
    {
        return file_exists($this->path($rel));
    }

    public function size(string $rel): int
    {
        $absPath = $this->path($rel);
        return file_exists($absPath) ? (int) filesize($absPath) : 0;
    }

    public function mime(string $path): string
    {
        // Accept both absolute and relative paths
        $absPath = (str_starts_with($path, '/') || str_contains($path, ':\\'))
            ? $path
            : $this->path($path);

        if (!file_exists($absPath)) {
            return 'application/octet-stream';
        }

        $mime = mime_content_type($absPath);
        return $mime !== false ? $mime : 'application/octet-stream';
    }
}
