<?php
/**
 * CPT Builder taxonomy import/export.
 *
 * @package CptBuilder
 * @since 1.1.0
 */

namespace CptBuilder;

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

/**
 * Handles export and import of taxonomy definitions.
 */
final class TaxonomyImportExport {

	/**
	 * Identifier embedded in exports so imports can recognise the format.
	 */
	private const SOURCE = 'wp-powersuite-cpt-builder-taxonomies';

	private TaxonomyStorage $storage;

	private TaxonomyValidator $validator;

	public function __construct( TaxonomyStorage $storage, TaxonomyValidator $validator ) {
		$this->storage   = $storage;
		$this->validator = $validator;
	}

	/**
	 * Build an export payload for one or all taxonomies.
	 *
	 * @param string|null $key Specific key, or null to export everything.
	 * @return array<string, mixed>
	 */
	public function export( ?string $key = null ): array {
		$definitions = $this->storage->all();

		if ( $key !== null ) {
			$definitions = isset( $definitions[ $key ] ) ? [ $key => $definitions[ $key ] ] : [];
		}

		return [
			'schema'     => Config::SCHEMA_VERSION,
			'source'     => self::SOURCE,
			'generated'  => gmdate( 'Y-m-d H:i:s' ),
			'taxonomies' => array_values( $definitions ),
		];
	}

	/**
	 * Encode an export payload as pretty JSON.
	 *
	 * @param string|null $key Specific key, or null for all.
	 */
	public function exportJson( ?string $key = null ): string {
		return (string) wp_json_encode( $this->export( $key ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
	}

	/**
	 * Import definitions from a JSON string.
	 *
	 * @param string $json               Raw JSON.
	 * @param bool   $overwrite          Whether to overwrite existing keys.
	 * @param bool   $import_as_disabled Whether to force imported taxonomies disabled.
	 * @return array{imported: int, skipped: array<int, string>, errors: array<int, string>}
	 */
	public function import( string $json, bool $overwrite, bool $import_as_disabled ): array {
		$result = [
			'imported' => 0,
			'skipped'  => [],
			'errors'   => [],
		];

		$json = ImportExport::normalizeJson( $json );
		if ( $json === '' ) {
			$result['errors'][] = __( 'No import data was provided.', 'wppowersuite-lite' );
			return $result;
		}

		if ( strlen( $json ) > ImportExport::MAX_IMPORT_BYTES ) {
			$result['errors'][] = __( 'The import data is too large (limit: 1 MB).', 'wppowersuite-lite' );
			return $result;
		}

		$data = json_decode( $json, true, ImportExport::MAX_JSON_DEPTH );
		if ( ! is_array( $data ) || json_last_error() !== JSON_ERROR_NONE ) {
			$result['errors'][] = __( 'The import data is not valid JSON.', 'wppowersuite-lite' );
			return $result;
		}

		$entries = $this->extractEntries( $data );
		if ( $entries === [] ) {
			$result['errors'][] = __( 'No taxonomy definitions were found in the import data.', 'wppowersuite-lite' );
			return $result;
		}

		foreach ( $entries as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$key = $this->validator->sanitizeKey( (string) ( $entry['key'] ?? '' ) );
			if ( $key === '' ) {
				$result['errors'][] = __( 'Skipped an entry with a missing or invalid key.', 'wppowersuite-lite' );
				continue;
			}

			$already_exists = $this->storage->exists( $key );
			if ( $already_exists && ! $overwrite ) {
				$result['skipped'][] = $key;
				continue;
			}

			$existing   = $already_exists ? $this->storage->get( $key ) : null;
			$definition = $this->validator->buildDefinition( $entry, $existing );

			if ( $import_as_disabled ) {
				$definition['enabled'] = false;
			}

			$errors = $this->validator->validateDefinition( $definition, $already_exists, $already_exists ? $key : null );
			if ( $errors !== [] ) {
				$result['errors'][] = sprintf(
					/* translators: 1: taxonomy key, 2: error message. */
					__( '“%1$s” was skipped: %2$s', 'wppowersuite-lite' ),
					$key,
					implode( ' ', $errors )
				);
				continue;
			}

			$this->storage->save( $key, $definition );
			++$result['imported'];
		}

		return $result;
	}

	/**
	 * Extract the list of raw definition entries from decoded import data.
	 *
	 * @param array<string, mixed>|array<int, mixed> $data Decoded JSON.
	 * @return array<int, mixed>
	 */
	private function extractEntries( array $data ): array {
		if ( isset( $data['taxonomies'] ) && is_array( $data['taxonomies'] ) ) {
			return array_values( $data['taxonomies'] );
		}

		if ( array_is_list( $data ) ) {
			return $data;
		}

		if ( isset( $data['key'] ) ) {
			return [ $data ];
		}

		return [];
	}
}
