<?php
/**
 * functions.php — Tema filho MUSICAS.CO.MZ
 *
 * Integra os custom fields do plugin plays-block:
 *   stream_url, stream, download_url, downloadable, duration (ms), bpm,
 *   purchase_url, purchase_title, copyright, editor_note, cover,
 *   color, waveform_data, like_count, download_count.
 *
 * Leitura de meta: plugin (get_post_meta) primeiro, ACF como fallback.
 *
 * - Link do artista: taxonomia 'artist', com arquivo reescrito para /artista/
 *   (ver bloco "Opção B" no fim). Lookup por nome e por slug. SEM fallback:
 *   artista não cadastrado não recebe link. Filtrável via `musicas_artist_url`
 *   e `musicas_artist_taxonomies`.
 * - Artistas principais compostos ("A & B") são divididos: cada um recebe o
 *   seu próprio link no texto e o seu próprio nó MusicGroup no byArtist.
 * - Secção "Sobre o artista": vem do campo `editor_note` do plugin.
 * - Schema JSON-LD: dispatcher único em wp_head escolhe pelo campo `type`
 *   do plays-block — single -> MusicRecording, album -> MusicAlbum,
 *   playlist/series -> MusicPlaylist. Faixas-filhas via meta `parent`,
 *   n.º de faixas via `post-count-all`, datas publicação/modificação incluídas.
 * - Títulos com feat./ft./featuring são divididos em artista principal +
 *   participações (refletido no texto e no schema byArtist).
 */

/* ===================================================================
 * 1. Setup do tema / estilos / tamanhos de imagem
 * =================================================================== */
add_action( 'wp_enqueue_scripts', 'enqueue_parent_styles' );
function enqueue_parent_styles() {
	wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
}

add_action( 'after_setup_theme', 'musicas_theme_setup' );
function musicas_theme_setup() {
	add_theme_support( 'post-thumbnails' );
	add_image_size( 'music_hero_800', 800, 800, true ); // quadrado, recortado
}


/* ===================================================================
 * 2. Helpers — título, feat., artista
 * =================================================================== */

/**
 * Normaliza traços e divide o título "ARTISTA – TÍTULO".
 */
if ( ! function_exists( 'ldc_parse_music_title' ) ) {
	function ldc_parse_music_title( $raw_title ) {
		$t = wp_strip_all_tags( (string) $raw_title );
		$t = html_entity_decode( $t, ENT_QUOTES, 'UTF-8' );
		$t = preg_replace( '/\s+/u', ' ', $t );
		$t = trim( $t );

		$artist = '';
		$song   = '';

		// Separador artista/título: traço rodeado por ESPAÇOS ( - – — ‒ ― ),
		// ou travessão/meia-risca ( – — ‒ ― ) mesmo sem espaços.
		// Um hífen "-" SEM espaços nunca separa (T-Rex, Hip-Hop, Jay-Z, will-i-am).
		if ( preg_match( '/^(.*?)\s+[-\x{2012}-\x{2015}]\s+(.+)$/u', $t, $m ) ) {
			$artist = trim( $m[1] );
			$song   = trim( $m[2] );
		} elseif ( preg_match( '/^(.*?)[\x{2012}-\x{2015}]\s*(.+)$/u', $t, $m ) ) {
			$artist = trim( $m[1] );
			$song   = trim( $m[2] );
		} else {
			$song = $t;
		}

		$upper = function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $t, 'UTF-8' ) : strtoupper( $t );

		return array(
			'full'   => $t,
			'upper'  => $upper,
			'artist' => $artist,
			'song'   => $song,
		);
	}
}

if ( ! function_exists( 'musicas_parse_title_artist_song' ) ) {
	function musicas_parse_title_artist_song( $raw_title ) {
		$p = ldc_parse_music_title( $raw_title );
		return array(
			'full'   => $p['full'],
			'artist' => $p['artist'],
			'song'   => $p['song'],
		);
	}
}

/**
 * Divide uma lista de artistas ("A, B & C", "A x B", "A e B").
 */
if ( ! function_exists( 'musicas_split_artists' ) ) {
	function musicas_split_artists( $str ) {
		$str = trim( (string) $str, " \t\n\r()[]" );
		if ( $str === '' ) {
			return array();
		}
		$parts = preg_split( '/\s*,\s*|\s*&\s*|\s*\+\s*|\s+x\s+|\s+(?:e|and)\s+/iu', $str );
		$parts = array_map( 'trim', (array) $parts );
		return array_values( array_filter( $parts, function ( $s ) {
			return $s !== '';
		} ) );
	}
}

/**
 * Extrai participações (feat./ft./featuring/part.) do artista e da música.
 * Devolve: main_artist, featured[], song_clean, song (original), artists_all[].
 */
if ( ! function_exists( 'musicas_extract_feat' ) ) {
	function musicas_extract_feat( $artist, $song ) {
		// Exige espaço a seguir para evitar falsos positivos ("Feature", "Part of Me").
		$feat_re   = '/\b(?:feat\.?|ft\.?|featuring|part\.|com\s+(?:a\s+)?participa(?:ç|c)(?:ã|a)o\s+de)\s+/iu';
		$kw        = '(?:feat\.?|ft\.?|featuring|part\.)';
		$featured  = array();
		$main      = trim( (string) $artist );
		$song      = trim( (string) $song );
		$song_clean = $song;

		// 1) feat na parte do artista: "Main feat. Guest"
		if ( $main !== '' && preg_match( $feat_re, $main ) ) {
			$parts = preg_split( $feat_re, $main, 2 );
			$main  = trim( $parts[0] );
			if ( ! empty( $parts[1] ) ) {
				$featured = array_merge( $featured, musicas_split_artists( $parts[1] ) );
			}
		}

		// 2) feat na parte da música: "Song (feat. Guest)" ou "Song feat. Guest"
		if ( $song !== '' ) {
			if ( preg_match( '/^(.*?)[\(\[]\s*' . $kw . '\s+(.+?)\s*[\)\]]\s*$/iu', $song, $m ) ) {
				$song_clean = trim( $m[1] );
				$featured   = array_merge( $featured, musicas_split_artists( $m[2] ) );
			} elseif ( preg_match( '/^(.*?)\s+' . $kw . '\s+(.+)$/iu', $song, $m ) ) {
				$song_clean = trim( $m[1] );
				$featured   = array_merge( $featured, musicas_split_artists( $m[2] ) );
			}
		}

		$song_clean = trim( preg_replace( '/[\s\-–—]+$/u', '', $song_clean ) );
		if ( $song_clean === '' ) {
			$song_clean = $song;
		}

		// Remove duplicados (case-insensitive) e o próprio artista principal.
		$seen     = array();
		$clean_ft = array();
		foreach ( $featured as $f ) {
			$f = trim( $f );
			$k = function_exists( 'mb_strtolower' ) ? mb_strtolower( $f, 'UTF-8' ) : strtolower( $f );
			if ( $f === '' || isset( $seen[ $k ] ) ) {
				continue;
			}
			$seen[ $k ] = true;
			$clean_ft[] = $f;
		}

		$all = array_merge( array( $main ), $clean_ft );
		$all = array_values( array_filter( array_map( 'trim', $all ) ) );

		return array(
			'main_artist' => $main,
			'featured'    => $clean_ft,
			'song_clean'  => $song_clean,
			'song'        => $song,
			'artists_all' => $all,
		);
	}
}

/**
 * Taxonomia de artistas ativa no site.
 * O arquivo real é /artist/ (taxonomia 'artist'); 'artista' fica como
 * fallback para compatibilidade. Filtrável via `musicas_artist_taxonomies`.
 */
if ( ! function_exists( 'musicas_artist_taxonomy' ) ) {
	function musicas_artist_taxonomy() {
		$candidates = apply_filters( 'musicas_artist_taxonomies', array( 'artist', 'artista' ) );
		foreach ( (array) $candidates as $tax ) {
			if ( taxonomy_exists( $tax ) ) {
				return $tax;
			}
		}
		return '';
	}
}

/**
 * Devolve o termo do artista na taxonomia ativa.
 * Procura primeiro pelo NOME exato e depois pelo SLUG sanitizado
 * ("Mr. Bow" -> "mr-bow"), para apanhar variações de pontuação/maiúsculas.
 */
if ( ! function_exists( 'musicas_artist_term' ) ) {
	function musicas_artist_term( $artist_name ) {
		$artist_name = trim( (string) $artist_name );
		$tax         = musicas_artist_taxonomy();
		if ( $artist_name === '' || $tax === '' ) {
			return null;
		}

		$term = get_term_by( 'name', $artist_name, $tax );
		if ( ! $term || is_wp_error( $term ) ) {
			$term = get_term_by( 'slug', sanitize_title( $artist_name ), $tax );
		}

		return ( $term && ! is_wp_error( $term ) ) ? $term : null;
	}
}

/**
 * URL do artista APENAS se estiver cadastrado (termo na taxonomia ativa).
 * Devolve '' quando o artista não existe no site. Filtrável via
 * `musicas_artist_registered_url` para outras lógicas de "cadastrado".
 */
if ( ! function_exists( 'musicas_artist_registered_url' ) ) {
	function musicas_artist_registered_url( $artist_name, $post_id = 0 ) {
		$url  = '';
		$term = musicas_artist_term( $artist_name );

		if ( $term ) {
			$link = get_term_link( $term );
			if ( ! is_wp_error( $link ) ) {
				$url = $link;
			}
		}

		return apply_filters( 'musicas_artist_registered_url', $url, trim( (string) $artist_name ), $post_id );
	}
}

/**
 * URL do artista: SÓ existe se o termo estiver cadastrado na taxonomia ativa.
 *
 * Não há fallback: um /artista/{slug}/ inventado para um artista inexistente
 * devolve 404 e serve-o ao Google via HTML e JSON-LD. Melhor nenhum link do
 * que um link partido — musicas_artist_link() devolve o nome sem <a> e
 * musicas_strip_empty() limpa o 'url' do schema.
 *
 * Personalizável via filtro `musicas_artist_url`.
 */
if ( ! function_exists( 'musicas_artist_url' ) ) {
	function musicas_artist_url( $artist_name, $post_id = 0 ) {
		$artist_name = trim( (string) $artist_name );
		$url         = musicas_artist_registered_url( $artist_name, $post_id );

		return apply_filters( 'musicas_artist_url', $url, $artist_name, $post_id );
	}
}

/**
 * Link <a> para o artista (ou só o nome escapado se não houver URL).
 * Com $only_if_registered = true, só cria link se o artista estiver cadastrado.
 */
if ( ! function_exists( 'musicas_artist_link' ) ) {
	function musicas_artist_link( $artist_name, $post_id = 0, $only_if_registered = false ) {
		$name = esc_html( $artist_name );
		$url  = $only_if_registered
			? musicas_artist_registered_url( $artist_name, $post_id )
			: musicas_artist_url( $artist_name, $post_id );
		return $url ? '<a href="' . esc_url( $url ) . '">' . $name . '</a>' : $name;
	}
}

/**
 * Divide o artista principal em vários ("Aymos & Mr Bow" -> [Aymos, Mr Bow]).
 * Se a divisão falhar, devolve o nome completo num array de 1 elemento.
 */
if ( ! function_exists( 'musicas_main_artists' ) ) {
	function musicas_main_artists( $main_artist ) {
		$main_artist = trim( (string) $main_artist );
		if ( $main_artist === '' ) {
			return array();
		}
		$parts = musicas_split_artists( $main_artist );
		return ! empty( $parts ) ? $parts : array( $main_artist );
	}
}

/**
 * HTML dos artistas principais, cada nome com o seu próprio link.
 *   2 artistas  -> "A & B"
 *   3+ artistas -> "A, B & C" (vírgulas, & antes do último)
 */
if ( ! function_exists( 'musicas_main_artists_links' ) ) {
	function musicas_main_artists_links( $main_artist, $post_id = 0 ) {
		$names = musicas_main_artists( $main_artist );
		$links = array();
		foreach ( $names as $n ) {
			$links[] = musicas_artist_link( $n, $post_id );
		}

		$n = count( $links );
		if ( $n <= 1 ) {
			return $n ? $links[0] : '';
		}
		$last = array_pop( $links );
		return implode( ', ', $links ) . ' & ' . $last;
	}
}

/**
 * Frase de participação para o texto:
 *   1 artista  -> " com a participação de X"
 *   2+ artistas -> " com as participações dos artistas X, Y e Z"
 * Recebe já os nomes/links dos convidados.
 */
if ( ! function_exists( 'musicas_feat_phrase' ) ) {
	function musicas_feat_phrase( $artists ) {
		$artists = array_values( array_filter( (array) $artists ) );
		$n       = count( $artists );
		if ( $n === 0 ) {
			return '';
		}
		if ( $n === 1 ) {
			return ' com a participação de ' . $artists[0];
		}
		$last = array_pop( $artists );
		return ' com as participações dos artistas ' . implode( ', ', $artists ) . ' e ' . $last;
	}
}


/* ===================================================================
 * 3. Helpers — meta, áudio, cor, duração, capa, waveform, ações
 * =================================================================== */

if ( ! function_exists( 'musicas_get_meta' ) ) {
	function musicas_get_meta( $post_id, $key, $default = '' ) {
		$val = get_post_meta( $post_id, $key, true );
		if ( $val !== '' && $val !== null && $val !== false && $val !== array() ) {
			return $val;
		}
		if ( function_exists( 'get_field' ) ) {
			$f = get_field( $key, $post_id );
			if ( $f !== '' && $f !== null && $f !== false ) {
				return $f;
			}
		}
		return $default;
	}
}

if ( ! function_exists( 'musicas_resolve_audio' ) ) {
	function musicas_resolve_audio( $post_id ) {
		$stream_url   = (string) musicas_get_meta( $post_id, 'stream_url' );
		$stream       = (string) musicas_get_meta( $post_id, 'stream' );
		$download_url = (string) musicas_get_meta( $post_id, 'download_url' );
		$downloadable = musicas_get_meta( $post_id, 'downloadable' );
		$purchase_url = (string) musicas_get_meta( $post_id, 'purchase_url' );
		$purchase_ttl = (string) musicas_get_meta( $post_id, 'purchase_title' );

		$drive_id   = (string) musicas_get_meta( $post_id, 'google_drive_link' );
		$mp3_direct = (string) musicas_get_meta( $post_id, 'music_link' );
		$drive_url  = $drive_id ? 'https://docs.google.com/uc?export=download&id=' . rawurlencode( $drive_id ) : '';

		$stream_final   = $stream_url ?: ( $stream ?: ( $mp3_direct ?: $drive_url ) );
		$download_final = $download_url ?: ( $drive_url ?: $mp3_direct );

		return array(
			'stream'         => $stream_final,
			'download'       => $download_final,
			'downloadable'   => filter_var( $downloadable, FILTER_VALIDATE_BOOLEAN ) || ( $download_final !== '' ),
			'purchase'       => $purchase_url,
			'purchase_title' => $purchase_ttl ?: 'Comprar',
		);
	}
}

if ( ! function_exists( 'musicas_sanitize_color' ) ) {
	function musicas_sanitize_color( $val ) {
		$val = trim( (string) $val );
		if ( $val === '' ) {
			return '';
		}
		if ( function_exists( 'sanitize_hex_color' ) ) {
			$hex = sanitize_hex_color( $val );
			if ( $hex ) {
				return $hex;
			}
		} elseif ( preg_match( '/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i', $val ) ) {
			return strtolower( $val );
		}
		if ( preg_match( '/^(?:rgb|rgba|hsl|hsla)\(\s*[\d.,%\s\/]+\)$/i', $val ) ) {
			return $val;
		}
		if ( preg_match( '/^[a-z]{3,20}$/i', $val ) ) {
			return strtolower( $val );
		}
		return '';
	}
}

/**
 * Normaliza qualquer formato de duração para SEGUNDOS.
 * O plugin grava `duration` em milissegundos; valores >= 2h (se lidos como
 * segundos) são tratados como ms e divididos por 1000.
 */
if ( ! function_exists( 'musicas_duration_to_seconds' ) ) {
	function musicas_duration_to_seconds( $raw ) {
		$raw = trim( (string) $raw );
		if ( $raw === '' ) {
			return 0;
		}
		if ( preg_match( '/^PT/i', $raw ) ) {
			preg_match( '/(\d+)H/i', $raw, $h );
			preg_match( '/(\d+)M/i', $raw, $m );
			preg_match( '/(\d+)S/i', $raw, $s );
			return ( isset( $h[1] ) ? (int) $h[1] * 3600 : 0 )
				+ ( isset( $m[1] ) ? (int) $m[1] * 60 : 0 )
				+ ( isset( $s[1] ) ? (int) $s[1] : 0 );
		}
		if ( preg_match( '/^(\d{1,2}):([0-5]\d)(?::([0-5]\d))?$/', $raw, $m ) ) {
			if ( isset( $m[3] ) ) {
				return (int) $m[1] * 3600 + (int) $m[2] * 60 + (int) $m[3];
			}
			return (int) $m[1] * 60 + (int) $m[2];
		}
		if ( preg_match( '/^\d+$/', $raw ) ) {
			$v = (int) $raw;
			if ( $v >= 7200 ) {
				$v = (int) round( $v / 1000 );
			}
			return $v;
		}
		return 0;
	}
}

if ( ! function_exists( 'musicas_format_duration_display' ) ) {
	function musicas_format_duration_display( $raw ) {
		$sec = musicas_duration_to_seconds( $raw );
		if ( $sec <= 0 ) {
			return '';
		}
		$h = floor( $sec / 3600 );
		$sec %= 3600;
		$m = floor( $sec / 60 );
		$s = $sec % 60;
		return $h ? sprintf( '%d:%02d:%02d', $h, $m, $s ) : sprintf( '%d:%02d', $m, $s );
	}
}

if ( ! function_exists( 'musicas_iso8601_duration' ) ) {
	function musicas_iso8601_duration( $raw ) {
		$sec = musicas_duration_to_seconds( $raw );
		if ( $sec <= 0 ) {
			return 'PT3M';
		}
		$h = floor( $sec / 3600 );
		$sec %= 3600;
		$m = floor( $sec / 60 );
		$s = $sec % 60;
		$iso = 'PT' . ( $h ? $h . 'H' : '' ) . ( $m ? $m . 'M' : '' ) . ( $s ? $s . 'S' : '' );
		return ( $iso === 'PT' ) ? 'PT0S' : $iso;
	}
}

if ( ! function_exists( 'musicas_strip_empty' ) ) {
	function musicas_strip_empty( $arr ) {
		foreach ( $arr as $k => $v ) {
			if ( is_array( $v ) ) {
				$v          = musicas_strip_empty( $v );
				$arr[ $k ] = $v;
			}
			if ( $v === null || $v === '' || ( is_array( $v ) && empty( $v ) ) ) {
				unset( $arr[ $k ] );
			}
		}
		return $arr;
	}
}

if ( ! function_exists( 'musicas_hero_html' ) ) {
	function musicas_hero_html( $post_id ) {
		$img = get_the_post_thumbnail_url( $post_id, 'music_hero_800' );
		if ( ! $img ) {
			$cover = (string) musicas_get_meta( $post_id, 'cover' );
			if ( $cover && filter_var( $cover, FILTER_VALIDATE_URL ) ) {
				$img = $cover;
			}
		}
		if ( ! $img ) {
			return '';
		}
		$img        = esc_url( $img );
		$title_attr = esc_attr( get_the_title( $post_id ) );

		return '<figure class="music-cover">'
			. '<img src="' . $img . '" alt="' . $title_attr . '" title="' . $title_attr . '" width="640" height="640" loading="lazy" />'
			. '<figcaption class="wp-element-caption">Capa de ' . $title_attr . '</figcaption>'
			. '</figure>';
	}
}

if ( ! function_exists( 'musicas_waveform_svg' ) ) {
	function musicas_waveform_svg( $post_id ) {
		$data = musicas_get_meta( $post_id, 'waveform_data' );
		if ( ! is_array( $data ) || empty( $data ) ) {
			return '';
		}
		$data = array_values( array_map( 'intval', $data ) );
		if ( empty( $data ) ) {
			return '';
		}

		$max_bars = 96;
		$n        = count( $data );
		if ( $n > $max_bars ) {
			$step    = $n / $max_bars;
			$sampled = array();
			for ( $i = 0; $i < $max_bars; $i++ ) {
				$sampled[] = $data[ (int) floor( $i * $step ) ];
			}
			$data = $sampled;
			$n    = $max_bars;
		}

		$peak = max( $data );
		if ( $peak <= 0 ) {
			$peak = 1;
		}

		$bar_w = 3;
		$gap   = 1;
		$h     = 48;
		$w     = $n * ( $bar_w + $gap );

		$bars = '';
		foreach ( $data as $i => $v ) {
			$bh = max( 2, (int) round( ( abs( $v ) / $peak ) * $h ) );
			$x  = $i * ( $bar_w + $gap );
			$y  = (int) round( ( $h - $bh ) / 2 );
			$bars .= '<rect x="' . $x . '" y="' . $y . '" width="' . $bar_w . '" height="' . $bh . '" rx="1"/>';
		}

		return '<svg class="music-waveform" viewBox="0 0 ' . $w . ' ' . $h . '" width="100%" height="' . $h . '" '
			. 'preserveAspectRatio="none" role="img" aria-label="Forma de onda do áudio" '
			. 'fill="var(--cover-color, currentColor)">' . $bars . '</svg>';
	}
}

/**
 * Nó(s) byArtist para o schema.
 * O artista principal é dividido ("Aymos & Mr Bow" -> 2 nós MusicGroup),
 * cada um com a URL do respetivo arquivo /artist/{slug}/ quando cadastrado.
 * O primeiro nó principal recebe o @id. Participações seguem a mesma regra.
 */
if ( ! function_exists( 'musicas_schema_artists' ) ) {
	function musicas_schema_artists( $feat, $id_artist, $post_id = 0 ) {
		$mains = musicas_main_artists( $feat['main_artist'] );
		if ( empty( $mains ) ) {
			$mains = array( 'Artista' );
		}

		$nodes = array();
		foreach ( $mains as $i => $name ) {
			$node = array(
				'@type' => 'MusicGroup',
				'name'  => $name,
				'url'   => musicas_artist_url( $name, $post_id ) ?: null,
			);
			if ( 0 === $i ) {
				$node = array( '@type' => 'MusicGroup', '@id' => $id_artist ) + $node;
			}
			$nodes[] = $node;
		}

		foreach ( (array) $feat['featured'] as $f ) {
			$nodes[] = array(
				'@type' => 'MusicGroup',
				'name'  => $f,
				'url'   => musicas_artist_registered_url( $f, $post_id ) ?: null,
			);
		}

		return ( count( $nodes ) === 1 ) ? $nodes[0] : $nodes;
	}
}

if ( ! function_exists( 'musicas_detect_release_from_title' ) ) {
	function musicas_detect_release_from_title( $raw_title ) {
		$title = wp_strip_all_tags( html_entity_decode( (string) $raw_title, ENT_QUOTES, 'UTF-8' ) );
		$title = preg_replace( '/\s+/u', ' ', trim( $title ) );

		$re_ep   = '/(?:^|[\s\-])EP$/iu';
		$re_alb1 = '/\((?:álbum|album)\)\s*$/iu';
		$re_alb2 = '/\[(?:álbum|album)\]\s*$/iu';
		$re_vol  = '/\bvol(?:\.|ume)?\s*\d*\s*$/iu';

		$type  = 'album';
		$strip = $title;

		if ( preg_match( $re_ep, $title ) ) {
			$type  = 'ep';
			$strip = preg_replace( $re_ep, '', $strip );
		} elseif ( preg_match( $re_alb1, $title ) || preg_match( $re_alb2, $title ) || preg_match( $re_vol, $title ) ) {
			$type  = 'album';
			$strip = preg_replace( $re_alb1, '', $strip );
			$strip = preg_replace( $re_alb2, '', $strip );
			$strip = preg_replace( $re_vol, '', $strip );
		}

		$strip = preg_replace( '/\s*-\s*$/u', '', trim( $strip ) );
		return array(
			'type'           => $type,
			'title_stripped' => $strip,
		);
	}
}

/**
 * Tipo efetivo do conteúdo para o schema.
 * Lê o campo `type` do plays-block (single|album|series|playlist);
 * se vazio/inválido, deduz pelo post_type (station->single, album->album,
 * playlist->playlist). Filtrável via `musicas_effective_type`.
 */
if ( ! function_exists( 'musicas_effective_type' ) ) {
	function musicas_effective_type( $post_id ) {
		$allowed = array( 'single', 'album', 'series', 'playlist' );
		$type    = strtolower( trim( (string) musicas_get_meta( $post_id, 'type' ) ) );

		if ( ! in_array( $type, $allowed, true ) ) {
			$map  = array(
				'station'  => 'single',
				'album'    => 'album',
				'playlist' => 'playlist',
			);
			$pt   = get_post_type( $post_id );
			$type = isset( $map[ $pt ] ) ? $map[ $pt ] : 'single';
		}

		return apply_filters( 'musicas_effective_type', $type, $post_id );
	}
}

/**
 * Faixas de um álbum/playlist do plays-block.
 * A relação real fica no PRÓPRIO álbum: meta `post` = CSV de IDs
 * ("218819,218820,...") e meta `items` = ["ID:Título", ...].
 * (O meta `parent` das faixas NÃO é fiável e não é usado.)
 * Devolve array de WP_Post publicados, na ordem do CSV, máx. $limit.
 */
if ( ! function_exists( 'musicas_child_track_ids' ) ) {
	function musicas_child_track_ids( $post_id ) {
		$ids = array();

		// 1) meta `post`: "218819,218820,..."
		$csv = (string) musicas_get_meta( $post_id, 'post' );
		if ( $csv !== '' ) {
			foreach ( explode( ',', $csv ) as $part ) {
				$id = absint( trim( $part ) );
				if ( $id ) {
					$ids[] = $id;
				}
			}
		}

		// 2) fallback meta `items`: ["218819:Djimetta - Minha Mala", ...]
		if ( empty( $ids ) ) {
			$items = musicas_get_meta( $post_id, 'items' );
			foreach ( (array) $items as $item ) {
				$id = absint( strtok( (string) $item, ':' ) );
				if ( $id ) {
					$ids[] = $id;
				}
			}
		}

		return array_values( array_unique( array_filter( $ids ) ) );
	}
}

if ( ! function_exists( 'musicas_child_tracks' ) ) {
	function musicas_child_tracks( $post_id, $limit = 100 ) {
		$ids = musicas_child_track_ids( $post_id );
		if ( empty( $ids ) ) {
			return array();
		}

		$allowed = apply_filters( 'musicas_track_post_types', array( 'station' ) );
		$tracks  = array();
		foreach ( array_slice( $ids, 0, (int) $limit ) as $id ) {
			if ( $id === (int) $post_id ) {
				continue; // nunca incluir o próprio álbum/playlist
			}
			$p = get_post( $id );
			if ( $p && 'publish' === $p->post_status && in_array( $p->post_type, (array) $allowed, true ) ) {
				$tracks[] = $p;
			}
		}
		return $tracks;
	}
}

/**
 * Converte faixas (WP_Post[]) em nós MusicRecording para `track`.
 * Usa o título parseado e a `duration` (ms) do plugin quando existe.
 */
if ( ! function_exists( 'musicas_schema_track_nodes' ) ) {
	function musicas_schema_track_nodes( $tracks ) {
		$nodes = array();
		$pos   = 0;
		foreach ( (array) $tracks as $t ) {
			$pos++;
			$parsed  = ldc_parse_music_title( get_the_title( $t ) );
			$feat_t  = musicas_extract_feat( $parsed['artist'] ?: $parsed['full'], $parsed['song'] ?: $parsed['full'] );
			$node    = array(
				'@type'    => 'MusicRecording',
				'position' => $pos,
				'name'     => $feat_t['song_clean'] ?: ( $parsed['song'] ?: $parsed['full'] ),
				'url'      => get_permalink( $t ),
			);
			$dur_raw = musicas_get_meta( $t->ID, 'duration' );
			if ( $dur_raw !== '' && $dur_raw !== null ) {
				$node['duration'] = musicas_iso8601_duration( $dur_raw );
			}
			if ( $feat_t['main_artist'] !== '' ) {
				$node['byArtist'] = array(
					'@type' => 'MusicGroup',
					'name'  => $feat_t['main_artist'],
				);
			}
			$nodes[] = $node;
		}
		return $nodes;
	}
}

/**
 * Datas de publicação/modificação em ISO 8601 (W3C) para o schema.
 */
if ( ! function_exists( 'musicas_schema_dates' ) ) {
	function musicas_schema_dates( $post_id ) {
		return array(
			'datePublished' => get_the_date( DATE_W3C, $post_id ) ?: null,
			'dateModified'  => get_the_modified_date( DATE_W3C, $post_id ) ?: null,
		);
	}
}

/**
 * Álbum-pai de uma faixa. Tenta os metas `parent` e `post` da faixa;
 * só aceita se o alvo for um post `album` publicado (validação evita
 * valores errados nestes campos).
 */
if ( ! function_exists( 'musicas_schema_in_album' ) ) {
	function musicas_schema_in_album( $post_id ) {
		$candidates = array(
			absint( musicas_get_meta( $post_id, 'parent' ) ),
			absint( musicas_get_meta( $post_id, 'post' ) ),
		);

		foreach ( $candidates as $parent_id ) {
			if ( ! $parent_id || $parent_id === (int) $post_id ) {
				continue;
			}
			$parent = get_post( $parent_id );
			if ( ! $parent || 'publish' !== $parent->post_status || 'album' !== $parent->post_type ) {
				continue;
			}
			$purl   = get_permalink( $parent );
			$parsed = ldc_parse_music_title( get_the_title( $parent ) );
			$det    = musicas_detect_release_from_title( $parsed['song'] ?: $parsed['full'] );
			return array(
				'@type' => 'MusicAlbum',
				'@id'   => trailingslashit( $purl ) . '#musicalbum',
				'name'  => $det['title_stripped'] ?: ( $parsed['song'] ?: $parsed['full'] ),
				'url'   => $purl,
			);
		}

		return null;
	}
}


/* ===================================================================
 * 4. Conteúdo SEO — single (post_type "station")
 * =================================================================== */
add_filter( 'the_content', 'musicas_station_seo_content', 20 );
function musicas_station_seo_content( $content ) {
	if ( ! is_singular( array( 'station' ) ) ) {
		return $content;
	}

	$tem_texto = trim( wp_strip_all_tags( strip_shortcodes( $content ) ) ) !== '';
	$tem_media = (bool) preg_match( '/<(img|iframe|video|audio)|wp-block-image/i', $content );
	if ( $tem_texto || $tem_media ) {
		return $content;
	}

	$post_id = get_the_ID();
	$p       = ldc_parse_music_title( get_the_title() );
	$feat    = musicas_extract_feat( $p['artist'] ?: $p['full'], $p['song'] ?: $p['full'] );
	$audio   = musicas_resolve_audio( $post_id );

	$artist_name = $feat['main_artist'] ?: $p['full'];
	$artist_html = esc_html( $artist_name );
	// "Aymos & Mr Bow" -> cada artista com o seu próprio link /artist/{slug}/.
	$artist_link = musicas_main_artists_links( $artist_name, $post_id );
	$song_html   = esc_html( $feat['song_clean'] ?: ( $p['song'] ?: $p['full'] ) );

	// Participações: só com link se o artista estiver cadastrado.
	$feat_links = array();
	foreach ( $feat['featured'] as $f ) {
		$feat_links[] = musicas_artist_link( $f, $post_id, true );
	}
	$feat_inline = musicas_feat_phrase( $feat_links );

	// Dados reais.
	$dur_disp = musicas_format_duration_display( musicas_get_meta( $post_id, 'duration' ) ?: musicas_get_meta( $post_id, 'duracao_da_musica' ) );
	$bpm      = (string) musicas_get_meta( $post_id, 'bpm' );
	$genero   = '';
	$terms    = get_the_terms( $post_id, 'genre' );
	if ( $terms && ! is_wp_error( $terms ) ) {
		$genero = esc_html( implode( ', ', wp_list_pluck( $terms, 'name' ) ) );
	}
	$bitrate = (string) musicas_get_meta( $post_id, 'bitrate_kbps' ) ?: '256';
	$size    = (string) musicas_get_meta( $post_id, 'tamanho_mb' );
	$copyr   = musicas_get_meta( $post_id, 'copyright' );
	$note    = musicas_get_meta( $post_id, 'editor_note' );
	$color   = musicas_sanitize_color( musicas_get_meta( $post_id, 'color' ) );

	// Introdução — o (feat. …) aparece a seguir ao artista principal.
	$intro  = sprintf(
		'“%s” é o mais recente lançamento de %s%s, já disponível para ouvir em streaming e fazer download no Musicas.co.mz.',
		$song_html,
		$artist_link,
		$feat_inline
	);
	$extras = array();
	if ( $genero ) {
		$extras[] = 'uma faixa de ' . $genero;
	}
	if ( $dur_disp ) {
		$extras[] = 'com ' . esc_html( $dur_disp ) . ' de duração';
	}
	if ( $extras ) {
		$intro .= ' É ' . implode( ', ', $extras ) . '.';
	}

	// Ficha técnica.
	$ficha  = '<li>Artista: ' . $artist_html . '</li>';
	if ( $feat_links ) {
		$ficha .= '<li>Participação: ' . implode( ', ', array_map( 'esc_html', $feat['featured'] ) ) . '</li>';
	}
	$ficha .= '<li>Faixa: ' . $song_html . '</li>';
	if ( $genero ) {
		$ficha .= '<li>Género: ' . $genero . '</li>';
	}
	if ( $dur_disp ) {
		$ficha .= '<li>Duração: ' . esc_html( $dur_disp ) . '</li>';
	}
	if ( $bpm !== '' ) {
		$ficha .= '<li>BPM: ' . esc_html( $bpm ) . '</li>';
	}
	$ficha .= '<li>Formato: MP3 a ' . esc_html( $bitrate ) . ' Kbps</li>';
	if ( $size !== '' ) {
		$ficha .= '<li>Tamanho: ' . esc_html( $size ) . ' MB</li>';
	}

	$hero       = musicas_hero_html( $post_id );
	$copyr_html = $copyr ? '<p class="music-copyright"><small>' . wp_kses_post( $copyr ) . '</small></p>' : '';
	$sec_style  = $color ? ' style="--cover-color:' . esc_attr( $color ) . '"' : '';

	// Secção do artista vinda do campo `editor_note` (só aparece se houver).
	$about_html = '';
	if ( $note ) {
		$about_html = '<h2>Sobre ' . $artist_html . '</h2>' . wp_kses_post( wpautop( $note ) );
	}

	$html = '<section class="music-post"' . $sec_style . '>'
		. $hero
		. '<p>' . $intro . '</p>'
		. '<h2>Detalhes da faixa</h2><ul>' . $ficha . '</ul>'
		. '<h2>Ouvir e fazer download</h2>'
		. '<p>Pode ouvir “' . $song_html . '” diretamente no leitor do site ou fazer o download do ficheiro MP3 para guardar e ouvir offline em qualquer dispositivo.</p>'
		. $about_html
		. $copyr_html
		. '</section>';

	$html = apply_filters( 'musicas_station_seo_html', $html, $post_id, $p, $audio );

	return wpautop( $html );
}


/* ===================================================================
 * 5. Schema JSON-LD — MusicRecording / MusicAlbum / MusicPlaylist
 *    Um único dispatcher em wp_head escolhe o tipo pelo campo `type`
 *    do plays-block (single|album|playlist).
 * =================================================================== */
function musicas_station_schema( $post_id ) {
	$permalink = get_permalink( $post_id );
	$parsed    = ldc_parse_music_title( get_the_title( $post_id ) );
	$feat      = musicas_extract_feat( $parsed['artist'] ?: $parsed['full'], $parsed['song'] ?: $parsed['full'] );
	$audio_src = musicas_resolve_audio( $post_id );

	$image = get_the_post_thumbnail_url( $post_id, 'large' );
	if ( ! $image ) {
		$cover = (string) musicas_get_meta( $post_id, 'cover' );
		if ( $cover && filter_var( $cover, FILTER_VALIDATE_URL ) ) {
			$image = $cover;
		}
	}
	if ( ! $image && function_exists( 'get_site_icon_url' ) ) {
		$image = get_site_icon_url();
	}

	$genres = array();
	$terms  = get_the_terms( $post_id, 'genre' );
	if ( $terms && ! is_wp_error( $terms ) ) {
		foreach ( $terms as $t ) {
			$genres[] = $t->name;
		}
	}
	if ( empty( $genres ) ) {
		$genres = array( 'Música' );
	}

	$duracao_raw = musicas_get_meta( $post_id, 'duration' ) ?: musicas_get_meta( $post_id, 'duracao_da_musica' );
	$duration    = musicas_iso8601_duration( $duracao_raw );

	$mp3_url      = $audio_src['stream'] ?: $audio_src['download'];
	$bitrate_kbps = trim( (string) musicas_get_meta( $post_id, 'bitrate_kbps' ) );
	$size_mb      = trim( (string) musicas_get_meta( $post_id, 'tamanho_mb' ) );
	$copyright    = wp_strip_all_tags( (string) musicas_get_meta( $post_id, 'copyright' ) );

	$id_recording = trailingslashit( $permalink ) . '#musicrecording';
	$id_audio     = trailingslashit( $permalink ) . '#audio';
	$id_artist    = trailingslashit( $permalink ) . '#artist';

	$by_artist = musicas_schema_artists( $feat, $id_artist, $post_id );

	$song_name = $feat['song_clean'] ?: ( $parsed['song'] ?: $parsed['full'] );

	$actions = array(
		array(
			'@type'  => 'ListenAction',
			'target' => array(
				array(
					'@type'          => 'EntryPoint',
					'urlTemplate'    => $permalink,
					'actionPlatform' => array(
						'https://schema.org/DesktopWebPlatform',
						'https://schema.org/AndroidPlatform',
						'https://schema.org/IOSPlatform',
					),
				),
			),
			'expectsAcceptanceOf' => array(
				'@type'    => 'Offer',
				'category' => 'free',
			),
		),
	);
	if ( $audio_src['downloadable'] && $audio_src['download'] ) {
		$actions[] = array(
			'@type'  => 'DownloadAction',
			'target' => array(
				'@type'       => 'EntryPoint',
				'urlTemplate' => $audio_src['download'],
			),
		);
	}
	if ( $audio_src['purchase'] ) {
		$actions[] = array(
			'@type'  => 'BuyAction',
			'name'   => $audio_src['purchase_title'],
			'target' => array(
				'@type'       => 'EntryPoint',
				'urlTemplate' => $audio_src['purchase'],
			),
		);
	}

	$audio = array(
		'@type'           => 'AudioObject',
		'@id'             => $id_audio,
		'name'            => $parsed['full'],
		'url'             => $mp3_url ?: $permalink,
		'contentUrl'      => $mp3_url ?: null,
		'encodingFormat'  => 'audio/mpeg',
		'inLanguage'      => 'pt-MZ',
		'duration'        => $duration,
		'thumbnailUrl'    => $image ?: null,
		'bitrate'         => $bitrate_kbps ? $bitrate_kbps . ' kbps' : null,
		'contentSize'     => $size_mb ? $size_mb . ' MB' : null,
		// 'embedUrl' removido: /embed/{id} nunca existiu como rota — dava 404 e,
		// por vezes, conteúdo duplicado. Campo opcional e sem valor para SEO.
		// Para o repor, confirmar primeiro que /musica/{slug}/embed/ responde 200
		// e usar: 'embedUrl' => get_post_embed_url( $post_id ) ?: null,
		'potentialAction' => $actions,
	);

	$like_count = (int) musicas_get_meta( $post_id, 'like_count' );
	$dl_count   = (int) musicas_get_meta( $post_id, 'download_count' );
	$stats      = array();
	if ( $like_count > 0 ) {
		$stats[] = array(
			'@type'                => 'InteractionCounter',
			'interactionType'      => 'https://schema.org/LikeAction',
			'userInteractionCount' => $like_count,
		);
	}
	if ( $dl_count > 0 ) {
		$stats[] = array(
			'@type'                => 'InteractionCounter',
			'interactionType'      => 'https://schema.org/DownloadAction',
			'userInteractionCount' => $dl_count,
		);
	}

	$desc = sprintf(
		'Ouça e faça download de "%s" de %s no Musicas.co.mz.',
		$song_name,
		$feat['main_artist'] ?: 'artista'
	);

	$dates    = musicas_schema_dates( $post_id );
	$in_album = musicas_schema_in_album( $post_id );

	$schema = array(
		'@context'             => 'https://schema.org',
		'@type'                => 'MusicRecording',
		'@id'                  => $id_recording,
		'name'                 => $song_name,
		'byArtist'             => $by_artist,
		'inAlbum'              => $in_album,
		'url'                  => $permalink,
		'image'                => $image ?: null,
		'genre'                => $genres,
		'description'          => $desc,
		'inLanguage'           => 'pt-MZ',
		'duration'             => $duration,
		'datePublished'        => $dates['datePublished'],
		'dateModified'         => $dates['dateModified'],
		'audio'                => $audio,
		'interactionStatistic' => $stats,
		'copyrightNotice'      => $copyright ?: null,
		'publisher'            => array( '@id' => home_url( '/' ) . '#organization' ),
		'isPartOf'             => array( '@id' => home_url( '/' ) . '#website' ),
	);

	$schema = musicas_strip_empty( $schema );

	$json = wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG );
	if ( ! $json ) {
		return;
	}

	echo "\n" . '<!-- Schema Personalizado: MusicRecording (station) -->' . "\n";
	echo '<script type="application/ld+json">' . $json . '</script>' . "\n";
}


/* ===================================================================
 * 6. Conteúdo SEO — álbum/EP (post_type "album")
 * =================================================================== */
add_filter( 'the_content', 'musicas_album_seo_content', 20 );
function musicas_album_seo_content( $content ) {
	if ( ! is_singular( 'album' ) ) {
		return $content;
	}

	$tem_texto = trim( wp_strip_all_tags( strip_shortcodes( $content ) ) ) !== '';
	$tem_media = (bool) preg_match( '/<(img|iframe|video|audio)|wp-block-image/i', $content );
	if ( $tem_texto || $tem_media ) {
		return $content;
	}

	$post_id = get_the_ID();
	$p       = ldc_parse_music_title( get_the_title() );
	$det     = musicas_detect_release_from_title( $p['song'] ?: $p['full'] );
	$is_ep   = ( $det['type'] === 'ep' );
	$tipo    = $is_ep ? 'EP' : 'Álbum';
	$audio   = musicas_resolve_audio( $post_id );

	// feat. (a partir do artista e do título já sem o sufixo EP/Álbum).
	$feat        = musicas_extract_feat( $p['artist'] ?: $p['full'], $det['title_stripped'] ?: ( $p['song'] ?: $p['full'] ) );
	$artist_name = $feat['main_artist'] ?: $p['full'];
	$artist_html = esc_html( $artist_name );
	// "Aymos & Mr Bow" -> cada artista com o seu próprio link /artist/{slug}/.
	$artist_link  = musicas_main_artists_links( $artist_name, $post_id );
	$titulo_limpo = esc_html( $feat['song_clean'] ?: ( $det['title_stripped'] ?: ( $p['song'] ?: $p['full'] ) ) );

	$feat_links = array();
	foreach ( $feat['featured'] as $f ) {
		$feat_links[] = musicas_artist_link( $f, $post_id, true );
	}
	$feat_inline = musicas_feat_phrase( $feat_links );

	$genero = esc_html( (string) musicas_get_meta( $post_id, 'album_genero' ) ?: '' );
	$ano    = esc_html( (string) musicas_get_meta( $post_id, 'album_ano' ) ?: '' );
	$label  = esc_html( (string) musicas_get_meta( $post_id, 'album_label' ) ?: '' );
	$num_fx = esc_html( (string) musicas_get_meta( $post_id, 'album_num_faixas' ) ?: '' );
	$copyr  = musicas_get_meta( $post_id, 'copyright' );
	$note   = musicas_get_meta( $post_id, 'editor_note' );
	$color  = musicas_sanitize_color( musicas_get_meta( $post_id, 'color' ) );

	$ficha  = '<li>Artista: ' . $artist_html . '</li>';
	if ( $feat_links ) {
		$ficha .= '<li>Participação: ' . implode( ', ', array_map( 'esc_html', $feat['featured'] ) ) . '</li>';
	}
	$ficha .= '<li>Título: ' . $titulo_limpo . '</li>';
	$ficha .= '<li>Tipo: ' . $tipo . '</li>';
	if ( $genero ) {
		$ficha .= '<li>Género: ' . $genero . '</li>';
	}
	if ( $label ) {
		$ficha .= '<li>Editora: ' . $label . '</li>';
	}
	if ( $ano ) {
		$ficha .= '<li>Ano: ' . $ano . '</li>';
	}
	if ( $num_fx ) {
		$ficha .= '<li>N.º de faixas: ' . $num_fx . '</li>';
	}
	$ficha .= '<li>Formato: MP3 (Zip) a 256 Kbps</li>';

	$hero       = musicas_hero_html( $post_id );
	$copyr_html = $copyr ? '<p class="music-copyright"><small>' . wp_kses_post( $copyr ) . '</small></p>' : '';
	$sec_style  = $color ? ' style="--cover-color:' . esc_attr( $color ) . '"' : '';

	$about_html = '';
	if ( $note ) {
		$about_html = '<h2>Sobre ' . $artist_html . '</h2>' . wp_kses_post( wpautop( $note ) );
	}

	$html = '<section class="music-album"' . $sec_style . '>'
		. $hero
		. '<p>“' . $titulo_limpo . '” é o mais recente ' . $tipo . ' de ' . $artist_link . $feat_inline . ', disponível para ouvir e fazer download no Musicas.co.mz.</p>'
		. '<h2>Ficha do ' . $tipo . '</h2><ul>' . $ficha . '</ul>'
		. '<h2>Ouvir e fazer download</h2>'
		. '<p>Ouça as faixas no leitor do site ou faça o download do ' . $tipo . ' para a sua playlist.</p>'
		. $about_html
		. $copyr_html
		. '</section>';

	$html = apply_filters( 'musicas_album_seo_html', $html, $post_id, $p, $audio );

	return wpautop( $html );
}


/* ===================================================================
 * 7. Schema JSON-LD — MusicAlbum (post_type "album")
 * =================================================================== */
function musicas_album_schema( $post_id ) {
	$url = get_permalink( $post_id );
	$p       = ldc_parse_music_title( get_the_title( $post_id ) );
	$det     = musicas_detect_release_from_title( $p['song'] ?: $p['full'] );
	$is_ep   = ( $det['type'] === 'ep' );
	$feat    = musicas_extract_feat( $p['artist'] ?: $p['full'], $det['title_stripped'] ?: ( $p['song'] ?: $p['full'] ) );

	$image = get_the_post_thumbnail_url( $post_id, 'large' );
	if ( ! $image ) {
		$cover = (string) musicas_get_meta( $post_id, 'cover' );
		if ( $cover && filter_var( $cover, FILTER_VALIDATE_URL ) ) {
			$image = $cover;
		}
	}
	if ( ! $image && function_exists( 'get_site_icon_url' ) ) {
		$image = get_site_icon_url();
	}

	$genero = musicas_get_meta( $post_id, 'album_genero' ) ?: null;
	$label  = musicas_get_meta( $post_id, 'album_label' ) ?: null;
	$num_fx = (int) ( musicas_get_meta( $post_id, 'album_num_faixas' ) ?: 0 );
	$copyr  = wp_strip_all_tags( (string) musicas_get_meta( $post_id, 'copyright' ) );

	$genres = array();
	$terms  = get_the_terms( $post_id, 'genre' );
	if ( $terms && ! is_wp_error( $terms ) ) {
		foreach ( $terms as $t ) {
			$genres[] = $t->name;
		}
	}
	if ( $genero ) {
		$genres[] = $genero;
	}
	if ( empty( $genres ) ) {
		$genres = array( 'Música' );
	}

	$id_album  = trailingslashit( $url ) . '#musicalbum';
	$id_artist = trailingslashit( $url ) . '#artist';

	$by_artist = musicas_schema_artists( $feat, $id_artist, $post_id );

	$album_title = $feat['song_clean'] ?: ( $det['title_stripped'] ?: ( $p['song'] ?: $p['full'] ) );

	// Faixas do álbum (meta `post`/`items` do plays-block) e n.º de faixas:
	// campo manual -> contagem real das faixas. (`post-count-all` é o
	// contador de REPRODUÇÕES do plugin — nunca usar como n.º de faixas.)
	$tracks      = musicas_child_tracks( $post_id );
	$track_nodes = musicas_schema_track_nodes( $tracks );
	if ( ! $num_fx ) {
		$num_fx = count( musicas_child_track_ids( $post_id ) );
	}

	$dates = musicas_schema_dates( $post_id );
	$desc  = sprintf(
		'Ouça e faça download do %s "%s" de %s no Musicas.co.mz.',
		$is_ep ? 'EP' : 'álbum',
		$album_title,
		$feat['main_artist'] ?: 'artista'
	);

	$schema = array(
		'@context'            => 'https://schema.org',
		'@type'               => 'MusicAlbum',
		'@id'                 => $id_album,
		'name'                => $album_title,
		'byArtist'            => $by_artist,
		'albumProductionType' => 'https://schema.org/StudioAlbum',
		'albumReleaseType'    => $is_ep ? 'https://schema.org/EPRelease' : 'https://schema.org/AlbumRelease',
		'url'                 => $url,
		'image'               => $image ?: null,
		'description'         => $desc,
		'genre'               => array_values( array_unique( $genres ) ),
		'inLanguage'          => 'pt-MZ',
		'datePublished'       => $dates['datePublished'],
		'dateModified'        => $dates['dateModified'],
		'numTracks'           => $num_fx ?: null,
		'track'               => $track_nodes ?: null,
		'copyrightNotice'     => $copyr ?: null,
		'recordLabel'         => $label ? array(
			'@type' => 'Organization',
			'name'  => $label,
		) : null,
		'publisher'           => array( '@id' => home_url( '/' ) . '#organization' ),
		'isPartOf'            => array( '@id' => home_url( '/' ) . '#website' ),
	);

	$schema = musicas_strip_empty( $schema );

	$json = wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG );
	if ( ! $json ) {
		return;
	}

	echo "\n<!-- Schema: MusicAlbum (deteta EP/Álbum pelo título) -->\n";
	echo '<script type="application/ld+json">' . $json . '</script>' . "\n";
}


/**
 * Schema JSON-LD — MusicPlaylist (campo `type` = playlist no plays-block).
 * Usa: cover, post-count-all, parent (faixas-filhas), copyright, genre.
 */
function musicas_playlist_schema( $post_id ) {
	$url    = get_permalink( $post_id );
	$parsed = ldc_parse_music_title( get_the_title( $post_id ) );
	$name   = $parsed['full'];

	$image = get_the_post_thumbnail_url( $post_id, 'large' );
	if ( ! $image ) {
		$cover = (string) musicas_get_meta( $post_id, 'cover' );
		if ( $cover && filter_var( $cover, FILTER_VALIDATE_URL ) ) {
			$image = $cover;
		}
	}
	if ( ! $image && function_exists( 'get_site_icon_url' ) ) {
		$image = get_site_icon_url();
	}

	$genres = array();
	$terms  = get_the_terms( $post_id, 'genre' );
	if ( $terms && ! is_wp_error( $terms ) ) {
		foreach ( $terms as $t ) {
			$genres[] = $t->name;
		}
	}

	// Faixas (meta `post`/`items`) e n.º de faixas pela contagem real.
	$tracks      = musicas_child_tracks( $post_id );
	$track_nodes = musicas_schema_track_nodes( $tracks );
	$num_tracks  = count( musicas_child_track_ids( $post_id ) );

	$copyr = wp_strip_all_tags( (string) musicas_get_meta( $post_id, 'copyright' ) );
	$dates = musicas_schema_dates( $post_id );
	$desc  = sprintf( 'Ouça a playlist "%s" no Musicas.co.mz%s.',
		$name,
		$num_tracks ? ' — ' . $num_tracks . ' faixas' : ''
	);

	$schema = array(
		'@context'        => 'https://schema.org',
		'@type'           => 'MusicPlaylist',
		'@id'             => trailingslashit( $url ) . '#musicplaylist',
		'name'            => $name,
		'url'             => $url,
		'image'           => $image ?: null,
		'description'     => $desc,
		'genre'           => $genres ?: null,
		'inLanguage'      => 'pt-MZ',
		'datePublished'   => $dates['datePublished'],
		'dateModified'    => $dates['dateModified'],
		'numTracks'       => $num_tracks ?: null,
		'track'           => $track_nodes ?: null,
		'copyrightNotice' => $copyr ?: null,
		'publisher'       => array( '@id' => home_url( '/' ) . '#organization' ),
		'isPartOf'        => array( '@id' => home_url( '/' ) . '#website' ),
	);

	$schema = musicas_strip_empty( $schema );

	$json = wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG );
	if ( ! $json ) {
		return;
	}

	echo "\n<!-- Schema: MusicPlaylist -->\n";
	echo '<script type="application/ld+json">' . $json . '</script>' . "\n";
}


/**
 * Dispatcher: escolhe o schema pelo tipo efetivo do plays-block.
 *   single           -> MusicRecording
 *   album            -> MusicAlbum
 *   playlist/series  -> MusicPlaylist
 * Post types abrangidos filtráveis via `musicas_schema_post_types`.
 */
add_action( 'wp_head', 'musicas_music_schema_dispatch', 9 );
function musicas_music_schema_dispatch() {
	$post_types = apply_filters( 'musicas_schema_post_types', array( 'station', 'album', 'playlist' ) );
	if ( ! is_singular( $post_types ) ) {
		return;
	}

	$post_id = get_the_ID();
	$type    = musicas_effective_type( $post_id );

	if ( 'album' === $type ) {
		musicas_album_schema( $post_id );
	} elseif ( 'playlist' === $type || 'series' === $type ) {
		musicas_playlist_schema( $post_id );
	} else {
		musicas_station_schema( $post_id );
	}
}


/**
 * 🛡️ Anti-hotlink: força downloads a partir do próprio site.
 *
 * Quando o utilizador chega ao endpoint /download/?id=XXX vindo de fora
 * (Google, WhatsApp, outro site, link direto sem referer), é redirecionado
 * para a página da publicação correspondente. Só quem está a navegar dentro
 * do musicas.co.mz consegue acionar o download direto.
 */
add_action('wp_loaded', function () {

    // 1) Só agimos no endpoint /download/
    $request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
    if ( strpos($request_uri, '/download/') === false
         && strpos($request_uri, '/download?') === false ) {
        return;
    }

    // 2) Apenas GET
    $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
    if ( strtoupper($method) !== 'GET' ) {
        return;
    }

    // 3) Tem de existir um id válido na query string
    $post_id = isset($_GET['id']) ? absint($_GET['id']) : 0;
    if ( ! $post_id ) {
        return;
    }

    // 4) O post tem de existir e estar publicado
    $post = get_post($post_id);
    if ( ! $post || $post->post_status !== 'publish' ) {
        return;
    }

    // 5) Normaliza hosts
    $normalize_host = function ($host) {
        $host = strtolower( (string) $host );
        return preg_replace('/^www\./', '', $host);
    };

    $site_host    = $normalize_host( wp_parse_url( home_url(), PHP_URL_HOST ) );
    $referer      = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
    $referer_host = $referer ? $normalize_host( wp_parse_url($referer, PHP_URL_HOST) ) : '';

    $allowed_hosts = apply_filters('musicas_download_allowed_hosts', [ $site_host ], $post_id);
    $allowed_hosts = array_map($normalize_host, (array) $allowed_hosts);

    // 6) Externo ou sem referer → redireciona para a publicação
    if ( empty($referer_host) || ! in_array($referer_host, $allowed_hosts, true) ) {

        $permalink = get_permalink($post_id);

        // 🔒 Salvaguarda: se por alguma razão devolver o home_url, NÃO redireciona
        $home = untrailingslashit( home_url('/') );
        if ( $permalink && untrailingslashit($permalink) !== $home ) {
            wp_safe_redirect( $permalink, 302 );
            exit;
        }
        return; // sem permalink válido, deixa seguir
    }

    // 7) Navegação interna → deixa o plays-block servir o ficheiro

}, 1);


/* ===================================================================
 *  Yoast SEO — Meta description automática para station e album
 *  Só preenche quando o autor NÃO escreveu manualmente no Yoast.
 * =================================================================== */

/**
 * Gera uma frase curta (≤ 160 chars) para meta description / OG / Twitter.
 * Usa o título parseado e, quando existe, género da taxonomia 'genre'.
 */
if ( ! function_exists( 'musicas_build_auto_metadesc' ) ) {
	function musicas_build_auto_metadesc( $post_id ) {
		$post = get_post( $post_id );
		if ( ! $post ) {
			return '';
		}

		$type = get_post_type( $post );
		if ( ! in_array( $type, array( 'station', 'album' ), true ) ) {
			return '';
		}

		$p = ldc_parse_music_title( $post->post_title );

		$artist = $p['artist'] !== '' ? $p['artist'] : 'artista';
		$song   = $p['song']   !== '' ? $p['song']   : $p['full'];

		// Género (opcional, primeiro termo da taxonomia 'genre')
		$genero = '';
		$terms  = get_the_terms( $post_id, 'genre' );
		if ( $terms && ! is_wp_error( $terms ) ) {
			$first  = reset( $terms );
			$genero = $first ? $first->name : '';
		}

		if ( $type === 'station' ) {
			// Ex.: Download MP3 de "Nome da Música" por Artista (Afrohouse).
			// Ouça e baixe grátis no MUSICAS.CO.MZ com qualidade 256 Kbps.
			$desc  = 'Download MP3 a música "' . $song . '" de ' . $artist;
			if ( $genero !== '' ) {
				$desc .= ' no estilo musical ' . $genero;
			}
			$desc .= '. Ouça e baixe grátis no MUSICAS.CO.MZ com qualidade 256 Kbps.';
		} else {
			// album — deteta EP/Álbum pelo título
			$det        = musicas_detect_release_from_title( $song );
			$tipo_label = ( $det['type'] === 'ep' ) ? 'EP' : 'Álbum';
			$album_name = $det['title_stripped'] !== '' ? $det['title_stripped'] : $song;

			$desc  = 'Download ' . $tipo_label . ' "' . $album_name . '" de ' . $artist;
			if ( $genero !== '' ) {
				$desc .= ' no estilo musical ' . $genero;
			}
			$desc .= '. Ouça e baixe as faixas em MP3 no MUSICAS.CO.MZ.';
		}

		// Limpa, normaliza espaços e corta nos 158 caracteres sem partir palavras.
		$desc = wp_strip_all_tags( $desc );
		$desc = preg_replace( '/\s+/u', ' ', $desc );
		$desc = trim( $desc );

		$limit = 158;
		if ( function_exists( 'mb_strlen' ) && mb_strlen( $desc, 'UTF-8' ) > $limit ) {
			$cut = mb_substr( $desc, 0, $limit, 'UTF-8' );
			$sp  = mb_strrpos( $cut, ' ', 0, 'UTF-8' );
			if ( $sp !== false ) {
				$cut = mb_substr( $cut, 0, $sp, 'UTF-8' );
			}
			$desc = rtrim( $cut, " .,;:-" ) . '…';
		}

		return $desc;
	}
}

/**
 * Filtro Yoast — só sobrepõe quando o campo do Yoast está vazio.
 */
if ( ! function_exists( 'musicas_yoast_auto_desc' ) ) {
	function musicas_yoast_auto_desc( $desc ) {
		// Se o Yoast já tem algo (template ou manual), respeita.
		if ( is_string( $desc ) && trim( $desc ) !== '' ) {
			return $desc;
		}
		if ( ! is_singular( array( 'station', 'album' ) ) ) {
			return $desc;
		}
		$auto = musicas_build_auto_metadesc( get_the_ID() );
		return $auto !== '' ? $auto : $desc;
	}
}

add_filter( 'wpseo_metadesc',            'musicas_yoast_auto_desc', 10, 1 );
add_filter( 'wpseo_opengraph_desc',      'musicas_yoast_auto_desc', 10, 1 );
add_filter( 'wpseo_twitter_description', 'musicas_yoast_auto_desc', 10, 1 );

/* ===================================================================
 * 8. Correção da data de publicação no upload do plays-block
 *
 * O formulário do plugin (templates/form/upload.php) usa
 * <input type="date"> — envia só "Y-m-d", sem hora. Esse valor cru vai
 * direto para wp_insert_post(), o MySQL completa-o como 00:00:00 e o
 * WordPress converte qualquer data futura em post agendado à meia-noite.
 *
 * Além disso o plugin faz 'post_date_gmt' => $post_date, ou seja, grava a
 * hora local como se fosse UTC. Em Moçambique (UTC+2) isso faz o post ser
 * realmente publicado 2h depois da hora mostrada no admin.
 *
 * Aqui completamos a hora e recalculamos a post_date_gmt com o fuso certo,
 * via os filtros que o próprio plugin expõe (não é preciso tocar no plugin).
 * =================================================================== */
if ( ! function_exists( 'musicas_fix_upload_post_date' ) ) {
	function musicas_fix_upload_post_date( $post ) {
		if ( empty( $post['post_date'] ) ) {
			return $post;
		}

		$date = trim( (string) $post['post_date'] );

		// Só age quando a data vem sem hora (Y-m-d), como no formulário.
		if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) {
			return $post;
		}

		// Data de hoje  -> publica já (hora atual do site).
		// Outra data    -> mantém a data escolhida com a hora atual.
		$local = ( $date === current_time( 'Y-m-d' ) )
			? current_time( 'mysql' )
			: $date . ' ' . current_time( 'H:i:s' );

		$local = apply_filters( 'musicas_upload_post_date', $local, $date, $post );

		$post['post_date']     = $local;
		$post['post_date_gmt'] = get_gmt_from_date( $local ); // conversão de fuso correta

		return $post;
	}
}

add_filter( 'frontend_upload_post',       'musicas_fix_upload_post_date', 10, 1 );
add_filter( 'frontend_upload_post_track', 'musicas_fix_upload_post_date', 10, 1 );


/* ===================================================================
 *  Opção B — arquivo de artistas em /artista/{slug}/
 *
 *  1. Reescreve o slug da taxonomia 'artist' (registada pelo plays-block)
 *     sem tocar no plugin.
 *  2. Alinha o fallback de musicas_artist_url() com a nova base.
 *  3. 301 dos /artist/{slug}/ antigos para o arquivo novo.
 *
 *  >>> DEPOIS DE PUBLICAR: Definições -> Ligações permanentes -> Guardar.
 *      Sem isso, /artista/ devolve 404.
 * =================================================================== */

/**
 * Muda a base do arquivo da taxonomia 'artist' para /artista/.
 * Preserva o resto do array `rewrite` definido pelo plays-block.
 */
add_filter( 'register_taxonomy_args', 'musicas_artist_rewrite_slug', 10, 2 );
function musicas_artist_rewrite_slug( $args, $taxonomy ) {
	if ( 'artist' !== $taxonomy ) {
		return $args;
	}

	// Se o plugin desativou o rewrite, não força um arquivo que não existia.
	if ( isset( $args['rewrite'] ) && false === $args['rewrite'] ) {
		return $args;
	}

	$rw = ( isset( $args['rewrite'] ) && is_array( $args['rewrite'] ) )
		? $args['rewrite']
		: array();

	$rw['slug']      = 'artista';
	$args['rewrite'] = $rw;

	return $args;
}

/**
 * 301 dos /artist/{slug}/ legados para o arquivo real.
 * Só corre em 404; artista não cadastrado continua a dar 404 (de propósito).
 * Prioridade 1 para correr antes do redirect_canonical() do core.
 */
add_action( 'template_redirect', 'musicas_redirect_artist_legacy', 1 );
function musicas_redirect_artist_legacy() {
	if ( ! is_404() ) {
		return;
	}

	$uri  = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
	$path = (string) wp_parse_url( $uri, PHP_URL_PATH );

	if ( ! preg_match( '#^/artist/([^/]+)#', $path, $m ) ) {
		return;
	}

	$tax = musicas_artist_taxonomy();
	if ( '' === $tax ) {
		return;
	}

	$term = get_term_by( 'slug', sanitize_title( urldecode( $m[1] ) ), $tax );
	if ( ! $term || is_wp_error( $term ) ) {
		return; // artista não cadastrado -> 404 honesto
	}

	$link = get_term_link( $term );
	if ( is_wp_error( $link ) ) {
		return;
	}

	// Salvaguarda anti-loop: nunca redirecionar para o próprio caminho pedido.
	$target = (string) wp_parse_url( $link, PHP_URL_PATH );
	if ( untrailingslashit( $target ) === untrailingslashit( $path ) ) {
		return;
	}

	wp_safe_redirect( $link, 301 );
	exit;
}

/* ===================================================================
 * 9. Remove a secção "station-release" (data de publicação) do tema pai
 *
 * O tema waveme imprime a data na página da faixa através de:
 *   theme/template-hooks.php
 *     if ( ! function_exists( 'ffl_release_date' ) ) :
 *     function ffl_release_date(){
 *         echo sprintf('<p class="station-release">%s</p>', ffl_posted_on(false));
 *     }
 *     endif;
 *     add_action( 'play_content', 'ffl_release_date', 60);
 *
 * Como o functions.php do tema filho carrega ANTES do tema pai, ao declarar
 * aqui a função primeiro o pai deixa de a criar (por causa do function_exists)
 * e o add_action passa a chamar esta versão vazia — o <p> nunca é impresso.
 *
 * Optou-se por esta via em vez de remove_action() porque o tema usa o prefixo
 * de hooks `play_` e o plugin usa `playblock_`; assim a remoção funciona
 * independentemente do prefixo/prioridade que estiver ativo.
 *
 * Nota: ffl_posted_on() NÃO é tocada — continua a servir os posts normais.
 * =================================================================== */
if ( ! function_exists( 'ffl_release_date' ) ) {
	function ffl_release_date() {
		// Intencionalmente vazio: não imprime a data de lançamento.
		// Para voltar a mostrá-la, remover esta secção do functions.php.
	}
}



/* ===================================================================
 * 10. Arquivo de artista — /artista/{slug}/ (taxonomia 'artist')
 *
 * Substitui o loop do tema-pai, que listava TODOS os artistas do site em
 * cada página de artista (conteúdo duplicado em milhares de URLs).
 *
 * REQUER o template `taxonomy-artist.php` na raiz do tema filho.
 *
 * Reutiliza: musicas_artist_taxonomy(), musicas_get_meta(),
 * ldc_parse_music_title(), musicas_format_duration_display(),
 * musicas_child_track_ids(), musicas_strip_empty().
 * =================================================================== */

/**
 * True quando estamos no arquivo da taxonomia de artistas.
 */
if ( ! function_exists( 'musicas_is_artist_archive' ) ) {
	function musicas_is_artist_archive() {
		$tax = musicas_artist_taxonomy();
		return ( '' !== $tax && is_tax( $tax ) );
	}
}

/**
 * Consulta de lançamentos de um artista.
 *
 * $kind: 'track' | 'album' | 'playlist'
 *
 * Suporta os dois cenários possíveis do plays-block:
 *   a) CPTs separados (station / album / playlist) — usa o post_type;
 *   b) tudo em `station` com o meta `type` — filtra por meta_query.
 */
if ( ! function_exists( 'musicas_artist_release_query' ) ) {
	function musicas_artist_release_query( $term, $kind = 'track', $args = array() ) {
		if ( ! $term || empty( $term->term_id ) ) {
			return new WP_Query( array( 'post__in' => array( 0 ), 'post_type' => 'station' ) );
		}

		$map = array(
			'track'    => array( 'cpt' => 'station',  'types' => array( 'single' ) ),
			'album'    => array( 'cpt' => 'album',    'types' => array( 'album' ) ),
			'playlist' => array( 'cpt' => 'playlist', 'types' => array( 'playlist', 'series' ) ),
		);

		if ( ! isset( $map[ $kind ] ) ) {
			$kind = 'track';
		}

		$cpt   = $map[ $kind ]['cpt'];
		$types = $map[ $kind ]['types'];

		$base = array(
			'post_status'         => 'publish',
			'posts_per_page'      => 24,
			'orderby'             => 'date',
			'order'               => 'DESC',
			'ignore_sticky_posts' => true,
			'tax_query'           => array(
				array(
					'taxonomy'         => $term->taxonomy,
					'field'            => 'term_id',
					'terms'            => (int) $term->term_id,
					'include_children' => false,
				),
			),
		);

		if ( post_type_exists( $cpt ) ) {

			// Cenário (a): CPT próprio.
			$base['post_type'] = $cpt;

		} else {

			// Cenário (b): tudo em `station`, distinguido pelo meta `type`.
			$base['post_type'] = 'station';

			if ( 'track' === $kind ) {
				$base['meta_query'] = array(
					'relation' => 'OR',
					array( 'key' => 'type', 'compare' => 'NOT EXISTS' ),
					array( 'key' => 'type', 'value' => '', 'compare' => '=' ),
					array(
						'key'     => 'type',
						'value'   => array( 'album', 'playlist', 'series' ),
						'compare' => 'NOT IN',
					),
				);
			} else {
				$base['meta_query'] = array(
					array( 'key' => 'type', 'value' => $types, 'compare' => 'IN' ),
				);
			}
		}

		$args = wp_parse_args( $args, $base );
		$args = apply_filters( 'musicas_artist_release_query_args', $args, $term, $kind );

		return new WP_Query( $args );
	}
}

/**
 * Imagem do artista: term meta -> capa do lançamento mais recente -> ''.
 * Cacheada 12h num transient.
 */
if ( ! function_exists( 'musicas_artist_image' ) ) {
	function musicas_artist_image( $term ) {
		if ( ! $term || empty( $term->term_id ) ) {
			return '';
		}

		$keys = apply_filters(
			'musicas_artist_image_meta_keys',
			array( 'cover', 'image', 'thumbnail', 'artist_image', '_avatar' )
		);

		foreach ( (array) $keys as $key ) {
			$val = get_term_meta( $term->term_id, $key, true );
			if ( is_numeric( $val ) ) {
				$src = wp_get_attachment_image_url( (int) $val, 'medium_large' );
				if ( $src ) {
					return $src;
				}
			} elseif ( is_string( $val ) && filter_var( $val, FILTER_VALIDATE_URL ) ) {
				return $val;
			}
		}

		$cache_key = 'mz_art_img_' . (int) $term->term_id;
		$cached    = get_transient( $cache_key );
		if ( false !== $cached ) {
			return $cached;
		}

		$img = '';
		$q   = musicas_artist_release_query( $term, 'track', array(
			'posts_per_page' => 1,
			'fields'         => 'ids',
			'no_found_rows'  => true,
		) );

		if ( ! empty( $q->posts ) ) {
			$pid = (int) $q->posts[0];
			$img = get_the_post_thumbnail_url( $pid, 'medium_large' );
			if ( ! $img ) {
				$cover = (string) musicas_get_meta( $pid, 'cover' );
				if ( $cover && filter_var( $cover, FILTER_VALIDATE_URL ) ) {
					$img = $cover;
				}
			}
		}

		$img = $img ? $img : '';
		set_transient( $cache_key, $img, 12 * HOUR_IN_SECONDS );

		return $img;
	}
}

/**
 * Biografia do artista = descrição do termo. Nunca é auto-gerada:
 * texto inventado em milhares de páginas é pior do que nenhum texto.
 */
if ( ! function_exists( 'musicas_artist_bio' ) ) {
	function musicas_artist_bio( $term ) {
		if ( ! $term || empty( $term->term_id ) ) {
			return '';
		}
		$bio = term_description( $term->term_id, $term->taxonomy );
		return ( trim( wp_strip_all_tags( (string) $bio ) ) !== '' ) ? $bio : '';
	}
}

/**
 * Géneros mais frequentes do artista.
 * Devolve array de arrays: [ 'id' => int, 'name' => string, 'url' => string ]
 */
if ( ! function_exists( 'musicas_artist_top_genres' ) ) {
	function musicas_artist_top_genres( $term_id, $limit = 4 ) {
		$term_id = (int) $term_id;
		$limit   = max( 1, (int) $limit );

		$cache_key = 'mz_art_gen_' . $term_id . '_' . $limit;
		$cached    = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$out = array();
		$tax = musicas_artist_taxonomy();

		if ( '' !== $tax && taxonomy_exists( 'genre' ) ) {

			$term = get_term( $term_id, $tax );

			if ( $term && ! is_wp_error( $term ) ) {

				$q = musicas_artist_release_query( $term, 'track', array(
					'posts_per_page' => 40,
					'fields'         => 'ids',
					'no_found_rows'  => true,
				) );

				$counts = array();
				$objs   = array();

				foreach ( (array) $q->posts as $pid ) {
					$terms = get_the_terms( $pid, 'genre' );
					if ( ! $terms || is_wp_error( $terms ) ) {
						continue;
					}
					foreach ( $terms as $t ) {
						$tid            = (int) $t->term_id;
						$counts[ $tid ] = isset( $counts[ $tid ] ) ? $counts[ $tid ] + 1 : 1;
						$objs[ $tid ]   = $t;
					}
				}

				arsort( $counts );
				$counts = array_slice( $counts, 0, $limit, true );

				foreach ( array_keys( $counts ) as $tid ) {
					$link = get_term_link( $objs[ $tid ] );
					if ( is_wp_error( $link ) ) {
						continue;
					}
					$out[] = array(
						'id'   => (int) $tid,
						'name' => $objs[ $tid ]->name,
						'url'  => $link,
					);
				}
			}
		}

		set_transient( $cache_key, $out, 12 * HOUR_IN_SECONDS );

		return $out;
	}
}

/**
 * Artistas relacionados (mesmo género principal).
 * Serve para linking interno entre arquivos de artista.
 */
if ( ! function_exists( 'musicas_artist_related' ) ) {
	function musicas_artist_related( $term_id, $limit = 12 ) {
		$term_id = (int) $term_id;
		$limit   = max( 1, (int) $limit );

		$cache_key = 'mz_art_rel_' . $term_id . '_' . $limit;
		$cached    = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$out     = array();
		$tax     = musicas_artist_taxonomy();
		$generos = musicas_artist_top_genres( $term_id, 1 );

		if ( '' !== $tax && ! empty( $generos ) ) {

			$q = new WP_Query( array(
				'post_type'           => 'station',
				'post_status'         => 'publish',
				'posts_per_page'      => 60,
				'fields'              => 'ids',
				'no_found_rows'       => true,
				'ignore_sticky_posts' => true,
				'orderby'             => 'date',
				'order'               => 'DESC',
				'tax_query'           => array(
					array(
						'taxonomy' => 'genre',
						'field'    => 'term_id',
						'terms'    => (int) $generos[0]['id'],
					),
				),
			) );

			$vistos = array( $term_id => true );

			foreach ( (array) $q->posts as $pid ) {
				if ( count( $out ) >= $limit ) {
					break;
				}
				$terms = get_the_terms( $pid, $tax );
				if ( ! $terms || is_wp_error( $terms ) ) {
					continue;
				}
				foreach ( $terms as $t ) {
					$tid = (int) $t->term_id;
					if ( isset( $vistos[ $tid ] ) || count( $out ) >= $limit ) {
						continue;
					}
					$link = get_term_link( $t );
					if ( is_wp_error( $link ) ) {
						continue;
					}
					$vistos[ $tid ] = true;
					$out[]          = array( 'name' => $t->name, 'url' => $link );
				}
			}
		}

		set_transient( $cache_key, $out, 12 * HOUR_IN_SECONDS );

		return $out;
	}
}

/**
 * Cartão de faixa / álbum / playlist para as grelhas do arquivo.
 * Liga sempre ao permalink — nunca ao endpoint /download/.
 */
if ( ! function_exists( 'musicas_artist_card_html' ) ) {
	function musicas_artist_card_html( $post_id, $kind = 'track' ) {
		$post_id = (int) $post_id;
		if ( ! $post_id ) {
			return '';
		}

		$url   = get_permalink( $post_id );
		$bruto = get_the_title( $post_id );

		// Mostra só o nome da faixa/álbum: o artista já está no H1 da página.
		$titulo = $bruto;
		$p      = ldc_parse_music_title( $bruto );
		if ( ! empty( $p['song'] ) ) {
			$titulo = $p['song'];
		}

		$img = get_the_post_thumbnail_url( $post_id, 'medium' );
		if ( ! $img ) {
			$cover = (string) musicas_get_meta( $post_id, 'cover' );
			if ( $cover && filter_var( $cover, FILTER_VALIDATE_URL ) ) {
				$img = $cover;
			}
		}

		$meta = '';

		if ( 'track' === $kind ) {
			$dur = musicas_format_duration_display( musicas_get_meta( $post_id, 'duration' ) );
			if ( $dur ) {
				$meta .= '<span class="mz-card__dur">' . esc_html( $dur ) . '</span>';
			}
		} else {
			$n = count( musicas_child_track_ids( $post_id ) );
			if ( $n > 0 ) {
				$meta .= '<span class="mz-card__dur">' . (int) $n . ( 1 === $n ? ' faixa' : ' faixas' ) . '</span>';
			}
		}

		$ano = get_the_date( 'Y', $post_id );
		if ( $ano ) {
			$meta .= '<span class="mz-card__year">' . esc_html( $ano ) . '</span>';
		}

		$html  = '<article class="mz-card mz-card--' . esc_attr( $kind ) . '">';
		$html .= '<a class="mz-card__link" href="' . esc_url( $url ) . '">';

		if ( $img ) {
			$html .= '<img class="mz-card__img" src="' . esc_url( $img ) . '"'
				. ' alt="' . esc_attr( $bruto ) . '"'
				. ' width="300" height="300" loading="lazy" decoding="async" />';
		}

		$html .= '<h3 class="mz-card__title">' . esc_html( $titulo ) . '</h3>';

		if ( '' !== $meta ) {
			$html .= '<p class="mz-card__meta">' . $meta . '</p>';
		}

		$html .= '</a></article>';

		return apply_filters( 'musicas_artist_card_html', $html, $post_id, $kind );
	}
}

/**
 * Nº total de publicações do artista (faixas + álbuns + playlists).
 * Usado para o noindex de artistas vazios e para a meta description.
 */
if ( ! function_exists( 'musicas_artist_content_count' ) ) {
	function musicas_artist_content_count( $term ) {
		if ( ! $term || empty( $term->term_id ) ) {
			return 0;
		}

		$cache_key = 'mz_art_cnt_' . (int) $term->term_id;
		$cached    = get_transient( $cache_key );
		if ( false !== $cached ) {
			return (int) $cached;
		}

		$types = array( 'station' );
		foreach ( array( 'album', 'playlist' ) as $pt ) {
			if ( post_type_exists( $pt ) ) {
				$types[] = $pt;
			}
		}

		$q = new WP_Query( array(
			'post_type'           => $types,
			'post_status'         => 'publish',
			'posts_per_page'      => 1,
			'fields'              => 'ids',
			'ignore_sticky_posts' => true,
			'tax_query'           => array(
				array(
					'taxonomy' => $term->taxonomy,
					'field'    => 'term_id',
					'terms'    => (int) $term->term_id,
				),
			),
		) );

		$n = (int) $q->found_posts;
		set_transient( $cache_key, $n, 6 * HOUR_IN_SECONDS );

		return $n;
	}
}

/**
 * Alinha a query principal do arquivo com a do template, para que a
 * paginação do WordPress (/artista/x/page/2/) e o rel=next/prev do Yoast
 * batam certo com o que é realmente listado.
 */
add_action( 'pre_get_posts', 'musicas_artist_archive_query' );
function musicas_artist_archive_query( $query ) {
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}

	$tax = musicas_artist_taxonomy();
	if ( '' === $tax || ! $query->is_tax( $tax ) ) {
		return;
	}

	$query->set( 'post_type', 'station' );
	$query->set( 'posts_per_page', 24 );
	$query->set( 'orderby', 'date' );
	$query->set( 'order', 'DESC' );
}

/**
 * CSS mínimo, só nesta página. Evita depender do CSS do tema-pai,
 * que assume a grelha antiga de "todos os artistas".
 */
add_action( 'wp_head', 'musicas_artist_archive_css', 20 );
function musicas_artist_archive_css() {
	if ( ! musicas_is_artist_archive() ) {
		return;
	}

	$css = '.mz-artist-archive{max-width:1200px;margin:0 auto;padding:24px 16px}'
		. '.mz-artist-hero{display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap;margin-bottom:32px}'
		. '.mz-artist-hero__cover img{width:180px;height:180px;object-fit:cover;border-radius:12px;display:block}'
		. '.mz-artist-hero__info{flex:1 1 320px;min-width:0}'
		. '.mz-artist-hero__eyebrow{margin:0;font-size:12px;letter-spacing:.08em;text-transform:uppercase;opacity:.6}'
		. '.mz-artist-hero__title{margin:4px 0 12px;font-size:clamp(28px,4vw,44px);line-height:1.1}'
		. '.mz-artist-hero__meta{display:flex;flex-wrap:wrap;gap:8px 16px;list-style:none;margin:0 0 14px;padding:0;font-size:14px;opacity:.85}'
		. '.mz-artist-hero__meta li{margin:0}'
		. '.mz-tag{display:inline-block;padding:2px 10px;border:1px solid currentColor;border-radius:999px;font-size:12px;text-decoration:none;opacity:.8}'
		. '.mz-artist-hero__bio{font-size:15px;line-height:1.6;max-width:70ch}'
		. '.mz-artist-section{margin:40px 0}'
		. '.mz-artist-section__title{font-size:20px;margin:0 0 16px}'
		. '.mz-grid{display:grid;gap:20px 16px;grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}'
		. '.mz-card__link{display:block;text-decoration:none;color:inherit}'
		. '.mz-card__img{width:100%;height:auto;aspect-ratio:1/1;object-fit:cover;border-radius:8px;display:block;margin-bottom:8px}'
		. '.mz-card__title{font-size:14px;line-height:1.35;margin:0 0 2px;font-weight:600}'
		. '.mz-card__meta{margin:0;font-size:12px;opacity:.6;display:flex;gap:10px}'
		. '.mz-related{list-style:none;display:flex;flex-wrap:wrap;gap:8px;padding:0;margin:0}'
		. '.mz-related a{display:inline-block;padding:6px 14px;border-radius:999px;border:1px solid rgba(128,128,128,.35);text-decoration:none;font-size:14px}'
		. '.mz-pagination ul{list-style:none;display:flex;flex-wrap:wrap;gap:8px;padding:0;margin:32px 0 0;justify-content:center}'
		. '.mz-pagination a,.mz-pagination span{display:inline-block;padding:8px 14px;border-radius:8px;border:1px solid rgba(128,128,128,.3);text-decoration:none}'
		. '.mz-pagination .current{font-weight:700;border-color:currentColor}'
		. '.mz-empty{opacity:.7}';

	echo "\n" . '<style id="mz-artist-archive-css">' . $css . '</style>' . "\n";
}

/**
 * Schema JSON-LD — MusicGroup + ItemList das faixas desta página.
 */
add_action( 'wp_head', 'musicas_artist_schema', 9 );
function musicas_artist_schema() {
	if ( ! musicas_is_artist_archive() ) {
		return;
	}

	$term = get_queried_object();
	if ( ! $term || empty( $term->term_id ) ) {
		return;
	}

	$url = get_term_link( $term );
	if ( is_wp_error( $url ) ) {
		return;
	}

	$img  = musicas_artist_image( $term );
	$bio  = trim( wp_strip_all_tags( (string) musicas_artist_bio( $term ) ) );
	$gens = musicas_artist_top_genres( $term->term_id, 4 );

	$generos = array();
	foreach ( $gens as $g ) {
		$generos[] = $g['name'];
	}

	$paged = max( 1, (int) get_query_var( 'paged' ) );

	$q = musicas_artist_release_query( $term, 'track', array(
		'posts_per_page' => 24,
		'paged'          => $paged,
		'fields'         => 'ids',
		'no_found_rows'  => true,
	) );

	$itens = array();
	$pos   = 0;
	foreach ( (array) $q->posts as $pid ) {
		$pos++;
		$permalink = get_permalink( $pid );
		$itens[]   = array(
			'@type'    => 'ListItem',
			'position' => $pos,
			'item'     => array(
				'@type' => 'MusicRecording',
				'@id'   => trailingslashit( $permalink ) . '#musicrecording',
				'name'  => get_the_title( $pid ),
				'url'   => $permalink,
			),
		);
	}

	$schema = array(
		'@context'    => 'https://schema.org',
		'@type'       => 'MusicGroup',
		'@id'         => trailingslashit( $url ) . '#artist',
		'name'        => $term->name,
		'url'         => $url,
		'image'       => $img ? $img : null,
		'description' => $bio ? $bio : null,
		'genre'       => ! empty( $generos ) ? $generos : null,
		'track'       => ! empty( $itens ) ? array(
			'@type'           => 'ItemList',
			'numberOfItems'   => count( $itens ),
			'itemListElement' => $itens,
		) : null,
		'subjectOf'   => array( '@id' => home_url( '/' ) . '#website' ),
	);

	$schema = musicas_strip_empty( $schema );

	$json = wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG );
	if ( ! $json ) {
		return;
	}

	echo "\n" . '<!-- Schema: MusicGroup (arquivo de artista) -->' . "\n";
	echo '<script type="application/ld+json">' . $json . '</script>' . "\n";
}

/**
 * Yoast — título do arquivo de artista.
 * Só promete "biografia" quando a descrição do termo está preenchida.
 */
add_filter( 'wpseo_title', 'musicas_artist_seo_title', 20, 1 );
function musicas_artist_seo_title( $title ) {
	if ( ! musicas_is_artist_archive() ) {
		return $title;
	}

	$term = get_queried_object();
	if ( ! $term || empty( $term->name ) ) {
		return $title;
	}

	$paged  = max( 1, (int) get_query_var( 'paged' ) );
	$sufixo = ( $paged > 1 ) ? ' — Página ' . $paged : '';
	$tembio = trim( wp_strip_all_tags( (string) musicas_artist_bio( $term ) ) ) !== '';

	if ( $tembio ) {
		return $term->name . ': biografia, músicas e álbuns' . $sufixo . ' | Musicas.co.MZ';
	}

	return $term->name . ' — Músicas e Álbuns para Download MP3' . $sufixo . ' | Musicas.co.MZ';
}

/**
 * Yoast — meta description / OG / Twitter do arquivo de artista.
 */
add_filter( 'wpseo_metadesc',            'musicas_artist_seo_desc', 20, 1 );
add_filter( 'wpseo_opengraph_desc',      'musicas_artist_seo_desc', 20, 1 );
add_filter( 'wpseo_twitter_description', 'musicas_artist_seo_desc', 20, 1 );
function musicas_artist_seo_desc( $desc ) {
	if ( ! musicas_is_artist_archive() ) {
		return $desc;
	}

	$term = get_queried_object();
	if ( ! $term || empty( $term->name ) ) {
		return $desc;
	}

	$n       = musicas_artist_content_count( $term );
	$generos = musicas_artist_top_genres( $term->term_id, 2 );

	$out = 'Baixar músicas de ' . $term->name . ' em MP3';

	if ( ! empty( $generos ) ) {
		$nomes = array();
		foreach ( $generos as $g ) {
			$nomes[] = $g['name'];
		}
		$out .= ' (' . implode( ', ', $nomes ) . ')';
	}

	$out .= '. ';

	if ( $n > 0 ) {
		$out .= $n . ( 1 === $n ? ' lançamento disponível' : ' lançamentos disponíveis' )
			. ' para ouvir e fazer download grátis no Musicas.co.MZ.';
	} else {
		$out .= 'Ouça e faça download grátis no Musicas.co.MZ.';
	}

	$out = trim( preg_replace( '/\s+/u', ' ', wp_strip_all_tags( $out ) ) );

	$limit = 158;
	if ( function_exists( 'mb_strlen' ) && mb_strlen( $out, 'UTF-8' ) > $limit ) {
		$cut = mb_substr( $out, 0, $limit, 'UTF-8' );
		$sp  = mb_strrpos( $cut, ' ', 0, 'UTF-8' );
		if ( false !== $sp ) {
			$cut = mb_substr( $cut, 0, $sp, 'UTF-8' );
		}
		$out = rtrim( $cut, " .,;:-" ) . '…';
	}

	return $out;
}

/**
 * Yoast — imagem OG do arquivo de artista.
 */
add_filter( 'wpseo_opengraph_image', 'musicas_artist_seo_image', 20, 1 );
function musicas_artist_seo_image( $img ) {
	if ( ! musicas_is_artist_archive() ) {
		return $img;
	}
	$novo = musicas_artist_image( get_queried_object() );
	return $novo ? $novo : $img;
}

/**
 * Artistas sem nada publicado ficam noindex — evita páginas vazias no índice.
 */
add_filter( 'wpseo_robots_array', 'musicas_artist_robots', 20, 1 );
function musicas_artist_robots( $robots ) {
	if ( ! musicas_is_artist_archive() ) {
		return $robots;
	}
	if ( 0 === musicas_artist_content_count( get_queried_object() ) ) {
		$robots['index'] = 'noindex';
	}
	return $robots;
}

/**
 * Limpa os transientes do artista sempre que um lançamento é guardado.
 */
add_action( 'save_post', 'musicas_artist_flush_cache', 10, 2 );
function musicas_artist_flush_cache( $post_id, $post ) {
	if ( ! $post instanceof WP_Post ) {
		return;
	}
	if ( ! in_array( $post->post_type, array( 'station', 'album', 'playlist' ), true ) ) {
		return;
	}

	$tax = musicas_artist_taxonomy();
	if ( '' === $tax ) {
		return;
	}

	$terms = get_the_terms( $post_id, $tax );
	if ( ! $terms || is_wp_error( $terms ) ) {
		return;
	}

	foreach ( $terms as $t ) {
		$tid = (int) $t->term_id;
		delete_transient( 'mz_art_img_' . $tid );
		delete_transient( 'mz_art_cnt_' . $tid );
		delete_transient( 'mz_art_rel_' . $tid . '_12' );
		foreach ( array( 1, 2, 4 ) as $lim ) {
			delete_transient( 'mz_art_gen_' . $tid . '_' . $lim );
		}
	}
}



/* ===================================================================
 * 11. Override do template do arquivo de artista
 *
 * O tema `waveme` intercepta `template_include` e impõe o
 * `archive-station.php` a TODOS os arquivos, incluindo /artista/{slug}/.
 * Isso curto-circuita a hierarquia de templates do WordPress: sem este
 * filtro, o `taxonomy-artist.php` do tema filho nunca chega a ser usado
 * (foi exatamente o que aconteceu na primeira tentativa de deploy).
 *
 * PHP_INT_MAX garante que corremos depois de qualquer plugin ou tema.
 *
 * NÃO REMOVER: sem isto o arquivo de artista volta a listar todos os
 * termos da taxonomia em vez das faixas do artista.
 * =================================================================== */

add_filter( 'template_include', 'musicas_force_artist_template', PHP_INT_MAX );
function musicas_force_artist_template( $template ) {

	if ( ! musicas_is_artist_archive() ) {
		return $template;
	}

	$nosso = get_stylesheet_directory() . '/taxonomy-artist.php';

	return file_exists( $nosso ) ? $nosso : $template;
}