<?php
/**
 * Admin: Player Builder metaboxes, columns, and preview.
 *
 * @package The Studio Player
 */

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

/**
 * Resolve the current playlist post ID for admin edit screens.
 *
 * @return int
 */
function thespl_resolve_current_playlist_admin_post_id() {
	global $post;

	if ( $post instanceof WP_Post && 'thespl_playlist' === $post->post_type ) {
		return (int) $post->ID;
	}

	$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
	if ( ! $screen || ! isset( $screen->post_type ) || 'thespl_playlist' !== $screen->post_type ) {
		return 0;
	}

	$post_id       = function_exists( 'thespl_get_requested_admin_post_id' ) ? thespl_get_requested_admin_post_id() : 0;
	$playlist_post = $post_id > 0 ? get_post( $post_id ) : null;
	if ( ! $playlist_post instanceof WP_Post || 'thespl_playlist' !== $playlist_post->post_type ) {
		return 0;
	}

	return (int) $playlist_post->ID;
}

/**
 * Enqueue assets for the playlist editor.
 *
 * @param string $hook Current admin hook.
 * @return void
 */
function thespl_enqueue_playlist_editor_assets( $hook ) {
	if ( ! is_string( $hook ) || ( 'post.php' !== $hook && 'post-new.php' !== $hook ) ) {
		return;
	}

	$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
	if ( ! $screen || ! isset( $screen->post_type ) || 'thespl_playlist' !== $screen->post_type ) {
		return;
	}
	$tpl_slug = function_exists( 'thespl_get_default_template_slug' ) ? thespl_get_default_template_slug() : '';
	$post_id  = thespl_resolve_current_playlist_admin_post_id();
	if ( $post_id > 0 ) {
		$current = get_post_meta( $post_id, '_thespl_playlist_template', true );
		$current = is_string( $current ) ? sanitize_key( $current ) : '';
		if ( '' !== $current && function_exists( 'thespl_template_slug_is_valid' ) && thespl_template_slug_is_valid( $current ) ) {
			$tpl_slug = $current;
		}
	}
	$GLOBALS['thespl_current_template_slug'] = $tpl_slug;

	wp_enqueue_media();
	wp_enqueue_script( 'jquery-ui-sortable' );
	wp_enqueue_style( 'wp-color-picker' );
	thespl_player_enqueue_assets();

	$plugin_url = THESPL_PLUGIN_URL;
	$version    = THESPL_VERSION;

	$variant_engine = thespl_get_variant_engine();
	$variant_count  = $variant_engine->get_editor_variant_count();
	$raw_json       = '';
	if ( $post_id > 0 ) {
		$raw_json = get_post_meta( $post_id, '_thespl_playlist_json', true );
		$raw_json = is_string( $raw_json ) ? $raw_json : '';
	}
	$is_read_only = $variant_engine->playlist_json_contains_extra_variants( $raw_json );
	wp_localize_script(
		'thespl-player-core',
		'thesplPlaylistEditorConfig',
		array(
			'postId'       => $post_id,
			'rawJson'      => $raw_json,
			'variantCount' => $variant_count,
			'readOnly'     => $is_read_only,
			'i18n'         => array(
				'move'            => __( 'Move', 'the-studio-player' ),
				'title'           => __( 'Title', 'the-studio-player' ),
				'artist'          => __( 'Artist', 'the-studio-player' ),
				'variants'        => __( 'Variants', 'the-studio-player' ),
				'variantsEditor'  => __( 'Variants editor', 'the-studio-player' ),
				'social'          => __( 'Social URL', 'the-studio-player' ),
				'audio'           => __( 'Audio', 'the-studio-player' ),
				'cover'           => __( 'Cover', 'the-studio-player' ),
				'choose'          => __( 'Choose', 'the-studio-player' ),
				'clear'           => __( 'Clear', 'the-studio-player' ),
				'remove'          => __( 'Remove', 'the-studio-player' ),
				'missingRequired' => __( 'Please complete Title, Artist and Audio.', 'the-studio-player' ),
				'variantBase'     => __( 'Variant', 'the-studio-player' ),
				'variantInfoBase' => __( 'Variant notes', 'the-studio-player' ),
				'readOnlyNotice'  => __( 'This playlist contains Premium-only variant data. Open it in an active Pro build to edit all variants safely.', 'the-studio-player' ),
			),
		)
	);

	wp_register_script( 'thespl-admin-playlist-editor', $plugin_url . 'assets/js/admin-playlist-editor.js', array( 'jquery', 'jquery-ui-sortable' ), $version, true );

	wp_enqueue_script( 'thespl-admin-playlist-editor' );

	$preview_nonce = wp_create_nonce( 'thespl_preview_render' );
	$magic_nonce   = wp_create_nonce( 'thespl_magic_palette' );
	$ajax_url      = admin_url( 'admin-ajax.php' );
	$palettes      = array();
	$palette_meta  = array();
	if ( $post_id > 0 ) {
		$design_raw = get_post_meta( $post_id, '_thespl_playlist_design', true );
		$design_raw = is_array( $design_raw ) ? $design_raw : array();
		if ( function_exists( 'thespl_normalize_playlist_design' ) ) {
			$design_raw = thespl_normalize_playlist_design( $design_raw );
		}
		if ( is_array( $design_raw ) && ! empty( $design_raw ) ) {
			$keys = array(
				'accent_color',
				'accent_2_color',
				'base_color',
				'text_color',
				'waveform_color',
				'ring_base_color',
				'button_bg_color',
				'button_text_color',
				'button_active_bg_color',
				'button_active_text_color',
				'chip_bg_color',
				'chip_br_color',
			);
			foreach ( $keys as $key ) {
				if ( isset( $design_raw[ $key ] ) && is_string( $design_raw[ $key ] ) ) {
					$hex = sanitize_hex_color( $design_raw[ $key ] );
					if ( $hex && ! in_array( $hex, $palettes, true ) ) {
						$palettes[] = $hex;
					}
					if ( $hex ) {
						if ( ! isset( $palette_meta[ $hex ] ) || ! is_array( $palette_meta[ $hex ] ) ) {
							$palette_meta[ $hex ] = array();
						}
						$label = 'Design: ' . $key;
						if ( ! in_array( $label, $palette_meta[ $hex ], true ) ) {
							$palette_meta[ $hex ][] = $label;
						}
					}
				}
			}
		}
		$analysis = thespl_generate_magic_palette( $post_id );
		if ( is_array( $analysis ) ) {
			if ( isset( $analysis['tokens'] ) && is_array( $analysis['tokens'] ) ) {
				foreach ( $analysis['tokens'] as $token_key => $val ) {
					if ( ! is_string( $val ) ) {
						continue;
					}
					$hex = sanitize_hex_color( $val );
					if ( $hex && ! in_array( $hex, $palettes, true ) ) {
						$palettes[] = $hex;
					}
					if ( $hex ) {
						if ( ! isset( $palette_meta[ $hex ] ) || ! is_array( $palette_meta[ $hex ] ) ) {
							$palette_meta[ $hex ] = array();
						}
						$label = 'Tokens: ' . $token_key;
						if ( ! in_array( $label, $palette_meta[ $hex ], true ) ) {
							$palette_meta[ $hex ][] = $label;
						}
					}
				}
			}
			if ( isset( $analysis['sources'] ) && is_array( $analysis['sources'] ) ) {
				$sources = $analysis['sources'];
				if ( isset( $sources['global_styles'] ) && is_array( $sources['global_styles'] ) ) {
					foreach ( array( 'background', 'text', 'link' ) as $field ) {
						if ( isset( $sources['global_styles'][ $field ] ) && is_string( $sources['global_styles'][ $field ] ) ) {
							$hex = sanitize_hex_color( $sources['global_styles'][ $field ] );
							if ( $hex && ! in_array( $hex, $palettes, true ) ) {
								$palettes[] = $hex;
							}
							if ( $hex ) {
								if ( ! isset( $palette_meta[ $hex ] ) || ! is_array( $palette_meta[ $hex ] ) ) {
									$palette_meta[ $hex ] = array();
								}
								$label = 'Global styles: ' . $field;
								if ( ! in_array( $label, $palette_meta[ $hex ], true ) ) {
									$palette_meta[ $hex ][] = $label;
								}
							}
						}
					}
				}
				if ( isset( $sources['global_palette']['palette'] ) && is_array( $sources['global_palette']['palette'] ) ) {
					foreach ( $sources['global_palette']['palette'] as $slug => $color ) {
						if ( ! is_string( $color ) ) {
							continue;
						}
						$hex = sanitize_hex_color( $color );
						if ( $hex && ! in_array( $hex, $palettes, true ) ) {
							$palettes[] = $hex;
						}
						if ( $hex ) {
							if ( ! isset( $palette_meta[ $hex ] ) || ! is_array( $palette_meta[ $hex ] ) ) {
								$palette_meta[ $hex ] = array();
							}
							$label = 'Global palette: ' . $slug;
							if ( ! in_array( $label, $palette_meta[ $hex ], true ) ) {
								$palette_meta[ $hex ][] = $label;
							}
						}
					}
				}
				if ( isset( $sources['customizer']['derived'] ) && is_array( $sources['customizer']['derived'] ) ) {
					foreach ( $sources['customizer']['derived'] as $group_key => $group ) {
						if ( ! is_array( $group ) ) {
							continue;
						}
						foreach ( $group as $color ) {
							if ( ! is_string( $color ) ) {
								continue;
							}
							$hex = sanitize_hex_color( $color );
							if ( $hex && ! in_array( $hex, $palettes, true ) ) {
								$palettes[] = $hex;
							}
							if ( $hex ) {
								if ( ! isset( $palette_meta[ $hex ] ) || ! is_array( $palette_meta[ $hex ] ) ) {
									$palette_meta[ $hex ] = array();
								}
								$label = 'Customizer derived: ' . $group_key;
								if ( ! in_array( $label, $palette_meta[ $hex ], true ) ) {
									$palette_meta[ $hex ][] = $label;
								}
							}
						}
					}
				}
				if ( isset( $sources['classic_css_probe'] ) && is_array( $sources['classic_css_probe'] ) ) {
					foreach ( array( 'bg', 'text', 'link' ) as $field ) {
						if ( isset( $sources['classic_css_probe'][ $field ] ) && is_string( $sources['classic_css_probe'][ $field ] ) ) {
							$hex = sanitize_hex_color( $sources['classic_css_probe'][ $field ] );
							if ( $hex && ! in_array( $hex, $palettes, true ) ) {
								$palettes[] = $hex;
							}
							if ( $hex ) {
								if ( ! isset( $palette_meta[ $hex ] ) || ! is_array( $palette_meta[ $hex ] ) ) {
									$palette_meta[ $hex ] = array();
								}
								$label = 'Classic CSS probe: ' . $field;
								if ( ! in_array( $label, $palette_meta[ $hex ], true ) ) {
									$palette_meta[ $hex ][] = $label;
								}
							}
						}
					}
				}
				if ( isset( $sources['resolved'] ) && is_array( $sources['resolved'] ) ) {
					foreach ( array( 'bg', 'text', 'accent', 'accent_2', 'waveform' ) as $field ) {
						if ( isset( $sources['resolved'][ $field ] ) && is_string( $sources['resolved'][ $field ] ) ) {
							$hex = sanitize_hex_color( $sources['resolved'][ $field ] );
							if ( $hex && ! in_array( $hex, $palettes, true ) ) {
								$palettes[] = $hex;
							}
							if ( $hex ) {
								if ( ! isset( $palette_meta[ $hex ] ) || ! is_array( $palette_meta[ $hex ] ) ) {
									$palette_meta[ $hex ] = array();
								}
								$label = 'Resolved: ' . $field;
								if ( ! in_array( $label, $palette_meta[ $hex ], true ) ) {
									$palette_meta[ $hex ][] = $label;
								}
							}
						}
					}
				}
			}
		}
	}

	$template_schemas     = array();
	$template_folders     = array();
	$default_marker_slugs = array();
	$templates_entries    = function_exists( 'thespl_get_player_template_entries' ) ? thespl_get_player_template_entries() : array();
	if ( is_array( $templates_entries ) ) {
		foreach ( $templates_entries as $entry ) {
			if ( ! isset( $entry['slug'] ) || ! is_string( $entry['slug'] ) || '' === $entry['slug'] ) {
				continue;
			}
			$slug   = sanitize_key( $entry['slug'] );
			$dir    = isset( $entry['dir'] ) && is_string( $entry['dir'] ) ? $entry['dir'] : '';
			$source = isset( $entry['source'] ) && is_string( $entry['source'] ) ? $entry['source'] : '';
			if ( '' === $slug || '' === $dir ) {
				continue;
			}
			if ( ! empty( $entry['has_default_marker'] ) ) {
				$default_marker_slugs[] = $slug;
			}
			$folder                    = basename( untrailingslashit( $dir ) );
			$template_folders[ $slug ] = array(
				'src'    => $source,
				'folder' => $folder,
			);
			if ( $post_id > 0 && function_exists( 'thespl_design_get_full_schema' ) ) {
				$schema = thespl_design_get_full_schema( $post_id, $slug );
				if ( is_array( $schema ) ) {
					$template_schemas[ $slug ] = $schema;
				}
			}
		}
	}
	$default_marker_slugs = array_values( array_unique( $default_marker_slugs ) );
	$uploads_base_url     = '';
	$uploads              = wp_upload_dir();
	if ( ! empty( $uploads['baseurl'] ) && is_string( $uploads['baseurl'] ) ) {
		$uploads_base_url = trailingslashit( $uploads['baseurl'] ) . 'the-studio-player/templates/player/';
	}
	$default_slug = '';
	if ( function_exists( 'thespl_get_default_template_slug' ) ) {
		$default_slug = thespl_get_default_template_slug();
	}
	wp_register_script( 'thespl-admin-preview', $plugin_url . 'assets/js/admin-preview.js', array( 'thespl-player-core', 'thespl-player-store', 'thespl-player-ui' ), $version, true );
	wp_localize_script(
		'thespl-admin-preview',
		'thesplPreviewConfig',
		array(
			'ajaxUrl'               => $ajax_url,
			'nonce'                 => $preview_nonce,
			'magicNonce'            => $magic_nonce,
			'pluginUrl'             => THESPL_PLUGIN_URL,
			'uploadsBaseUrl'        => $uploads_base_url,
			'defaultSlug'           => $default_slug,
			'defaultMarkerSlugs'    => $default_marker_slugs,
			'defaultMarkerConflict' => count( $default_marker_slugs ) > 1,
			'version'               => (string) THESPL_VERSION,
			'postId'                => $post_id,
			'currentTemplate'       => $tpl_slug,
			'templates'             => $template_schemas,
			'templateFolders'       => $template_folders,
			'palettes'              => $palettes,
			'paletteMeta'           => $palette_meta,
			'errorTitle'            => __( 'The Studio Player could not render the selected player template.', 'the-studio-player' ),
			'errorBody'             => __( 'Please check your custom player template file for syntax or runtime errors. If your site becomes unavailable after editing a template, you can rename the template folder or the plugin folder via FTP to deactivate it and restore access.', 'the-studio-player' ),
			'assetErrorTitle'       => __( 'The selected template assets could not be loaded.', 'the-studio-player' ),
			'assetErrorBody'        => __( 'The preview HTML was rendered, but the template JavaScript dependencies are missing or not reachable. Check whether script.js exists in the selected template folder and whether the template slug is still valid.', 'the-studio-player' ),
			'slugMismatchTitle'     => __( 'Template slug mismatch detected.', 'the-studio-player' ),
			'slugMismatchBody'      => __( 'The selected template slug does not match the slug selector used inside template dependencies. This often happens after renaming a template folder. Keep folder name and internal data-template-slug bindings in sync.', 'the-studio-player' ),
		)
	);

	wp_enqueue_script( 'thespl-admin-preview' );

	wp_register_script( 'thespl-admin-preview-controls', $plugin_url . 'assets/js/admin-preview-controls.js', array(), $version, true );
	wp_enqueue_script( 'thespl-admin-preview-controls' );

	wp_register_script( 'thespl-admin-design', $plugin_url . 'assets/js/admin-design.js', array( 'wp-color-picker', 'thespl-admin-preview' ), $version, true );
	wp_localize_script(
		'thespl-admin-design',
		'thesplDesignI18n',
		array(
			'confirmResetTitle'   => __( 'Reset design to template defaults?', 'the-studio-player' ),
			'confirmResetMessage' => __( 'This will reset all design colors to the template defaults for this player. Any unsaved changes in the design panel will be lost. If you pressed this by mistake and have not saved yet, reload the page to restore your last saved design.', 'the-studio-player' ),
			'confirmResetConfirm' => __( 'Reset to defaults', 'the-studio-player' ),
			'confirmResetCancel'  => __( 'Cancel', 'the-studio-player' ),
			'lockLabelLock'       => __( 'Lock design', 'the-studio-player' ),
			'lockLabelUnlock'     => __( 'Unlock design', 'the-studio-player' ),
			'lockStatusLocked'    => __( 'Design changes are locked.', 'the-studio-player' ),
			'lockStatusUnlocked'  => __( 'Design changes are unlocked.', 'the-studio-player' ),
		)
	);
	wp_enqueue_script( 'thespl-admin-design' );
}
add_action( 'admin_enqueue_scripts', 'thespl_enqueue_playlist_editor_assets' );

if ( ! function_exists( 'thespl_playlist_json_contains_extra_variants' ) ) {
	/**
	 * Check whether playlist JSON contains more variants than the current editor supports.
	 *
	 * @param string   $raw_json       Raw playlist JSON.
	 * @param int|null $variant_limit  Deprecated parameter retained for compatibility.
	 * @return bool
	 */
	function thespl_playlist_json_contains_extra_variants( $raw_json, $variant_limit ) {
		unset( $variant_limit );

		return thespl_get_variant_engine()->playlist_json_contains_extra_variants( $raw_json );
	}
}

/**
 * Registriert die Metaboxen für den Playlist-Editor.
 *
 * @return void
 */
function thespl_register_playlist_meta_boxes() {
	add_meta_box(
		'thespl_playlist_preview',
		__( 'Player Interface', 'the-studio-player' ),
		'thespl_render_playlist_preview_metabox',
		'thespl_playlist',
		'normal',
		'default'
	);
}
add_action( 'add_meta_boxes_thespl_playlist', 'thespl_register_playlist_meta_boxes' );

/**
 * Rendert die Playlist-Editor-UI direkt nach dem Beitragstitel.
 *
 * @param WP_Post $post Aktueller Beitrag.
 * @return void
 */
function thespl_render_playlist_editor_after_title( $post ) {
	if ( ! $post || ! ( $post instanceof WP_Post ) ) {
		return;
	}
	if ( 'thespl_playlist' !== $post->post_type ) {
		return;
	}
	thespl_render_admin_top_nav( 'edit.php?post_type=thespl_playlist' );
	thespl_render_playlist_json_metabox( $post );
}
add_action( 'edit_form_after_title', 'thespl_render_playlist_editor_after_title' );

/**
 * Render top navigation on the playlist list screen.
 *
 * @return void
 */
function thespl_render_playlist_list_top_nav() {
	$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
	if ( ! $screen || ! isset( $screen->id ) ) {
		return;
	}
	if ( 'edit-thespl_playlist' !== (string) $screen->id ) {
		return;
	}
	thespl_render_admin_top_nav( 'edit.php?post_type=thespl_playlist' );
}
add_action( 'all_admin_notices', 'thespl_render_playlist_list_top_nav' );

/**
 * Registriert das einspaltige Layout für den Playlist-Editor.
 *
 * @param array $columns Vorhandene Spaltenzuordnung.
 * @param mixed $screen  Aktueller Screen.
 * @return array
 */
function thespl_playlist_screen_layout_columns( $columns, $screen ) {

	if ( is_object( $screen ) && isset( $screen->id ) && 'thespl_playlist' === $screen->id ) {
		$columns['thespl_playlist'] = 1;
	}
	return $columns;
}
add_filter( 'screen_layout_columns', 'thespl_playlist_screen_layout_columns', 10, 2 );

/**
 * Force a single-column layout for the playlist editor.
 *
 * @return int
 */
function thespl_playlist_force_single_column_layout() {

	return 1;
}
add_filter( 'get_user_option_screen_layout_thespl_playlist', 'thespl_playlist_force_single_column_layout', 10, 0 );

/**
 * Hide the screen options tab on playlist screens.
 *
 * @param bool      $show   Whether to show the screen options.
 * @param WP_Screen $screen Current screen object.
 * @return bool
 */
function thespl_hide_screen_options_on_playlist_screens( $show, $screen ) {
	$post_type = ( is_object( $screen ) && isset( $screen->post_type ) ) ? (string) $screen->post_type : '';
	if ( 'thespl_playlist' === $post_type ) {
		return false;
	}
	return $show;
}
add_filter( 'screen_options_show_screen', 'thespl_hide_screen_options_on_playlist_screens', 10, 2 );

/**
 * Force the playlist editor metaboxes into the main column.
 *
 * @param mixed $value The value to return instead of the option value.
 * @return mixed
 */
function thespl_force_playlist_metabox_order( $value ) {
	if ( ! is_array( $value ) ) {
		return $value;
	}

	$targets  = array( 'thespl_playlist_preview' );
	$contexts = array( 'normal', 'side', 'advanced' );

	$lists = array();
	foreach ( $contexts as $context ) {
		$lists[ $context ] = array();
		if ( isset( $value[ $context ] ) && is_string( $value[ $context ] ) && '' !== $value[ $context ] ) {
			$lists[ $context ] = array_values(
				array_filter(
					array_map( 'trim', explode( ',', $value[ $context ] ) )
				)
			);
		}
	}

	foreach ( $contexts as $context ) {
		if ( empty( $lists[ $context ] ) ) {
			continue;
		}
		$lists[ $context ] = array_values( array_diff( $lists[ $context ], $targets ) );
	}

	$normal = isset( $lists['normal'] ) ? $lists['normal'] : array();
	foreach ( array_reverse( $targets ) as $id ) {
		array_unshift( $normal, $id );
	}
	$lists['normal'] = array_values( array_unique( $normal ) );

	foreach ( $contexts as $context ) {
		$value[ $context ] = implode( ',', $lists[ $context ] );
	}

	return $value;
}
add_filter( 'get_user_option_meta-box-order_thespl_playlist', 'thespl_force_playlist_metabox_order', 10, 1 );

/**
 * Render the playlist editor JSON metabox.
 *
 * @param WP_Post $post Current post.
 * @return void
 */
function thespl_render_playlist_json_metabox( $post ) {
	wp_nonce_field( 'thespl_playlist_save', 'thespl_playlist_nonce' );
	$json = get_post_meta( $post->ID, '_thespl_playlist_json', true );
	if ( ! is_string( $json ) ) {
		$json = '';
	}

	$variant_engine = thespl_get_variant_engine();
	$variant_count  = $variant_engine->get_editor_variant_count();
	$is_read_only   = $variant_engine->playlist_json_contains_extra_variants( $json );

	$cfg = array(
		'postId'       => isset( $post->ID ) ? (int) $post->ID : 0,
		'variantCount' => $variant_count,
		'readOnly'     => $is_read_only,
		'adminPostUrl' => admin_url( 'post.php' ),
		'rawJson'      => $json,
		'i18n'         => array(
			'variants'        => __( 'Variants', 'the-studio-player' ),
			'tracks'          => __( 'Tracks', 'the-studio-player' ),
			'addTrack'        => __( 'Add track', 'the-studio-player' ),
			'addFromMedia'    => __( 'Add audio from Media Library', 'the-studio-player' ),
			'title'           => __( 'Title', 'the-studio-player' ),
			'artist'          => __( 'Artist', 'the-studio-player' ),
			'credits'         => __( 'Credits', 'the-studio-player' ),
			'social'          => __( 'Artist URL', 'the-studio-player' ),
			'audio'           => __( 'Audio', 'the-studio-player' ),
			'cover'           => __( 'Cover', 'the-studio-player' ),
			'choose'          => __( 'Choose', 'the-studio-player' ),
			'clear'           => __( 'Clear', 'the-studio-player' ),
			'remove'          => __( 'Remove', 'the-studio-player' ),
			'rawJson'         => __( 'Raw JSON', 'the-studio-player' ),
			'variantInfoBase' => __( 'Variant notes', 'the-studio-player' ),
			'readOnlyNotice'  => __( 'This playlist contains Premium-only variant data. Open it in an active Pro build to edit all variants safely.', 'the-studio-player' ),
		),
	);

	echo '<div class="thespl-playlist-editor" id="thespl-playlist-editor-' . (int) $cfg['postId'] . '">';
	if ( $is_read_only ) {
		$notice = esc_html( $cfg['i18n']['readOnlyNotice'] );
		if ( function_exists( 'thespl_has_pro_assets' ) && ! thespl_has_pro_assets() ) {
			$get_pro_url  = thespl_get_marketing_pricing_url();
			$get_pro_link = '<a href="' . esc_url( $get_pro_url ) . '" target="_blank" rel="noopener noreferrer" style="color:#C6730D;font-weight:600;">' . esc_html__( 'Get Pro', 'the-studio-player' ) . '</a>';
			$notice      .= ' ' . $get_pro_link;
		}
		echo '<div class="notice notice-warning inline"><p>' . wp_kses(
			$notice,
			array(
				'a' => array(
					'href'   => true,
					'target' => true,
					'rel'    => true,
					'style'  => true,
				),
			)
		) . '</p></div>';
	}

	echo '<div class="thespl-playlist-editor__section">';
	echo '<div class="thespl-playlist-editor__header">';
	echo '<h4>' . esc_html( $cfg['i18n']['tracks'] ) . '</h4>';
	echo '<button type="button" class="button button-secondary thespl-playlist-editor__add-track">' . esc_html( $cfg['i18n']['addTrack'] ) . '</button>';
	echo '<button type="button" class="button button-secondary thespl-playlist-editor__add-from-media">' . esc_html( $cfg['i18n']['addFromMedia'] ) . '</button>';
	echo '</div>';

	echo '<table class="widefat striped thespl-playlist-editor__table">';
	echo '<thead><tr>';
	echo '<th></th>';
	echo '<th>' . esc_html( $cfg['i18n']['title'] ) . '</th>';
	echo '<th>' . esc_html( $cfg['i18n']['artist'] ) . '</th>';
	echo '<th>' . esc_html( $cfg['i18n']['variants'] ) . '</th>';
	echo '<th>' . esc_html( $cfg['i18n']['credits'] ) . '</th>';
	echo '<th>' . esc_html( $cfg['i18n']['social'] ) . '</th>';
	echo '<th>' . esc_html( $cfg['i18n']['audio'] ) . '</th>';
	echo '<th>' . esc_html( $cfg['i18n']['cover'] ) . '</th>';
	echo '<th></th>';
	echo '</tr></thead>';
	echo '<tbody class="thespl-playlist-editor__tracks"></tbody>';
	echo '</table>';
	echo '<div class="thespl-playlist-editor__actions">';
	echo '<button type="button" class="button button-primary thespl-playlist-editor__save-playlist">' . esc_html__( 'Save Playlist', 'the-studio-player' ) . '</button>';
	do_action( 'thespl_playlist_editor_actions', $post );
	echo '</div>';
	echo '</div>';

	if ( $is_read_only ) {
		echo '<input type="hidden" name="thespl_playlist_json_locked" value="1" />';
	}
	echo '<textarea name="thespl_playlist_json" class="thespl-playlist-editor__hidden">' . esc_textarea( $json ) . '</textarea>';
}

/**
 * Render the playlist editor shortcode metabox.
 *
 * @param WP_Post $post Current post.
 * @return void
 */
function thespl_render_playlist_shortcode_metabox( $post ) {
	$post_id          = isset( $post->ID ) ? (int) $post->ID : 0;
	$current_template = get_post_meta( $post_id, '_thespl_playlist_template', true );
	$current_template = is_string( $current_template ) ? sanitize_key( $current_template ) : '';
	if ( '' === $current_template && function_exists( 'thespl_get_default_template_slug' ) ) {
		$current_template = thespl_get_default_template_slug();
	}
	$shortcode = thespl_get_player_shortcode_for_playlist( $post_id, $current_template );
	if ( '' === $shortcode ) {
		echo esc_html__( 'Save or publish this playlist to get the shortcode.', 'the-studio-player' );
		return;
	}
	echo '<input type="text" readonly class="thespl-shortcode-field" value="' . esc_attr( $shortcode ) . '" />';
}

/**
 * Render the playlist editor preview metabox.
 *
 * @param WP_Post $post Current post.
 * @return void
 */
function thespl_render_playlist_preview_metabox( $post ) {
	$post_id = isset( $post->ID ) ? (int) $post->ID : 0;
	if ( $post_id <= 0 ) {
		echo esc_html__( 'Save the playlist first to preview the player.', 'the-studio-player' );
		return;
	}
	$cid              = 'thespl-preview-controls-' . $post_id;
	$wid              = 'thespl-preview-wrap-' . $post_id;
	$sid              = 'thespl-preview-shortcode-' . $post_id;
	$bid              = 'thespl-preview-copy-' . $post_id;
	$mid              = 'thespl-preview-copied-' . $post_id;
	$row_id           = 'thespl-preview-row-' . $post_id;
	$panel_id         = 'thespl-design-panel-' . $post_id;
	$aid              = 'thespl-design-accent-' . $post_id;
	$wf_id            = 'thespl-design-waveform-' . $post_id;
	$ring_id          = 'thespl-design-ring-' . $post_id;
	$show_playlist_id = 'thespl-design-show-playlist-' . $post_id;
	$show_info_id     = 'thespl-design-show-info-' . $post_id;
	$current_template = get_post_meta( $post_id, '_thespl_playlist_template', true );
	$current_template = is_string( $current_template ) ? sanitize_key( $current_template ) : '';
	if ( '' === $current_template && function_exists( 'thespl_get_default_template_slug' ) ) {
		$current_template = thespl_get_default_template_slug();
	}
	thespl_player_enqueue_assets();
	$templates            = thespl_get_available_player_templates();
	$default_flags        = array();
	$default_marker_slugs = array();
	if ( function_exists( 'thespl_get_player_template_entries' ) ) {
		$entries = thespl_get_player_template_entries();
		if ( is_array( $entries ) ) {
			foreach ( $entries as $entry ) {
				if ( ! isset( $entry['slug'] ) || ! is_string( $entry['slug'] ) || '' === $entry['slug'] ) {
					continue;
				}
				$slug = sanitize_key( $entry['slug'] );
				if ( '' === $slug ) {
					continue;
				}
				if ( ! empty( $entry['has_default_marker'] ) ) {
					$default_flags[ $slug ] = true;
					$default_marker_slugs[] = $slug;
				}
			}
		}
	}
	$default_marker_slugs = array_values( array_unique( $default_marker_slugs ) );
	$default_marker_count = count( $default_marker_slugs );
	if ( function_exists( 'thespl_template_slug_is_valid' ) && ! thespl_template_slug_is_valid( $current_template ) && function_exists( 'thespl_get_default_template_slug' ) ) {
		$current_template = thespl_get_default_template_slug();
	}
	$full_schema                 = function_exists( 'thespl_design_get_full_schema' ) ? thespl_design_get_full_schema( $post_id, $current_template ) : array();
	$allow_user_css_overrides    = isset( $full_schema['flags']['allows_user_css_overrides'] ) ? (bool) $full_schema['flags']['allows_user_css_overrides'] : ( function_exists( 'thespl_template_allows_user_css_overrides' ) ? thespl_template_allows_user_css_overrides( $current_template ) : true );
	$supports_audio_info_panel   = isset( $full_schema['flags']['has_audio_info_panel'] ) ? (bool) $full_schema['flags']['has_audio_info_panel'] : ( function_exists( 'thespl_template_has_audio_info_panel' ) ? thespl_template_has_audio_info_panel( $current_template ) : true );
	$sel                         = 'thespl-template-select-' . $post_id;
	$save_id                     = 'thespl-save-btn-' . $post_id;
	$shortcode                   = thespl_get_player_shortcode_for_playlist( $post_id, $current_template );
	$design                      = isset( $full_schema['design'] ) && is_array( $full_schema['design'] ) ? $full_schema['design'] : ( function_exists( 'thespl_design_get_schema' ) ? thespl_design_get_schema( $post_id, $current_template ) : array() );
	$design_raw                  = get_post_meta( $post_id, '_thespl_playlist_design', true );
	$design_raw                  = is_array( $design_raw ) ? $design_raw : array();
	$tpl_defaults                = function_exists( 'thespl_template_get_design_defaults' ) ? thespl_template_get_design_defaults( $current_template ) : array();
	$tpl_accent                  = isset( $tpl_defaults['accent_color'] ) && is_string( $tpl_defaults['accent_color'] ) ? sanitize_hex_color( $tpl_defaults['accent_color'] ) : '';
	$tpl_accent2                 = isset( $tpl_defaults['accent_2_color'] ) && is_string( $tpl_defaults['accent_2_color'] ) ? sanitize_hex_color( $tpl_defaults['accent_2_color'] ) : '';
	$tpl_base                    = isset( $tpl_defaults['base_color'] ) && is_string( $tpl_defaults['base_color'] ) ? sanitize_hex_color( $tpl_defaults['base_color'] ) : '';
	$tpl_text                    = isset( $tpl_defaults['text_color'] ) && is_string( $tpl_defaults['text_color'] ) ? sanitize_hex_color( $tpl_defaults['text_color'] ) : '';
	$tpl_waveform                = isset( $tpl_defaults['waveform_color'] ) && is_string( $tpl_defaults['waveform_color'] ) ? sanitize_hex_color( $tpl_defaults['waveform_color'] ) : '';
	$tpl_ring                    = isset( $tpl_defaults['ring_base_color'] ) && is_string( $tpl_defaults['ring_base_color'] ) ? sanitize_hex_color( $tpl_defaults['ring_base_color'] ) : '';
	$accent                      = isset( $design['accent_color'] ) && is_string( $design['accent_color'] ) ? sanitize_hex_color( $design['accent_color'] ) : $tpl_accent;
	$accent2                     = isset( $design['accent_2_color'] ) && is_string( $design['accent_2_color'] ) ? sanitize_hex_color( $design['accent_2_color'] ) : $tpl_accent2;
	$base                        = isset( $design['base_color'] ) && is_string( $design['base_color'] ) ? sanitize_hex_color( $design['base_color'] ) : $tpl_base;
	$textcol                     = isset( $design['text_color'] ) && is_string( $design['text_color'] ) ? sanitize_hex_color( $design['text_color'] ) : $tpl_text;
	$waveform                    = isset( $design['waveform_color'] ) && is_string( $design['waveform_color'] ) ? sanitize_hex_color( $design['waveform_color'] ) : $tpl_waveform;
	$ringbase                    = isset( $design['ring_base_color'] ) && is_string( $design['ring_base_color'] ) ? sanitize_hex_color( $design['ring_base_color'] ) : $tpl_ring;
	$show_pl                     = isset( $full_schema['flags']['show_playlist'] ) ? (bool) $full_schema['flags']['show_playlist'] : ( ! isset( $design['show_playlist'] ) ? true : (bool) $design['show_playlist'] );
	$show_info                   = isset( $full_schema['flags']['show_audio_info'] ) ? (bool) $full_schema['flags']['show_audio_info'] : ( ! isset( $design['show_audio_info'] ) ? true : (bool) $design['show_audio_info'] );
	$panel_body_id               = 'thespl-design-panel-body-' . $post_id;
	$panel_toggle                = 'thespl-design-panel-toggle-' . $post_id;
	$debug_toggle_id             = 'thespl-design-debug-toggle-' . $post_id;
	$debug_panel_id              = 'thespl-design-debug-' . $post_id;
	$lock_toggle_id              = 'thespl-design-lock-toggle-' . $post_id;
	$lock_status_id              = 'thespl-design-lock-state-' . $post_id;
	$lock_input_id               = 'thespl-design-lock-input-' . $post_id;
	$btn_bg_id                   = 'thespl-design-button-bg-' . $post_id;
	$btn_tx_id                   = 'thespl-design-button-text-' . $post_id;
	$btn_act_bg_id               = 'thespl-design-button-active-bg-' . $post_id;
	$btn_act_tx_id               = 'thespl-design-button-active-text-' . $post_id;
	$chip_bg_id                  = 'thespl-design-chip-bg-' . $post_id;
	$chip_br_id                  = 'thespl-design-chip-br-' . $post_id;
	$buffer_alpha_id             = 'thespl-design-buffer-alpha-' . $post_id;
	$btn_bg_default              = isset( $tpl_defaults['button_bg_color'] ) && is_string( $tpl_defaults['button_bg_color'] ) ? sanitize_hex_color( $tpl_defaults['button_bg_color'] ) : '';
	$btn_text_default            = isset( $tpl_defaults['button_text_color'] ) && is_string( $tpl_defaults['button_text_color'] ) ? sanitize_hex_color( $tpl_defaults['button_text_color'] ) : '';
	$btn_act_bg_def              = isset( $tpl_defaults['button_active_bg_color'] ) && is_string( $tpl_defaults['button_active_bg_color'] ) ? sanitize_hex_color( $tpl_defaults['button_active_bg_color'] ) : '';
	$btn_act_tx_def              = isset( $tpl_defaults['button_active_text_color'] ) && is_string( $tpl_defaults['button_active_text_color'] ) ? sanitize_hex_color( $tpl_defaults['button_active_text_color'] ) : '';
	$chip_bg_default             = isset( $tpl_defaults['chip_bg_color'] ) && is_string( $tpl_defaults['chip_bg_color'] ) ? sanitize_hex_color( $tpl_defaults['chip_bg_color'] ) : '';
	$chip_br_default             = isset( $tpl_defaults['chip_br_color'] ) && is_string( $tpl_defaults['chip_br_color'] ) ? sanitize_hex_color( $tpl_defaults['chip_br_color'] ) : '';
	$btn_bg_val                  = isset( $design_raw['button_bg_color'] ) && is_string( $design_raw['button_bg_color'] ) ? sanitize_hex_color( $design_raw['button_bg_color'] ) : ( $btn_bg_default ? $btn_bg_default : '' );
	$btn_text_val                = isset( $design_raw['button_text_color'] ) && is_string( $design_raw['button_text_color'] ) ? sanitize_hex_color( $design_raw['button_text_color'] ) : ( $btn_text_default ? $btn_text_default : '' );
	$btn_act_bg_val              = isset( $design_raw['button_active_bg_color'] ) && is_string( $design_raw['button_active_bg_color'] ) ? sanitize_hex_color( $design_raw['button_active_bg_color'] ) : ( $btn_act_bg_def ? $btn_act_bg_def : '' );
	$btn_act_tx_val              = isset( $design_raw['button_active_text_color'] ) && is_string( $design_raw['button_active_text_color'] ) ? sanitize_hex_color( $design_raw['button_active_text_color'] ) : ( $btn_act_tx_def ? $btn_act_tx_def : '' );
	$buffer_alpha_val            = isset( $design['buffer_ring_alpha'] ) && is_numeric( $design['buffer_ring_alpha'] ) ? (float) $design['buffer_ring_alpha'] : 0.3;
	$design_initialized          = isset( $full_schema['lock']['initialized'] ) ? (bool) $full_schema['lock']['initialized'] : false;
	$design_initialized_meta_set = isset( $full_schema['lock']['initialized_meta_set'] ) ? (bool) $full_schema['lock']['initialized_meta_set'] : false;
	$design_unlock_until         = isset( $full_schema['lock']['unlock_until'] ) ? (int) $full_schema['lock']['unlock_until'] : 0;
	$design_now                  = isset( $full_schema['lock']['now'] ) ? (int) $full_schema['lock']['now'] : time();
	$design_locked               = isset( $full_schema['lock']['locked'] ) ? (bool) $full_schema['lock']['locked'] : false;

	echo '<div class="thespl-preview-metabox">';

	echo '<div class="thespl-preview-controls-label"><strong>' . esc_html__( 'Player Preview', 'the-studio-player' ) . '</strong></div>';
	echo '<div class="thespl-preview-controls" id="' . esc_attr( $cid ) . '">';
	echo '<button type="button" class="button" data-w="420" aria-pressed="false">' . esc_html__( 'Smartphone', 'the-studio-player' ) . '</button>';
	echo '<button type="button" class="button button-primary" data-w="768" aria-pressed="true">' . esc_html__( 'Tablet', 'the-studio-player' ) . '</button>';
	echo '<button type="button" class="button" data-w="1024" aria-pressed="false">' . esc_html__( 'Desktop', 'the-studio-player' ) . '</button>';
	echo '</div>';
	$def_accent         = $tpl_accent;
	$def_accent2        = $tpl_accent2;
	$def_base           = $tpl_base;
	$def_text           = $tpl_text;
	$def_waveform       = $tpl_waveform;
	$def_ring           = $tpl_ring;
	$magic_palette_meta = get_post_meta( $post_id, '_thespl_magic_palette', true );
	$magic_palette_meta = is_array( $magic_palette_meta ) ? $magic_palette_meta : array();
	$magic_page_bg      = isset( $magic_palette_meta['page_bg'] ) && is_string( $magic_palette_meta['page_bg'] ) ? sanitize_hex_color( $magic_palette_meta['page_bg'] ) : '';
	$magic_page_text    = isset( $magic_palette_meta['page_text'] ) && is_string( $magic_palette_meta['page_text'] ) ? sanitize_hex_color( $magic_palette_meta['page_text'] ) : '';
	$magic_accent1      = isset( $magic_palette_meta['accent_1'] ) && is_string( $magic_palette_meta['accent_1'] ) ? sanitize_hex_color( $magic_palette_meta['accent_1'] ) : '';
	$magic_accent2      = isset( $magic_palette_meta['accent_2'] ) && is_string( $magic_palette_meta['accent_2'] ) ? sanitize_hex_color( $magic_palette_meta['accent_2'] ) : '';
	if ( empty( $magic_palette_meta ) ) {
		$magic_page_bg   = $def_base;
		$magic_page_text = $def_text;
		$magic_accent1   = $def_accent;
		$magic_accent2   = $def_accent2;
	}
	echo '<div class="thespl-preview-row" id="' . esc_attr( $row_id ) . '">';
	echo '<div class="thespl-preview-wrap" id="' . esc_attr( $wid ) . '" data-thespl-default-accent="' . esc_attr( $def_accent ) . '" data-thespl-default-accent2="' . esc_attr( $def_accent2 ) . '" data-thespl-default-base="' . esc_attr( $def_base ) . '" data-thespl-default-text="' . esc_attr( $def_text ) . '" data-thespl-default-waveform="' . esc_attr( $def_waveform ) . '" data-thespl-default-ring="' . esc_attr( $def_ring ) . '" data-thespl-default-btn-bg="' . esc_attr( $btn_bg_default ) . '" data-thespl-default-btn-text="' . esc_attr( $btn_text_default ) . '" data-thespl-default-btn-active-bg="' . esc_attr( $btn_act_bg_def ) . '" data-thespl-default-btn-active-text="' . esc_attr( $btn_act_tx_def ) . '" data-thespl-default-chip-bg="' . esc_attr( $chip_bg_default ) . '" data-thespl-default-chip-br="' . esc_attr( $chip_br_default ) . '">';
	echo '<p>' . esc_html__( 'Loading player preview', 'the-studio-player' ) . '</p>';
	echo '</div>';
	echo '<div class="thespl-design-panel' . ( $design_locked ? ' is-design-locked' : '' ) . '" id="' . esc_attr( $panel_id ) . '" data-thespl-design-initialized="' . esc_attr( $design_initialized ? '1' : '0' ) . '" data-thespl-design-unlock-until="' . esc_attr( (string) $design_unlock_until ) . '" data-thespl-now="' . esc_attr( (string) $design_now ) . '" data-thespl-design-init-meta="' . esc_attr( $design_initialized_meta_set ? '1' : '0' ) . '">';
	echo '<div class="thespl-design-panel__header">';
	echo '<h4>' . esc_html__( 'Player Design', 'the-studio-player' ) . '</h4>';
	echo '<div class="thespl-design-panel__lock-controls">';
	echo '<button type="button" class="button button-secondary button-small thespl-design-panel__lock-toggle" id="' . esc_attr( $lock_toggle_id ) . '" aria-controls="' . esc_attr( $panel_body_id ) . '" aria-pressed="' . esc_attr( $design_locked ? 'false' : 'true' ) . '">' . esc_html( $design_locked ? __( 'Unlock design', 'the-studio-player' ) : __( 'Lock design', 'the-studio-player' ) ) . '</button>';
	echo '<span class="thespl-design-panel__lock-state" id="' . esc_attr( $lock_status_id ) . '" aria-live="polite">' . esc_html( $design_locked ? __( 'Design changes are locked.', 'the-studio-player' ) : __( 'Design changes are unlocked.', 'the-studio-player' ) ) . '</span>';
	echo '</div>';
	echo '</div>';
	echo '<div class="thespl-design-panel__lock-overlay" aria-hidden="' . esc_attr( $design_locked ? 'false' : 'true' ) . '">' . esc_html__( 'Design changes are locked.', 'the-studio-player' ) . '</div>';
	echo '<div class="thespl-design-panel__body thespl-design-panel__lock-scope" id="' . esc_attr( $panel_body_id ) . '">';
	if ( $default_marker_count > 1 ) {
		echo '<div class="notice notice-warning"><p>' . esc_html__( 'More than one player template is marked as default. The system will pick one automatically, which may lead to inconsistent behaviour between admin preview and frontend. Please ensure only one template folder has the (default) suffix.', 'the-studio-player' ) . '</p></div>';
	}
	echo '<input type="hidden" id="' . esc_attr( $lock_input_id ) . '" name="thespl_design_lock_state" value="' . esc_attr( $design_locked ? 'locked' : 'unlocked' ) . '" />';
	echo '<label class="thespl-design-panel__template">' . esc_html__( 'Template', 'the-studio-player' ) . ':';
	echo '<select class="thespl-select" id="' . esc_attr( $sel ) . '" name="thespl_playlist_template">';
	foreach ( $templates as $slug => $label ) {
		$allows_overrides        = function_exists( 'thespl_template_allows_user_css_overrides' ) ? thespl_template_allows_user_css_overrides( $slug ) : true;
		$tpl_supports_audio_info = function_exists( 'thespl_template_has_audio_info_panel' ) ? thespl_template_has_audio_info_panel( $slug ) : true;
		$display_label           = $label;
		if ( isset( $default_flags[ $slug ] ) && $default_flags[ $slug ] ) {
			$display_label .= ' ' . __( '(Default)', 'the-studio-player' );
		}
		if ( ! $allows_overrides ) {
			$display_label .= ' ' . __( '(locked)', 'the-studio-player' );
		}
		echo '<option value="' . esc_attr( $slug ) . '" data-thespl-allow-user-css-overrides="' . esc_attr( $allows_overrides ? '1' : '0' ) . '" data-thespl-has-audio-info-panel="' . esc_attr( $tpl_supports_audio_info ? '1' : '0' ) . '" ' . selected( $slug, $current_template, false ) . '>' . esc_html( $display_label ) . '</option>';
	}
	echo '</select>';
	echo '</label>';
	$disable_user_css_class = $allow_user_css_overrides ? '' : ' is-user-css-disabled';
	echo '<div class="thespl-user-css' . esc_attr( $disable_user_css_class ) . '" data-thespl-user-css-reason="' . esc_attr__( 'User CSS locked by template author', 'the-studio-player' ) . '">';
	echo '<div class="thespl-user-css__note" data-thespl-user-css-note="1"><p>' . esc_html__( 'User CSS locked by template author', 'the-studio-player' ) . '</p></div>';

	echo '<div class="thespl-user-css__controls" data-thespl-user-css-controls="1">';
		echo '<p class="thespl-field-label">' . esc_html__( 'Color scheme / Magic Palette', 'the-studio-player' ) . '</p>';
		$magic_label_idle   = __( 'Make me a Palette', 'the-studio-player' );
		$magic_label_busy   = __( 'Generating color palette', 'the-studio-player' );
		$magic_status_ok    = __( 'Palette derived from the color scheme.', 'the-studio-player' );
		$magic_status_error = __( 'Could not generate palette.', 'the-studio-player' );
		$magic_status_id    = 'thespl-magic-status-' . $post_id;
	echo '<div class="field thespl-design-panel__actions">'
		. '<button type="button" class="button button-secondary button-small" data-thespl-reset="1"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . '>' . esc_html__( 'Reset to default', 'the-studio-player' ) . '</button>'
		. '</div>';
		$page_bg_input_id  = 'thespl-magic-page-bg-' . $post_id;
		$page_txt_input_id = 'thespl-magic-page-text-' . $post_id;
		$accent1_input_id  = 'thespl-magic-accent1-' . $post_id;
		$accent2_input_id  = 'thespl-magic-accent2-' . $post_id;
		echo '<div class="field thespl-magic-inputs">';
		echo '<label for="' . esc_attr( $page_bg_input_id ) . '" class="thespl-field-label">' . esc_html__( 'Page background (HEX, optional)', 'the-studio-player' ) . '</label>';
		echo '<input type="text" id="' . esc_attr( $page_bg_input_id ) . '" name="thespl_magic_palette[page_bg]" class="regular-text thespl-magic-color" value="' . esc_attr( $magic_page_bg ) . '" placeholder="#111111" />';
		echo '</div>';
		echo '<div class="field thespl-magic-inputs">';
		echo '<label for="' . esc_attr( $page_txt_input_id ) . '" class="thespl-field-label">' . esc_html__( 'Page text (HEX, optional)', 'the-studio-player' ) . '</label>';
		echo '<input type="text" id="' . esc_attr( $page_txt_input_id ) . '" name="thespl_magic_palette[page_text]" class="regular-text thespl-magic-color" value="' . esc_attr( $magic_page_text ) . '" placeholder="#ffffff" />';
		echo '</div>';
		echo '<div class="field thespl-magic-inputs">';
		echo '<label for="' . esc_attr( $accent1_input_id ) . '" class="thespl-field-label">' . esc_html__( 'Accent 1 (HEX, optional)', 'the-studio-player' ) . '</label>';
		echo '<input type="text" id="' . esc_attr( $accent1_input_id ) . '" name="thespl_magic_palette[accent_1]" class="regular-text thespl-magic-color" value="' . esc_attr( $magic_accent1 ) . '" placeholder="#ff4b4b" />';
		echo '</div>';
		echo '<div class="field thespl-magic-inputs">';
		echo '<label for="' . esc_attr( $accent2_input_id ) . '" class="thespl-field-label">' . esc_html__( 'Accent 2 (HEX, optional)', 'the-studio-player' ) . '</label>';
		echo '<input type="text" id="' . esc_attr( $accent2_input_id ) . '" name="thespl_magic_palette[accent_2]" class="regular-text thespl-magic-color" value="' . esc_attr( $magic_accent2 ) . '" placeholder="#ffb6b6" />';
		echo '</div>';
		echo '<p class="thespl-magic-help-bottom"><button type="button" class="button button-primary button-small" id="thespl-magic-btn-' . (int) $post_id . '"'
			. ' data-thespl-label-idle="' . esc_attr( $magic_label_idle ) . '"'
			. ' data-thespl-label-busy="' . esc_attr( $magic_label_busy ) . '"'
			. ' data-thespl-status-success="' . esc_attr( $magic_status_ok ) . '"'
			. ' data-thespl-status-error="' . esc_attr( $magic_status_error ) . '"'
			. ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . '>' . esc_html( $magic_label_idle ) . '</button></p>';
		echo '<div id="' . esc_attr( $magic_status_id ) . '" class="thespl-magic-status" aria-live="polite"></div>';

	echo '<div class="thespl-accordion">';

	echo '<details class="thespl-accordion__item">';
	echo '<summary class="thespl-accordion__header">' . esc_html__( 'Base Colors', 'the-studio-player' ) . '</summary>';
	echo '<div class="thespl-accordion__body">';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $aid ) . '-text">' . esc_html__( 'Accent', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $aid ) . '-text" value="' . esc_attr( $accent ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $aid ) . '" style="background:' . esc_attr( $accent ) . '"></button><input type="color" id="' . esc_attr( $aid ) . '" name="thespl_playlist_design[accent_color]" value="' . esc_attr( $accent ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	$aid2 = 'thespl-design-accent2-' . $post_id;
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $aid2 ) . '-text">' . esc_html__( 'Accent 2', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $aid2 ) . '-text" value="' . esc_attr( $accent2 ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $aid2 ) . '" style="background:' . esc_attr( $accent2 ) . '"></button><input type="color" id="' . esc_attr( $aid2 ) . '" name="thespl_playlist_design[accent_2_color]" value="' . esc_attr( $accent2 ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	$base_id = 'thespl-design-base-' . $post_id;
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $base_id ) . '-text">' . esc_html__( 'Background', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $base_id ) . '-text" value="' . esc_attr( $base ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $base_id ) . '" style="background:' . esc_attr( $base ) . '"></button><input type="color" id="' . esc_attr( $base_id ) . '" name="thespl_playlist_design[base_color]" value="' . esc_attr( $base ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	$text_id = 'thespl-design-text-' . $post_id;
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $text_id ) . '-text">' . esc_html__( 'Text', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $text_id ) . '-text" value="' . esc_attr( $textcol ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $text_id ) . '" style="background:' . esc_attr( $textcol ) . '"></button><input type="color" id="' . esc_attr( $text_id ) . '" name="thespl_playlist_design[text_color]" value="' . esc_attr( $textcol ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $wf_id ) . '-text">' . esc_html__( 'Waveform', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $wf_id ) . '-text" value="' . esc_attr( $waveform ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $wf_id ) . '" style="background:' . esc_attr( $waveform ) . '"></button><input type="color" id="' . esc_attr( $wf_id ) . '" name="thespl_playlist_design[waveform_color]" value="' . esc_attr( $waveform ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $ring_id ) . '-text">' . esc_html__( 'Waveform background', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $ring_id ) . '-text" value="' . esc_attr( $ringbase ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $ring_id ) . '" style="background:' . esc_attr( $ringbase ) . '"></button><input type="color" id="' . esc_attr( $ring_id ) . '" name="thespl_playlist_design[ring_base_color]" value="' . esc_attr( $ringbase ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '<div class="field"><label for="' . esc_attr( $buffer_alpha_id ) . '">' . esc_html__( 'Prebuffer alpha', 'the-studio-player' ) . '</label><div class="thespl-range-control"><input type="number" min="0" max="1" step="0.05" id="' . esc_attr( $buffer_alpha_id ) . '" name="thespl_playlist_design[buffer_ring_alpha]" value="' . esc_attr( $buffer_alpha_val ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '</div>';
	echo '</details>';

	echo '<details class="thespl-accordion__item">';
	echo '<summary class="thespl-accordion__header">' . esc_html__( 'Buttons', 'the-studio-player' ) . '</summary>';
	echo '<div class="thespl-accordion__body">';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $btn_bg_id ) . '-text">' . esc_html__( 'Button background', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $btn_bg_id ) . '-text" value="' . esc_attr( $btn_bg_val ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $btn_bg_id ) . '" style="background:' . esc_attr( $btn_bg_val ) . '"></button><input type="color" id="' . esc_attr( $btn_bg_id ) . '" name="thespl_playlist_design[button_bg_color]" value="' . esc_attr( $btn_bg_val ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $btn_tx_id ) . '-text">' . esc_html__( 'Button text', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $btn_tx_id ) . '-text" value="' . esc_attr( $btn_text_val ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $btn_tx_id ) . '" style="background:' . esc_attr( $btn_text_val ) . '"></button><input type="color" id="' . esc_attr( $btn_tx_id ) . '" name="thespl_playlist_design[button_text_color]" value="' . esc_attr( $btn_text_val ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $btn_act_bg_id ) . '-text">' . esc_html__( 'Active button background', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $btn_act_bg_id ) . '-text" value="' . esc_attr( $btn_act_bg_val ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $btn_act_bg_id ) . '" style="background:' . esc_attr( $btn_act_bg_val ) . '"></button><input type="color" id="' . esc_attr( $btn_act_bg_id ) . '" name="thespl_playlist_design[button_active_bg_color]" value="' . esc_attr( $btn_act_bg_val ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $btn_act_tx_id ) . '-text">' . esc_html__( 'Active button text', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $btn_act_tx_id ) . '-text" value="' . esc_attr( $btn_act_tx_val ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $btn_act_tx_id ) . '" style="background:' . esc_attr( $btn_act_tx_val ) . '"></button><input type="color" id="' . esc_attr( $btn_act_tx_id ) . '" name="thespl_playlist_design[button_active_text_color]" value="' . esc_attr( $btn_act_tx_val ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '</div>';
	echo '</details>';

	echo '<details class="thespl-accordion__item">';
	echo '<summary class="thespl-accordion__header">' . esc_html__( 'Variant Chips', 'the-studio-player' ) . '</summary>';
	echo '<div class="thespl-accordion__body">';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $chip_bg_id ) . '-text">' . esc_html__( 'Chip background', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $chip_bg_id ) . '-text" value="' . esc_attr( isset( $design['chip_bg_color'] ) ? sanitize_hex_color( $design['chip_bg_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.06 ) ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $chip_bg_id ) . '" style="background:' . esc_attr( isset( $design['chip_bg_color'] ) ? sanitize_hex_color( $design['chip_bg_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.06 ) ) . '"></button><input type="color" id="' . esc_attr( $chip_bg_id ) . '" name="thespl_playlist_design[chip_bg_color]" value="' . esc_attr( isset( $design['chip_bg_color'] ) ? sanitize_hex_color( $design['chip_bg_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.06 ) ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '<div class="field thespl-color-field"><label for="' . esc_attr( $chip_br_id ) . '-text">' . esc_html__( 'Chip border', 'the-studio-player' ) . '</label><div class="thespl-color-control"><input type="text" id="' . esc_attr( $chip_br_id ) . '-text" value="' . esc_attr( isset( $design['chip_br_color'] ) ? sanitize_hex_color( $design['chip_br_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.16 ) ) . '"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /><button type="button" class="thespl-swatch" data-swatch-for="' . esc_attr( $chip_br_id ) . '" style="background:' . esc_attr( isset( $design['chip_br_color'] ) ? sanitize_hex_color( $design['chip_br_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.16 ) ) . '"></button><input type="color" id="' . esc_attr( $chip_br_id ) . '" name="thespl_playlist_design[chip_br_color]" value="' . esc_attr( isset( $design['chip_br_color'] ) ? sanitize_hex_color( $design['chip_br_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.16 ) ) . '" class="thespl-color-picker"' . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' /></div></div>';
	echo '</div>';
	echo '</details>';

	echo '</div>';
	echo '</details>';

	echo '<div class="thespl-design-panel__visibility">';
	echo '<div class="field checkbox"><label><input type="checkbox" id="' . esc_attr( $show_playlist_id ) . '" name="thespl_playlist_design[show_playlist]" value="1" ' . checked( $show_pl, true, false ) . ( $allow_user_css_overrides ? '' : ' disabled="disabled"' ) . ' />' . esc_html__( 'Show playlist', 'the-studio-player' ) . '</label></div>';
	$audio_info_disabled      = ! $allow_user_css_overrides || ! $supports_audio_info_panel;
	$audio_info_label_default = esc_html__( 'Show audio info', 'the-studio-player' );
	$audio_info_label_missing = esc_html__( "Show audio info – this template doesn't have an audio info panel.", 'the-studio-player' );
	$audio_info_label         = $supports_audio_info_panel ? $audio_info_label_default : $audio_info_label_missing;
	echo '<div class="field checkbox" data-thespl-audio-info-label-default="' . esc_attr( $audio_info_label_default ) . '" data-thespl-audio-info-label-missing="' . esc_attr( $audio_info_label_missing ) . '"><label><input type="checkbox" id="' . esc_attr( $show_info_id ) . '" name="thespl_playlist_design[show_audio_info]" value="1" ' . checked( $show_info, true, false ) . disabled( $audio_info_disabled, true, false ) . ' /><span data-thespl-audio-info-label-text="1">' . esc_html( $audio_info_label ) . '</span></label></div>';
	echo '</div>';

	echo '</div>';
	echo '</div>';
	echo '<div class="field thespl-design-panel__save thespl-design-panel__lock-scope">'
		. '<button type="button" class="button button-primary" id="' . esc_attr( $save_id ) . '">' . esc_html__( 'Save Player', 'the-studio-player' ) . '</button>'
		. '</div>';
	echo '<div class="thespl-design-debug__wrap">';
	echo '<button type="button" class="button button-link" id="' . esc_attr( $debug_toggle_id ) . '" aria-expanded="false" aria-controls="' . esc_attr( $debug_panel_id ) . '">' . esc_html__( 'Show palette analysis', 'the-studio-player' ) . '</button>';
	echo '<div id="' . esc_attr( $debug_panel_id ) . '" class="thespl-design-debug" aria-hidden="true"></div>';
	echo '</div>';
	echo '<div class="thespl-design-panel__shortcode">';
	echo '<label class="thespl-field-label">' . esc_html__( 'Shortcode', 'the-studio-player' ) . '</label>';
	echo '<div class="thespl-shortcode-row">';
	echo '<code id="' . esc_attr( $sid ) . '" role="button" tabindex="0">' . esc_html( $shortcode ) . '</code>';
	echo '<button type="button" class="button button-secondary button-small" id="' . esc_attr( $bid ) . '">' . esc_html__( 'Copy', 'the-studio-player' ) . '</button>';
	echo '</div>';
	echo '<span id="' . esc_attr( $mid ) . '" class="thespl-shortcode-copied" aria-live="polite">' . esc_html__( 'Copied to clipboard', 'the-studio-player' ) . '</span>';
	echo '</div>';
	echo '</div>';
	echo '</div>';
	echo '</div>';
}

/**
 * Add the shortcode column to the playlist list table.
 *
 * @param array $columns Columns.
 * @return array
 */
function thespl_playlist_posts_columns( $columns ) {
	$out = array();
	foreach ( $columns as $key => $label ) {
		$out[ $key ] = $label;
		if ( 'title' === $key ) {
			$out['thespl_shortcode'] = __( 'Shortcode', 'the-studio-player' );
		}
	}
	if ( ! isset( $out['thespl_shortcode'] ) ) {
		$out['thespl_shortcode'] = __( 'Shortcode', 'the-studio-player' );
	}
	return $out;
}
add_filter( 'manage_thespl_playlist_posts_columns', 'thespl_playlist_posts_columns' );

/**
 * Render the shortcode column content.
 *
 * @param string $column  Column name.
 * @param int    $post_id Post ID.
 * @return void
 */
function thespl_playlist_posts_custom_column( $column, $post_id ) {
	if ( 'thespl_shortcode' !== $column ) {
		return;
	}
	$tpl = get_post_meta( $post_id, '_thespl_playlist_template', true );
	$tpl = is_string( $tpl ) ? sanitize_key( $tpl ) : '';
	if ( '' === $tpl && function_exists( 'thespl_get_default_template_slug' ) ) {
		$tpl = thespl_get_default_template_slug();
	}
	$shortcode = thespl_get_player_shortcode_for_playlist( $post_id, $tpl );
	echo '<code>' . esc_html( $shortcode ) . '</code>';
}
add_action( 'manage_thespl_playlist_posts_custom_column', 'thespl_playlist_posts_custom_column', 10, 2 );

/**
 * Add copy action link to playlist rows in the list table.
 *
 * @param array   $actions Row actions.
 * @param WP_Post $post    Current post object.
 * @return array
 */
function thespl_playlist_row_actions_copy_link( $actions, $post ) {
	if ( ! $post instanceof WP_Post ) {
		return $actions;
	}
	if ( 'thespl_playlist' !== $post->post_type ) {
		return $actions;
	}
	if ( ! current_user_can( 'edit_post', $post->ID ) ) {
		return $actions;
	}
	$url                    = add_query_arg(
		array(
			'action' => 'thespl_duplicate_playlist',
			'post'   => $post->ID,
		),
		admin_url( 'admin.php' )
	);
	$url                    = wp_nonce_url( $url, 'thespl_duplicate_playlist' );
	$label                  = esc_html__( 'Duplicate Playlist', 'the-studio-player' );
	$actions['thespl_copy'] = '<a href="' . esc_url( $url ) . '">' . $label . '</a>';
	return $actions;
}
add_filter( 'post_row_actions', 'thespl_playlist_row_actions_copy_link', 10, 2 );

/**
 * Handle playlist duplication from the list table copy action.
 *
 * @return void
 */
function thespl_handle_duplicate_playlist() {
	check_admin_referer( 'thespl_duplicate_playlist' );

	$post_id = isset( $_GET['post'] ) ? absint( wp_unslash( $_GET['post'] ) ) : 0;
	if ( $post_id <= 0 ) {
		wp_die( esc_html__( 'Invalid playlist.', 'the-studio-player' ) );
	}
	$post = get_post( $post_id );
	if ( ! $post || 'thespl_playlist' !== $post->post_type ) {
		wp_die( esc_html__( 'Invalid playlist.', 'the-studio-player' ) );
	}
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		wp_die( esc_html__( 'You are not allowed to duplicate this playlist.', 'the-studio-player' ) );
	}
	/* translators: %s: original playlist title. */
	$new_title   = sprintf( __( '%s (Duplicate)', 'the-studio-player' ), $post->post_title );
	$new_post_id = wp_insert_post(
		array(
			'post_type'    => 'thespl_playlist',
			'post_status'  => 'draft',
			'post_title'   => $new_title,
			'post_content' => $post->post_content,
			'post_author'  => get_current_user_id(),
		)
	);
	if ( is_wp_error( $new_post_id ) || ! $new_post_id ) {
		wp_die( esc_html__( 'Could not duplicate playlist.', 'the-studio-player' ) );
	}
	$meta_keys = array(
		'_thespl_playlist_json',
		'_thespl_playlist_template',
		'_thespl_playlist_design',
	);
	foreach ( $meta_keys as $meta_key ) {
		$value = get_post_meta( $post_id, $meta_key, true );
		if ( '' === $value || null === $value ) {
			delete_post_meta( $new_post_id, $meta_key );
		} else {
			update_post_meta( $new_post_id, $meta_key, $value );
		}
	}
	$redirect_url = add_query_arg(
		array(
			'post'          => $new_post_id,
			'action'        => 'edit',
			'thespl_copied' => 1,
		),
		admin_url( 'post.php' )
	);
	wp_safe_redirect( $redirect_url );
	exit;
}
add_action( 'admin_action_thespl_duplicate_playlist', 'thespl_handle_duplicate_playlist' );

/**
 * Normalisiert das Design-Array der Playlist und ergänzt Standardwerte.
 *
 * @param array $design_in Rohes Design-Array aus dem Request.
 * @return array Normalisierte Design-Einstellungen.
 */
function thespl_normalize_playlist_design( $design_in ) {
	$design_in = is_array( $design_in ) ? $design_in : array();
	$template  = isset( $GLOBALS['thespl_current_template_slug'] ) && is_string( $GLOBALS['thespl_current_template_slug'] ) ? sanitize_key( $GLOBALS['thespl_current_template_slug'] ) : '';

	if ( function_exists( 'thespl_design_normalize' ) ) {
		return thespl_design_normalize( $design_in, $template );
	}

	$defaults          = function_exists( 'thespl_template_get_design_defaults' ) ? thespl_template_get_design_defaults( $template ) : array();
	$accent_default    = isset( $defaults['accent_color'] ) && is_string( $defaults['accent_color'] ) ? sanitize_hex_color( $defaults['accent_color'] ) : '';
	$accent2_default   = isset( $defaults['accent_2_color'] ) && is_string( $defaults['accent_2_color'] ) ? sanitize_hex_color( $defaults['accent_2_color'] ) : '';
	$base_default      = isset( $defaults['base_color'] ) && is_string( $defaults['base_color'] ) ? sanitize_hex_color( $defaults['base_color'] ) : '';
	$text_default      = isset( $defaults['text_color'] ) && is_string( $defaults['text_color'] ) ? sanitize_hex_color( $defaults['text_color'] ) : '';
	$wf_default        = isset( $defaults['waveform_color'] ) && is_string( $defaults['waveform_color'] ) ? sanitize_hex_color( $defaults['waveform_color'] ) : '';
	$ring_default      = isset( $defaults['ring_base_color'] ) && is_string( $defaults['ring_base_color'] ) ? sanitize_hex_color( $defaults['ring_base_color'] ) : '';
	$buffer_alpha_def  = 0.3;
	$buffer_alpha_meta = isset( $defaults['buffer_ring_alpha'] ) ? $defaults['buffer_ring_alpha'] : null;
	if ( is_numeric( $buffer_alpha_meta ) ) {
		$buffer_alpha_def = (float) $buffer_alpha_meta;
	}
	if ( $buffer_alpha_def < 0 ) {
		$buffer_alpha_def = 0.0;
	} elseif ( $buffer_alpha_def > 1 ) {
		$buffer_alpha_def = 1.0;
	}
	$accent            = isset( $design_in['accent_color'] ) && is_string( $design_in['accent_color'] ) ? sanitize_hex_color( $design_in['accent_color'] ) : $accent_default;
	$accent2           = isset( $design_in['accent_2_color'] ) && is_string( $design_in['accent_2_color'] ) ? sanitize_hex_color( $design_in['accent_2_color'] ) : $accent2_default;
	$base              = isset( $design_in['base_color'] ) && is_string( $design_in['base_color'] ) ? sanitize_hex_color( $design_in['base_color'] ) : $base_default;
	$textcol           = isset( $design_in['text_color'] ) && is_string( $design_in['text_color'] ) ? sanitize_hex_color( $design_in['text_color'] ) : $text_default;
	$waveform          = isset( $design_in['waveform_color'] ) && is_string( $design_in['waveform_color'] ) ? sanitize_hex_color( $design_in['waveform_color'] ) : $wf_default;
	$ringbase          = isset( $design_in['ring_base_color'] ) && is_string( $design_in['ring_base_color'] ) ? sanitize_hex_color( $design_in['ring_base_color'] ) : $ring_default;
	$buffer_alpha_post = isset( $design_in['buffer_ring_alpha'] ) ? $design_in['buffer_ring_alpha'] : null;
	$buffer_alpha      = $buffer_alpha_def;
	if ( is_numeric( $buffer_alpha_post ) ) {
		$buffer_alpha = (float) $buffer_alpha_post;
		if ( $buffer_alpha < 0 ) {
			$buffer_alpha = 0.0;
		} elseif ( $buffer_alpha > 1 ) {
			$buffer_alpha = 1.0;
		}
	}
	$btn_bg          = isset( $design_in['button_bg_color'] ) && is_string( $design_in['button_bg_color'] ) ? sanitize_hex_color( $design_in['button_bg_color'] ) : thespl_mix_hex( $base, $accent, 0.15 );
	$btn_text        = isset( $design_in['button_text_color'] ) && is_string( $design_in['button_text_color'] ) ? sanitize_hex_color( $design_in['button_text_color'] ) : thespl_best_text_on_bg( $btn_bg );
	$btn_active_bg   = isset( $design_in['button_active_bg_color'] ) && is_string( $design_in['button_active_bg_color'] ) ? sanitize_hex_color( $design_in['button_active_bg_color'] ) : $accent;
	$btn_active_text = isset( $design_in['button_active_text_color'] ) && is_string( $design_in['button_active_text_color'] ) ? sanitize_hex_color( $design_in['button_active_text_color'] ) : thespl_best_text_on_bg( $btn_active_bg );
	$chip_bg         = isset( $design_in['chip_bg_color'] ) && is_string( $design_in['chip_bg_color'] ) ? sanitize_hex_color( $design_in['chip_bg_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.06 );
	$chip_br         = isset( $design_in['chip_br_color'] ) && is_string( $design_in['chip_br_color'] ) ? sanitize_hex_color( $design_in['chip_br_color'] ) : thespl_mix_hex( $base, '#ffffff', 0.16 );
	$show_pl         = array_key_exists( 'show_playlist', $design_in ) ? (bool) $design_in['show_playlist'] : false;
	$show_info       = array_key_exists( 'show_audio_info', $design_in ) ? (bool) $design_in['show_audio_info'] : false;
	$design          = array(
		'accent_color'             => is_string( $accent ) ? $accent : '',
		'accent_2_color'           => is_string( $accent2 ) ? $accent2 : '',
		'base_color'               => is_string( $base ) ? $base : '',
		'text_color'               => is_string( $textcol ) ? $textcol : '',
		'waveform_color'           => is_string( $waveform ) ? $waveform : '',
		'ring_base_color'          => is_string( $ringbase ) ? $ringbase : '',
		'buffer_ring_alpha'        => $buffer_alpha,
		'button_bg_color'          => is_string( $btn_bg ) ? $btn_bg : thespl_mix_hex( $base, $accent, 0.15 ),
		'button_text_color'        => is_string( $btn_text ) ? $btn_text : thespl_best_text_on_bg( $btn_bg ),
		'button_active_bg_color'   => is_string( $btn_active_bg ) ? $btn_active_bg : $accent,
		'button_active_text_color' => is_string( $btn_active_text ) ? $btn_active_text : thespl_best_text_on_bg( $btn_active_bg ),
		'chip_bg_color'            => is_string( $chip_bg ) ? $chip_bg : thespl_mix_hex( $base, '#ffffff', 0.06 ),
		'chip_br_color'            => is_string( $chip_br ) ? $chip_br : thespl_mix_hex( $base, '#ffffff', 0.16 ),
		'show_playlist'            => (bool) $show_pl,
		'show_audio_info'          => (bool) $show_info,
	);
	return $design;
}

/**
 * Sanitize raw magic palette input from the request.
 *
 * Accepts the raw, unslashed magic palette array from the request and returns
 * a sanitized subset that mirrors the current behavior in thespl_save_playlist_meta().
 * Only known keys are processed, only valid hex colors are kept, and invalid
 * or empty values are dropped.
 *
 * @param array $magic_in Raw magic palette array from the request (after wp_unslash()).
 * @return array Sanitized magic palette suitable for storage in post meta.
 */
function thespl_sanitize_magic_palette_input( $magic_in ) {
	if ( ! is_array( $magic_in ) ) {
		$magic_in = array();
	}

	$magic_out = array();
	$keys      = array(
		'page_bg',
		'page_text',
		'accent_1',
		'accent_2',
	);

	foreach ( $keys as $key ) {
		if ( isset( $magic_in[ $key ] ) && is_string( $magic_in[ $key ] ) ) {
			$hex = sanitize_hex_color( $magic_in[ $key ] );
			if ( '' !== $hex ) {
				$magic_out[ $key ] = $hex;
			}
		}
	}

	return $magic_out;
}

/**
 * Speichert Metadaten einer THESPL-Playlist beim Speichern des Beitrags.
 *
 * @param int $post_id Playlist-Post-ID.
 * @return void
 */
function thespl_save_playlist_meta( $post_id ) {
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return;
	}
	if ( ! isset( $_POST['thespl_playlist_nonce'] ) ) {
		return;
	}
	check_admin_referer( 'thespl_playlist_save', 'thespl_playlist_nonce' );
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return;
	}
	$initialized_meta_raw = get_post_meta( $post_id, '_thespl_design_initialized', true );
	$initialized_meta_set = '' !== $initialized_meta_raw;
	$unlock_until_raw     = get_post_meta( $post_id, '_thespl_design_unlock_until', true );
	if ( ! is_numeric( $unlock_until_raw ) ) {
		update_post_meta( $post_id, '_thespl_design_unlock_until', 0 );
	}
	if ( ! $initialized_meta_set ) {
		$existing_design = get_post_meta( $post_id, '_thespl_playlist_design', true );
		$existing_design = is_array( $existing_design ) ? $existing_design : array();
		$has_design      = false;
		foreach ( $existing_design as $design_value ) {
			if ( is_string( $design_value ) && '' !== trim( $design_value ) ) {
				$has_design = true;
				break;
			}
			if ( is_numeric( $design_value ) ) {
				$has_design = true;
				break;
			}
			if ( is_bool( $design_value ) && true === $design_value ) {
				$has_design = true;
				break;
			}
		}
		update_post_meta( $post_id, '_thespl_design_initialized', $has_design ? 1 : 0 );
	}
	if ( ! isset( $_POST['thespl_playlist_json_locked'] ) || '1' !== sanitize_key( wp_unslash( $_POST['thespl_playlist_json_locked'] ) ) ) {
		if ( isset( $_POST['thespl_playlist_json'] ) ) {
			$raw       = sanitize_textarea_field( wp_unslash( $_POST['thespl_playlist_json'] ) );
			$raw       = is_string( $raw ) ? $raw : '';
			$sanitized = thespl_sanitize_playlist_json_string( $raw );
			if ( '' === $sanitized ) {
				delete_post_meta( $post_id, '_thespl_playlist_json' );
			} else {
				update_post_meta( $post_id, '_thespl_playlist_json', $sanitized );
			}
		}
	}
	$template = isset( $_POST['thespl_playlist_template'] ) ? sanitize_key( wp_unslash( $_POST['thespl_playlist_template'] ) ) : '';
	if ( '' === $template ) {
		$template = 'default';
	}
	$available = thespl_get_available_player_templates();
	if ( ! isset( $available[ $template ] ) && function_exists( 'thespl_get_default_template_slug' ) ) {
		$template = thespl_get_default_template_slug();
	}
	update_post_meta( $post_id, '_thespl_playlist_template', $template );
	$lock_state = isset( $_POST['thespl_design_lock_state'] ) ? sanitize_key( wp_unslash( $_POST['thespl_design_lock_state'] ) ) : '';
	if ( 'unlocked' === $lock_state ) {
		$unlock_window = (int) apply_filters( 'thespl_design_lock_unlock_window', 1800, $post_id );
		$unlock_window = max( 300, $unlock_window );
		update_post_meta( $post_id, '_thespl_design_unlock_until', time() + $unlock_window );
	} elseif ( 'locked' === $lock_state ) {
		update_post_meta( $post_id, '_thespl_design_unlock_until', 0 );
	}
	$magic_in = array();
	if ( isset( $_POST['thespl_magic_palette'] ) && is_array( $_POST['thespl_magic_palette'] ) ) {
		$magic_keys = array(
			'page_bg',
			'page_text',
			'accent_1',
			'accent_2',
		);
		foreach ( $magic_keys as $magic_key ) {
			if ( ! isset( $_POST['thespl_magic_palette'][ $magic_key ] ) || ! is_scalar( $_POST['thespl_magic_palette'][ $magic_key ] ) ) {
				continue;
			}
			$magic_in[ $magic_key ] = sanitize_text_field( wp_unslash( (string) $_POST['thespl_magic_palette'][ $magic_key ] ) );
		}
	}
	$magic = thespl_sanitize_magic_palette_input( $magic_in );
	if ( ! empty( $magic ) ) {
		update_post_meta( $post_id, '_thespl_magic_palette', $magic );
	} else {
		delete_post_meta( $post_id, '_thespl_magic_palette' );
	}

	$allow_user_css_overrides = function_exists( 'thespl_template_allows_user_css_overrides' ) ? thespl_template_allows_user_css_overrides( $template ) : true;
	if ( ! $allow_user_css_overrides ) {
		return;
	}
	if ( ! isset( $_POST['thespl_playlist_design'] ) ) {
		return;
	}

	$design_in = array();
	if ( isset( $_POST['thespl_playlist_design'] ) && is_array( $_POST['thespl_playlist_design'] ) ) {
		$design_keys = array(
			'accent_color',
			'accent_2_color',
			'base_color',
			'text_color',
			'waveform_color',
			'ring_base_color',
			'buffer_ring_alpha',
			'button_bg_color',
			'button_text_color',
			'button_active_bg_color',
			'button_active_text_color',
			'chip_bg_color',
			'chip_br_color',
			'show_playlist',
			'show_audio_info',
		);
		foreach ( $design_keys as $design_key ) {
			if ( ! isset( $_POST['thespl_playlist_design'][ $design_key ] ) || ! is_scalar( $_POST['thespl_playlist_design'][ $design_key ] ) ) {
				continue;
			}
			$design_in[ $design_key ] = sanitize_text_field( wp_unslash( (string) $_POST['thespl_playlist_design'][ $design_key ] ) );
		}
	}

	if ( function_exists( 'thespl_design_normalize' ) ) {
		$design = thespl_design_normalize( $design_in, $template );
		update_post_meta( $post_id, '_thespl_playlist_design', $design );
		$has_design_values = false;
		if ( is_array( $design ) ) {
			foreach ( $design as $design_value ) {
				if ( is_string( $design_value ) && '' !== trim( $design_value ) ) {
					$has_design_values = true;
					break;
				}
				if ( is_numeric( $design_value ) ) {
					$has_design_values = true;
					break;
				}
				if ( is_bool( $design_value ) && true === $design_value ) {
					$has_design_values = true;
					break;
				}
			}
		}
		if ( $has_design_values ) {
			update_post_meta( $post_id, '_thespl_design_initialized', 1 );
		}
	}
}
add_action( 'save_post_thespl_playlist', 'thespl_save_playlist_meta' );

/**
 * Sanitisiert den JSON-String des Playlist-Editors.
 *
 * @param string $raw Roh-JSON.
 * @return string
 */
function thespl_sanitize_playlist_json_string( $raw ) {
	$raw = is_string( $raw ) ? $raw : '';
	$raw = trim( $raw );
	if ( '' === $raw ) {
		return '';
	}
	$decoded = json_decode( $raw, true );
	if ( ! is_array( $decoded ) ) {
		return '';
	}
	$normalized = thespl_get_variant_engine()->normalize_playlist_payload( $decoded );

	return wp_json_encode( $normalized );
}

/**
 * Rendert die Player-Vorschau im Playlist-Editor per AJAX.
 *
 * @return void
 */
function thespl_ajax_preview_render() {
	$post_id = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0;

	$template = isset( $_POST['template'] ) ? sanitize_key( wp_unslash( $_POST['template'] ) ) : 'default';

	if ( ! check_ajax_referer( 'thespl_preview_render', 'nonce', false ) ) {
		wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
	}
	if ( $post_id <= 0 || ! current_user_can( 'edit_post', $post_id ) ) {
		wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
	}
	$available = thespl_get_available_player_templates();
	if ( ! isset( $available[ $template ] ) ) {
		$template = 'default';
	}
	$GLOBALS['thespl_current_template_slug'] = $template;
	try {
		$html = thespl_render_player_template(
			$template,
			array(
				'playlist_id'   => $post_id,
				'template'      => $template,
				'debug_message' => '',
			)
		);
	} catch ( \Throwable $e ) {
		wp_send_json_error( array( 'message' => 'render_failed' ), 500 );
	}
	wp_send_json_success( array( 'html' => $html ) );
}
add_action( 'wp_ajax_thespl_preview_render', 'thespl_ajax_preview_render' );

/**
 * AJAX: Generate a magic color palette and save it to the design meta.
 *
 * @return void
 */
function thespl_ajax_magic_palette() {
	$post_id = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0;
	if ( ! check_ajax_referer( 'thespl_magic_palette', 'nonce', false ) ) {
		wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
	}
	if ( $post_id <= 0 || ! current_user_can( 'edit_post', $post_id ) ) {
		wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
	}
	$current_template = get_post_meta( $post_id, '_thespl_playlist_template', true );
	$current_template = is_string( $current_template ) ? sanitize_key( $current_template ) : 'default';
	if ( '' === $current_template ) {
		$current_template = 'default';
	}
	$allow_user_css_overrides = function_exists( 'thespl_template_allows_user_css_overrides' ) ? thespl_template_allows_user_css_overrides( $current_template ) : true;
	if ( ! $allow_user_css_overrides ) {
		wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
	}
	$page_bg   = isset( $_POST['page_bg'] ) ? sanitize_hex_color( wp_unslash( (string) $_POST['page_bg'] ) ) : '';
	$page_text = isset( $_POST['page_text'] ) ? sanitize_hex_color( wp_unslash( (string) $_POST['page_text'] ) ) : '';
	$hints     = array();
	if ( '' !== $page_bg ) {
		$hints['page_bg'] = $page_bg;
	}
	if ( '' !== $page_text ) {
		$hints['page_text'] = $page_text;
	}
	$acc1 = isset( $_POST['accent_1'] ) ? sanitize_hex_color( wp_unslash( (string) $_POST['accent_1'] ) ) : '';
	if ( '' !== $acc1 ) {
		$hints['accent_1'] = $acc1;
	}
	$acc2 = isset( $_POST['accent_2'] ) ? sanitize_hex_color( wp_unslash( (string) $_POST['accent_2'] ) ) : '';
	if ( '' !== $acc2 ) {
		$hints['accent_2'] = $acc2;
	}
	$magic = array();
	if ( '' !== $page_bg ) {
		$magic['page_bg'] = $page_bg;
	}
	if ( '' !== $page_text ) {
		$magic['page_text'] = $page_text;
	}
	if ( '' !== $acc1 ) {
		$magic['accent_1'] = $acc1;
	}
	if ( '' !== $acc2 ) {
		$magic['accent_2'] = $acc2;
	}
	if ( ! empty( $magic ) ) {
		update_post_meta( $post_id, '_thespl_magic_palette', $magic );
	} else {
		delete_post_meta( $post_id, '_thespl_magic_palette' );
	}
	$res = thespl_generate_magic_palette( $post_id, $hints );
	wp_send_json_success( $res );
}
add_action( 'wp_ajax_thespl_magic_palette', 'thespl_ajax_magic_palette' );
