<?php

/**
 * Plugin Name:       Brightfern E-Invoicing for WooCommerce
 * Description:       Turn WooCommerce orders into EN 16931-compliant e-invoices. Generate, validate before sending, and send AND receive over Peppol with your own Access Point — no network lock-in.
 * Version:           0.1.17
 * Requires at least: 6.4
 * Requires PHP:      8.1
 * Requires Plugins:  woocommerce
 * Author:            BrightFern Labs
 * Author URI:        https://brightfernlabs.com
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       brightfern-e-invoicing-for-woocommerce
 * Domain Path:       /languages
 *
 * Outbound transmission is the paid capability, so its code is excluded from the
 * WordPress.org (free) build entirely rather than gated at runtime — WP.org
 * guideline 5 forbids shipping payment-locked functionality. Freemius' processor
 * strips the path below when it generates the free version.
 *
 *
 * @package Brightfern
 */
declare (strict_types = 1);
defined( 'ABSPATH' ) || exit;
define( 'BRIGHTFERN_VERSION', '0.1.17' );
define( 'BRIGHTFERN_FILE', __FILE__ );
define( 'BRIGHTFERN_DIR', plugin_dir_path( __FILE__ ) );
define( 'BRIGHTFERN_URL', plugin_dir_url( __FILE__ ) );
/*
 * -----------------------------------------------------------------------------
 * Freemius bootstrap
 * -----------------------------------------------------------------------------
 * Freemius is the merchant of record: it handles checkout, EU VAT (MOSS/OSS),
 * invoicing and refunds, so we never touch Stripe or tax admin ourselves.
 *
 * The single source of truth for "is this site allowed to use a Pro feature?"
 * is brightfern_fs()->can_use_premium_code__premium_only() / is_paying(). We wrap it
 * in brightfern_is_pro() (see below) so the rest of the codebase never calls the SDK
 * directly and stays testable.
 *
 * Freemius setup is complete: the product is created in the dashboard (id 36135,
 * slug "brightfern-e-invoicing"), the SDK is vendored at ./freemius/ (official release, not via
 * Composer — it predates our Composer usage and upstream doesn't publish it there;
 * unrelated production dependencies DO ship via vendor/), and plugin_id/public_key below are
 * the real values. brightfern_fs() guards on file_exists() and returns null when
 * ./freemius/ is absent (e.g. a stripped-down dev clone) — and brightfern_is_pro()
 * genuinely does degrade gracefully from that, falling back to a constant/filter.
 * The premium seam itself does NOT: it calls brightfern_fs()->is__premium_only()
 * directly (OutboundFlow.php, OrderMetabox.php), which fatals on that null rather
 * than degrading — deliberately, since brightfern_is_pro() is invisible to
 * Freemius' build processor (see OutboundFlow.php's docblock) and any guard on
 * the seam itself stops the processor recognising and stripping the block. Not a
 * real-world gap: the freemius/ directory ships in every actual build, WP.org and
 * premium alike, so this only matters for a deliberately mutilated dev checkout.
 */
if ( !function_exists( 'brightfern_fs' ) ) {
    /**
     * Lazily initialise the Freemius instance.
     *
     * @return \Freemius|null Null when the SDK is absent (dev / CI / WP.org build without premium).
     */
    function brightfern_fs() {
        static $fs = null;
        if ( null !== $fs ) {
            return ( $fs ?: null );
        }
        $sdk = BRIGHTFERN_DIR . 'freemius/start.php';
        if ( !file_exists( $sdk ) ) {
            $fs = false;
            // Cache "not available" so we only stat once per request.
            return null;
        }
        require_once $sdk;
        // @phpstan-ignore-next-line — fs_dynamic_init is defined by the SDK.
        $fs = fs_dynamic_init( array(
            'id'               => '36135',
            'slug'             => 'brightfern-e-invoicing',
            'type'             => 'plugin',
            'public_key'       => 'pk_25d172399c3e0d37c8dcf358abf01',
            'is_premium'       => false,
            'has_addons'       => false,
            'has_paid_plans'   => true,
            'menu'             => array(
                'slug'   => 'brightfern-settings',
                'parent' => array(
                    'slug' => 'woocommerce',
                ),
            ),
            'is_live'          => true,
            'is_org_compliant' => true,
        ) );
        return $fs;
    }

    // Init Freemius as early as the SDK expects.
    brightfern_fs();
    if ( function_exists( 'brightfern_fs' ) && brightfern_fs() ) {
        brightfern_fs()->add_action( 'after_uninstall', 'brightfern_uninstall_cleanup' );
        do_action( 'brightfern_fs_loaded' );
    }
}
/**
 * Uninstall cleanup, run when the user deletes the plugin from the admin.
 *
 * This hangs off Freemius' `after_uninstall` action rather than a top-level
 * uninstall.php: the SDK registers its own uninstall handler for the plugin
 * file, and WordPress only ever runs one of the two, so a sibling uninstall.php
 * would silently never fire (and Freemius rejects the file at deploy time).
 *
 * Received e-invoices are deliberately NOT dropped by default — they may be
 * subject to legal retention. A site owner who really wants a full wipe can
 * define BRIGHTFERN_UNINSTALL_DROP_DATA in wp-config.php.
 */
function brightfern_uninstall_cleanup() : void {
    delete_option( 'brightfern_db_version' );
    // Settings (may contain provider credentials) are always removed.
    delete_option( 'brightfern_settings' );
    // Scheduled jobs should stop regardless of whether data is kept.
    foreach ( array('brightfern_poll_inbound', 'brightfern_resolve_outbound') as $brightfern_cron_hook ) {
        $timestamp = wp_next_scheduled( $brightfern_cron_hook );
        if ( $timestamp ) {
            wp_unschedule_event( $timestamp, $brightfern_cron_hook );
        }
    }
    if ( !defined( 'BRIGHTFERN_UNINSTALL_DROP_DATA' ) || !BRIGHTFERN_UNINSTALL_DROP_DATA ) {
        return;
    }
    global $wpdb;
    $table = $wpdb->prefix . 'brightfern_inbound';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Opt-in full wipe of our own custom table; its name comes from $wpdb->prefix, never user input, and table names cannot be bound via prepare().
    $wpdb->query( "DROP TABLE IF EXISTS {$table}" );
}

/**
 * The one gate every Pro feature checks.
 *
 * Centralising this means: (a) the SEND/RECEIVE/national-format code reads
 * `if ( ! brightfern_is_pro() ) return;` and never knows about Freemius, and
 * (b) tests and local dev can force-enable Pro via the constant or filter
 * without a license.
 *
 * @return bool
 */
function brightfern_is_pro() : bool {
    // Escape hatch for local dev / CI: define( 'BRIGHTFERN_FORCE_PRO', true ) in wp-config.php.
    if ( defined( 'BRIGHTFERN_FORCE_PRO' ) && BRIGHTFERN_FORCE_PRO ) {
        return true;
    }
    $fs = ( function_exists( 'brightfern_fs' ) ? brightfern_fs() : null );
    $is_pro = ( $fs ? (bool) $fs->is_paying() : false );
    /**
     * Filter the effective Pro state. Last resort override for tests / staging.
     *
     * @param bool $is_pro
     */
    return (bool) apply_filters( 'brightfern_is_pro', $is_pro );
}

/*
 * -----------------------------------------------------------------------------
 * Autoloader + boot
 * -----------------------------------------------------------------------------
 */
$brightfern_autoload = BRIGHTFERN_DIR . 'vendor/autoload.php';
if ( file_exists( $brightfern_autoload ) ) {
    require_once $brightfern_autoload;
} else {
    // Minimal PSR-4 fallback so the plugin runs even before `composer install`.
    spl_autoload_register( static function ( string $class ) : void {
        if ( !str_starts_with( $class, 'Brightfern\\' ) ) {
            return;
        }
        $relative = str_replace( '\\', '/', substr( $class, strlen( 'Brightfern\\' ) ) );
        $path = BRIGHTFERN_DIR . 'src/' . $relative . '.php';
        if ( is_readable( $path ) ) {
            require_once $path;
        }
    } );
}
/**
 * Declare HPOS (High-Performance Order Storage) compatibility.
 * We only ever touch orders through the CRUD API ($order->get_meta / update_meta_data),
 * never raw post-meta, so we are HPOS-safe.
 */
add_action( 'before_woocommerce_init', static function () : void {
    if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', BRIGHTFERN_FILE, true );
    }
} );
/**
 * Boot the plugin once WooCommerce is known to be loaded.
 */
add_action( 'plugins_loaded', static function () : void {
    if ( !class_exists( 'WooCommerce' ) ) {
        add_action( 'admin_notices', static function () : void {
            echo '<div class="notice notice-error"><p>';
            echo esc_html__( 'Brightfern requires WooCommerce to be installed and active.', 'brightfern-e-invoicing-for-woocommerce' );
            echo '</p></div>';
        } );
        return;
    }
    ( new \Brightfern\Plugin() )->boot();
} );
/**
 * Activation: create the inbound storage table.
 */
register_activation_hook( __FILE__, static function () : void {
    \Brightfern\Inbound\InboundStore::install();
} );