<?php
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * Morningscore API base URL.
 *
 * Resolution order: wp-config constant, RANK_API_BASE_URL env var, production default.
 *
 * @return string Base URL without trailing slash (e.g. https://api.morningscore.io/v1).
 */
function rank_get_api_base_url() {
    if ( ! defined( 'RANK_API_BASE_URL' ) ) {
        $env_url = getenv( 'RANK_API_BASE_URL' );
        if ( is_string( $env_url ) && $env_url !== '' ) {
            define( 'RANK_API_BASE_URL', untrailingslashit( $env_url ) );
        } else {
            define( 'RANK_API_BASE_URL', 'https://api.morningscore.io/v1' );
        }
    }

    return RANK_API_BASE_URL;
}

function rank_get_api_key() {
    return get_option( 'rank_api_key', '' );
}

/**
 * Plugin version from the main plugin file header.
 */
function rank_get_plugin_version() {
    static $version = null;

    if ( null !== $version ) {
        return $version;
    }

    if ( ! function_exists( 'get_plugin_data' ) ) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }

    $plugin_data = get_plugin_data( RANK_PLUGIN_DIR . 'rank-main.php', false, false );
    $version     = isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : '0.0.0';

    return $version;
}

// Define health check categories recognized by the plugin
function rank_get_health_check_categories() {
    return array(
        'missing_meta_description' => 'Missing Meta Description',
        'short_meta_description'   => 'Short Meta Description',
        'long_meta_description'    => 'Long Meta Description',
        'missing_title_tag'        => 'Missing Title Tag',
        'short_title_tag'          => 'Short Title Tag',
        'long_title_tag'           => 'Long Title Tag',
        'missing_alt_tags'         => 'Missing Alt Tag',
        'broken_links_external'    => 'Broken External Link',
        'broken_links_internal'    => 'Broken Internal Link',
        'duplicate_meta_description' => 'Duplicate Meta Description',
        'duplicate_title'          => 'Duplicate Title',
        'long_h1_h2_tag'           => 'Long H1/H2 Tag',
        'missing_h1_tag'           => 'Missing H1 Tag',
        'large_images'             => 'Large Image',
        // Add more categories as needed, matching API expectations
    );
}

// Map issue types to their processing requirements and display information
function rank_get_issue_type_config($issue_type = null) {
    $configs = array(
        'missing_meta_description' => array(
            'label' => 'Missing Meta Descriptions',
            'display_text' => 'Missing meta description',
            'content_type' => 'text',
            'solution_label' => 'Meta Description',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'short_meta_description' => array(
            'label' => 'Short Meta Descriptions',
            'display_text' => 'Meta description too short',
            'content_type' => 'text',
            'solution_label' => 'Suggested Meta Description',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'long_meta_description' => array(
            'label' => 'Long Meta Descriptions',
            'display_text' => 'Meta description too long',
            'content_type' => 'text',
            'solution_label' => 'Suggested Meta Description',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'missing_title_tag' => array(
            'label' => 'Missing Title Tags',
            'display_text' => 'Missing title tag',
            'content_type' => 'text',
            'solution_label' => 'Title',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'short_title_tag' => array(
            'label' => 'Short Title Tags',
            'display_text' => 'Title tag too short',
            'content_type' => 'text',
            'solution_label' => 'Suggested Title',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'long_title_tag' => array(
            'label' => 'Long Title Tags',
            'display_text' => 'Title tag too long',
            'content_type' => 'text',
            'solution_label' => 'Suggested Title',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'missing_alt_tags' => array(
            'label' => 'Missing Alt Tags',
            'display_text' => 'Missing alt text',
            'content_type' => 'text',
            'solution_label' => 'Alt Text',
            'identifier_is_url' => true,
            'handler_type' => 'database_override',
        ),
        'long_h1_h2_tag' => array(
            'label' => 'Long H1/H2 Tags',
            'display_text' => 'Heading tag too long',
            'content_type' => 'text',
            'solution_label' => 'Suggested Heading',
            'identifier_is_url' => false,
            'handler_type' => 'content_fix',
        ),
        'missing_h1_tag' => array(
            'label' => 'Missing H1 Tags',
            'display_text' => 'Missing H1 tag',
            'content_type' => 'text',
            'solution_label' => 'Suggested H1',
            'identifier_is_url' => false,
            'handler_type' => 'content_fix',
        ),
        'large_images' => array(
            'label' => 'Large Images',
            'display_text' => 'Image file too large',
            'content_type' => 'image',
            'solution_label' => 'Optimized Image',
            'identifier_is_url' => true,
            'handler_type' => 'content_fix',
        ),
        'broken_links_external' => array(
            'label' => 'Broken External Links',
            'display_text' => 'Broken external link',
            'content_type' => 'url',
            'solution_label' => 'Suggested Fixed URL',
            'identifier_is_url' => true,
            'handler_type' => 'option_suggestion',
        ),
        'duplicate_meta_description' => array(
            'label' => 'Duplicate Meta Descriptions',
            'display_text' => 'Duplicate meta description',
            'content_type' => 'text',
            'solution_label' => 'Meta Description',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'duplicate_title' => array(
            'label' => 'Duplicate Title Tags',
            'display_text' => 'Duplicate title tag',
            'content_type' => 'text',
            'solution_label' => 'Title',
            'identifier_is_url' => false,
            'handler_type' => 'database_override',
        ),
        'broken_links_internal' => array(
            'label' => 'Broken Internal Links',
            'display_text' => 'Broken internal link',
            'content_type' => 'url',
            'solution_label' => 'Suggested Fixed URL',
            'identifier_is_url' => true,
            'handler_type' => 'option_suggestion',
        ),
    );
    
    if ($issue_type !== null) {
        return isset($configs[$issue_type]) ? $configs[$issue_type] : null;
    }
    
    return $configs;
}

/**
 * Health issue types the plugin can AUTOMATICALLY fix (auto-applied).
 *
 * Derived from rank_get_issue_type_config(): only handler types that actually
 * write a fix — 'database_override' and 'content_fix'. Suggestion-only types
 * like broken links ('option_suggestion') are excluded because the plugin
 * cannot safely auto-apply them without the user choosing a target URL.
 *
 * This is the single source of truth consumed by ms-chat (via the agent
 * connection/status endpoint) so the chat agent never offers or attempts a
 * fix the plugin can't perform. Adding a new fixable issue type to
 * rank_get_issue_type_config() with a fixing handler_type is enough — this
 * list and the agent update automatically.
 *
 * @return string[] Issue type keys (e.g. missing_title_tag).
 */
function rank_get_fixable_issue_types() {
    $fixable = array();

    foreach ( rank_get_issue_type_config() as $issue_type => $config ) {
        $handler = isset( $config['handler_type'] ) ? $config['handler_type'] : '';
        if ( 'database_override' === $handler || 'content_fix' === $handler ) {
            $fixable[] = $issue_type;
        }
    }

    return $fixable;
}

/**
 * Normalize URL for consistent comparison across the system.
 * Handles URL encoding and trailing slashes.
 * Used by both frontend filters and admin/processor code.
 *
 * @param string $url The URL to normalize
 * @return string The normalized URL
 */
if ( ! function_exists('rank_normalize_url') ) {
    function rank_normalize_url($url) {
        if (empty($url)) {
            return '';
        }
        
        // Get the current WordPress site domain
        $wp_host = parse_url(home_url(), PHP_URL_HOST);
        $url_host = parse_url($url, PHP_URL_HOST);
        
        // Treat www / non-www as the same site. The Morningscore API may report a
        // page under a host variant (e.g. www.example.com) that differs from the
        // site's canonical host (e.g. example.com). Strip the leading "www." from
        // both hosts for the internal/external decision.
        $strip_www = function($host) {
            if (empty($host)) return '';
            $host = strtolower($host);
            return (strpos($host, 'www.') === 0) ? substr($host, 4) : $host;
        };
        
        // Skip normalization for external URLs (different domain, ignoring www.)
        if ($url_host && $wp_host && $strip_www($url_host) !== $strip_www($wp_host)) {
            return $url;
        }
        
        // Internal URL: canonicalize the host to the WordPress home_url() host so
        // www and non-www variants normalize to the exact same string everywhere
        // (validation, storage, already-fixed checks, and frontend matching).
        if ($url_host && $wp_host && $url_host !== $wp_host) {
            $url = preg_replace(
                '#^(https?://)' . preg_quote($url_host, '#') . '#i',
                '$1' . $wp_host,
                $url,
                1
            );
        }
        
        // Only normalize internal URLs (same domain)
        $normalized = urldecode($url);
        
        // Remove trailing slash for consistency (except for root URLs)
        $normalized = rtrim($normalized, '/');
        
        // If it's just the protocol and domain, keep it as is
        if (preg_match('#^https?://[^/]+$#', $normalized)) {
            return $normalized;
        }
        
        return $normalized;
    }
}

/**
 * Derive a stable per-heading identifier for long_h1_h2_tag fixes.
 *
 * A single page can have several over-long headings (e.g. one H1 and one H2),
 * each reported as its own issue. We track/detect those fixes per heading by
 * storing this value in the override row's object_id column, so fixing one
 * heading never marks a still-too-long heading on the same page as fixed.
 *
 * Mirrors the synthetic-object_id approach already used for per-image alt tags.
 * The same string on both the storage and detection sides yields the same id.
 *
 * @param string $heading The original heading text (the issue identifier / tag).
 * @return int Non-negative synthetic id (0 when no heading is available).
 */
if ( ! function_exists('rank_heading_identity') ) {
    function rank_heading_identity($heading) {
        $heading = is_string($heading) ? trim($heading) : '';
        if ($heading === '') {
            return 0;
        }
        // 2 billion base keeps these clear of real post/attachment IDs and the
        // 1-billion image synthetic IDs used elsewhere.
        return 2000000000 + abs(crc32($heading));
    }
}

/**
 * Lightweight debug logging that works in all contexts.
 * - Admin/AJAX/Cron: delegates to Rank_Processor::add_debug_log() if loaded.
 * - Frontend (logged-in admin): logs directly to the same debug log file.
 * - Frontend (regular visitor): does nothing (debug is off or user not admin).
 *
 * @param string $message The debug message.
 * @param string $level   Log level (default 'info').
 */
function rank_debug_log($message, $level = 'info') {
    // If the processor class is loaded (admin/ajax/cron), delegate to it
    if ( class_exists('Rank_Processor', false) ) {
        Rank_Processor::add_debug_log($message, $level);
        return;
    }
    
    // Frontend: only log for logged-in admins with debug enabled
    if ( ! is_user_logged_in() || ! current_user_can('manage_options') ) {
        return;
    }
    
    if ( ! get_option('rank_debug_enabled', false) ) {
        return;
    }
    
    // Write directly to the same log file the processor uses
    $logs_dir = RANK_PLUGIN_DIR . 'logs/';
    $log_file = $logs_dir . 'rank-debug.log';
    
    if ( ! file_exists($logs_dir) ) {
        wp_mkdir_p($logs_dir);
    }
    
    $timestamp = current_time('Y-m-d H:i:s');
    $formatted = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;
    file_put_contents($log_file, $formatted, FILE_APPEND | LOCK_EX);
}

/**
 * Transient TTL for the "recently failed" image cache. Long enough to
 * cover a single bulk-fix run (minutes–hours), short enough that the
 * user can retry the same image the next day if they want to.
 */
if ( ! defined( 'RANK_IMAGE_FAILED_TTL' ) ) {
    define( 'RANK_IMAGE_FAILED_TTL', 6 * HOUR_IN_SECONDS );
}

/**
 * Mark an image URL as "recently failed optimization" so subsequent pages
 * referencing the same image skip the API call instead of repeating the
 * failure. Cached for RANK_IMAGE_FAILED_TTL.
 *
 * Called when validation fails (not smaller than original, not a valid
 * image, etc.). Pairs with rank_is_image_recently_failed() which the
 * already-fixed check consults before spending API credits.
 *
 * @param string $image_url The image URL that failed optimization.
 * @param string $reason    Short reason label for the dedicated log.
 */
if ( ! function_exists( 'rank_mark_image_failed' ) ) {
    function rank_mark_image_failed( $image_url, $reason = '' ) {
        if ( empty( $image_url ) ) {
            return;
        }
        $key = 'rank_img_fail_' . md5( $image_url );
        set_transient( $key, array(
            'url'      => $image_url,
            'reason'   => $reason,
            'failed_at' => time(),
        ), RANK_IMAGE_FAILED_TTL );
    }
}

/**
 * Has this image URL recently failed optimization? Page-agnostic: any
 * page referencing the same image will see the same result. Used by the
 * already-fixed check to skip large_images issues whose file we already
 * know the optimizer can't improve.
 *
 * @param string $image_url The image URL to check.
 * @return bool True if a recent failure is cached for this URL.
 */
if ( ! function_exists( 'rank_is_image_recently_failed' ) ) {
    function rank_is_image_recently_failed( $image_url ) {
        if ( empty( $image_url ) ) {
            return false;
        }
        $key = 'rank_img_fail_' . md5( $image_url );
        return (bool) get_transient( $key );
    }
}