<?php

namespace Formatrica\Assets;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

final class Manager
{
    /**
     * @var array<int, string>
     */
    private const INLINE_ASSET_ALLOWLIST = [
        'app/Assets/frontend-theme-base.css',
        'app/Assets/frontend-inline.js',
    ];
    private const INLINE_ASSET_MAX_BYTES = 65536;

    /**
     * @var array<string, mixed>|null
     */
    private ?array $manifest = null;

    private bool $frontend_inline_applied = false;
    private string $frontend_inline_css = '';

    public function register_block_assets(): void
    {
        $block_asset = $this->get_asset('src/block/index.js');

        if ($block_asset) {
            wp_register_script(
                'formatrica-block',
                FORMATRICA_BUILD_URL . $block_asset['file'],
                ['wp-blocks', 'wp-element', 'wp-components', 'wp-data', 'wp-i18n', 'wp-block-editor', 'wp-server-side-render'],
                $this->asset_version($block_asset['file']),
                true
            );

            if (!empty($block_asset['css'])) {
                foreach ($block_asset['css'] as $index => $css) {
                    $handle = 'formatrica-block-style' . ($index > 0 ? '-' . $index : '');
                    wp_register_style(
                        $handle,
                        FORMATRICA_BUILD_URL . $css,
                        ['wp-edit-blocks'],
                        $this->asset_version($css)
                    );
                }
            }
        }

        // Editor preview needs the FRONTEND form CSS so the server-side-rendered
        // form looks like the real thing inside the Gutenberg canvas. Distinct
        // handle — 'formatrica-block-style' is the block bundle's own CSS.
        $base_css_path = FORMATRICA_PATH . 'app/Assets/frontend-theme-base.css';
        if (file_exists($base_css_path)) {
            wp_register_style(
                'formatrica-block-editor',
                FORMATRICA_URL . 'app/Assets/frontend-theme-base.css',
                ['wp-edit-blocks'],
                (string) (@filemtime($base_css_path) ?: FORMATRICA_VERSION)
            );
            // Small editor-only polish. The Disabled wrapper handles real
            // inertness; this just tidies the preview + empty-state card. The
            // edition overlay (Pro-markup selectors) rides along so editor
            // previews of Pro forms paint correctly in the Pro package.
            wp_add_inline_style(
                'formatrica-block-editor',
                '.formatrica-block-preview .formatrica{margin:0}'
                . '.formatrica-block-empty{border:1px dashed #c3c4c7;padding:24px;text-align:center;background:#fff}'
                . (string) apply_filters('formatrica_frontend_theme_css', '')
            );
        }
    }

    public function register_admin_assets(string $hook): void
    {
        // Builder page: also load the frontend theme-preset overlay stylesheet
        // so the Form Preview paints Pro presets with the REAL frontend CSS.
        // The handle is registered by the Pro provider (Phase 8 — the preset
        // stylesheet ships only in Pro); in Free nothing is registered and
        // this is a no-op.
        if (strpos($hook, 'formatrica-builder') !== false) {
            if (wp_style_is('formatrica-frontend-themes', 'registered')) {
                wp_enqueue_style('formatrica-frontend-themes');
            }
        }

        $asset = $this->get_asset($this->admin_entry());

        // Fail closed in Free packages: the edition build dir (build/free/) and
        // its manifest must exist. A miss is a packaging error — log it and
        // register nothing; never fall back to another edition's bundle.
        if (!$asset && $this->edition() === 'free') {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Formatrica: ' . $this->admin_entry() . ' missing from the build/free manifest in the ' . FORMATRICA_PACKAGE . ' package; admin assets not registered.'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- WP_DEBUG-gated packaging-error diagnostic
            }
            return;
        }

        if ($asset) {
            wp_register_script(
                'formatrica-admin',
                FORMATRICA_BUILD_URL . $asset['file'],
                ['wp-i18n'],
                $this->asset_version($asset['file']),
                true
            );

            if (!empty($asset['css'])) {
                foreach ($asset['css'] as $index => $css) {
                    $handle = 'formatrica-admin-style' . ($index > 0 ? '-' . $index : '');
                    wp_register_style(
                        $handle,
                        FORMATRICA_BUILD_URL . $css,
                        [],
                        $this->asset_version($css)
                    );
                }
            }
        } else {
            // Fallback when Vite manifest is missing: register the non-hashed build files
            $fallback_js = FORMATRICA_BUILD_PATH . 'js/admin.js';
            if (file_exists($fallback_js)) {
                wp_register_script(
                    'formatrica-admin',
                    FORMATRICA_BUILD_URL . 'js/admin.js',
                    ['wp-i18n'],
                    FORMATRICA_VERSION,
                    true
                );
            }

            // Best-effort: register the first admin CSS we can find
            $css_dir = FORMATRICA_BUILD_PATH . 'css/';
            if (is_dir($css_dir)) {
                $css_files = glob($css_dir . 'admin*.css');
                if (is_array($css_files) && !empty($css_files)) {
                    // Convert absolute path to URL by taking the basename
                    foreach (array_values($css_files) as $index => $path) {
                        $basename = basename($path);
                        $handle = 'formatrica-admin-style' . ($index > 0 ? '-' . $index : '');
                        wp_register_style(
                            $handle,
                            FORMATRICA_BUILD_URL . 'css/' . $basename,
                            [],
                            FORMATRICA_VERSION
                        );
                    }
                }
            }
        }
    }

    public function register_frontend_assets(): void
    {
        $asset = $this->get_asset($this->frontend_entry());
        $style_handles = [];

        if ($asset) {
            wp_register_script(
                'formatrica-frontend',
                FORMATRICA_BUILD_URL . $asset['file'],
                [],
                $this->asset_version($asset['file']),
                true
            );

            if (!empty($asset['css'])) {
                foreach ($asset['css'] as $index => $css) {
                    $handle = 'formatrica-frontend-style' . ($index > 0 ? '-' . $index : '');
                    wp_register_style(
                        $handle,
                        FORMATRICA_BUILD_URL . $css,
                        [],
                        $this->asset_version($css)
                    );
                    $style_handles[] = $handle;
                }
            }
        }

        if (!wp_script_is('formatrica-frontend', 'registered')) {
            wp_register_script('formatrica-frontend', false, [], FORMATRICA_VERSION, true);
            $inline_js = $this->frontend_inline_script();
            $inline_js = wp_kses_no_null($inline_js, ['slash_zero' => 'keep']);
            $inline_js = preg_replace('#</script#i', '<\\/script', $inline_js);
            wp_add_inline_script('formatrica-frontend', $inline_js);
        }

        // Localize HERE (after BOTH registration paths — manifest and
        // fallback), not at shortcode-parse time: in block themes the
        // shortcode renders before registration, and wp_localize_script on an
        // unregistered handle fails silently — which broke the REST config
        // (and submissions) on FSE pages. Prints only if the handle is
        // enqueued, so this is a no-op on pages without a form.
        wp_localize_script(
            'formatrica-frontend',
            'formatricaFrontend',
            [
                'restUrl' => esc_url_raw(rest_url('formatrica/v1')),
                'friendlyCaptchaUrl' => esc_url(FORMATRICA_BUILD_URL . 'js/friendly-captcha.js'),
                // Visitor-facing runtime strings, translated server-side so the
                // frontend never needs the wp-i18n script (language packs and
                // bundled .mo files both apply).
                'i18n' => [
                    'thanks' => __('Thanks!', 'formatrica'),
                    'submitFailed' => __('Submission failed. Please try again.', 'formatrica'),
                    'securityFailed' => __('Security verification failed. Please try again.', 'formatrica'),
                    'completeVerification' => __('Please complete the verification.', 'formatrica'),
                    'fieldRequired' => __('This field is required.', 'formatrica'),
                    'geoUnsupported' => __('Your browser does not support location capture.', 'formatrica'),
                    'geoLocating' => __('Locating…', 'formatrica'),
                    'geoDenied' => __('Location permission was denied.', 'formatrica'),
                    'geoUnavailable' => __('Your location is currently unavailable.', 'formatrica'),
                    'geoTimeout' => __('Location lookup timed out.', 'formatrica'),
                    'geoFailed' => __('Could not capture your location.', 'formatrica'),
                    'enterLinkUrl' => __('Enter the link URL:', 'formatrica'),
                ],
            ]
        );

        if (empty($style_handles)) {
            $fallback = 'formatrica-frontend-inline';
            wp_register_style($fallback, false, [], FORMATRICA_VERSION);
            $style_handles[] = $fallback;
        }

        $this->add_inline_theme_css($style_handles);

        // Pro theme presets (P17, forms.themes): the preset stylesheet ships
        // only in Pro and is registered by the Pro provider (Phase 8). The
        // shortcode's enqueue of 'formatrica-frontend-themes' stays a harmless
        // queued-unknown-handle no-op in Free.
    }

    /**
     * Which edition's build output this package loads: the pro package loads
     * build/pro/, every other package stamp loads build/free/ (fail closed —
     * an unknown stamp must never resolve to the Pro bundles). Mirrors the
     * FORMATRICA_BUILD_PATH definition in formatrica.php.
     */
    private function edition(): string
    {
        return (defined('FORMATRICA_PACKAGE') && FORMATRICA_PACKAGE === 'pro') ? 'pro' : 'free';
    }

    /** Manifest key of this edition's admin entrypoint. */
    private function admin_entry(): string
    {
        return 'src/admin-' . $this->edition() . '/main.js';
    }

    /** Manifest key of this edition's frontend entrypoint. */
    private function frontend_entry(): string
    {
        return 'src/frontend-' . $this->edition() . '/main.js';
    }

    /**
     * Cache-busting version for a built asset: the file's mtime so every rebuild
     * invalidates the browser cache. Built filenames like js/admin.js are not
     * content-hashed, so a static plugin version would serve a stale bundle
     * across dev rebuilds. Falls back to the plugin version if the file is gone.
     */
    private function asset_version(string $relative): string
    {
        $path = FORMATRICA_BUILD_PATH . ltrim($relative, '/');
        $mtime = is_string($relative) && $relative !== '' ? @filemtime($path) : false;

        return $mtime ? (string) $mtime : FORMATRICA_VERSION;
    }

    private function get_asset(string $entry): ?array
    {
        $manifest = $this->manifest();

        if (!isset($manifest[$entry])) {
            return null;
        }

        $asset = $manifest[$entry];

        $asset['file'] = $asset['file'] ?? '';
        $asset['css'] = $asset['css'] ?? [];

        return $asset;
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function manifest(): array
    {
        if (null !== $this->manifest) {
            return $this->manifest;
        }

        $primary = FORMATRICA_BUILD_PATH . 'manifest.json';
        $secondary = FORMATRICA_BUILD_PATH . '.vite/manifest.json';
        $path = file_exists($primary) ? $primary : $secondary;

        if (!file_exists($path)) {
            $this->manifest = [];
            return $this->manifest;
        }

        $contents = file_get_contents($path);
        $manifest = $contents ? json_decode($contents, true) : null;

        $this->manifest = is_array($manifest) ? $manifest : [];

        return $this->manifest;
    }

    /**
     * @param array<int, string> $handles
     */
    private function add_inline_theme_css(array $handles): void
    {
        if ($this->frontend_inline_applied) {
            return;
        }

        $css = $this->frontend_theme_css();

        if ('' === $css) {
            return;
        }

        $css = wp_kses_no_null($css, ['slash_zero' => 'keep']);
        $css = preg_replace('#</style#i', '<\\/style', $css);

        foreach ($handles as $handle) {
            if (wp_style_is($handle, 'registered')) {
                wp_add_inline_style($handle, $css);
                $this->frontend_inline_applied = true;
                break;
            }
        }

        if (!$this->frontend_inline_applied && wp_style_is('wp-block-library', 'registered')) {
            wp_add_inline_style('wp-block-library', $css);
            $this->frontend_inline_applied = true;
        }

        $this->frontend_inline_css = $css;
    }

    public function print_frontend_inline_css(): void
    {
        if ($this->frontend_inline_css === '') {
            return;
        }

        $handle = 'formatrica-theme-vars';
        wp_register_style($handle, false, [], FORMATRICA_VERSION);
        wp_enqueue_style($handle);
        $fallback_css = wp_kses_no_null($this->frontend_inline_css, ['slash_zero' => 'keep']);
        $fallback_css = preg_replace('#</style#i', '<\\/style', $fallback_css);
        wp_add_inline_style($handle, $fallback_css);
        $this->frontend_inline_css = '';
    }

    private function frontend_theme_css(): string
    {
        $settings = function_exists('wp_get_global_settings') ? (array) wp_get_global_settings() : [];
        $text = $this->get_setting_value($settings, ['color', 'text'], '#1d2327');
        $background = $this->get_setting_value($settings, ['color', 'background'], '#ffffff');
        $border = $this->mix_hex($background, $text, 0.16) ?? '#dcdcde';
        $radius = $this->normalise_length(
            $this->get_setting_value($settings, ['border', 'radius'], ''),
            '8px'
        );
        $spacing = $this->normalise_length(
            $this->get_setting_value($settings, ['spacing', 'blockGap'], ''),
            '1rem'
        );
        $field_surface = $this->mix_hex('#ffffff', $background, 0.1) ?? $background;
        $text_hex = $this->normalize_hex($text) ?? '#1d2327';
        $background_hex = $this->normalize_hex($background) ?? '#ffffff';
        $accent_hex = $this->resolve_primary_color($settings, $background_hex, '#2271b1');
        $accent_focus = $this->hex_to_rgba($accent_hex, 0.18);
        $button_text = $this->preferred_button_text_color($accent_hex);
        $accent_text = $this->resolve_accent_text_color($accent_hex, $background_hex, $text_hex);

        $radius_css = $radius === '8px' ? 'var(--wp--preset--radius--medium, 8px)' : $radius;
        $spacing_css = $spacing === '1rem' ? 'var(--wp--preset--spacing--30, 1rem)' : $spacing;

        $text_chain = $this->preset_var_chain([
            'color--contrast',
            'color--foreground',
            'color--base',
        ], $text);

        $variables = '.formatrica{'
            . '--fmt-text: ' . $text_chain . ';'
            . '--fmt-surface: var(--wp--preset--color--background, ' . $background . ');'
            . '--fmt-field-surface: ' . $field_surface . ';'
            . '--fmt-border: ' . $border . ';'
            . '--fmt-accent: ' . $accent_hex . ';'
            . '--fmt-focus-ring: ' . $accent_focus . ';'
            . '--fmt-radius: ' . $radius_css . ';'
            . '--fmt-spacing: ' . $spacing_css . ';'
            . '--fmt-button-radius: var(--wp--preset--radius--pill, 999px);'
            . '--fmt-button-text: ' . $button_text . ';'
            . '--fmt-accent-text: ' . $accent_text . ';'
            . '--fmt-success: var(--wp--preset--color--success, var(--wp--preset--color--vivid-green-cyan, #16a34a));'
            . '--fmt-error: var(--wp--preset--color--error, #d63638);'
            . '}';

        $base = $this->load_inline_asset(
            'app/Assets/frontend-theme-base.css',
            '.formatrica{font-family:inherit;color:var(--fmt-text,inherit);}'
        );

        // Edition overlay (Phase 8, plan §9.1): the Pro provider appends its
        // Pro-markup selectors (multi-step, drafts, modal, Pro fields) here.
        // Generic extension point — this Free file names no Pro path.
        $extra = (string) apply_filters('formatrica_frontend_theme_css', '');

        return $variables . "\n" . $base . ($extra !== '' ? "\n" . $extra : '');
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function get_setting_value(array $settings, array $path, string $default): string
    {
        $value = $settings;

        foreach ($path as $segment) {
            if (!is_array($value) || !array_key_exists($segment, $value)) {
                return $default;
            }

            $value = $value[$segment];
        }

        if (is_string($value) && $value !== '') {
            return $this->normalize_setting_reference($value, $default);
        }

        return $default;
    }

    /**
     * @param array<string, mixed> $settings
     */
    /**
     * Palette colours are only candidates: anything that cannot hold WCAG
     * non-text contrast (3:1) against the page background is rejected — themes
     * routinely put their base/background swatch first (e.g. Twenty
     * Twenty-Five's white "base"), and an accent that dissolves into the
     * surface takes the submit button, focus ring, and price tag with it.
     * Always returns a normalized hex.
     */
    private function resolve_primary_color(array $settings, string $background_hex, string $fallback): string
    {
        $palette = isset($settings['color']['palette']) && is_array($settings['color']['palette'])
            ? $settings['color']['palette']
            : [];

        $candidates = [];

        foreach (['primary', 'accent', 'accent-1'] as $slug) {
            $candidates[] = $this->find_palette_color($palette, $slug);
        }

        // First-entry heuristic only for palettes that carry actual branding;
        // the core 'default' group is the same generic swatch list on every
        // site (starting with plain black) and would mask the plugin default.
        foreach (['theme', 'custom'] as $group) {
            if (!empty($palette[$group]) && is_array($palette[$group])) {
                $candidates[] = $palette[$group][0]['color'] ?? null;
            }
        }

        foreach ($candidates as $candidate) {
            $candidate_hex = is_string($candidate) ? $this->normalize_hex($candidate) : null;

            if ($candidate_hex !== null && $this->contrast_ratio($candidate_hex, $background_hex) >= 3.0) {
                return $candidate_hex;
            }
        }

        return $fallback;
    }

    /**
     * @param array<string, mixed> $palette
     */
    private function find_palette_color(array $palette, string $slug): ?string
    {
        foreach (['theme', 'default', 'custom'] as $group) {
            if (empty($palette[$group]) || !is_array($palette[$group])) {
                continue;
            }

            foreach ($palette[$group] as $entry) {
                if (
                    is_array($entry)
                    && isset($entry['slug'], $entry['color'])
                    && $entry['slug'] === $slug
                    && is_string($entry['color'])
                    && $entry['color'] !== ''
                ) {
                    return $entry['color'];
                }
            }
        }

        return null;
    }

    private function preferred_button_text_color(string $hex): string
    {
        $rgb = $this->hex_to_rgb($hex);

        if (!$rgb) {
            return '#ffffff';
        }

        [$r, $g, $b] = $rgb;
        $luma = (0.299 * $r) + (0.587 * $g) + (0.114 * $b);

        return $luma > 186 ? '#101010' : '#ffffff';
    }

    private function hex_to_rgba(string $hex, float $alpha): string
    {
        $rgb = $this->hex_to_rgb($hex);

        if (!$rgb) {
            return sprintf('rgba(34, 113, 177, %.2f)', max(0, min(1, $alpha)));
        }

        [$r, $g, $b] = $rgb;

        return sprintf('rgba(%d, %d, %d, %.3f)', $r, $g, $b, max(0, min(1, $alpha)));
    }

    /**
     * @return array{0:int,1:int,2:int}|null
     */
    private function hex_to_rgb(string $hex): ?array
    {
        $normalized = $this->normalize_hex($hex);

        if (!$normalized) {
            return null;
        }

        $hexValue = substr($normalized, 1);

        if (strlen($hexValue) === 3) {
            $hexValue = $hexValue[0] . $hexValue[0] . $hexValue[1] . $hexValue[1] . $hexValue[2] . $hexValue[2];
        }

        $int = hexdec($hexValue);

        return [
            ($int >> 16) & 0xFF,
            ($int >> 8) & 0xFF,
            $int & 0xFF,
        ];
    }

    private function normalize_hex(string $hex): ?string
    {
        $hex = trim($hex);

        if ($hex === '') {
            return null;
        }

        if ($hex[0] !== '#') {
            $hex = '#' . $hex;
        }

        if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $hex)) {
            return null;
        }

        return strtolower($hex);
    }

    private function mix_hex(string $base, string $overlay, float $ratio): ?string
    {
        $base_rgb = $this->hex_to_rgb($base);
        $overlay_rgb = $this->hex_to_rgb($overlay);

        if (!$base_rgb || !$overlay_rgb) {
            return null;
        }

        $ratio = max(0, min(1, $ratio));
        $inverse = 1 - $ratio;

        $mixed = [
            (int) round(($base_rgb[0] * $inverse) + ($overlay_rgb[0] * $ratio)),
            (int) round(($base_rgb[1] * $inverse) + ($overlay_rgb[1] * $ratio)),
            (int) round(($base_rgb[2] * $inverse) + ($overlay_rgb[2] * $ratio)),
        ];

        return sprintf('#%02x%02x%02x', $mixed[0], $mixed[1], $mixed[2]);
    }

    private function resolve_accent_text_color(string $accent, string $background, string $text): string
    {
        $accent_hex = $this->normalize_hex($accent) ?? '#2271b1';
        $background_hex = $this->normalize_hex($background) ?? '#ffffff';
        $text_hex = $this->normalize_hex($text) ?? '#1d2327';

        if ($this->contrast_ratio($accent_hex, $background_hex) >= 4.5) {
            return $accent_hex;
        }

        if ($this->contrast_ratio($text_hex, $background_hex) >= 4.5) {
            return $text_hex;
        }

        $mixed = $this->mix_hex($accent_hex, $text_hex, 0.5);

        if ($mixed && $this->contrast_ratio($mixed, $background_hex) >= 4.5) {
            return $mixed;
        }

        if ($this->contrast_ratio('#2271b1', $background_hex) >= 4.5) {
            return '#2271b1';
        }

        return $text_hex;
    }

    private function contrast_ratio(string $first, string $second): float
    {
        $first_luminance = $this->relative_luminance($first);
        $second_luminance = $this->relative_luminance($second);

        if ($first_luminance === null || $second_luminance === null) {
            return 1.0;
        }

        $lighter = max($first_luminance, $second_luminance);
        $darker = min($first_luminance, $second_luminance);

        return ($lighter + 0.05) / ($darker + 0.05);
    }

    private function relative_luminance(string $hex): ?float
    {
        $rgb = $this->hex_to_rgb($hex);

        if (!$rgb) {
            return null;
        }

        $components = array_map(
            static function (int $channel): float {
                $normalized = $channel / 255;

                if ($normalized <= 0.03928) {
                    return $normalized / 12.92;
                }

                return pow(($normalized + 0.055) / 1.055, 2.4);
            },
            $rgb
        );

        return (0.2126 * $components[0]) + (0.7152 * $components[1]) + (0.0722 * $components[2]);
    }

    private function normalize_setting_reference(string $value, string $fallback): string
    {
        if ($value === '') {
            return $fallback;
        }

        if (str_starts_with($value, 'var:preset|')) {
            $parts = explode('|', substr($value, strlen('var:preset|')));

            if (!empty($parts)) {
                return 'var(--wp--preset--' . implode('--', $parts) . ')';
            }
        }

        return $value;
    }

    private function normalise_length(string $value, string $fallback): string
    {
        $value = trim($value);

        if ($value === '') {
            return $fallback;
        }

        if (preg_match('/^var\\(--[a-z0-9\\-]+\\)$/i', $value)) {
            return $value;
        }

        if (preg_match('/^-?\\d*\\.?\\d+(px|em|rem|%)$/i', $value)) {
            return $value;
        }

        return $fallback;
    }

    private function preset_var_chain(array $slugs, string $fallback): string
    {
        $expression = $fallback;

        foreach (array_reverse($slugs) as $slug) {
            $expression = 'var(--wp--preset--' . $slug . ', ' . $expression . ')';
        }

        return $expression;
    }

    private function frontend_inline_script(): string
    {
        return $this->load_inline_asset('app/Assets/frontend-inline.js', '(function(){})();');
    }

    private function load_inline_asset(string $relative_path, string $fallback = ''): string
    {
        $normalized_relative_path = ltrim(str_replace('\\', '/', $relative_path), '/');

        if (!in_array($normalized_relative_path, self::INLINE_ASSET_ALLOWLIST, true)) {
            return $fallback;
        }

        $plugin_root = realpath(FORMATRICA_PATH);

        if (!is_string($plugin_root) || $plugin_root === '') {
            return $fallback;
        }

        $candidate_path = $plugin_root . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $normalized_relative_path);
        $resolved_path = realpath($candidate_path);

        if (!is_string($resolved_path) || $resolved_path === '') {
            return $fallback;
        }

        $normalized_plugin_root = rtrim($plugin_root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

        if (!str_starts_with($resolved_path, $normalized_plugin_root) || !is_readable($resolved_path)) {
            return $fallback;
        }

        $size = filesize($resolved_path);

        if (!is_int($size) || $size <= 0 || $size > self::INLINE_ASSET_MAX_BYTES) {
            return $fallback;
        }

        $contents = file_get_contents($resolved_path);

        if (!is_string($contents) || $contents === '') {
            return $fallback;
        }

        return trim($contents);
    }
}
