<?php

defined( 'ABSPATH' ) || exit;

/**
 * Site Knowledge module.
 *
 * Exports a comprehensive `knowledge.json` snapshot of the entire WordPress
 * site — designed to be dropped into Claude Code or any other AI tool as
 * context for "learn about this site".
 *
 * Goes beyond plain-text dumps like `llms-full.txt` by capturing structure:
 *  - Site identity (name, URL, locale, permalink scheme, front-page setup, theme).
 *  - Navigation (every registered nav location + every menu, as a nested tree).
 *  - Post types (every public + custom CPT, with hierarchical nesting for
 *    page-like CPTs and SEO meta auto-detected from Yoast / Rank Math /
 *    SEOPress / All in One SEO / WP core).
 *  - Taxonomies (every public + custom tax, with hierarchical nesting for
 *    category-like taxes).
 *  - Developer info (HTTP server / PHP / WordPress / theme / plugins /
 *    database) — only emitted in developer mode.
 *
 * User controls:
 *  - Mode: Normal (content-focused) or Developer (adds environment block).
 *  - Per-post-type include checkboxes (excludes the ones not ticked).
 *  - Per-taxonomy include checkboxes.
 *  - Post status filter (publish always; draft / private / pending / future optional).
 *  - Whether to include full post_content (default off — usually too much).
 *  - Whether to include featured-image URLs.
 *  - Max items per post type (default 500, range 1–5000) as a safety cap.
 *
 * Download is triggered inline on `admin_init` via a nonced query param —
 * the same pattern as the Import/Export module to avoid admin-post.php pitfalls.
 *
 * @package BW_Dev
 */

class BW_Dev_Module_Site_Knowledge implements BW_Dev_Module_Interface {

	const QUERY_PARAM    = 'bw_dev_knowledge_action';
	const NONCE_ACTION   = 'bw_dev_knowledge_export';
	const FORMAT_NAME    = 'bw-dev-site-knowledge';
	const FORMAT_VERSION = 1;

	const ALLOWED_MODES    = array( 'normal', 'developer' );
	const ALLOWED_STATUSES = array( 'publish', 'draft', 'private', 'pending', 'future' );
	const DEFAULT_EXCLUDE  = array( 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_navigation' );

	public function slug(): string {
		return 'site_knowledge';
	}

	public function label(): string {
		return __( 'Site Knowledge', 'bw-dev' );
	}

	public function group(): string {
		return 'indexing';
	}

	public function default_settings(): array {
		return array(
			'mode'                  => 'normal',
			'post_types'            => array( 'post', 'page' ),
			'taxonomies'            => array( 'category', 'post_tag' ),
			'statuses'              => array( 'publish' ),
			'include_full_content'  => false,
			'include_featured_image'=> true,
			'max_items_per_type'    => 500,
		);
	}

	public function sanitize( array $data ): array {
		$mode = isset( $data['mode'] ) ? sanitize_key( (string) $data['mode'] ) : 'normal';
		if ( ! in_array( $mode, self::ALLOWED_MODES, true ) ) {
			$mode = 'normal';
		}

		$post_types = array();
		if ( isset( $data['post_types'] ) && is_array( $data['post_types'] ) ) {
			foreach ( $data['post_types'] as $pt ) {
				$pt = sanitize_key( (string) $pt );
				if ( '' !== $pt && post_type_exists( $pt ) ) {
					$post_types[] = $pt;
				}
			}
		}

		$taxonomies = array();
		if ( isset( $data['taxonomies'] ) && is_array( $data['taxonomies'] ) ) {
			foreach ( $data['taxonomies'] as $tx ) {
				$tx = sanitize_key( (string) $tx );
				if ( '' !== $tx && taxonomy_exists( $tx ) ) {
					$taxonomies[] = $tx;
				}
			}
		}

		$statuses = array();
		if ( isset( $data['statuses'] ) && is_array( $data['statuses'] ) ) {
			foreach ( $data['statuses'] as $st ) {
				$st = sanitize_key( (string) $st );
				if ( in_array( $st, self::ALLOWED_STATUSES, true ) ) {
					$statuses[] = $st;
				}
			}
		}
		if ( empty( $statuses ) ) {
			$statuses = array( 'publish' );
		}

		$max = isset( $data['max_items_per_type'] ) ? (int) $data['max_items_per_type'] : 500;
		$max = max( 1, min( 5000, $max ) );

		return array(
			'mode'                   => $mode,
			'post_types'             => array_values( array_unique( $post_types ) ),
			'taxonomies'             => array_values( array_unique( $taxonomies ) ),
			'statuses'               => array_values( array_unique( $statuses ) ),
			'include_full_content'   => ! empty( $data['include_full_content'] ),
			'include_featured_image' => ! empty( $data['include_featured_image'] ),
			'max_items_per_type'     => $max,
		);
	}

	public function register(): void {
		add_action( 'admin_init', array( $this, 'maybe_handle_action' ) );
	}

	/* ---------------------------------------------------------------------
	 * Action dispatch
	 * ------------------------------------------------------------------- */

	public function maybe_handle_action(): void {
		if ( ! is_admin() ) {
			return;
		}
		$query = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page  = isset( $query['page'] ) ? sanitize_key( (string) $query['page'] ) : '';
		if ( BW_Dev_Admin_Page::MENU_SLUG !== $page ) {
			return;
		}
		$action = isset( $query[ self::QUERY_PARAM ] ) ? sanitize_key( (string) $query[ self::QUERY_PARAM ] ) : '';
		if ( 'download' !== $action ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to do this.', 'bw-dev' ), '', array( 'response' => 403 ) );
		}
		check_admin_referer( self::NONCE_ACTION );

		$settings = $this->resolve_settings();
		$payload  = $this->build_knowledge( $settings );

		nocache_headers();
		header( 'Content-Type: application/json; charset=' . get_bloginfo( 'charset' ) );
		header( 'Content-Disposition: attachment; filename="knowledge.json"' );
		echo wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		exit;
	}

	private function resolve_settings(): array {
		$saved    = (array) bw_dev()->settings()->get( $this->slug() );
		$defaults = $this->default_settings();
		return array_replace( $defaults, $saved );
	}

	/* ---------------------------------------------------------------------
	 * Knowledge builder
	 * ------------------------------------------------------------------- */

	private function build_knowledge( array $settings ): array {
		$payload = array(
			'_meta'      => $this->build_meta( $settings ),
			'site'       => $this->build_site_info(),
			'navigation' => $this->build_navigation(),
			'post_types' => $this->build_post_types( $settings ),
			'taxonomies' => $this->build_taxonomies( $settings ),
		);
		if ( 'developer' === $settings['mode'] ) {
			$payload['developer'] = $this->build_developer_info();
		}
		return $payload;
	}

	private function build_meta( array $settings ): array {
		return array(
			'format'         => self::FORMAT_NAME,
			'format_version' => self::FORMAT_VERSION,
			'plugin_version' => defined( 'BW_DEV_VERSION' ) ? BW_DEV_VERSION : '',
			'generated_at'   => gmdate( 'c' ),
			'source_site'    => home_url( '/' ),
			'site_name'      => get_bloginfo( 'name' ),
			'site_tagline'   => get_bloginfo( 'description' ),
			'mode'           => $settings['mode'],
			'post_types_included'  => $settings['post_types'],
			'taxonomies_included'  => $settings['taxonomies'],
			'statuses_included'    => $settings['statuses'],
			'include_full_content' => $settings['include_full_content'],
			'max_items_per_type'   => $settings['max_items_per_type'],
		);
	}

	private function build_site_info(): array {
		$theme       = wp_get_theme();
		$show_on     = (string) get_option( 'show_on_front', 'posts' );
		$front_page  = (int) get_option( 'page_on_front', 0 );
		$posts_page  = (int) get_option( 'page_for_posts', 0 );
		$front_setup = array(
			'mode' => $show_on,
		);
		if ( 'page' === $show_on ) {
			$front_setup['front_page_id'] = $front_page;
			$front_setup['front_page_url'] = $front_page ? (string) get_permalink( $front_page ) : '';
			$front_setup['posts_page_id'] = $posts_page;
			$front_setup['posts_page_url'] = $posts_page ? (string) get_permalink( $posts_page ) : '';
		}

		return array(
			'name'                => get_bloginfo( 'name' ),
			'tagline'             => get_bloginfo( 'description' ),
			'site_url'            => site_url(),
			'home_url'            => home_url(),
			'admin_url'           => admin_url(),
			'language'            => get_bloginfo( 'language' ),
			'locale'              => get_locale(),
			'charset'             => (string) get_option( 'blog_charset' ),
			'timezone'            => wp_timezone_string(),
			'date_format'         => (string) get_option( 'date_format' ),
			'time_format'         => (string) get_option( 'time_format' ),
			'permalink_structure' => (string) get_option( 'permalink_structure' ) ?: '(plain)',
			'front_page'          => $front_setup,
			'active_theme'        => $theme instanceof WP_Theme
				? array(
					'name'        => $theme->get( 'Name' ),
					'version'     => $theme->get( 'Version' ),
					'stylesheet'  => $theme->get_stylesheet(),
					'template'    => $theme->get_template(),
					'parent_name' => $theme->parent() instanceof WP_Theme ? $theme->parent()->get( 'Name' ) : null,
				)
				: null,
		);
	}

	/* ---------------------------------------------------------------------
	 * Navigation
	 * ------------------------------------------------------------------- */

	private function build_navigation(): array {
		$registered_locations = get_registered_nav_menus();
		$location_assignments = get_nav_menu_locations();

		$locations = array();
		foreach ( $registered_locations as $location_slug => $location_label ) {
			$assigned_menu_id = isset( $location_assignments[ $location_slug ] ) ? (int) $location_assignments[ $location_slug ] : 0;
			$locations[]      = array(
				'slug'             => (string) $location_slug,
				'label'            => (string) $location_label,
				'assigned_menu_id' => $assigned_menu_id ?: null,
			);
		}

		$menus = array();
		$wp_menus = wp_get_nav_menus();
		if ( ! is_wp_error( $wp_menus ) && is_array( $wp_menus ) ) {
			foreach ( $wp_menus as $menu ) {
				$menus[] = $this->build_menu( $menu, $location_assignments );
			}
		}

		return array(
			'locations' => $locations,
			'menus'     => $menus,
		);
	}

	private function build_menu( $menu, array $location_assignments ): array {
		$id    = (int) $menu->term_id;
		$items = wp_get_nav_menu_items( $id );
		if ( ! is_array( $items ) ) {
			$items = array();
		}

		$assigned_locations = array();
		foreach ( $location_assignments as $location_slug => $menu_id ) {
			if ( (int) $menu_id === $id ) {
				$assigned_locations[] = (string) $location_slug;
			}
		}

		// Map each item to a normalised array first.
		$flat = array();
		foreach ( $items as $item ) {
			$flat[ (int) $item->ID ] = array(
				'id'        => (int) $item->ID,
				'title'     => (string) $item->title,
				'url'       => (string) $item->url,
				'parent_id' => (int) $item->menu_item_parent,
				'type'      => (string) $item->type,
				'object'    => (string) $item->object,
				'object_id' => (int) $item->object_id,
				'classes'   => array_values( array_filter( array_map( 'strval', (array) $item->classes ) ) ),
				'target'    => (string) $item->target,
				'xfn'       => (string) $item->xfn,
				'children'  => array(),
			);
		}

		// Build the tree.
		$tree = array();
		foreach ( $flat as $id_ref => $node ) {
			if ( 0 !== $node['parent_id'] && isset( $flat[ $node['parent_id'] ] ) ) {
				$flat[ $node['parent_id'] ]['children'][] = &$flat[ $id_ref ];
			} else {
				$tree[] = &$flat[ $id_ref ];
			}
		}
		// Drop parent_id from the output — redundant once nested.
		$strip = function ( array &$nodes ) use ( &$strip ) {
			foreach ( $nodes as &$n ) {
				unset( $n['parent_id'] );
				if ( ! empty( $n['children'] ) ) {
					$strip( $n['children'] );
				}
			}
		};
		$strip( $tree );

		return array(
			'id'                  => $id,
			'name'                => (string) $menu->name,
			'slug'                => (string) $menu->slug,
			'description'         => (string) $menu->description,
			'item_count'          => count( $flat ),
			'assigned_locations'  => $assigned_locations,
			'items'               => $tree,
		);
	}

	/* ---------------------------------------------------------------------
	 * Post types
	 * ------------------------------------------------------------------- */

	private function build_post_types( array $settings ): array {
		$out          = array();
		$enabled      = array_flip( (array) $settings['post_types'] );
		$max          = (int) $settings['max_items_per_type'];
		$statuses     = (array) $settings['statuses'];
		$with_content = (bool) $settings['include_full_content'];
		$with_image   = (bool) $settings['include_featured_image'];

		$post_types = get_post_types( array(), 'objects' );
		foreach ( $post_types as $pt ) {
			if ( in_array( $pt->name, self::DEFAULT_EXCLUDE, true ) ) {
				continue;
			}
			if ( ! isset( $enabled[ $pt->name ] ) ) {
				continue;
			}
			$ids = get_posts(
				array(
					'post_type'        => $pt->name,
					'post_status'      => $statuses,
					'posts_per_page'   => $max,
					'fields'           => 'ids',
					'no_found_rows'    => true,
					'orderby'          => $pt->hierarchical ? array( 'menu_order' => 'ASC', 'title' => 'ASC' ) : array( 'date' => 'DESC' ),
				)
			);
			$items = array();
			foreach ( $ids as $post_id ) {
				$items[ (int) $post_id ] = $this->build_post_item( (int) $post_id, $with_content, $with_image );
			}

			// Hierarchical nesting via parent links.
			if ( $pt->hierarchical && ! empty( $items ) ) {
				$tree = array();
				foreach ( $items as $id_ref => $node ) {
					$parent = (int) $node['parent_id'];
					if ( 0 !== $parent && isset( $items[ $parent ] ) ) {
						$items[ $parent ]['children'][] = &$items[ $id_ref ];
					} else {
						$tree[] = &$items[ $id_ref ];
					}
				}
				$strip = function ( array &$nodes ) use ( &$strip ) {
					foreach ( $nodes as &$n ) {
						unset( $n['parent_id'] );
						if ( ! empty( $n['children'] ) ) {
							$strip( $n['children'] );
						}
					}
				};
				$strip( $tree );
				$rendered_items = $tree;
			} else {
				$rendered_items = array();
				foreach ( $items as $node ) {
					unset( $node['parent_id'], $node['children'] );
					$rendered_items[] = $node;
				}
			}

			$out[] = array(
				'slug'         => (string) $pt->name,
				'label'        => (string) $pt->labels->name,
				'singular'     => (string) $pt->labels->singular_name,
				'public'       => (bool) $pt->public,
				'hierarchical' => (bool) $pt->hierarchical,
				'has_archive'  => (bool) $pt->has_archive,
				'rest_base'    => (string) ( $pt->rest_base ?: $pt->name ),
				'supports'     => array_keys( (array) get_all_post_type_supports( $pt->name ) ),
				'taxonomies'   => array_values( (array) get_object_taxonomies( $pt->name ) ),
				'archive_url'  => $pt->has_archive ? (string) get_post_type_archive_link( $pt->name ) : null,
				'total_in_db'  => array_sum( (array) wp_count_posts( $pt->name ) ),
				'returned'     => count( $ids ),
				'items'        => $rendered_items,
			);
		}

		return $out;
	}

	private function build_post_item( int $post_id, bool $with_content, bool $with_image ): array {
		$post = get_post( $post_id );
		if ( ! ( $post instanceof WP_Post ) ) {
			return array();
		}
		$author = get_userdata( (int) $post->post_author );

		$entry = array(
			'id'           => (int) $post->ID,
			'title'        => (string) $post->post_title,
			'slug'         => (string) $post->post_name,
			'status'       => (string) $post->post_status,
			'url'          => (string) get_permalink( $post ),
			'parent_id'    => (int) $post->post_parent,
			'menu_order'   => (int) $post->menu_order,
			'created'      => mysql2date( 'c', $post->post_date_gmt, false ),
			'modified'     => mysql2date( 'c', $post->post_modified_gmt, false ),
			'author'       => $author instanceof WP_User ? $author->display_name : null,
			'excerpt'      => wp_strip_all_tags( (string) get_the_excerpt( $post ) ),
			'seo'          => $this->collect_seo_meta( $post ),
			'taxonomies'   => $this->collect_post_taxonomies( $post ),
			'children'     => array(),
		);

		if ( $with_image ) {
			$thumb_id = (int) get_post_thumbnail_id( $post );
			$entry['featured_image'] = $thumb_id ? (string) wp_get_attachment_url( $thumb_id ) : null;
		}
		if ( $with_content ) {
			$entry['content'] = (string) $post->post_content;
		}

		return $entry;
	}

	private function collect_post_taxonomies( WP_Post $post ): array {
		$tax_names = (array) get_object_taxonomies( $post->post_type );
		$out       = array();
		foreach ( $tax_names as $tax ) {
			$terms = wp_get_object_terms( $post->ID, $tax, array( 'fields' => 'all' ) );
			if ( is_wp_error( $terms ) || empty( $terms ) ) {
				continue;
			}
			$out[ $tax ] = array_map(
				static function ( $t ) {
					return array(
						'id'   => (int) $t->term_id,
						'name' => (string) $t->name,
						'slug' => (string) $t->slug,
					);
				},
				$terms
			);
		}
		return $out;
	}

	/* ---------------------------------------------------------------------
	 * SEO plugin detection + meta read
	 * ------------------------------------------------------------------- */

	private function collect_seo_meta( WP_Post $post ): array {
		$source = $this->detect_seo_source();
		$title  = '';
		$desc   = '';
		$canon  = '';
		$og_img = '';
		$schema = '';

		switch ( $source ) {
			case 'yoast':
				$title  = (string) get_post_meta( $post->ID, '_yoast_wpseo_title', true );
				$desc   = (string) get_post_meta( $post->ID, '_yoast_wpseo_metadesc', true );
				$canon  = (string) get_post_meta( $post->ID, '_yoast_wpseo_canonical', true );
				$og_img = (string) get_post_meta( $post->ID, '_yoast_wpseo_opengraph-image', true );
				$schema = (string) get_post_meta( $post->ID, '_yoast_wpseo_schema_page_type', true );
				break;
			case 'rank_math':
				$title  = (string) get_post_meta( $post->ID, 'rank_math_title', true );
				$desc   = (string) get_post_meta( $post->ID, 'rank_math_description', true );
				$canon  = (string) get_post_meta( $post->ID, 'rank_math_canonical_url', true );
				$og_img = (string) get_post_meta( $post->ID, 'rank_math_facebook_image', true );
				$schema = (string) get_post_meta( $post->ID, 'rank_math_rich_snippet', true );
				break;
			case 'seopress':
				$title  = (string) get_post_meta( $post->ID, '_seopress_titles_title', true );
				$desc   = (string) get_post_meta( $post->ID, '_seopress_titles_desc', true );
				$canon  = (string) get_post_meta( $post->ID, '_seopress_robots_canonical', true );
				$og_img = (string) get_post_meta( $post->ID, '_seopress_social_fb_img', true );
				break;
			case 'aioseo':
				$title  = (string) get_post_meta( $post->ID, '_aioseo_title', true );
				$desc   = (string) get_post_meta( $post->ID, '_aioseo_description', true );
				$canon  = (string) get_post_meta( $post->ID, '_aioseo_canonical_url', true );
				$og_img = (string) get_post_meta( $post->ID, '_aioseo_og_image_custom_url', true );
				break;
			case 'none':
			default:
				// Fall through to defaults.
				break;
		}

		return array(
			'title'        => '' !== $title ? $title : $post->post_title,
			'description'  => '' !== $desc ? $desc : wp_strip_all_tags( (string) get_the_excerpt( $post ) ),
			'canonical'    => '' !== $canon ? $canon : (string) get_permalink( $post ),
			'og_image'     => '' !== $og_img ? $og_img : null,
			'schema_type'  => '' !== $schema ? $schema : null,
			'_source'      => $source,
		);
	}

	private function detect_seo_source(): string {
		static $cached = null;
		if ( null !== $cached ) {
			return $cached;
		}
		$active = array_flip( (array) get_option( 'active_plugins', array() ) );
		if ( isset( $active['wordpress-seo/wp-seo.php'] ) || isset( $active['wordpress-seo-premium/wp-seo-premium.php'] ) ) {
			$cached = 'yoast';
		} elseif ( isset( $active['seo-by-rank-math/rank-math.php'] ) || isset( $active['seo-by-rank-math-pro/rank-math-pro.php'] ) ) {
			$cached = 'rank_math';
		} elseif ( isset( $active['wp-seopress/seopress.php'] ) || isset( $active['wp-seopress-pro/seopress-pro.php'] ) ) {
			$cached = 'seopress';
		} elseif ( isset( $active['all-in-one-seo-pack/all_in_one_seo_pack.php'] ) || isset( $active['all-in-one-seo-pack-pro/all_in_one_seo_pack.php'] ) ) {
			$cached = 'aioseo';
		} else {
			$cached = 'none';
		}
		return $cached;
	}

	/* ---------------------------------------------------------------------
	 * Taxonomies
	 * ------------------------------------------------------------------- */

	private function build_taxonomies( array $settings ): array {
		$out     = array();
		$enabled = array_flip( (array) $settings['taxonomies'] );
		$taxes   = get_taxonomies( array(), 'objects' );

		foreach ( $taxes as $tax ) {
			if ( ! isset( $enabled[ $tax->name ] ) ) {
				continue;
			}
			$terms = get_terms(
				array(
					'taxonomy'   => $tax->name,
					'hide_empty' => false,
					'number'     => 5000,
				)
			);
			if ( is_wp_error( $terms ) ) {
				$terms = array();
			}
			$flat = array();
			foreach ( $terms as $term ) {
				$flat[ (int) $term->term_id ] = array(
					'id'          => (int) $term->term_id,
					'name'        => (string) $term->name,
					'slug'        => (string) $term->slug,
					'description' => (string) $term->description,
					'count'       => (int) $term->count,
					'parent_id'   => (int) $term->parent,
					'url'         => (string) get_term_link( $term ),
					'children'    => array(),
				);
			}
			if ( $tax->hierarchical ) {
				$tree = array();
				foreach ( $flat as $id_ref => $node ) {
					$parent = (int) $node['parent_id'];
					if ( 0 !== $parent && isset( $flat[ $parent ] ) ) {
						$flat[ $parent ]['children'][] = &$flat[ $id_ref ];
					} else {
						$tree[] = &$flat[ $id_ref ];
					}
				}
				$strip = function ( array &$nodes ) use ( &$strip ) {
					foreach ( $nodes as &$n ) {
						unset( $n['parent_id'] );
						if ( ! empty( $n['children'] ) ) {
							$strip( $n['children'] );
						}
					}
				};
				$strip( $tree );
				$rendered_terms = $tree;
			} else {
				$rendered_terms = array();
				foreach ( $flat as $node ) {
					unset( $node['parent_id'], $node['children'] );
					$rendered_terms[] = $node;
				}
			}

			$out[] = array(
				'slug'         => (string) $tax->name,
				'label'        => (string) $tax->labels->name,
				'singular'     => (string) $tax->labels->singular_name,
				'hierarchical' => (bool) $tax->hierarchical,
				'public'       => (bool) $tax->public,
				'rest_base'    => (string) ( $tax->rest_base ?: $tax->name ),
				'object_types' => array_values( (array) $tax->object_type ),
				'total'        => count( $flat ),
				'terms'        => $rendered_terms,
			);
		}

		return $out;
	}

	/* ---------------------------------------------------------------------
	 * Developer info (mode = developer)
	 * ------------------------------------------------------------------- */

	private function build_developer_info(): array {
		global $wpdb;
		$active_plugin_files = (array) get_option( 'active_plugins', array() );
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		$all_plugins = get_plugins();
		$plugins     = array();
		foreach ( $all_plugins as $file => $data ) {
			$plugins[] = array(
				'file'    => (string) $file,
				'name'    => isset( $data['Name'] ) ? (string) $data['Name'] : $file,
				'version' => isset( $data['Version'] ) ? (string) $data['Version'] : '',
				'active'  => in_array( $file, $active_plugin_files, true ),
			);
		}

		$themes = array();
		foreach ( wp_get_themes() as $slug => $theme ) {
			$themes[] = array(
				'stylesheet' => (string) $slug,
				'name'       => $theme->get( 'Name' ),
				'version'    => $theme->get( 'Version' ),
				'parent'     => $theme->parent() instanceof WP_Theme ? $theme->parent()->get( 'Stylesheet' ) : null,
			);
		}

		return array(
			'server' => array(
				'software' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? (string) wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) : null,
				'hostname' => (string) gethostname(),
				'os'       => function_exists( 'php_uname' ) ? php_uname( 's' ) . ' ' . php_uname( 'r' ) : PHP_OS,
			),
			'php' => array(
				'version'           => PHP_VERSION,
				'sapi'              => php_sapi_name(),
				'memory_limit'      => ini_get( 'memory_limit' ),
				'max_execution_time' => (int) ini_get( 'max_execution_time' ),
				'upload_max_filesize' => ini_get( 'upload_max_filesize' ),
				'post_max_size'     => ini_get( 'post_max_size' ),
				'opcache_enabled'   => function_exists( 'opcache_get_status' ),
			),
			'wordpress' => array(
				'version'             => get_bloginfo( 'version' ),
				'site_url'            => site_url(),
				'home_url'            => home_url(),
				'locale'              => get_locale(),
				'multisite'           => is_multisite(),
				'debug'               => defined( 'WP_DEBUG' ) && WP_DEBUG,
				'debug_log'           => defined( 'WP_DEBUG_LOG' ) ? WP_DEBUG_LOG : null,
				'memory_limit'        => defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : null,
				'max_memory_limit'    => defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : null,
				'disallow_file_edit'  => defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT,
				'auto_updater_off'    => defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED,
				'disable_wp_cron'     => defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON,
			),
			'database' => array(
				'server_version' => $wpdb instanceof wpdb ? (string) $wpdb->db_version() : '',
				'charset'        => $wpdb instanceof wpdb ? (string) $wpdb->charset : '',
				'collation'      => $wpdb instanceof wpdb ? (string) $wpdb->collate : '',
				'table_prefix'   => $wpdb instanceof wpdb ? (string) $wpdb->prefix : '',
			),
			'themes'  => $themes,
			'plugins' => $plugins,
		);
	}

	/* ---------------------------------------------------------------------
	 * Settings tab
	 * ------------------------------------------------------------------- */

	public function render_tab(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$current = $this->resolve_settings();
		$base    = BW_Dev_Settings::OPTION . '[' . $this->slug() . ']';

		$pt_enabled  = array_flip( (array) $current['post_types'] );
		$tx_enabled  = array_flip( (array) $current['taxonomies'] );
		$st_enabled  = array_flip( (array) $current['statuses'] );

		$pt_objects = get_post_types( array( 'public' => true ), 'objects' );
		foreach ( self::DEFAULT_EXCLUDE as $skip ) {
			unset( $pt_objects[ $skip ] );
		}
		$tx_objects = get_taxonomies( array( 'public' => true ), 'objects' );

		$download_url = wp_nonce_url(
			add_query_arg(
				array(
					'page'             => BW_Dev_Admin_Page::MENU_SLUG,
					'tab'              => $this->slug(),
					self::QUERY_PARAM  => 'download',
				),
				admin_url( 'options-general.php' )
			),
			self::NONCE_ACTION
		);

		$seo_label_map = array(
			'yoast'     => 'Yoast SEO',
			'rank_math' => 'Rank Math',
			'seopress'  => 'SEOPress',
			'aioseo'    => 'All in One SEO',
			'none'      => __( 'none (WP core fallback)', 'bw-dev' ),
		);
		$detected_seo = $this->detect_seo_source();
		?>
		<p class="description">
			<?php esc_html_e( 'Export a comprehensive snapshot of this site as a structured JSON file — designed to be dropped into Claude Code or any other AI tool to give it complete context about the site.', 'bw-dev' ); ?>
		</p>

		<h3><?php esc_html_e( 'Mode', 'bw-dev' ); ?></h3>
		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row"><?php esc_html_e( 'Export mode', 'bw-dev' ); ?></th>
					<td>
						<fieldset>
							<label style="display:block;">
								<input type="radio" name="<?php echo esc_attr( $base . '[mode]' ); ?>" value="normal" <?php checked( $current['mode'], 'normal' ); ?> />
								<strong><?php esc_html_e( 'Normal', 'bw-dev' ); ?></strong> — <?php esc_html_e( 'site identity, navigation, post types, taxonomies. Best for content-focused AI tasks.', 'bw-dev' ); ?>
							</label>
							<label style="display:block;margin-top:6px;">
								<input type="radio" name="<?php echo esc_attr( $base . '[mode]' ); ?>" value="developer" <?php checked( $current['mode'], 'developer' ); ?> />
								<strong><?php esc_html_e( 'Developer', 'bw-dev' ); ?></strong> — <?php esc_html_e( 'normal + server/PHP/WP/database/themes/plugins block. Best for AI-assisted dev work.', 'bw-dev' ); ?>
							</label>
						</fieldset>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'Post types to include', 'bw-dev' ); ?></h3>
		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row"><?php esc_html_e( 'Tick the ones to export', 'bw-dev' ); ?></th>
					<td>
						<fieldset style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px 18px;">
							<?php foreach ( $pt_objects as $pt ) : ?>
								<label style="display:flex;align-items:flex-start;gap:6px;">
									<input type="checkbox" name="<?php echo esc_attr( $base . '[post_types][]' ); ?>" value="<?php echo esc_attr( $pt->name ); ?>" <?php checked( isset( $pt_enabled[ $pt->name ] ) ); ?> />
									<span>
										<?php echo esc_html( $pt->labels->singular_name ); ?>
										<br /><code style="color:#646970;font-size:11px;"><?php echo esc_html( $pt->name ); ?></code>
									</span>
								</label>
							<?php endforeach; ?>
						</fieldset>
						<p class="description" style="margin-top:8px;">
							<?php esc_html_e( 'WordPress system post types (attachments, revisions, nav-menu items, block templates, etc.) are always excluded.', 'bw-dev' ); ?>
						</p>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'Taxonomies to include', 'bw-dev' ); ?></h3>
		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row"><?php esc_html_e( 'Tick the ones to export', 'bw-dev' ); ?></th>
					<td>
						<fieldset style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px 18px;">
							<?php foreach ( $tx_objects as $tx ) : ?>
								<label style="display:flex;align-items:flex-start;gap:6px;">
									<input type="checkbox" name="<?php echo esc_attr( $base . '[taxonomies][]' ); ?>" value="<?php echo esc_attr( $tx->name ); ?>" <?php checked( isset( $tx_enabled[ $tx->name ] ) ); ?> />
									<span>
										<?php echo esc_html( $tx->labels->singular_name ); ?>
										<br /><code style="color:#646970;font-size:11px;"><?php echo esc_html( $tx->name ); ?></code>
									</span>
								</label>
							<?php endforeach; ?>
						</fieldset>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'Filters', 'bw-dev' ); ?></h3>
		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row"><?php esc_html_e( 'Post statuses', 'bw-dev' ); ?></th>
					<td>
						<fieldset>
							<?php foreach ( self::ALLOWED_STATUSES as $st ) : ?>
								<label style="display:inline-block;margin-right:14px;">
									<input type="checkbox" name="<?php echo esc_attr( $base . '[statuses][]' ); ?>" value="<?php echo esc_attr( $st ); ?>" <?php checked( isset( $st_enabled[ $st ] ) ); ?> />
									<?php echo esc_html( $st ); ?>
								</label>
							<?php endforeach; ?>
						</fieldset>
						<p class="description"><?php esc_html_e( '"publish" is always restored if you uncheck everything.', 'bw-dev' ); ?></p>
					</td>
				</tr>
				<tr>
					<th scope="row"><?php esc_html_e( 'Per-post extras', 'bw-dev' ); ?></th>
					<td>
						<label style="display:block;margin:4px 0;">
							<input type="checkbox" name="<?php echo esc_attr( $base . '[include_full_content]' ); ?>" value="1" <?php checked( ! empty( $current['include_full_content'] ) ); ?> />
							<?php esc_html_e( 'Include full post content (HTML body). Off by default — usually too large for AI context.', 'bw-dev' ); ?>
						</label>
						<label style="display:block;margin:4px 0;">
							<input type="checkbox" name="<?php echo esc_attr( $base . '[include_featured_image]' ); ?>" value="1" <?php checked( ! empty( $current['include_featured_image'] ) ); ?> />
							<?php esc_html_e( 'Include featured image URLs.', 'bw-dev' ); ?>
						</label>
					</td>
				</tr>
				<tr>
					<th scope="row"><label for="bw-dev-knowledge-max"><?php esc_html_e( 'Max items per post type', 'bw-dev' ); ?></label></th>
					<td>
						<input type="number" id="bw-dev-knowledge-max" name="<?php echo esc_attr( $base . '[max_items_per_type]' ); ?>" value="<?php echo esc_attr( (string) (int) $current['max_items_per_type'] ); ?>" min="1" max="5000" step="50" class="small-text" />
						<p class="description"><?php esc_html_e( 'Safety cap. Default 500. Range 1–5000.', 'bw-dev' ); ?></p>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'SEO meta source', 'bw-dev' ); ?></h3>
		<p>
			<?php
			printf(
				/* translators: %s: detected SEO plugin name */
				esc_html__( 'Auto-detected: %s. Each exported post includes a `seo` block with title, description, canonical, og_image where available, and a `_source` field naming the plugin (or "none" for the WP core fallback).', 'bw-dev' ),
				'<strong>' . esc_html( $seo_label_map[ $detected_seo ] ?? $detected_seo ) . '</strong>'
			);
			?>
		</p>

		<h3 style="margin-top:30px;"><?php esc_html_e( 'Save settings, then generate', 'bw-dev' ); ?></h3>
		<p class="description"><?php esc_html_e( 'Save changes first (button at the bottom), then click below to download. The download uses your most recently saved settings.', 'bw-dev' ); ?></p>
		<p>
			<a class="button button-primary" href="<?php echo esc_url( $download_url ); ?>">
				<span class="dashicons dashicons-download" style="font-size:16px;width:16px;height:16px;vertical-align:-3px;margin-right:4px;"></span>
				<?php esc_html_e( 'Download knowledge.json', 'bw-dev' ); ?>
			</a>
		</p>

		<h3 style="margin-top:30px;"><?php esc_html_e( 'What knowledge.json contains', 'bw-dev' ); ?></h3>
		<ul style="list-style:disc;margin-left:20px;">
			<li><strong>_meta</strong> — format/version, plugin version, generated_at ISO timestamp, source site URL, site name + tagline, the mode + filter choices used to build it.</li>
			<li><strong>site</strong> — name, tagline, URLs, locale, charset, timezone, date/time formats, permalink structure, front-page setup (static page or latest posts), active theme (name, version, stylesheet, template, parent).</li>
			<li><strong>navigation</strong> — every registered theme nav location + every menu (id, name, slug, description, assigned locations) with items as a nested tree (title, URL, type, object/object_id, classes, target).</li>
			<li><strong>post_types</strong> — one block per ticked post type, with metadata (slug, label, public, hierarchical, has_archive, rest_base, supports, taxonomies, archive_url, total_in_db, returned) and an items[] array (id, title, slug, status, URL, parent_id, menu_order, dates, author, excerpt, SEO meta, taxonomy assignments, optional featured image + full content). Hierarchical post types nest children[].</li>
			<li><strong>taxonomies</strong> — one block per ticked taxonomy, with metadata + terms (id, name, slug, description, count, URL). Hierarchical taxonomies nest children[].</li>
			<li><strong>developer</strong> (developer mode only) — server (software / hostname / OS), PHP (version, SAPI, memory limit, max execution, opcache flag), WordPress (version, URLs, locale, multisite, debug flags, memory limits, key constants), database (server version, charset, collation, table prefix), themes list, plugins list (file, name, version, active).</li>
		</ul>
		<?php
	}

	public function uninstall(): void {
		// Settings live under bw_dev_settings — root option drop covers them.
	}
}
