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

/**
 * What the site sends the service: its DATA, not the shape of it.
 *
 * This is the rule that keeps the two pieces apart. Only the site knows its own design system —
 * which colours, which fonts, which tokens; HOW that gets told to the model belongs to the service.
 * If the two were mixed (by sending composed text instead of data), improving the wording would
 * mean updating the plugin on every installed site — and the improvements would sit still for
 * months.
 *
 * There are no exceptions left. The content inventory was the last one — it used to travel as prose
 * composed here — and it now goes as data under `inventory`, with the service writing the sentences
 * ({@see Aura\Prompt\Inventory}). The copy of that formatting the plugin used to carry for older
 * services is gone with it: there is one service, it ships from the same build as this plugin, and a
 * duplicate nothing can reach is a duplicate that drifts.
 */
class AIPB_Site_Payload {

	/**
	 * Who is asking, and above all WHAT THEY CAN RENDER.
	 *
	 * The service moves faster than the installed sites: the directive list is the promise that a
	 * prompt will never teach the model something this engine cannot print. Without it, a site
	 * stuck on an old version would receive markup that lands on the page as text — and nobody
	 * would notice until a visitor saw it.
	 */
	public static function engine() {
		return array(
			'plugin'     => AIPB_VERSION,
			'dsl'        => AIPB_Template_Engine::DSL,
			'directives' => AIPB_Template_Engine::DIRECTIVES,
		);
	}

	/**
	 * The site: design system, content inventory, available icons.
	 *
	 * Everything goes on every request, including what a given mode will not use. It is a few KB
	 * against a 15 KB prompt, and in exchange there is only one contract: the day the service
	 * starts using the palette to plan a page too, no site will need updating.
	 */
	public static function site() {
		$site = array(
			'id'     => self::id(),
			'url'    => home_url(),
			'design' => self::design(),
			'icons'  => class_exists( 'AIPB_Icons' ) ? array_values( AIPB_Icons::names() ) : array(),
			// The two languages of a generation, which are not always the same one.
			//
			// `lang` is the SITE's: the copy the model writes — headings, paragraphs, button labels
			// — is read by visitors, and it has to be in their language. `lang_admin` is the one the
			// person at the keyboard is using right now: section names, briefs and the notes that
			// come back into the chat are addressed to them.
			//
			// They collapse into one on almost every installation, and then the service says it
			// once. They come apart on an Italian site edited from an English admin — where, before
			// this, the answer was half in one language and half in the other, because the prompts
			// were written in Italian and the interface in English and nobody had told the model
			// which of the two to use.
			'lang'       => get_locale(),
			'lang_admin' => function_exists( 'determine_locale' ) ? determine_locale() : get_locale(),
		);

		// The content inventory, as DATA. The prose is written by the service ({@see
		// Aura\Prompt\Inventory}), which is why this sends values and not sentences.
		//
		// There used to be a second branch here for a service too old to compose the prose itself,
		// and the plugin carried its own copy of it. That copy is gone: the plugin and the service
		// ship from the same build — `bin/build-plugin.php` writes this very server's address into
		// the zip — so there is exactly one service, and it has always declared `site-inventory`.
		// A duplicate of 170 lines of prompt that nothing can reach is a duplicate that drifts.
		if ( class_exists( 'AIPB_Site_Context' ) ) {
			$site['inventory'] = AIPB_Site_Context::data();
		}

		return $site;
	}

	/**
	 * WHO is asking, in a stable way.
	 *
	 * The service knows which KEY asked, not which SITE: with a key shared between installations —
	 * which is the norm until licensing exists — the usage all lands in one heap, and there is no
	 * telling a customer how much they used, nor noticing that one site alone is consuming for ten.
	 *
	 * Adding it NOW costs a line; adding it later costs a plugin update on every installed site —
	 * exactly the problem this architecture exists to avoid. It is a hash of the address: stable
	 * across updates, revealing nothing beyond what the address itself reveals (it travels right
	 * next to it), and independent of the database, so a dump restored elsewhere does not carry the
	 * wrong identity with it.
	 */
	protected static function id() {
		return substr( hash( 'sha256', home_url() ), 0, 16 );
	}

	/** Palette, fonts, defined tokens and art direction, as values. */
	protected static function design() {
		if ( ! class_exists( 'AIPB_Design' ) ) {
			return array();
		}

		list( $groups, $schema ) = self::tokens();

		return array(
			'direction'    => AIPB_Design::direction(),
			// The structured half of the direction: motion rules, rhythm, the "never" list.
			'manifest'     => AIPB_Design::manifest(),
			'palette'      => self::palette(),
			'fonts'        => self::fonts(),
			'groups'       => $groups,
			'token_schema' => $schema,
			// The effects the model may reference: id, where each belongs, when to use it. The CSS
			// stays home on purpose — a model that sees code copies code (see AIPB_Effects::index).
			'effects'      => class_exists( 'AIPB_Effects' ) ? AIPB_Effects::index() : array(),
		);
	}

	protected static function palette() {
		$out = array();

		foreach ( AIPB_Design::palette() as $colour ) {
			$out[] = array(
				'slug'  => isset( $colour['slug'] ) ? (string) $colour['slug'] : '',
				'name'  => isset( $colour['name'] ) ? (string) $colour['name'] : '',
				'value' => isset( $colour['value'] ) ? (string) $colour['value'] : '',
			);
		}

		return $out;
	}

	protected static function fonts() {
		$out = array();

		foreach ( AIPB_Design::fonts() as $font ) {
			$out[] = array(
				'slug'     => isset( $font['slug'] ) ? (string) $font['slug'] : '',
				'name'     => isset( $font['name'] ) ? (string) $font['name'] : '',
				'family'   => isset( $font['family'] ) ? (string) $font['family'] : '',
				'category' => isset( $font['category'] ) ? (string) $font['category'] : 'sans-serif',
				'weights'  => isset( $font['weights'] ) ? array_map( 'intval', (array) $font['weights'] ) : array( 400 ),
				// The full stack is worked out by the site: the fallbacks depend on the font's
				// category, which is a choice of this design system and not of the service.
				'stack' => AIPB_Design::font_stack( (string) $font['family'], (string) $font['category'] ),
			);
		}

		return $out;
	}

	/**
	 * The structural tokens, in two shapes.
	 *
	 * The SCHEMA lists every key that exists (measuring a mockup needs it: the model has to know
	 * which tokens it may fill). The VALUES list only the ones actually defined: an empty token has
	 * no var() to use, and proposing one would produce invalid CSS.
	 *
	 * @return array{0:array,1:array} the defined groups, and the full schema
	 */
	protected static function tokens() {
		$groups = array();
		$schema = array();

		foreach ( AIPB_Design::groups() as $group_key => $group ) {
			$defined = array();
			$keys    = array();

			foreach ( $group['items'] as $key => $item ) {
				$keys[] = array( 'key' => (string) $key, 'label' => (string) $item[1] );

				if ( '' !== $item[0] ) {
					$defined[] = array(
						'key'   => (string) $key,
						'value' => AIPB_Design::with_unit( $item[0], $group_key . ':' . $key ),
						'label' => (string) $item[1],
					);
				}
			}

			if ( $defined ) {
				$groups[] = array(
					'label'  => (string) $group['label'],
					'prefix' => (string) $group['prefix'],
					'items'  => $defined,
				);
			}

			$schema[] = array( 'key' => (string) $group_key, 'label' => (string) $group['label'], 'items' => $keys );
		}

		return array( $groups, $schema );
	}
}
