<?php
/**
 * Exposes the Starter Site kits (websites/*.json) as read-only Template Library
 * entries, so a kit page can be imported into any page with one click.
 *
 * The kits are FILES, while normal templates are `jalpi_template` posts. Rather
 * than duplicating every kit page into the database (43 posts that would drift
 * out of sync whenever a kit is rebuilt), this reads the JSON on demand and
 * presents each page as a virtual template with a string id: `kit:<kit>:<slug>`.
 *
 * @package Jalpi_Page_Builder
 */

defined( 'ABSPATH' ) || exit;

class Jalpi_Kit_Templates {

	/**
	 * Map a kit page slug onto a Template Library category. First match wins,
	 * so more specific patterns must come first ('blog-single' before 'blog').
	 * Anything unmatched falls through to 'landing-pages'.
	 */
	private const SLUG_CATEGORY_MAP = [
		'home'             => 'home-pages',
		'about'            => 'about-pages',
		'contact'          => 'contact-pages',
		'service-details'  => 'services-pages',
		'services'         => 'services-pages',
		'programs'         => 'services-pages',
		'program-details'  => 'services-pages',
		'portfolio-details' => 'portfolio-pages',
		'portfolio'        => 'portfolio-pages',
		'case-studies'     => 'portfolio-pages',
		'gallery'          => 'portfolio-pages',
		'blog-single'      => 'blog-pages',
		'blog'             => 'blog-pages',
		// These pages share a name with a SECTION category (FAQ, Testimonials,
		// Pricing, Team). They must NOT be filed there — importing "FAQ" would
		// then drop a whole page on the canvas. The section categories are fed
		// by extract_sections(); the pages themselves go to a page category.
		'faq'              => 'about-pages',
		'testimonials'     => 'about-pages',
		'pricing'          => 'services-pages',
		'team'             => 'about-pages',
		'coaches'          => 'about-pages',
		'coach-details'    => 'about-pages',
		'careers'          => 'about-pages',
		'404'              => '404-pages',
		'coming-soon'      => 'coming-soon',
		'maintenance'      => 'maintenance',
		'cart'             => 'woocommerce',
		'checkout'         => 'woocommerce',
		'shop'             => 'woocommerce',
		'product'          => 'woocommerce',
		'my-account'       => 'woocommerce',
		'schedule'         => 'services-pages',
		'events'           => 'services-pages',
	];

	/** Slugs that are legal/utility pages rather than showcase templates. */
	private const SKIP_SLUGS = [ 'privacy-policy', 'terms', 'terms-conditions' ];

	/**
	 * Section detection. A section is classified by the widgets it contains
	 * (a `faq` widget means it IS an FAQ section) or by a kit CSS class.
	 * Checked in order — the first rule that matches wins, so specific markers
	 * must precede generic ones.
	 *
	 * 'widget'  — section contains at least one node of this type
	 * 'class'   — section's cssClass matches this regex
	 * 'label'   — human name, suffixed onto the kit name
	 */
	private const SECTION_RULES = [
		[ 'category' => 'faq',            'widget' => 'faq',           'label' => 'FAQ' ],
		[ 'category' => 'pricing-tables', 'widget' => 'pricing-table', 'label' => 'Pricing' ],
		[ 'category' => 'testimonials',   'widget' => 'testimonials',  'label' => 'Testimonials' ],
		[ 'category' => 'statistics',     'widget' => 'counter',       'label' => 'Statistics' ],
		[ 'category' => 'logo-carousel',  'widget' => 'marquee',       'label' => 'Logo Carousel' ],
		[ 'category' => 'forms',          'widget' => 'form',          'label' => 'Form' ],
		[ 'category' => 'hero-sections',  'class'  => '/\b[a-z0-9]+-(hero|pagehero)\b/i', 'label' => 'Hero' ],
		[ 'category' => 'cta-blocks',     'class'  => '/\b[a-z0-9]+-cta\b/i',             'label' => 'CTA' ],
	];

	/** Widget types that mark a section as a Team block. */
	private const TEAM_HINT_SLUGS = [ 'team', 'coaches' ];

	/**
	 * Read every kit manifest. Returns [ kit_id => manifest ].
	 * Kits without an `id` are skipped, matching the Starter Sites card loader.
	 */
	private static function load_kits(): array {
		static $cache = null;
		if ( null !== $cache ) {
			return $cache;
		}

		$cache = [];
		$dir   = JALPI_DIR . 'websites/';
		foreach ( (array) glob( $dir . '*.json' ) as $file ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local plugin file
			$kit = json_decode( (string) file_get_contents( $file ), true );
			if ( ! is_array( $kit ) || empty( $kit['id'] ) || empty( $kit['pages'] ) ) {
				continue;
			}
			$cache[ sanitize_key( $kit['id'] ) ] = $kit;
		}
		return $cache;
	}


	/**
	 * URL of a generated preview thumbnail for a template id, or '' when none
	 * has been shot yet. Files are produced by bin/shoot-thumbs.js and named
	 * after the id with non-alphanumerics replaced by underscores.
	 */
	public static function generated_thumb( string $id ): string {
		$stem = preg_replace( '/[^A-Za-z0-9_-]/', '_', $id );

		// Hand-drawn header previews take precedence over the shot PNGs.
		// The header screenshots captured only the announcement bar — the
		// sticky nav never rendered — so the PNG exists but shows a blank
		// strip. Match "kitchrome_<kit>_header" to the bundled SVG instead.
		if ( preg_match( '/^kitchrome_([a-z0-9-]+)_header$/', $stem, $m ) ) {
			$svg = $m[1] . '-header.svg';
			if ( file_exists( JALPI_DIR . 'templates/bundled/thumbs/' . $svg ) ) {
				return JALPI_URL . 'templates/bundled/thumbs/' . $svg;
			}
		}

		$path = JALPI_DIR . 'websites/thumbnails/templates/' . $stem . '.png';
		return file_exists( $path )
			? JALPI_URL . 'websites/thumbnails/templates/' . $stem . '.png'
			: '';
	}

	/** Resolve a kit page slug to a category slug. */
	private static function category_for_slug( string $slug ): string {
		return self::SLUG_CATEGORY_MAP[ $slug ] ?? 'landing-pages';
	}

	/** Collect every node type contained in a subtree. */
	private static function node_types( array $node, array &$seen ): void {
		if ( isset( $node['type'] ) ) {
			$seen[ $node['type'] ] = true;
		}
		foreach ( (array) ( $node['children'] ?? [] ) as $child ) {
			if ( is_array( $child ) ) {
				self::node_types( $child, $seen );
			}
		}
	}

	/**
	 * Classify one section node. Returns [ category, label ] or null when the
	 * section is generic page content that should not be offered on its own.
	 */
	private static function classify_section( array $section, string $page_slug ): ?array {
		$types = [];
		foreach ( (array) ( $section['children'] ?? [] ) as $child ) {
			if ( is_array( $child ) ) {
				self::node_types( $child, $types );
			}
		}
		$class = (string) ( $section['settings']['cssClass'] ?? '' );

		foreach ( self::SECTION_RULES as $rule ) {
			if ( ! empty( $rule['widget'] ) && isset( $types[ $rule['widget'] ] ) ) {
				return [ $rule['category'], $rule['label'] ];
			}
			if ( ! empty( $rule['class'] ) && preg_match( $rule['class'], $class ) ) {
				return [ $rule['category'], $rule['label'] ];
			}
		}

		// Team sections have no unique widget — they are image+heading grids.
		// Use the page they live on as the hint instead.
		if ( in_array( $page_slug, self::TEAM_HINT_SLUGS, true )
			&& isset( $types['image'] ) && isset( $types['heading'] ) ) {
			return [ 'team', 'Team' ];
		}

		// Contact sections: a maps widget, or the contact page's content.
		if ( isset( $types['maps'] ) ) {
			return [ 'contact-pages', 'Contact' ];
		}

		return null;
	}

	/**
	 * Extract standalone SECTION templates from a kit's pages.
	 *
	 * Section categories (FAQ, Testimonials, Pricing, Hero, CTA…) must import
	 * a single section, never a whole page. Each entry carries
	 * `type => 'section'` and its data is one section node, so the editor
	 * inserts it rather than replacing the page.
	 *
	 * Ids are `kitsec:<kit>:<page>:<sectionId>`.
	 */
	private static function extract_sections( string $kit_id, array $kit, bool $include_data ): array {
		$out       = [];
		$kit_name  = sanitize_text_field( $kit['name'] ?? $kit_id );
		$kit_cat   = sanitize_key( $kit['category'] ?? 'general' );
		// One entry per category per kit — a kit has many hero sections, but
		// the library only needs a representative, not 19 near-duplicates.
		$taken = [];

		foreach ( (array) ( $kit['pages'] ?? [] ) as $page ) {
			$page_slug = sanitize_title( $page['slug'] ?? '' );
			if ( '' === $page_slug || in_array( $page_slug, self::SKIP_SLUGS, true ) ) {
				continue;
			}
			$sections = (array) ( $page['canvas_data']['sections'] ?? [] );

			foreach ( $sections as $section ) {
				if ( ! is_array( $section ) ) {
					continue;
				}
				$hit = self::classify_section( $section, $page_slug );
				if ( null === $hit ) {
					continue;
				}
				[ $category, $label ] = $hit;
				if ( isset( $taken[ $category ] ) ) {
					continue;
				}
				$taken[ $category ] = true;

				$sec_id = sanitize_key( (string) ( $section['id'] ?? '' ) );
				if ( '' === $sec_id ) {
					continue;
				}

				$item = [
					'id'             => $sec_uid = 'kitsec:' . $kit_id . ':' . $page_slug . ':' . $sec_id,
					'name'           => $kit_name . ' — ' . $label,
					'description'    => sprintf(
						/* translators: 1: section label, 2: kit name */
						__( '%1$s section from the %2$s kit. Inserts one section.', 'jalpi-page-builder' ),
						$label,
						$kit_name
					),
					'type'           => 'section',
					'category'       => $category,
					'category_label' => Jalpi_Templates::CATEGORIES[ $category ] ?? '',
					'thumbnail'      => self::generated_thumb( $sec_uid ),
					'tags'           => [ $kit_cat, $kit_id, sanitize_key( $label ) ],
					'source'         => 'kit',
					'kit'            => $kit_id,
					'readonly'       => true,
					'created'        => '',
					'modified'       => '',
				];

				if ( $include_data ) {
					$item['data'] = $section;
				}
				$out[] = $item;
			}
		}

		return $out;
	}

	/**
	 * Header / footer templates shipped alongside a kit's pages. These import
	 * as single sections, not whole pages.
	 */
	private static function extract_chrome( string $kit_id, array $kit, bool $include_data ): array {
		$out      = [];
		$kit_name = sanitize_text_field( $kit['name'] ?? $kit_id );
		$kit_cat  = sanitize_key( $kit['category'] ?? 'general' );

		foreach ( (array) ( $kit['templates'] ?? [] ) as $tpl ) {
			$location = sanitize_key( $tpl['location'] ?? '' );
			$category = 'header' === $location ? 'headers' : ( 'footer' === $location ? 'footers' : '' );
			if ( '' === $category ) {
				continue;
			}

			$item = [
				'id'             => $chrome_uid = 'kitchrome:' . $kit_id . ':' . $location,
				'name'           => sanitize_text_field( $tpl['title'] ?? ( $kit_name . ' ' . ucfirst( $location ) ) ),
				'description'    => sprintf(
					/* translators: 1: header|footer, 2: kit name */
					__( '%1$s from the %2$s kit. Inserts one section.', 'jalpi-page-builder' ),
					ucfirst( $location ),
					$kit_name
				),
				'type'           => 'section',
				'category'       => $category,
				'category_label' => Jalpi_Templates::CATEGORIES[ $category ] ?? '',
				'thumbnail'      => self::generated_thumb( $chrome_uid ),
				'tags'           => [ $kit_cat, $kit_id, $location ],
				'source'         => 'kit',
				'kit'            => $kit_id,
				'readonly'       => true,
				'created'        => '',
				'modified'       => '',
			];

			if ( $include_data ) {
				$sections = (array) ( $tpl['canvas_data']['sections'] ?? [] );
				// A header/footer template is a one-section document; hand the
				// editor that node so it inserts instead of replacing the page.
				$item['data'] = $sections[0] ?? ( $tpl['canvas_data'] ?? [] );
			}
			$out[] = $item;
		}

		return $out;
	}

	/** Public URL for a kit's screenshot, or '' when it has none. */
	private static function kit_thumbnail( array $kit ): string {
		$raw = trim( (string) ( $kit['thumbnail'] ?? '' ) );
		if ( '' === $raw ) {
			return '';
		}
		if ( preg_match( '#^https://#i', $raw ) ) {
			return esc_url_raw( $raw );
		}
		$file = basename( $raw );
		if ( preg_match( '/^[A-Za-z0-9._-]+\.(png|jpe?g|webp)$/', $file )
			&& file_exists( JALPI_DIR . 'websites/thumbnails/' . $file ) ) {
			return JALPI_URL . 'websites/thumbnails/' . $file;
		}
		return '';
	}

	/**
	 * Build the virtual template list.
	 *
	 * Each kit contributes:
	 *  - one "whole site" card under landing-pages (its home page), and
	 *  - one card per remaining page, filed by slug.
	 *
	 * @param bool $include_data Attach the page's canvas_data (heavy).
	 */
	public static function all( bool $include_data = false ): array {
		$items = [];

		foreach ( self::load_kits() as $kit_id => $kit ) {
			$kit_name  = sanitize_text_field( $kit['name'] ?? $kit_id );
			$thumb     = self::kit_thumbnail( $kit );
			$kit_tags  = [ sanitize_key( $kit['category'] ?? 'general' ), $kit_id ];

			foreach ( (array) $kit['pages'] as $page ) {
				$slug = sanitize_title( $page['slug'] ?? '' );
				if ( '' === $slug || in_array( $slug, self::SKIP_SLUGS, true ) ) {
					continue;
				}

				$is_home = ( 'home' === $slug );

				// A kit's home page serves two purposes: it is the kit's
				// showcase card under Landing Pages, and a normal starting point
				// under Home Pages. List it in both, with distinct ids so
				// favorites and lookups stay unambiguous.
				$categories = $is_home
					? [ 'landing-pages', 'home-pages' ]
					: [ self::category_for_slug( $slug ) ];

				foreach ( $categories as $category ) {
					// The primary listing keeps the clean `kit:<kit>:<slug>` id;
					// the secondary one is suffixed so the two never collide.
					// NB: the suffix must stay [a-z0-9-] — WordPress compiles
					// route regexes with '@' as the delimiter, so an '@' here
					// makes the pattern fail to compile and the route 404s.
					$suffix = ( $is_home && 'home-pages' === $category ) ? '--home-pages' : '';

					$item = [
						'id'             => 'kit:' . $kit_id . ':' . $slug . $suffix,
						'name'           => $is_home
							? $kit_name
							: $kit_name . ' — ' . sanitize_text_field( $page['title'] ?? $slug ),
						'description'    => $is_home
							? sanitize_textarea_field( $kit['description'] ?? '' )
							: sprintf(
								/* translators: 1: page title, 2: kit name */
								__( '%1$s page from the %2$s kit.', 'jalpi-page-builder' ),
								sanitize_text_field( $page['title'] ?? $slug ),
								$kit_name
							),
						'type'           => 'page',
						'category'       => $category,
						'category_label' => Jalpi_Templates::CATEGORIES[ $category ] ?? '',
						// Only the kit's own home page gets the real screenshot —
						// it IS that screenshot. Inner pages have no image of
						// their own, and reusing the homepage shot for all 19
						// made every card look identical.
						// A generated screenshot of THIS page wins; the kit-level
						// screenshot is only right for the kit's own home page.
						'thumbnail'      => self::generated_thumb( 'kit:' . $kit_id . ':' . $slug . $suffix )
							?: ( $is_home ? $thumb : '' ),
						'tags'           => array_values( array_unique( array_merge( $kit_tags, [ $slug ] ) ) ),
						'source'         => 'kit',
						'kit'            => $kit_id,
						'readonly'       => true,
						'created'        => '',
						'modified'       => '',
					];

					if ( $include_data ) {
						$item['data'] = $page['canvas_data'] ?? [];
					}

					$items[] = $item;
				}
			}

			// Section-level templates: FAQ, Pricing, Hero, CTA, Team, … Each
			// imports ONE section, never the page it came from.
			$items = array_merge(
				$items,
				self::extract_sections( $kit_id, $kit, $include_data ),
				self::extract_chrome( $kit_id, $kit, $include_data )
			);
		}

		return $items;
	}

	/**
	 * Fetch a single virtual template by its `kit:<kit>:<slug>` id, with data.
	 * Returns null when the id does not resolve.
	 */
	public static function find( string $id ): ?array {
		// Section and chrome ids carry their own prefixes; they are looked up
		// by scanning the full list rather than by parsing, because their
		// structure differs from the page ids handled below.
		if ( 0 === strpos( $id, 'kitsec:' ) || 0 === strpos( $id, 'kitchrome:' ) ) {
			foreach ( self::all( true ) as $item ) {
				if ( $item['id'] === $id ) {
					return $item;
				}
			}
			return null;
		}

		if ( 0 !== strpos( $id, 'kit:' ) ) {
			return null;
		}
		$parts = explode( ':', $id, 3 );
		if ( 3 !== count( $parts ) ) {
			return null;
		}
		[ , $kit_id, $slug_part ] = $parts;
		$kit_id = sanitize_key( $kit_id );

		// The slug may carry a `--<category>` disambiguator (see all()).
		$suffix = '';
		if ( false !== strpos( $slug_part, '--' ) ) {
			[ $slug_part, $cat ] = explode( '--', $slug_part, 2 );
			$suffix = '--' . sanitize_key( $cat );
		}
		$slug = sanitize_title( $slug_part );

		$wanted = 'kit:' . $kit_id . ':' . $slug . $suffix;
		foreach ( self::all( true ) as $item ) {
			if ( $item['id'] === $wanted ) {
				return $item;
			}
		}
		return null;
	}

	/** Per-category counts contributed by kits, keyed by category slug. */
	public static function counts(): array {
		$counts = [];
		foreach ( self::all( false ) as $item ) {
			$cat            = $item['category'];
			$counts[ $cat ] = ( $counts[ $cat ] ?? 0 ) + 1;
		}
		return $counts;
	}
}
