<?php
/**
 * Phase 31 — Bundled form templates
 * ----------------------------------
 * Ships a curated set of payment-form starter templates INSIDE the plugin so
 * the "Choose a template" gallery works offline + the gallery grows beyond
 * what's published on paymentpageplugin.com.
 *
 * How it integrates with the existing remote-fetch flow:
 *   - PaymentPage\API\PaymentPage::get_form_import_templates() fetches the
 *     remote list from paymentpageplugin.com (1-hour transient cached).
 *   - This class hooks the `payment_page_form_templates` filter to PREPEND
 *     bundled templates to that list.
 *   - The gallery UI in app/PostTypes/Form.php:166 then renders all of them.
 *
 * Each bundled template is a JSON file under app/Templates/bundled/. Adding
 * new ones is a copy-paste-edit job — no code changes, no PHP knowledge needed.
 * The randomizer tool (1.6.0) will generate JSON files programmatically.
 *
 * IDs use a negative-int convention (-1, -2, …) so they NEVER collide with
 * the positive WP-post IDs from paymentpageplugin.com. The import endpoint
 * (RestAPI/Administration::import_template) handles negative IDs by reading
 * the JSON directly instead of round-tripping through wp_remote_get.
 */

namespace PaymentPage\Templates;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class BundledTemplates {

	/** @var BundledTemplates|null */
	protected static $instance = null;

	public static function instance(): BundledTemplates {
		if ( ! isset( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function setup(): void {
		add_filter( 'payment_page_form_templates', array( $this, 'prepend_bundled' ), 10, 1 );

		// (Phase 40) Register a public REST route that emits a per-template SVG
		// preview thumbnail. Bundled templates ship without screenshot JPEGs (the
		// remote paymentpageplugin.com templates carry their own JPEG covers via
		// `image_path`); rather than ship binary assets in the plugin zip + risk
		// a filesystem-path bug on edge-case hosts, the screenshot is generated
		// on demand from the template's metadata (title, sub_title, category).
		// The route is unauthenticated because the preview leaks nothing — it's
		// the same content the bundled JSON files already expose at the gallery.
		add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
	}

	/**
	 * (Phase 40) Register the SVG-preview REST endpoint.
	 *
	 * Endpoint: GET /wp-json/payment-page/v1/bundled-templates/{slug}/preview.svg
	 * Returns: image/svg+xml, ~1.5KB per template
	 * Auth:    none (preview is public, contains no merchant data)
	 */
	public function register_rest_routes(): void {
		register_rest_route(
			'payment-page/v1',
			'/bundled-templates/(?P<slug>[a-z0-9\-]+)/preview\.svg',
			array(
				'methods'             => 'GET',
				'permission_callback' => '__return_true',
				'callback'            => array( $this, 'rest_svg_preview' ),
				'args'                => array(
					'slug' => array(
						'sanitize_callback' => 'sanitize_title',
					),
				),
			)
		);
	}

	/**
	 * REST callback that streams the SVG for a single bundled-template slug.
	 *
	 * @param \WP_REST_Request $request
	 * @return void  Sends raw SVG + exits — bypasses the JSON wrapper because
	 *               we want the right Content-Type for direct <img src=…> use.
	 */
	public function rest_svg_preview( $request ) {
		$slug = sanitize_title( (string) $request->get_param( 'slug' ) );
		$json = __DIR__ . '/bundled/' . $slug . '.json';
		if ( ! file_exists( $json ) ) {
			return new \WP_Error( 'not_found', 'Unknown bundled-template slug.', array( 'status' => 404 ) );
		}
		$raw     = file_get_contents( $json );
		$decoded = is_string( $raw ) ? json_decode( $raw, true ) : null;
		if ( ! is_array( $decoded ) ) {
			return new \WP_Error( 'parse_error', 'Bundled template metadata could not be parsed.', array( 'status' => 500 ) );
		}
		$svg = $this->render_svg( $slug, (string) ( $decoded['title'] ?? '' ), (string) ( $decoded['sub_title'] ?? '' ) );
		// Cache aggressively — the SVG is deterministic per slug+plugin-version.
		header( 'Content-Type: image/svg+xml; charset=utf-8' );
		header( 'Cache-Control: public, max-age=86400, immutable' );
		echo $svg; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped — content built from escaped pieces below
		exit;
	}

	/**
	 * Build a deterministic, branded SVG thumbnail for a bundled template.
	 *
	 * Design: 600x400 (matches remote JPEG aspect), pastel background derived
	 * from a hash of the slug for visual uniqueness, category glyph picked from
	 * the slug, title + subtitle in system-ui. No external font deps.
	 *
	 * @param string $slug
	 * @param string $title
	 * @param string $sub_title
	 * @return string
	 */
	protected function render_svg( string $slug, string $title, string $sub_title ): string {
		$glyphs = array(
			'trainer'      => array( '&#x1F3CB;', '#FF7A45' ),
			'restaurant'   => array( '&#x1F374;', '#FF9F40' ),
			'course'       => array( '&#x1F393;', '#5B7FFF' ),
			'membership'   => array( '&#x1F511;', '#9C5BFF' ),
			'photography'  => array( '&#x1F4F7;', '#FF5B92' ),
			'event'        => array( '&#x1F39F;', '#FFB13B' ),
			'crowd'        => array( '&#x1F31F;', '#3DB5FF' ),
			'subscription' => array( '&#x1F4E6;', '#56C870' ),
			'therapy'      => array( '&#x1F33F;', '#3FBFAC' ),
			'digital'      => array( '&#x1F4D6;', '#7A6BFF' ),
		);
		$glyph = '&#x1F4B3;';
		$color = '#5B7FFF';
		foreach ( $glyphs as $key => $pair ) {
			if ( strpos( $slug, $key ) !== false ) {
				$glyph = $pair[0];
				$color = $pair[1];
				break;
			}
		}
		$swirl = '#' . substr( md5( $slug ), 0, 6 );
		$t     = esc_html( $title );
		$s     = esc_html( $sub_title );

		// (Phase 46 / wp.org Plugin Check) Use sprintf instead of heredoc —
		// wp.org's PluginCheck.CodeAnalysis.Heredoc.NotAllowed disallows
		// heredoc syntax. Same output, just sprintf'd.
		return sprintf(
			'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 400" role="img" aria-label="%1$s template preview">'
			. '<defs><linearGradient id="bg-%2$s" x1="0" y1="0" x2="1" y2="1">'
			. '<stop offset="0%%" stop-color="%3$s" stop-opacity="0.16"/>'
			. '<stop offset="100%%" stop-color="%4$s" stop-opacity="0.08"/>'
			. '</linearGradient></defs>'
			. '<rect width="600" height="400" fill="#FAFBFD"/>'
			. '<rect width="600" height="400" fill="url(#bg-%2$s)"/>'
			. '<circle cx="500" cy="80" r="120" fill="%3$s" fill-opacity="0.10"/>'
			. '<circle cx="120" cy="330" r="80" fill="%4$s" fill-opacity="0.10"/>'
			. '<g transform="translate(60 80)">'
			. '<rect width="64" height="64" rx="14" fill="%3$s" fill-opacity="0.18"/>'
			. '<text x="32" y="48" text-anchor="middle" font-size="40" font-family="system-ui, -apple-system, Segoe UI, sans-serif">%5$s</text>'
			. '</g>'
			. '<text x="60" y="220" font-family="system-ui, -apple-system, Segoe UI, sans-serif" font-weight="700" font-size="34" fill="#1F2937">%1$s</text>'
			. '<text x="60" y="258" font-family="system-ui, -apple-system, Segoe UI, sans-serif" font-size="18" fill="#4B5563">%6$s</text>'
			. '<g transform="translate(60 300)" font-family="system-ui, -apple-system, Segoe UI, sans-serif">'
			. '<rect width="180" height="44" rx="8" fill="%3$s"/>'
			. '<text x="90" y="29" text-anchor="middle" font-size="15" font-weight="600" fill="white">Use this template</text>'
			. '<text x="200" y="29" font-size="13" fill="#6B7280">Bundled with Payment Page</text>'
			. '</g>'
			. '</svg>',
			$t,      // %1$s  title (used twice — header + image label)
			$slug,   // %2$s  slug (used twice in linearGradient id + url ref)
			$color,  // %3$s  primary color (used 4× — gradient stop, circle, icon bg, button bg)
			$swirl,  // %4$s  swirl secondary color (gradient stop + bottom circle)
			$glyph,  // %5$s  category emoji
			$s       // %6$s  subtitle
		);
	}

	/**
	 * Read every JSON file under app/Templates/bundled/ and prepend to the
	 * gallery list. Remote templates from paymentpageplugin.com follow.
	 *
	 * @param array $remote_templates Output of API\PaymentPage::get_form_import_templates()
	 * @return array Merged list: bundled first, then remote, dedup'd by id.
	 */
	public function prepend_bundled( $remote_templates ): array {
		$bundled = $this->load_all();
		if ( ! is_array( $remote_templates ) ) {
			$remote_templates = array();
		}

		// Dedup by id (defensive — bundled IDs are negative, remote IDs are
		// positive, so collisions are impossible by construction; still defensive).
		$seen = array();
		$out  = array();
		foreach ( array_merge( $bundled, $remote_templates ) as $t ) {
			$id = isset( $t['id'] ) ? (string) $t['id'] : '';
			if ( $id === '' || isset( $seen[ $id ] ) ) {
				continue;
			}
			$seen[ $id ] = true;
			$out[]       = $t;
		}
		return $out;
	}

	/**
	 * Load + parse every bundled template JSON. Bad JSON files are skipped
	 * silently (logged to debug.log via _doing_it_wrong).
	 *
	 * @return array List of template dicts ready to merge into the gallery.
	 */
	public function load_all(): array {
		$dir = __DIR__ . '/bundled';
		if ( ! is_dir( $dir ) ) {
			return array();
		}

		$out   = array();
		$files = glob( $dir . '/*.json' ) ?: array();
		sort( $files ); // deterministic order
		foreach ( $files as $file ) {
			$raw = file_get_contents( $file );
			if ( $raw === false ) {
				continue;
			}
			$decoded = json_decode( $raw, true );
			if ( ! is_array( $decoded ) || ! isset( $decoded['id'], $decoded['title'], $decoded['settings'] ) ) {
				_doing_it_wrong(
					__METHOD__,
					sprintf( 'Bundled template %s missing required keys (id/title/settings)', esc_html( basename( $file ) ) ),
					'1.5.0'
				);
				continue;
			}
			// Add an indicator so the UI can render a "Bundled" badge if it wants.
			$decoded['_bundled'] = true;

			// (Phase 40) Point `image_path` at the REST endpoint that generates
			// a per-template SVG preview on demand (see register_rest_routes()
			// + rest_svg_preview() below). Bundled templates don't ship binary
			// JPEG covers like the remote paymentpageplugin.com templates do —
			// instead the SVG is rendered from the title/sub_title at request
			// time + cached for a day by the client. This avoids any binary
			// asset deploy hassle, keeps the plugin zip small, and means each
			// new bundled JSON automatically gets a thumbnail with zero extra
			// work.
			$slug                   = basename( $file, '.json' );
			$decoded['image_path']  = rest_url( 'payment-page/v1/bundled-templates/' . $slug . '/preview.svg' );
			$decoded['preview_url'] = $decoded['image_path']; // good-enough fallback for previews

			$out[] = $decoded;
		}
		return $out;
	}

	/**
	 * Look up a single bundled template by its negative-int id. Used by the
	 * import_template REST endpoint to skip the remote fetch for bundled IDs.
	 *
	 * @param int|string $id
	 * @return array|null
	 */
	public function find_by_id( $id ): ?array {
		$id = (int) $id;
		if ( $id >= 0 ) {
			return null; // bundled IDs are negative
		}
		foreach ( $this->load_all() as $t ) {
			if ( (int) ( $t['id'] ?? 0 ) === $id ) {
				return $t;
			}
		}
		return null;
	}
}
