<?php

/**
 * The plugin bootstrap file
 *
 * @link              https://ancestorpress.com/
 * @since             0.1.0
 * @package           APFAAR_Family_Archive
 *
 * @wordpress-plugin
 * Plugin Name:       AP Family Archive
 * Description:       Publish your family history as a complete website: person pages, family group pages, surname index and full name, date and place search - conclusions only, sources cited, living people protected.
 * Version:           1.0.0
 * Requires at least: 6.2
 * Requires PHP:      8.0
 * Author:            AncestorPress
 * Author URI:        https://ancestorpress.com/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Copyright:         Copyright (c) 2026 Symposium Group Ltd
 * Text Domain:       ap-family-archive
 * Domain Path:       /languages
 *
 * Premium functionality is not included in the WordPress.org Free package; it is
 * supplied only in the separately distributed Pro package.
 */
// If this file is called directly, abort.
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * Current plugin version.
 */
define( 'APFAAR_VERSION', '1.0.0' );
/**
 * Plugin directory path and URL constants.
 */
define( 'APFAAR_PATH', plugin_dir_path( __FILE__ ) );
define( 'APFAAR_DIR', plugin_dir_path( __FILE__ ) );
define( 'APFAAR_FILE', __FILE__ );
define( 'APFAAR_URL', plugin_dir_url( __FILE__ ) );
// ── AncestorPress Core Library (ANCP Core) ────────────────────────────────────
// Embedded, versioned core bundled with this plugin (Freemius SDK pattern).
// Requiring the entry registers this bundle with the shared loader; the loader
// boots the newest registered version on plugins_loaded (priority 1). The core
// owns ALL genealogical data (people, families, events, sources, media...);
// this plugin consumes it exclusively for display and never creates or renames
// core tables. Never edit ancp-core/ in place (canonical source: ..\ancp-core,
// synced by its sync-core.ps1).
require_once APFAAR_PATH . 'ancp-core/ancp-core.php';
// ── Freemius SDK integration ──────────────────────────────────────────────────
// The separate package ships as a separately distributed package. There are no
// usage caps and no trialware in this plugin.
// Free-to-extension-package auto-deactivation.
if ( function_exists( 'apfaar_fs' ) ) {
    apfaar_fs()->set_basename( false, __FILE__ );
} else {
    if ( !function_exists( 'apfaar_fs' ) ) {
        /**
         * The plugin's Freemius instance.
         *
         * @return Freemius
         */
        function apfaar_fs() {
            global $apfaar_fs;
            if ( !isset( $apfaar_fs ) ) {
                // Activate multisite network integration.
                if ( !defined( 'WP_FS__PRODUCT_34243_MULTISITE' ) ) {
                    define( 'WP_FS__PRODUCT_34243_MULTISITE', true );
                }
                require_once APFAAR_DIR . 'vendor/freemius/start.php';
                $apfaar_fs = fs_dynamic_init( array(
                    'id'               => '34243',
                    'slug'             => 'ap-family-archive',
                    'type'             => 'plugin',
                    'public_key'       => 'pk_9e63caf25be28ac936d1558620f89',
                    'is_premium'       => false,
                    'premium_suffix'   => 'Pro',
                    'has_addons'       => false,
                    'has_paid_plans'   => true,
                    'is_org_compliant' => true,
                    'menu'             => array(
                        'slug'    => 'apfaar',
                        'support' => false,
                    ),
                    'is_live'          => true,
                ) );
            }
            return $apfaar_fs;
        }

        apfaar_fs();
        do_action( 'apfaar_fs_loaded' );
    }
    /**
     * Keep Freemius screens within the plugin page.
     *
     * @return string
     */
    function apfaar_fs_settings_url() {
        return admin_url( 'admin.php?page=apfaar' );
    }

    apfaar_fs()->add_filter( 'connect_url', 'apfaar_fs_settings_url' );
    apfaar_fs()->add_filter( 'after_skip_url', 'apfaar_fs_settings_url' );
    apfaar_fs()->add_filter( 'after_connect_url', 'apfaar_fs_settings_url' );
    apfaar_fs()->add_filter( 'after_pending_connect_url', 'apfaar_fs_settings_url' );
}
/**
 * Cleanup on uninstall via the Freemius hook (replaces uninstall.php - the
 * SDK owns the uninstall flow). Options always go; mapped archive pages are
 * trashed (recoverable) only on explicit opt-in. Core (ancp_) tables are
 * NEVER touched here - core data is collectively owned by the suite
 * (spec-00 Section 8).
 *
 * @return void
 */
function apfaar_fs_uninstall_cleanup() {
    $opted_in = (bool) get_option( 'apfaar_delete_data_on_uninstall' );
    foreach ( array(
        'hub',
        'surnames',
        'search',
        'person',
        'family',
        'places',
        'anniversaries',
        'whatsnew',
        'mostwanted',
        'statistics',
        'ahnentafel',
        'descendants',
        'source',
        'media',
        'mediaitem',
        'relationship'
    ) as $page_key ) {
        $page_id = absint( get_option( 'apfaar_page_' . $page_key, 0 ) );
        if ( $opted_in && $page_id ) {
            wp_trash_post( $page_id );
        }
        delete_option( 'apfaar_page_' . $page_key );
    }
    delete_option( 'apfaar_version' );
    delete_option( 'apfaar_db_version' );
    delete_option( 'apfaar_activation_time' );
    delete_option( 'apfaar_gedcom' );
    delete_option( 'apfaar_gedcoms_extra' );
    delete_option( 'apfaar_hub_intro' );
    delete_option( 'apfaar_whatsnew_days' );
    delete_option( 'apfaar_whatsnew_max' );
    delete_option( 'apfaar_most_wanted' );
    delete_option( 'apfaar_saved_reports' );
    delete_option( 'apfaar_display_style' );
    delete_option( 'apfaar_visitor_export' );
    delete_option( 'apfaar_delete_data_on_uninstall' );
}

apfaar_fs()->add_action( 'after_uninstall', 'apfaar_fs_uninstall_cleanup' );
// ── Activation / deactivation hooks ──────────────────────────────────────────
/**
 * Activate: boot the core, create/upgrade tables.
 *
 * @return void
 */
function apfaar_activate() {
    // Boot the core explicitly: the activation hook can fire without a fresh
    // plugins_loaded for the activating plugin, so we cannot rely on the
    // plugins_loaded boot here. Idempotent - a no-op if already booted.
    if ( class_exists( 'AncestorPress\\Core\\Loader' ) ) {
        \AncestorPress\Core\Loader::boot();
    }
    require_once APFAAR_PATH . 'includes/class-apfa-activator.php';
    APFAAR_Activator::activate();
}

register_activation_hook( __FILE__, 'apfaar_activate' );
/**
 * Deactivate (non-destructive - see the deactivator).
 *
 * @return void
 */
function apfaar_deactivate() {
    require_once APFAAR_PATH . 'includes/class-apfa-deactivator.php';
    APFAAR_Deactivator::deactivate();
}

register_deactivation_hook( __FILE__, 'apfaar_deactivate' );
// ── Load and run the plugin ───────────────────────────────────────────────────
require_once APFAAR_PATH . 'includes/class-apfa.php';
require_once APFAAR_PATH . 'includes/class-apfa-activator.php';
// Support assistant (vendored shared help widget, free). Local answers run
// on the AI provider the site administrator configures in WordPress - no
// account, token or licence of any kind. The OPTIONAL external support
// service is a separate, consent-gated path (see the readme's External
// services section).
require_once APFAAR_PATH . 'includes/class-apfa-support-copilot-boot.php';
// Register before the newest-wins loader negotiates (plugins_loaded 99) so
// this copy takes part in that negotiation. The config is queued separately
// on `init`, since it carries translated strings.
add_action( 'plugins_loaded', array('APFAAR_Support_Copilot_Boot', 'register') );
/**
 * Run core-schema creation/update check on admin loads after plugin updates.
 *
 * The activation hook only fires on first activation. When the plugin is
 * updated via FTP or dashboard upload without deactivation, the hook does
 * not re-fire. This plugin owns no tables of its own in 0.1.0, but it must
 * still make sure the shared core schema is present and current (the same
 * self-heal pattern as AP Family Trees / AP One-Name Studies).
 *
 * @return void
 */
/**
 * One-time migration from this plugin's former option-key prefix.
 *
 * Development builds up to 0.3.14 stored options under "apfa_" and created
 * the archive pages with the old shortcode names. Installs updated in place
 * get every stored key copied to its current name, the old keys removed,
 * suggestion posts re-typed and the plugin-created pages' shortcodes
 * rewritten. New installs skip in one option read.
 *
 * @return void
 */
function apfaar_maybe_migrate_prefix() {
    if ( false !== get_option( 'apfaar_version', false ) || false === get_option( 'apfa_version', false ) ) {
        // Finish a content pass a previous half-run left pending.
        if ( get_option( 'apfaar_prefix_content_pending' ) ) {
            add_action( 'init', 'apfaar_migrate_prefix_content', 20 );
        }
        return;
    }
    global $wpdb;
    // Copy every stored option to its current key, then drop the old one.
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-time key enumeration during the rename migration.
    $old_names = $wpdb->get_col( $wpdb->prepare( 'SELECT option_name FROM %i WHERE option_name LIKE %s', $wpdb->options, $wpdb->esc_like( 'apfa_' ) . '%' ) );
    foreach ( (array) $old_names as $old_name ) {
        $new_name = 'apfaar_' . substr( $old_name, 5 );
        if ( false === get_option( $new_name, false ) ) {
            add_option( $new_name, get_option( $old_name ) );
        }
        delete_option( $old_name );
    }
    // Post-content work must wait for init (post types registered, theme
    // loaded - wp_update_post at plugins_loaded breaks builder themes).
    update_option( 'apfaar_prefix_content_pending', 1 );
    add_action( 'init', 'apfaar_migrate_prefix_content', 20 );
}

add_action( 'plugins_loaded', 'apfaar_maybe_migrate_prefix', 4 );
/**
 * Deferred half of the prefix migration: re-type suggestion posts and rewrite
 * the plugin-created pages' shortcodes. Runs once on init, then clears its
 * pending flag.
 *
 * @return void
 */
function apfaar_migrate_prefix_content() {
    global $wpdb;
    // Contributor suggestions keep their history under the current type.
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-time post-type rename during the prefix migration.
    $wpdb->update( $wpdb->posts, array(
        'post_type' => 'apfaar_suggestion',
    ), array(
        'post_type' => 'apfa_suggestion',
    ) );
    // The plugin-created pages carry shortcodes under the old names.
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-time page-id enumeration during the prefix migration.
    $page_opts = $wpdb->get_results( $wpdb->prepare( 'SELECT option_name, option_value FROM %i WHERE option_name LIKE %s', $wpdb->options, $wpdb->esc_like( 'apfaar_page_' ) . '%' ) );
    foreach ( (array) $page_opts as $opt ) {
        $post = get_post( (int) $opt->option_value );
        if ( !$post || false === strpos( (string) $post->post_content, '[apfa_' ) ) {
            continue;
        }
        wp_update_post( array(
            'ID'           => $post->ID,
            'post_content' => wp_slash( str_replace( '[apfa_', '[apfaar_', $post->post_content ) ),
        ) );
    }
    delete_option( 'apfaar_prefix_content_pending' );
}

function apfaar_check_db_version() {
    // Admin-only: front-end loads must not pay for a schema probe.
    if ( !is_admin() ) {
        return;
    }
    if ( APFAAR_VERSION !== get_option( 'apfaar_db_version' ) || class_exists( 'AncestorPress\\Core\\Schema' ) && !\AncestorPress\Core\Schema::is_current() ) {
        APFAAR_Activator::create_tables();
        update_option( 'apfaar_db_version', APFAAR_VERSION );
    }
}

add_action( 'plugins_loaded', 'apfaar_check_db_version' );
// Co-existence guard notice: shown while the core prefix migration is blocked
// because an old, un-adopted AP Family Trees is still on the legacy tables.
add_action( 'admin_notices', array('APFAAR_Activator', 'render_coexistence_notice') );
/**
 * Boot the plugin orchestrator.
 *
 * @return void
 */
function apfaar_run() {
    $plugin = new APFAAR();
    $plugin->run();
}

apfaar_run();