<?php
/**
 * The plugin's icon set.
 *
 * @package TextTango
 */

declare( strict_types=1 );

namespace TextTango\Admin;

defined( 'ABSPATH' ) || exit;

/**
 * Line icons, from one published set (spec §11).
 *
 * ### Why a set rather than eight drawings
 *
 * Icons beside labels only work if they read as siblings, and that is a question
 * of optical size, stroke weight and corner treatment being identical across all
 * of them — the thing hand-drawing eight shapes reliably gets wrong. These are
 * Lucide (ISC licence, https://lucide.dev): a 24-unit grid, 2-unit stroke, round
 * caps and joins, taken unmodified so they cannot drift apart.
 *
 * ### Why they carry no colour
 *
 * Every path is stroked with `currentColor`, so an icon *is* whatever colour its
 * container's text is. `.tt-tab` already defines its colour at rest, on hover, on
 * focus and when active, and this inherits all four without restating any of
 * them — which also means an icon cannot end up a different colour from the label
 * it sits next to. The admin-menu mark is painted the same way and for the same
 * reason ({@see Menu::menu_icon_css()}).
 */
final class Icons {

	/**
	 * Attributes shared by every icon.
	 *
	 * `width`/`height` in pixels rather than `1em`: these sit beside 14px text,
	 * and an icon matching the cap height exactly reads as slightly too small.
	 */
	private const ATTRIBUTES = 'xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"'
		. ' fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"'
		. ' aria-hidden="true" focusable="false"';

	/**
	 * The path data, keyed by name.
	 *
	 * @return array<string, string>
	 */
	public static function all(): array {
		return array(
			// A plug: the connection to the account.
			'plug'               => '<path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/>'
				. '<path d="M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z"/>',

			// A bell: a message going out on an event.
			'bell'               => '<path d="M10.268 21a2 2 0 0 0 3.464 0"/>'
				. '<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>',

			// A shield with a tick: permission held, and honoured.
			'shield-check'       => '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>'
				. '<path d="m9 12 2 2 4-4"/>',

			// A megaphone: one message to many.
			'megaphone'          => '<path d="m3 11 18-5v12L3 14v-3z"/>'
				. '<path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/>',

			// A flask: a trial run, not the real thing.
			'flask-conical'      => '<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"/>'
				. '<path d="M6.453 15h11.094"/><path d="M8.5 2h7"/>',

			// An arrow into a door: signing in.
			'log-in'             => '<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>'
				. '<polyline points="10 17 15 12 10 7"/><line x1="15" x2="3" y1="12" y2="12"/>',

			// A written record.
			'scroll-text'        => '<path d="M15 12h-5"/><path d="M15 8h-5"/>'
				. '<path d="M19 17V5a2 2 0 0 0-2-2H4"/>'
				. '<path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/>',

			/*
			 * A paper plane: composing something to send. Deliberately not the
			 * megaphone above, which already means the Marketing settings tab —
			 * one glyph standing for two different screens is worse than two
			 * glyphs.
			 */
			'send'               => '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/>'
				. '<path d="m21.854 2.147-10.94 10.939"/>',

			// A clock turning back: what has already gone out.
			'history'            => '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>'
				. '<path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>',

			// A clipboard: a form somebody filled in.
			'clipboard-list'     => '<rect width="8" height="4" x="8" y="2" rx="1" ry="1"/>'
				. '<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>'
				. '<path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/>',

			// Sliders: the settings you only reach for deliberately.
			'sliders-horizontal' => '<line x1="21" x2="14" y1="4" y2="4"/><line x1="10" x2="3" y1="4" y2="4"/>'
				. '<line x1="21" x2="12" y1="12" y2="12"/><line x1="8" x2="3" y1="12" y2="12"/>'
				. '<line x1="21" x2="16" y1="20" y2="20"/><line x1="12" x2="3" y1="20" y2="20"/>'
				. '<line x1="14" x2="14" y1="2" y2="6"/><line x1="8" x2="8" y1="10" y2="14"/>'
				. '<line x1="16" x2="16" y1="18" y2="22"/>',
		);
	}

	/**
	 * Whether an icon exists.
	 *
	 * @param string $name Icon name.
	 */
	public static function has( string $name ): bool {
		return isset( self::all()[ $name ] );
	}

	/**
	 * One icon as SVG markup.
	 *
	 * Returns an empty string for a name that does not exist, so a mistyped icon
	 * leaves a gap rather than a broken element or a fatal.
	 *
	 * @param string $name Icon name.
	 */
	public static function get( string $name ): string {
		$paths = self::all()[ $name ] ?? '';

		if ( '' === $paths ) {
			return '';
		}

		return '<svg ' . self::ATTRIBUTES . '>' . $paths . '</svg>';
	}

	/**
	 * The tags and attributes an icon is allowed to use.
	 *
	 * Escaping on output applies to these the same as to anything else, even
	 * though the markup is a literal from the table above and no part of it comes
	 * from outside this file.
	 *
	 * Note `viewbox`, lowercase, and not a typo: `wp_kses()` lowercases every
	 * attribute name, so an entry spelled `viewBox` would be stripped and the
	 * icon would lose its coordinate system and render at the wrong size. HTML5
	 * maps the lowercase form back when parsing foreign content, so the rendered
	 * result is correct — this is the one place the two spellings have to differ.
	 *
	 * @return array<string, array<string, bool>>
	 */
	public static function allowed_html(): array {
		$shape = array(
			'd'      => true,
			'points' => true,
			'x1'     => true,
			'x2'     => true,
			'y1'     => true,
			'y2'     => true,
			'cx'     => true,
			'cy'     => true,
			'r'      => true,
			'rx'     => true,
			'ry'     => true,
			'width'  => true,
			'height' => true,
			'x'      => true,
			'y'      => true,
			'fill'   => true,
		);

		return array(
			'svg'      => array(
				'xmlns'           => true,
				'width'           => true,
				'height'          => true,
				'viewbox'         => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'stroke-linecap'  => true,
				'stroke-linejoin' => true,
				'aria-hidden'     => true,
				'focusable'       => true,
				'class'           => true,
			),
			'path'     => $shape,
			'circle'   => $shape,
			'line'     => $shape,
			'polyline' => $shape,
			'polygon'  => $shape,
			'rect'     => $shape,
		);
	}

	/**
	 * One icon, escaped for output.
	 *
	 * @param string $name Icon name.
	 */
	public static function render( string $name ): string {
		return wp_kses( self::get( $name ), self::allowed_html() );
	}
}
