<?php
/**
 * Shared helpers for file-backed icon libraries.
 *
 * @package DiviSupreme
 */

declare(strict_types=1);

namespace DiviSupreme\Extensions\IconLibraries;

if ( ! defined( 'ABSPATH' ) ) {
	die( 'Direct access forbidden.' );
}

/**
 * Base class for manifest-backed webfont icon libraries.
 */
abstract class AbstractFileIconLibrary implements IconLibraryInterface {

	/**
	 * @var array<int, array<string, mixed>>|null
	 */
	private ?array $manifest_cache = null;

	/**
	 * @var array<string, array<int, array<string, mixed>>>|null
	 */
	private ?array $search_index_cache = null;

	/**
	 * Precomputed lowercase search haystacks (avoids rebuilding per query).
	 *
	 * @var array<int, array{entry: array<string, mixed>, haystack: string}>|null
	 */
	private ?array $search_haystack_cache = null;

	/**
	 * Absolute path to library data directory (manifest, meta, webfont assets).
	 */
	protected function get_data_directory(): string {
		return DSM_DIR_PATH . 'divi-5/assets/icon-libraries/' . sanitize_key( $this->get_id() );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_variants(): array {
		return array();
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_default_variant(): string {
		return '';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_brand_mark(): string {
		return '';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_manifest(): array {
		if ( null !== $this->manifest_cache ) {
			return $this->manifest_cache;
		}

		$file = trailingslashit( $this->get_data_directory() ) . 'manifest.json';

		if ( ! is_readable( $file ) ) {
			$this->manifest_cache = array();
			return $this->manifest_cache;
		}

		$raw = file_get_contents( $file );

		if ( false === $raw ) {
			$this->manifest_cache = array();
			return $this->manifest_cache;
		}

		/** @var array<int, array<string, mixed>>|null $decoded */
		$decoded = json_decode( $raw, true );

		$this->manifest_cache = is_array( $decoded ) ? $decoded : array();

		return $this->manifest_cache;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_icon_count(): int {
		$meta_file = trailingslashit( $this->get_data_directory() ) . 'meta.json';

		if ( is_readable( $meta_file ) ) {
			$raw = file_get_contents( $meta_file );

			if ( false !== $raw ) {
				/** @var array<string, mixed>|null $decoded */
				$decoded = json_decode( $raw, true );

				if ( is_array( $decoded ) && isset( $decoded['count'] ) ) {
					return max( 0, (int) $decoded['count'] );
				}
			}
		}

		return count( $this->get_manifest() );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_icon( string $icon_id, string $variant = '' ): ?array {
		$icon_id = sanitize_key( $icon_id );

		if ( '' === $icon_id ) {
			return null;
		}

		foreach ( $this->get_manifest() as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$entry_id = isset( $entry['id'] ) ? sanitize_key( (string) $entry['id'] ) : '';

			if ( $entry_id !== $icon_id ) {
				continue;
			}

			if ( '' !== $variant ) {
				$entry_variant = isset( $entry['variant'] ) ? sanitize_key( (string) $entry['variant'] ) : '';

				if ( '' !== $entry_variant && $entry_variant !== sanitize_key( $variant ) ) {
					continue;
				}
			}

			return $entry;
		}

		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	public function search( string $query = '' ): array {
		$query = trim( strtolower( $query ) );

		if ( '' === $query ) {
			return $this->get_manifest();
		}

		$index = $this->get_search_index();
		$terms = preg_split( '/\s+/', $query );

		if ( ! is_array( $terms ) ) {
			return $this->get_manifest();
		}

		$terms = array_values(
			array_filter(
				$terms,
				static function ( string $term ): bool {
					return strlen( $term ) >= 2;
				}
			)
		);

		if ( array() === $terms ) {
			return $this->get_manifest();
		}

		$matches = array();

		foreach ( $this->get_search_haystacks() as $row ) {
			$matched = true;

			foreach ( $terms as $term ) {
				if ( ! str_contains( $row['haystack'], $term ) ) {
					$matched = false;
					break;
				}
			}

			if ( $matched ) {
				$matches[] = $row['entry'];
			}
		}

		return $matches;
	}

	/**
	 * @return array<int, array{entry: array<string, mixed>, haystack: string}>
	 */
	private function get_search_haystacks(): array {
		if ( null !== $this->search_haystack_cache ) {
			return $this->search_haystack_cache;
		}

		$this->search_haystack_cache = array();

		foreach ( $this->get_manifest() as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$this->search_haystack_cache[] = array(
				'entry'    => $entry,
				'haystack' => strtolower(
					(string) ( $entry['searchTerms'] ?? '' ) . ' ' .
					(string) ( $entry['name'] ?? '' ) . ' ' .
					(string) ( $entry['id'] ?? '' )
				),
			);
		}

		return $this->search_haystack_cache;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_manifest_total( string $query = '' ): int {
		return count( $this->search( $query ) );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_manifest_slice( int $offset, int $limit, string $query = '' ): array {
		if ( $limit <= 0 ) {
			return array();
		}

		$offset = max( 0, $offset );
		$rows   = $this->search( $query );

		return array_slice( $rows, $offset, $limit );
	}

	/**
	 * @return array<string, array<int, array<string, mixed>>>
	 */
	private function get_search_index(): array {
		if ( null !== $this->search_index_cache ) {
			return $this->search_index_cache;
		}

		$this->search_index_cache = array();

		foreach ( $this->get_manifest() as $entry ) {
			if ( ! is_array( $entry ) || ! isset( $entry['id'] ) ) {
				continue;
			}

			$key = strtolower( (string) $entry['id'] );
			$this->search_index_cache[ $key ][] = $entry;
		}

		return $this->search_index_cache;
	}

	/**
	 * Map an absolute path under the Divi 5 plugin root to a public URL.
	 *
	 * `DSM_DIR_URL` already points at `…/divi-5/`; do not prepend `divi-5/` again.
	 *
	 * @param string $absolute_path Filesystem path.
	 * @return string
	 */
	protected function resolve_plugin_asset_url( string $absolute_path ): string {
		$normalized_path = wp_normalize_path( $absolute_path );
		$divi5_root      = wp_normalize_path( trailingslashit( DSM_DIR_PATH ) . 'divi-5/' );

		if ( ! str_starts_with( $normalized_path, $divi5_root ) ) {
			return '';
		}

		$relative = ltrim( substr( $normalized_path, strlen( $divi5_root ) ), '/' );

		return trailingslashit( DSM_DIR_URL ) . $relative;
	}
}
