<?php
/**
 * Broken Links Checker Module
 *
 * Scan your site for broken links.
 *
 * @package BlaminhorEssentials
 */

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

/**
 * Broken Links module class
 */
class Blaminhor_Essentials_Module_Broken_Links extends Blaminhor_Essentials_Module {

    /**
     * Module ID
     *
     * @var string
     */
    protected $id = 'broken-links';

    /**
     * Module name
     *
     * @var string
     */
    protected $name = 'Broken Links Checker';

    /**
     * Table name for storing broken links
     *
     * @var string
     */
    private $table_name;

    /**
     * Database version
     *
     * @var string
     */
    private $db_version = '1.0';

    /**
     * Initialize module
     */
    protected function init() {
        global $wpdb;

        $this->name        = __( 'Broken Links Checker', 'blaminhor-essentials' );
        $this->description = __( 'Scan your site for broken internal and external links.', 'blaminhor-essentials' );
        $this->table_name  = $wpdb->prefix . 'blaminhor_essentials_broken_links';

        $this->maybe_create_table();
    }

    /**
     * Get default settings
     *
     * @return array
     */
    protected function get_default_settings() {
        return array(
            'post_types'       => array( 'post', 'page' ),
            'check_external'   => true,
            'check_internal'   => true,
            'timeout'          => 10,
            'last_scan'        => 0,
            'posts_per_batch'  => 1,
        );
    }

    /**
     * Register hooks
     */
    protected function register_hooks() {
        add_action( 'admin_init', array( $this, 'handle_settings_submission' ) );

        // AJAX handlers.
        add_action( 'wp_ajax_blaminhor_essentials_scan_links', array( $this, 'ajax_scan_links' ) );
        add_action( 'wp_ajax_blaminhor_essentials_recheck_link', array( $this, 'ajax_recheck_link' ) );
        add_action( 'wp_ajax_blaminhor_essentials_dismiss_link', array( $this, 'ajax_dismiss_link' ) );
        add_action( 'wp_ajax_blaminhor_essentials_clear_broken_links', array( $this, 'ajax_clear_results' ) );
    }

    /**
     * Create database table if needed
     */
    private function maybe_create_table() {
        $installed_version = get_option( 'blaminhor_essentials_broken_links_db_version', '0' );

        if ( version_compare( $installed_version, $this->db_version, '>=' ) ) {
            return;
        }

        global $wpdb;

        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            post_id bigint(20) unsigned NOT NULL,
            url varchar(2048) NOT NULL,
            anchor_text varchar(255) DEFAULT NULL,
            status_code int(11) DEFAULT NULL,
            error_message varchar(255) DEFAULT NULL,
            link_type varchar(20) NOT NULL DEFAULT 'external',
            checked_at datetime NOT NULL,
            dismissed tinyint(1) NOT NULL DEFAULT 0,
            PRIMARY KEY (id),
            KEY post_id (post_id),
            KEY status_code (status_code),
            KEY dismissed (dismissed)
        ) $charset_collate;";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );

        update_option( 'blaminhor_essentials_broken_links_db_version', $this->db_version );
    }

    /**
     * Return the broken-links table name (used by WP-CLI helpers).
     *
     * @return string
     */
    public function get_table_name() {
        return $this->table_name;
    }

    /**
     * Extract links from content
     *
     * @param string $content Post content.
     * @return array
     */
    public function extract_links( $content ) {
        $links = array();

        if ( empty( $content ) ) {
            return $links;
        }

        // Match <a> tags with href.
        preg_match_all( '/<a[^>]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/is', $content, $matches, PREG_SET_ORDER );

        foreach ( $matches as $match ) {
            $url         = $match[1];
            $anchor_text = wp_strip_all_tags( $match[2] );

            // Skip anchors, mailto, tel, javascript.
            if ( preg_match( '/^(#|mailto:|tel:|javascript:)/i', $url ) ) {
                continue;
            }

            // Determine link type.
            $site_url  = home_url();
            $link_type = ( strpos( $url, $site_url ) === 0 || strpos( $url, '/' ) === 0 ) ? 'internal' : 'external';

            $links[] = array(
                'url'         => $url,
                'anchor_text' => mb_substr( $anchor_text, 0, 255 ),
                'link_type'   => $link_type,
            );
        }

        return $links;
    }

    /**
     * Check if a URL is broken
     *
     * @param string $url URL to check.
     * @return array
     */
    public function check_url( $url ) {
        $timeout = $this->get_setting( 'timeout', 10 );

        // Convert relative URLs to absolute.
        if ( strpos( $url, '/' ) === 0 ) {
            $url = home_url( $url );
        }

        // Validate URL.
        if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
            return array(
                'is_broken'     => true,
                'status_code'   => 0,
                'error_message' => __( 'Invalid URL format', 'blaminhor-essentials' ),
            );
        }

        $response = wp_remote_head( $url, array(
            'timeout'     => $timeout,
            'redirection' => 5,
            'sslverify'   => false,
            'user-agent'  => 'Mozilla/5.0 (compatible; BlaminhorEssentials Link Checker)',
        ) );

        if ( is_wp_error( $response ) ) {
            return array(
                'is_broken'     => true,
                'status_code'   => 0,
                'error_message' => $response->get_error_message(),
            );
        }

        $status_code = wp_remote_retrieve_response_code( $response );

        // Consider 2xx and 3xx as valid.
        $is_broken = $status_code < 200 || $status_code >= 400;

        return array(
            'is_broken'     => $is_broken,
            'status_code'   => $status_code,
            /* translators: %d: HTTP status code */
            'error_message' => $is_broken ? sprintf( __( 'HTTP %d', 'blaminhor-essentials' ), $status_code ) : '',
        );
    }

    /**
     * AJAX: Scan links one post at a time with detailed feedback
     */
    public function ajax_scan_links() {
        check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        global $wpdb;

        $offset         = isset( $_POST['offset'] ) ? absint( wp_unslash( $_POST['offset'] ) ) : 0;
        $post_types     = $this->get_setting( 'post_types', array( 'post', 'page' ) );
        $check_external = $this->get_setting( 'check_external', true );
        $check_internal = $this->get_setting( 'check_internal', true );

        // Get total posts count.
        $placeholders = implode( ',', array_fill( 0, count( $post_types ), '%s' ) );
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
        $total_posts = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type IN ($placeholders) AND post_status = 'publish'",
                $post_types
            )
        );

        // Get single post.
        $post = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT ID, post_title, post_content, post_type FROM {$wpdb->posts}
                WHERE post_type IN ($placeholders) AND post_status = 'publish'
                ORDER BY ID ASC LIMIT 1 OFFSET %d",
                array_merge( $post_types, array( $offset ) )
            )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare

        if ( ! $post ) {
            // No more posts.
            $this->save_settings( array_merge( $this->settings, array( 'last_scan' => time() ) ) );

            wp_send_json_success( array(
                'complete'     => true,
                'progress'     => 100,
                'post_title'   => '',
                'links_found'  => 0,
                'broken_found' => 0,
                'broken_links' => array(),
            ) );
        }

        $links = $this->extract_links( $post->post_content );
        $links_count = count( $links );
        $broken_found = 0;
        $broken_links = array();

        foreach ( $links as $link ) {
            // Skip based on settings.
            if ( 'external' === $link['link_type'] && ! $check_external ) {
                continue;
            }
            if ( 'internal' === $link['link_type'] && ! $check_internal ) {
                continue;
            }

            $result = $this->check_url( $link['url'] );

            if ( $result['is_broken'] ) {
                $broken_found++;

                // Check if already exists.
                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $exists = $wpdb->get_var(
                    $wpdb->prepare(
                        "SELECT id FROM {$this->table_name} WHERE post_id = %d AND url = %s",
                        $post->ID,
                        $link['url']
                    )
                );
                // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared

                if ( $exists ) {
                    // Update existing.
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->update(
                        $this->table_name,
                        array(
                            'status_code'   => $result['status_code'],
                            'error_message' => $result['error_message'],
                            'checked_at'    => current_time( 'mysql' ),
                            'dismissed'     => 0,
                        ),
                        array( 'id' => $exists ),
                        array( '%d', '%s', '%s', '%d' ),
                        array( '%d' )
                    );
                    $link_id = $exists;
                } else {
                    // Insert new.
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->insert(
                        $this->table_name,
                        array(
                            'post_id'       => $post->ID,
                            'url'           => $link['url'],
                            'anchor_text'   => $link['anchor_text'],
                            'status_code'   => $result['status_code'],
                            'error_message' => $result['error_message'],
                            'link_type'     => $link['link_type'],
                            'checked_at'    => current_time( 'mysql' ),
                            'dismissed'     => 0,
                        ),
                        array( '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%d' )
                    );
                    $link_id = $wpdb->insert_id;

                    // A newly-detected broken link means the cached HTML for
                    // this post is now stale (it still shows a link that we
                    // know is dead). Notify the Cache module so it can purge
                    // just this post — not the whole site.
                    do_action( 'blaminhor_essentials_cache_purge_post_request', (int) $post->ID );
                }

                $broken_links[] = array(
                    'id'            => $link_id,
                    'url'           => $link['url'],
                    'anchor_text'   => $link['anchor_text'],
                    'error_message' => $result['error_message'],
                    'post_id'       => $post->ID,
                    'post_title'    => $post->post_title,
                    'edit_url'      => get_edit_post_link( $post->ID, 'raw' ),
                );
            }
        }

        $new_offset  = $offset + 1;
        $is_complete = $new_offset >= $total_posts;

        if ( $is_complete ) {
            $this->save_settings( array_merge( $this->settings, array( 'last_scan' => time() ) ) );
        }

        // Get post type label.
        $pt_obj    = get_post_type_object( $post->post_type );
        $pt_label  = $pt_obj ? $pt_obj->labels->singular_name : $post->post_type;

        wp_send_json_success( array(
            'offset'       => $new_offset,
            'total'        => (int) $total_posts,
            'complete'     => $is_complete,
            'progress'     => min( 100, round( ( $new_offset / max( 1, $total_posts ) ) * 100 ) ),
            'post_title'   => $post->post_title,
            'post_type'    => $pt_label,
            'links_found'  => $links_count,
            'broken_found' => $broken_found,
            'broken_links' => $broken_links,
        ) );
    }

    /**
     * AJAX: Recheck a single link
     */
    public function ajax_recheck_link() {
        check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        global $wpdb;

        $link_id = isset( $_POST['link_id'] ) ? absint( wp_unslash( $_POST['link_id'] ) ) : 0;

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $link = $wpdb->get_row(
            $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE id = %d", $link_id )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        if ( ! $link ) {
            wp_send_json_error( array( 'message' => __( 'Link not found.', 'blaminhor-essentials' ) ) );
        }

        $result = $this->check_url( $link->url );

        if ( $result['is_broken'] ) {
            // Update status.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->update(
                $this->table_name,
                array(
                    'status_code'   => $result['status_code'],
                    'error_message' => $result['error_message'],
                    'checked_at'    => current_time( 'mysql' ),
                ),
                array( 'id' => $link_id ),
                array( '%d', '%s', '%s' ),
                array( '%d' )
            );

            wp_send_json_success( array(
                'still_broken' => true,
                'status_code'  => $result['status_code'],
                'error'        => $result['error_message'],
            ) );
        } else {
            // Remove from table - link is fixed.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->delete( $this->table_name, array( 'id' => $link_id ), array( '%d' ) );

            wp_send_json_success( array(
                'still_broken' => false,
                'message'      => __( 'Link is now working!', 'blaminhor-essentials' ),
            ) );
        }
    }

    /**
     * AJAX: Dismiss a link
     */
    public function ajax_dismiss_link() {
        check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        global $wpdb;

        $link_id = isset( $_POST['link_id'] ) ? absint( wp_unslash( $_POST['link_id'] ) ) : 0;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update(
            $this->table_name,
            array( 'dismissed' => 1 ),
            array( 'id' => $link_id ),
            array( '%d' ),
            array( '%d' )
        );

        wp_send_json_success();
    }

    /**
     * AJAX: Clear all results
     */
    public function ajax_clear_results() {
        check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $wpdb->query( "TRUNCATE TABLE `{$this->table_name}`" );

        $this->save_settings( array_merge( $this->settings, array( 'last_scan' => 0 ) ) );

        wp_send_json_success( array( 'message' => __( 'Results cleared.', 'blaminhor-essentials' ) ) );
    }

    /**
     * Get broken links from database
     *
     * Public so WP-CLI (and future REST endpoints) can list the scanner's
     * output without duplicating the JOIN against wp_posts.
     *
     * @param bool $include_dismissed Include dismissed links.
     * @return array
     */
    public function get_broken_links( $include_dismissed = false ) {
        global $wpdb;

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
        if ( $include_dismissed ) {
            return $wpdb->get_results(
                "SELECT bl.*, p.post_title
                FROM {$this->table_name} bl
                LEFT JOIN {$wpdb->posts} p ON bl.post_id = p.ID
                ORDER BY bl.checked_at DESC"
            );
        }

        return $wpdb->get_results(
            "SELECT bl.*, p.post_title
            FROM {$this->table_name} bl
            LEFT JOIN {$wpdb->posts} p ON bl.post_id = p.ID
            WHERE dismissed = 0
            ORDER BY bl.checked_at DESC"
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
    }

    /**
     * Process settings
     *
     * @param array $data Form data.
     */
    protected function process_settings( $data ) {
        $settings = $this->settings;

        $settings['post_types'] = array();
        if ( isset( $data['post_types'] ) && is_array( $data['post_types'] ) ) {
            $settings['post_types'] = array_map( 'sanitize_key', $data['post_types'] );
        }

        $settings['check_external'] = isset( $data['check_external'] );
        $settings['check_internal'] = isset( $data['check_internal'] );
        $settings['timeout']        = isset( $data['timeout'] ) ? absint( $data['timeout'] ) : 10;

        $this->save_settings( $settings );

        add_action( 'admin_notices', function () {
            blaminhor_essentials_admin_notice( __( 'Settings saved.', 'blaminhor-essentials' ), 'success' );
        } );
    }

    /**
     * Render settings page
     */
    public function render_settings_page() {
        $broken_links = $this->get_broken_links();
        $last_scan    = $this->get_setting( 'last_scan', 0 );
        $post_types   = blaminhor_essentials_get_post_types();
        $enabled_pts  = $this->get_setting( 'post_types', array( 'post', 'page' ) );
        ?>
        <div class="wrap blaminhor-essentials-wrap">
            <div class="blaminhor-essentials-header">
                <h1><?php esc_html_e( 'Broken Links Checker', 'blaminhor-essentials' ); ?></h1>
                <p class="blaminhor-essentials-tagline"><?php esc_html_e( 'Scan your site for broken internal and external links.', 'blaminhor-essentials' ); ?></p>
            </div>

            <div class="blaminhor-essentials-tabs-wrapper">
                <div class="blaminhor-essentials-tabs">
                    <button class="blaminhor-essentials-tab active" data-tab="scan">
                        <?php esc_html_e( 'Scan', 'blaminhor-essentials' ); ?>
                        <?php if ( count( $broken_links ) > 0 ) : ?>
                            <span class="ap-badge-error"><?php echo esc_html( count( $broken_links ) ); ?></span>
                        <?php endif; ?>
                    </button>
                    <button class="blaminhor-essentials-tab" data-tab="settings"><?php esc_html_e( 'Settings', 'blaminhor-essentials' ); ?></button>
                </div>

                <!-- Scan Tab -->
                <div class="blaminhor-essentials-tab-content active" data-tab-content="scan">

                    <?php if ( $last_scan > 0 ) : ?>
                        <p class="description ap-mb-md">
                            <?php
                            printf(
                                /* translators: %s: Date and time of last scan */
                                esc_html__( 'Last scan: %s', 'blaminhor-essentials' ),
                                '<strong>' . esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $last_scan ) ) . '</strong>'
                            );
                            ?>
                        </p>
                    <?php endif; ?>

                    <!-- Scan Controls -->
                    <div class="ap-scan-controls">
                        <button type="button" id="start-scan" class="button button-primary button-hero">
                            <span class="dashicons dashicons-search ap-dashicon-align"></span>
                            <?php esc_html_e( 'Start Scan', 'blaminhor-essentials' ); ?>
                        </button>
                        <button type="button" id="stop-scan" class="button button-secondary ap-hidden">
                            <?php esc_html_e( 'Stop', 'blaminhor-essentials' ); ?>
                        </button>
                        <?php if ( count( $broken_links ) > 0 ) : ?>
                        <button type="button" id="clear-results" class="button button-secondary">
                            <?php esc_html_e( 'Clear Results', 'blaminhor-essentials' ); ?>
                        </button>
                        <?php endif; ?>
                    </div>

                    <!-- Progress Section -->
                    <div id="scan-progress" class="ap-hidden ap-mt-lg ap-mb-lg">
                        <div class="ap-progress-header">
                            <span id="scan-status"><?php esc_html_e( 'Preparing...', 'blaminhor-essentials' ); ?></span>
                            <span id="scan-count">0 / 0</span>
                        </div>
                        <div class="ap-progress-bar-wrapper">
                            <div class="ap-progress-bar" id="scan-bar" style="width: 0%;"></div>
                        </div>
                    </div>

                    <!-- Scan Log -->
                    <div id="scan-log" class="ap-scan-log ap-hidden"></div>

                    <!-- Broken Links Section -->
                    <div id="broken-links-section" <?php echo empty( $broken_links ) ? 'class="ap-hidden"' : ''; ?>>
                        <h3 id="broken-links-title">
                            <?php
                            if ( ! empty( $broken_links ) ) {
                                printf(
                                    /* translators: %d: Number of broken links */
                                    esc_html( _n( '%d Broken Link', '%d Broken Links', count( $broken_links ), 'blaminhor-essentials' ) ),
                                    count( $broken_links )
                                );
                            }
                            ?>
                        </h3>
                        <table class="widefat striped ap-sortable-table" id="broken-links-table">
                            <thead>
                                <tr>
                                    <th class="ap-sortable" data-sort="string"><?php esc_html_e( 'Source', 'blaminhor-essentials' ); ?> <span class="ap-sort-icon"></span></th>
                                    <th class="ap-sortable" data-sort="string"><?php esc_html_e( 'Broken Link', 'blaminhor-essentials' ); ?> <span class="ap-sort-icon"></span></th>
                                    <th class="ap-sortable" data-sort="string"><?php esc_html_e( 'Status', 'blaminhor-essentials' ); ?> <span class="ap-sort-icon"></span></th>
                                    <th class="ap-col-actions-lg"><?php esc_html_e( 'Actions', 'blaminhor-essentials' ); ?></th>
                                </tr>
                            </thead>
                            <tbody id="broken-links-body">
                                <?php foreach ( $broken_links as $link ) : ?>
                                    <tr data-link-id="<?php echo esc_attr( $link->id ); ?>" data-source="<?php echo esc_attr( $link->post_title ); ?>" data-url="<?php echo esc_attr( $link->url ); ?>" data-status="<?php echo esc_attr( $link->error_message ); ?>">
                                        <td>
                                            <a href="<?php echo esc_url( get_edit_post_link( $link->post_id ) ); ?>" target="_blank">
                                                <?php echo esc_html( $link->post_title ); ?>
                                            </a>
                                        </td>
                                        <td>
                                            <code class="ap-url-code"><?php echo esc_html( $link->url ); ?></code>
                                            <?php if ( $link->anchor_text ) : ?>
                                                <br><small class="ap-anchor-text"><?php echo esc_html( $link->anchor_text ); ?></small>
                                            <?php endif; ?>
                                        </td>
                                        <td>
                                            <span class="ap-status-badge ap-status-error">
                                                <?php echo esc_html( $link->error_message ); ?>
                                            </span>
                                        </td>
                                        <td>
                                            <button type="button" class="button button-small recheck-link">
                                                <?php esc_html_e( 'Recheck', 'blaminhor-essentials' ); ?>
                                            </button>
                                            <a href="<?php echo esc_url( get_edit_post_link( $link->post_id ) ); ?>" class="button button-small" target="_blank">
                                                <?php esc_html_e( 'Edit', 'blaminhor-essentials' ); ?>
                                            </a>
                                            <button type="button" class="button button-small dismiss-link">
                                                <?php esc_html_e( 'Dismiss', 'blaminhor-essentials' ); ?>
                                            </button>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>

                    <!-- No Broken Links Message -->
                    <?php if ( empty( $broken_links ) && $last_scan > 0 ) : ?>
                    <div class="blaminhor-essentials-notice success" id="no-broken-links">
                        <span class="dashicons dashicons-yes-alt"></span>
                        <?php esc_html_e( 'No broken links found! Your site is healthy.', 'blaminhor-essentials' ); ?>
                    </div>
                    <?php endif; ?>

                </div>

                <!-- Settings Tab -->
                <div class="blaminhor-essentials-tab-content" data-tab-content="settings">
                    <form method="post">
                        <?php $this->render_nonce_field(); ?>

                        <h3><?php esc_html_e( 'Scan Settings', 'blaminhor-essentials' ); ?></h3>

                        <table class="form-table">
                            <tr>
                                <th scope="row"><?php esc_html_e( 'Post Types to Scan', 'blaminhor-essentials' ); ?></th>
                                <td>
                                    <fieldset>
                                        <?php foreach ( $post_types as $pt ) : ?>
                                            <label class="ap-label-block">
                                                <input type="checkbox" name="post_types[]" value="<?php echo esc_attr( $pt->name ); ?>"
                                                    <?php checked( in_array( $pt->name, $enabled_pts, true ) ); ?>>
                                                <?php echo esc_html( $pt->labels->singular_name ); ?>
                                            </label>
                                        <?php endforeach; ?>
                                    </fieldset>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e( 'Link Types', 'blaminhor-essentials' ); ?></th>
                                <td>
                                    <label class="ap-label-block">
                                        <input type="checkbox" name="check_external" <?php checked( $this->get_setting( 'check_external', true ) ); ?>>
                                        <?php esc_html_e( 'Check external links', 'blaminhor-essentials' ); ?>
                                    </label>
                                    <label>
                                        <input type="checkbox" name="check_internal" <?php checked( $this->get_setting( 'check_internal', true ) ); ?>>
                                        <?php esc_html_e( 'Check internal links', 'blaminhor-essentials' ); ?>
                                    </label>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row">
                                    <label for="timeout"><?php esc_html_e( 'Request Timeout', 'blaminhor-essentials' ); ?></label>
                                </th>
                                <td>
                                    <input type="number" id="timeout" name="timeout" value="<?php echo esc_attr( $this->get_setting( 'timeout', 10 ) ); ?>" min="5" max="60" class="small-text">
                                    <?php esc_html_e( 'seconds', 'blaminhor-essentials' ); ?>
                                </td>
                            </tr>
                        </table>

                        <p class="submit">
                            <button type="submit" class="button button-primary"><?php esc_html_e( 'Save Settings', 'blaminhor-essentials' ); ?></button>
                        </p>
                    </form>
                </div>
            </div>
        </div>
        <?php
    }

    /**
     * Enqueue module-specific assets
     *
     * @param string $hook Current admin page hook.
     */
    public function enqueue_assets( $hook ) {
        // Only load on this module's page.
        if ( strpos( $hook, 'blaminhor-essentials-broken-links' ) === false ) {
            return;
        }

        $broken_links = $this->get_broken_links();

        /* translators: %d: Number of broken links (singular) */
        $text_singular = __( '%d Broken Link', 'blaminhor-essentials' );
        /* translators: %d: Number of broken links (plural) */
        $text_plural = __( '%d Broken Links', 'blaminhor-essentials' );

        $config = array(
            'nonce'       => wp_create_nonce( 'blaminhor_essentials_admin' ),
            'ajaxurl'     => admin_url( 'admin-ajax.php' ),
            'totalBroken' => count( $broken_links ),
            'strings'     => array(
                'leaveWarning'  => __( 'A scan is in progress. If you leave this page, the scan will be interrupted.', 'blaminhor-essentials' ),
                'scanning'      => __( 'Scanning...', 'blaminhor-essentials' ),
                'stopped'       => __( 'Stopped', 'blaminhor-essentials' ),
                'complete'      => __( 'Complete', 'blaminhor-essentials' ),
                'links'         => __( 'links', 'blaminhor-essentials' ),
                'broken'        => __( 'broken', 'blaminhor-essentials' ),
                'scanFailed'    => __( 'Scan failed.', 'blaminhor-essentials' ),
                'requestFailed' => __( 'Request failed.', 'blaminhor-essentials' ),
                'recheck'       => __( 'Recheck', 'blaminhor-essentials' ),
                'checking'      => __( 'Checking...', 'blaminhor-essentials' ),
                'edit'          => __( 'Edit', 'blaminhor-essentials' ),
                'dismiss'       => __( 'Dismiss', 'blaminhor-essentials' ),
                'confirmClear'  => __( 'Are you sure? This will clear all scan results.', 'blaminhor-essentials' ),
                'singularTitle' => $text_singular,
                'pluralTitle'   => $text_plural,
            ),
        );

        wp_add_inline_script(
            'blaminhor-essentials-modules',
            'jQuery(document).ready(function($) { BlaminhorEssentialsBrokenLinks.init(' . wp_json_encode( $config ) . '); });'
        );
    }
}
