<?php
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log -- error_log used for server-side error capture in catch blocks, not debug output
/**
 * Plugin Name: Instinctor
 * Plugin URI: https://instinctor.com/wordpress
 * Description: A professional visual website builder with a complete motion graphics engine. Ecommerce, smart chat and SEO in one system.
 * Version: 2.1.06
 * Author: Instinctor
 * License: GPL v2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: instinctor
 * Domain Path: /languages
 * Requires at least: 6.0
 * Tested up to: 7.0
 * Requires PHP: 8.1
 */

declare(strict_types=1);

namespace Instinctor;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// Plugin constants — available for other files to use.
// This bootstrap file itself always uses __DIR__ / __FILE__ so that
// file-system paths are correct even during an upgrade-over-existing.
if (!defined('INSTINCTOR_VERSION'))         define('INSTINCTOR_VERSION', '2.1.3153');
if (!defined('INSTINCTOR_FREE_TIER')) define('INSTINCTOR_FREE_TIER', true);
if (!defined('INSTINCTOR_PLUGIN_DIR'))      define('INSTINCTOR_PLUGIN_DIR', plugin_dir_path(__FILE__));
if (!defined('INSTINCTOR_PLUGIN_URL'))      define('INSTINCTOR_PLUGIN_URL', plugin_dir_url(__FILE__));
if (!defined('INSTINCTOR_PLUGIN_BASENAME')) define('INSTINCTOR_PLUGIN_BASENAME', plugin_basename(__FILE__));
// Shared secret for platform-to-plugin relay callbacks (X-Platform-Token header).
// Set it per install, in .env (standalone) or wp-config.php (WordPress). There is
// deliberately NO fallback: a default here would be identical in every copy of the
// plugin, and a copy is something customers hold. canPlatformCall() fails closed
// when the constant is absent, so an install without it simply refuses the
// platform routes (/solo/provision, /solo/set-pro, /solo/deprovision,
// /directory/admin/*) instead of accepting a secret everyone knows.

// Catch fatal errors during REST API requests
if (defined('REST_REQUEST') || (\strpos((string) ($_SERVER['REQUEST_URI'] ?? ''), 'wp-json') !== false)) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- substring check on server var, not echoed
    \register_shutdown_function(function() {
        $e = \error_get_last();
        if ($e && \in_array($e['type'], [\E_ERROR, \E_PARSE, \E_COMPILE_ERROR, \E_CORE_ERROR])) {
            \error_log('[Instinctor FATAL] ' . $e['message'] . ' in ' . $e['file'] . ':' . $e['line']);
        }
    });
}



// Instinctor never changes the site's permalink structure automatically (a silent
// global-setting change is not allowed). Pretty permalinks make Impulse page URLs
// nicer, so on WordPress we surface a dismissible, opt-in recommendation instead.
add_action('admin_notices', function () {
    if (!function_exists('current_user_can') || !current_user_can('manage_options')) return;
    if (get_option('permalink_structure')) return;
    echo '<div class="notice notice-info is-dismissible"><p>'
        . esc_html__('Instinctor works best with pretty permalinks. Plain ?p=123 URLs still work.', 'instinctor')
        . ' <a href="' . esc_url(admin_url('options-permalink.php')) . '">'
        . esc_html__('Open Permalink Settings', 'instinctor') . '</a></p></div>';
});

// Lucide icon library (SVG data for all 1671 icons)
require_once __DIR__ . '/includes/lucide-icons.php';

// Free-tier helpers (build-time gating: instinctor_locked / instinctor_free_watermark).
require_once __DIR__ . '/includes/free-tier.php';

// AutoUpdater trust anchor: defines INSTINCTOR_MANIFEST_PUBLIC_KEY used by
// Instinctor\Services\AutoUpdater to verify release-manifest signatures from
// platform.instinctor.com. v2.1.2844 phase 2. Guarded with file_exists because
// the WordPress.org build excludes bootstrap/ and stubs the self-updater, so
// the file is absent and the manifest key is unused there. Without the guard
// the WP build fatals on load (Failed opening required). v2.1.2844.
if (file_exists(__DIR__ . '/bootstrap/manifest-trust.php')) {
    require_once __DIR__ . '/bootstrap/manifest-trust.php';
}

// Impulse Page Builder preview (loads early to intercept ?impulse_preview requests)
require_once __DIR__ . '/admin/views/impulse-preview.php';

// Autoloader
spl_autoload_register(function (string $class): void {
    $prefix = 'Instinctor\\';
    $base_dir = __DIR__ . '/includes/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relative_class = substr($class, $len);
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

// Tier gating removed — all features unlocked
function instinctor_can(string $feature): bool {
    
    return true;
}

// Initialize plugin
function init(): void {
    Core\Plugin::instance();
}

add_action('plugins_loaded', __NAMESPACE__ . '\\init');

// Activation hook
register_activation_hook(__FILE__, function (): void {
    require_once __DIR__ . '/includes/Core/Activator.php';
    Core\Activator::activate();
});

// Deactivation hook
register_deactivation_hook(__FILE__, function (): void {
    require_once __DIR__ . '/includes/Core/Deactivator.php';
    Core\Deactivator::deactivate();
});

add_action('admin_enqueue_scripts', function () {
    wp_enqueue_style('instinctor-admin-menu-icon', plugins_url('assets/admin/instinctor-admin-menu-icon.css', __FILE__), array(), INSTINCTOR_VERSION);
});

// AJAX: Download remote graphic to local /instinctor-graphics/ folder.
// Registered for both WordPress (wp_ajax_*) and standalone (AjaxDispatcher).
$_impulse_download_graphic_handler = function () {
    if (!Core\Auth::can('edit_pages') && !Core\Auth::can('manage_options')) {
        wp_send_json_error('Permission denied');
    }

    if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
        check_ajax_referer('instinctor_download_graphic', 'nonce');
    }

    $url = esc_url_raw(wp_unslash($_POST['url'] ?? '')); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- esc_url_raw sanitizes
    if (empty($url)) {
        wp_send_json_error('No URL provided');
    }

    // Download the remote file
    $response = wp_remote_get($url, ['timeout' => 30]);
    if (is_wp_error($response)) {
        wp_send_json_error('Download failed: ' . $response->get_error_message());
    }

    $body = wp_remote_retrieve_body($response);
    if (empty($body)) {
        wp_send_json_error('Empty file');
    }

    $content_type = wp_remote_retrieve_header($response, 'content-type');

    // Determine extension from URL or content type
    $ext = strtolower(pathinfo((string) wp_parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
    if (!$ext || !in_array($ext, ['webp', 'png', 'jpg', 'jpeg', 'gif'])) {
        $mime_map = [
            'image/webp' => 'webp',
            'image/png' => 'png',
            'image/jpeg' => 'jpg',
            'image/gif' => 'gif',
        ];
        $ext = $mime_map[$content_type] ?? 'webp';
    }

    // Create target directory
    $upload_dir = wp_upload_dir();
    $graphics_dir = $upload_dir['basedir'] . '/instinctor-graphics';
    if (!file_exists($graphics_dir)) {
        wp_mkdir_p($graphics_dir);
    }

    // Generate safe filename from URL
    $basename = sanitize_file_name(pathinfo((string) wp_parse_url($url, PHP_URL_PATH), PATHINFO_FILENAME));
    if (empty($basename)) $basename = 'graphic-' . time();
    $filename = wp_unique_filename($graphics_dir, $basename . '.' . $ext);
    $filepath = $graphics_dir . '/' . $filename;

    if (file_put_contents($filepath, $body) === false) {
        wp_send_json_error('Could not save file');
    }

    $local_url = $upload_dir['baseurl'] . '/instinctor-graphics/' . $filename;

    wp_send_json_success([
        'local_url' => $local_url,
        'filename' => $filename,
    ]);
};

if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
    add_action('wp_ajax_instinctor_download_graphic', $_impulse_download_graphic_handler);
} else {
    Core\AjaxDispatcher::register('instinctor_download_graphic', $_impulse_download_graphic_handler);
}
unset($_impulse_download_graphic_handler);

// Cache mask SVG locally for CSS mask-image usage.
// Registered for both WordPress (wp_ajax_*) and standalone (AjaxDispatcher).
$_impulse_cache_mask_handler = function () {
    if (!Core\Auth::can('edit_pages') && !Core\Auth::can('manage_options')) {
        wp_send_json_error('Permission denied');
    }

    // Nonce check (WP only — standalone uses session auth via Auth::can above)
    if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
        check_ajax_referer('instinctor_cache_mask', 'nonce');
    }

    $shape_id = intval($_POST['shape_id'] ?? 0);
    if (!$shape_id) {
        wp_send_json_error('Missing shape ID');
    }

    // Resolve SVG data — WP mode reads from ilm_shapes table, standalone uses DB::
    $svg_data = '';
    $viewbox  = '0 0 24 24';

    if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- prepared (%d) single-row lookup on the plugin's own ilm_shapes table; caching not warranted
        $shape = $wpdb->get_row($wpdb->prepare(
            "SELECT svg_data, svg_viewbox FROM {$wpdb->prefix}ilm_shapes WHERE id = %d",
            $shape_id
        ), ARRAY_A);
        if ($shape && !empty($shape['svg_data'])) {
            $svg_data = $shape['svg_data'];
            $viewbox  = $shape['svg_viewbox'] ?: '0 0 24 24';
        }
    } else {
        // Standalone: read from platform shapes table via DB facade
        $shape = \Instinctor\Core\DB::get_row(
            'SELECT svg_data, svg_viewbox FROM ' . \Instinctor\Core\DB::table('shapes') . ' WHERE id = ?',
            [$shape_id]
        );
        if ($shape && !empty($shape['svg_data'])) {
            $svg_data = $shape['svg_data'];
            $viewbox  = $shape['svg_viewbox'] ?: '0 0 24 24';
        }
    }

    if (empty($svg_data)) {
        wp_send_json_error('Shape not found');
    }

    // Build a proper SVG file — solid black silhouette for masking.
    // Strip color attributes so everything inherits fill="#000" from root.
    $clean_svg = preg_replace('/\s*fill="[^"]*"/', '', $svg_data);
    $clean_svg = preg_replace('/\s*stroke="[^"]*"/', '', $clean_svg);
    $clean_svg = preg_replace('/\s*data-[cs]="[^"]*"/', '', $clean_svg);
    $svg_content = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="' . esc_attr($viewbox) . '" fill="#000" stroke="none">' . $clean_svg . '</svg>';

    // Resolve masks directory and URL
    if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
        $upload_dir = wp_upload_dir();
        $masks_dir  = $upload_dir['basedir'] . '/instinctor/masks';
        $masks_url  = $upload_dir['baseurl'] . '/instinctor/masks';
        if (!file_exists($masks_dir)) {
            wp_mkdir_p($masks_dir);
        }
        $filename = 'mask-' . $shape_id . '.svg';
        $filepath = $masks_dir . '/' . $filename;
        if (file_put_contents($filepath, $svg_content) === false) {
            wp_send_json_error('Could not save mask file');
        }
        $mask_url_out = $masks_url . '/' . $filename;
    } else {
        // Standalone: use Storage facade — handles path and URL resolution correctly
        $rel = 'masks/mask-' . $shape_id . '.svg';
        if (!\Instinctor\Core\Storage::put($rel, $svg_content)) {
            wp_send_json_error('Could not save mask file');
        }
        $mask_url_out = \Instinctor\Core\Storage::url($rel);
    }

    wp_send_json_success([
        'mask_url' => $mask_url_out,
        'shape_id' => $shape_id,
    ]);
};

if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
    add_action('wp_ajax_instinctor_cache_mask', $_impulse_cache_mask_handler);
} else {
    Core\AjaxDispatcher::register('instinctor_cache_mask', $_impulse_cache_mask_handler);
}
unset($_impulse_cache_mask_handler);
