<?php
/**
 * Plugin Name: AB Fusion Inventory
 * Plugin URI:  https://auditour.eu/
 * Description: Inventario, clasificacion, conversion y reversion de contenidos maquetados con Avada / Fusion Builder. Fase 1 (analisis) + Fase 2 (escritura con copia de seguridad y reversion).
 * Version:     2.1.0
 * Author:      Alex Borras
 * License:     GPL-2.0-or-later
 * Requires PHP: 7.4
 * Text Domain: ab-fusion-inventory
 */

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

define( 'ABFI_VERSION', '2.1.0' );
define( 'ABFI_CACHE_KEY', 'abfi_scan_result' );
define( 'ABFI_CAP', 'manage_options' );

/** Copia de seguridad del post_content original, previa a la conversion. */
define( 'ABFI_META_BACKUP', '_abfi_backup_content' );
define( 'ABFI_META_DATE', '_abfi_converted_at' );
define( 'ABFI_META_MODE', '_abfi_converted_mode' );

/** Opcion con los IDs excluidos manualmente. */
define( 'ABFI_OPT_EXCLUDE', 'abfi_excluded_ids' );

/** Log de la ultima ejecucion. Persiste para que no se pierda al recargar. */
define( 'ABFI_OPT_LOG', 'abfi_last_log' );

/**
 * Taxonomia de shortcodes Fusion.
 *
 * wrappers  : contenedores puros. Se eliminan sin perder nada (salvo columnas fraccionadas).
 * mappable  : tienen equivalente directo en HTML estandar.
 * Cualquier otro fusion_* se considera COMPLEJO -> reconstruccion manual.
 */
function abfi_wrapper_shortcodes() {
	return array(
		'fusion_builder_container',
		'fusion_builder_row',
		'fusion_builder_row_inner',
		'fusion_builder_column',
		'fusion_builder_column_inner',
		'fusion_text',
	);
}

function abfi_mappable_shortcodes() {
	return array(
		'fusion_title',
		'fusion_separator',
		'fusion_imageframe',
		'fusion_checklist',
		'fusion_li_item',
		'fusion_button',
		'fusion_highlight',
		'fusion_dropcap',
		'fusion_code',
		'fusion_blockquote',
	);
}

/* -------------------------------------------------------------------------
 *  CLASIFICACION (fuente unica de verdad)
 * ---------------------------------------------------------------------- */

/**
 * Analiza un post_content y devuelve su ficha de clasificacion.
 * La usan tanto el escaneo como la conversion, para que nunca puedan
 * divergir por culpa de una cache obsoleta.
 *
 * @param string $content
 * @return array
 */
function abfi_classify_content( $content ) {

	preg_match_all( '/\[\/?(fusion_[a-z0-9_]+)/i', $content, $m );
	$found = array_values( array_unique( array_map( 'strtolower', $m[1] ) ) );

	preg_match_all(
		'/\[fusion_builder_column(?:_inner)?\b[^\]]*\btype=["\']?([0-9]+_[0-9]+)["\']?/i',
		$content,
		$mc
	);
	$cols = array_values( array_unique( $mc[1] ) );

	$has_multicol = (bool) array_diff( $cols, array( '1_1' ) );

	$complex = array_values( array_diff( $found, abfi_wrapper_shortcodes(), abfi_mappable_shortcodes() ) );
	$mapped  = array_values( array_intersect( $found, abfi_mappable_shortcodes() ) );

	if ( $has_multicol || ! empty( $complex ) ) {
		$class = 'compleja';
	} elseif ( ! empty( $mapped ) ) {
		$class = 'media';
	} else {
		$class = 'simple';
	}

	return array(
		'found'    => $found,
		'cols'     => $cols,
		'multicol' => $has_multicol,
		'complex'  => $complex,
		'mapped'   => $mapped,
		'class'    => $class,
	);
}

/* -------------------------------------------------------------------------
 *  MENU
 * ---------------------------------------------------------------------- */

add_action( 'admin_menu', 'abfi_register_menu' );
function abfi_register_menu() {
	add_management_page(
		'Inventario Fusion',
		'Inventario Fusion',
		ABFI_CAP,
		'ab-fusion-inventory',
		'abfi_render_page'
	);
}

/* -------------------------------------------------------------------------
 *  ESCANEO (solo SELECT)
 * ---------------------------------------------------------------------- */

function abfi_scan() {
	global $wpdb;

	$rows = $wpdb->get_results(
		"SELECT ID, post_type, post_status, post_title, post_modified, post_content
		 FROM {$wpdb->posts}
		 WHERE post_content LIKE '%[fusion\\_%'
		   AND post_type   NOT IN ('revision','attachment','nav_menu_item','customize_changeset','oembed_cache')
		   AND post_status NOT IN ('auto-draft','trash','inherit')
		 ORDER BY post_type ASC, post_title ASC"
	);

	$items    = array();
	$sc_freq  = array();
	$col_freq = array();
	$by_type  = array();
	$by_class = array( 'simple' => 0, 'media' => 0, 'compleja' => 0 );

	foreach ( $rows as $row ) {

		$c = abfi_classify_content( $row->post_content );

		foreach ( $c['found'] as $sc ) {
			$sc_freq[ $sc ] = isset( $sc_freq[ $sc ] ) ? $sc_freq[ $sc ] + 1 : 1;
		}
		foreach ( $c['cols'] as $col ) {
			$col_freq[ $col ] = isset( $col_freq[ $col ] ) ? $col_freq[ $col ] + 1 : 1;
		}

		$by_class[ $c['class'] ]++;

		if ( ! isset( $by_type[ $row->post_type ] ) ) {
			$by_type[ $row->post_type ] = array( 'simple' => 0, 'media' => 0, 'compleja' => 0, 'total' => 0 );
		}
		$by_type[ $row->post_type ][ $c['class'] ]++;
		$by_type[ $row->post_type ]['total']++;

		$items[] = array(
			'id'       => (int) $row->ID,
			'type'     => $row->post_type,
			'status'   => $row->post_status,
			'title'    => $row->post_title,
			'modified' => $row->post_modified,
			'kb'       => round( strlen( $row->post_content ) / 1024, 1 ),
			'n_sc'     => count( $c['found'] ),
			'cols'     => $c['cols'],
			'multicol' => $c['multicol'],
			'mapped'   => $c['mapped'],
			'complex'  => $c['complex'],
			'class'    => $c['class'],
		);
	}

	arsort( $sc_freq );
	arsort( $col_freq );

	return array(
		'generated' => current_time( 'mysql' ),
		'items'     => $items,
		'sc_freq'   => $sc_freq,
		'col_freq'  => $col_freq,
		'by_type'   => $by_type,
		'by_class'  => $by_class,
		'total'     => count( $items ),
		'meta_keys' => abfi_fusion_meta_keys(),
		'cpt_seen'  => abfi_fusion_post_types(),
	);
}

function abfi_fusion_meta_keys() {
	global $wpdb;
	return $wpdb->get_results(
		"SELECT meta_key, COUNT(*) AS n
		 FROM {$wpdb->postmeta}
		 WHERE meta_key LIKE '%fusion%' OR meta_key LIKE '%avada%'
		 GROUP BY meta_key ORDER BY n DESC LIMIT 100",
		ARRAY_A
	);
}

function abfi_fusion_post_types() {
	global $wpdb;
	return $wpdb->get_results(
		"SELECT post_type, post_status, COUNT(*) AS n
		 FROM {$wpdb->posts}
		 WHERE (post_type LIKE 'fusion%' OR post_type LIKE 'avada%' OR post_type LIKE 'slide%')
		   AND post_status NOT IN ('auto-draft','inherit')
		 GROUP BY post_type, post_status ORDER BY n DESC",
		ARRAY_A
	);
}

function abfi_get_scan( $force = false ) {
	if ( ! $force ) {
		$cached = get_transient( ABFI_CACHE_KEY );
		if ( is_array( $cached ) ) {
			return $cached;
		}
	}
	$data = abfi_scan();
	set_transient( ABFI_CACHE_KEY, $data, HOUR_IN_SECONDS * 6 );
	return $data;
}

/* -------------------------------------------------------------------------
 *  CONVERSION (funciones puras: devuelven string, no escriben)
 * ---------------------------------------------------------------------- */

function abfi_to_html( $content ) {

	$c = $content;

	$c = preg_replace( '/\[\/?fusion_builder_container\b[^\]]*\]/i', "\n", $c );
	$c = preg_replace( '/\[\/?fusion_builder_row(?:_inner)?\b[^\]]*\]/i', "\n", $c );

	$c = preg_replace_callback(
		'/\[fusion_builder_column(_inner)?\b([^\]]*)\]/i',
		function ( $m ) {
			if ( preg_match( '/\btype=["\']?([0-9]+_[0-9]+)["\']?/i', $m[2], $t ) && '1_1' !== $t[1] ) {
				return "\n<!-- ABFI:COLUMNA " . $t[1] . " -->\n";
			}
			return "\n";
		},
		$c
	);
	$c = preg_replace( '/\[\/fusion_builder_column(?:_inner)?\]/i', "\n", $c );

	$c = preg_replace( '/\[\/?fusion_text\b[^\]]*\]/i', "\n", $c );

	$c = preg_replace_callback(
		'/\[fusion_title\b([^\]]*)\](.*?)\[\/fusion_title\]/is',
		function ( $m ) {
			$level = 2;
			if ( preg_match( '/\bsize=["\']?([1-6])["\']?/i', $m[1], $s ) ) {
				$level = (int) $s[1];
			}
			return "\n<h{$level}>" . trim( wp_strip_all_tags( $m[2], false ) ) . "</h{$level}>\n";
		},
		$c
	);

	$c = preg_replace( '/\[fusion_imageframe\b[^\]]*\](.*?)\[\/fusion_imageframe\]/is', "\n$1\n", $c );
	$c = preg_replace( '/\[fusion_imageframe\b[^\]]*\]/i', '', $c );

	$c = preg_replace( '/\[fusion_li_item\b[^\]]*\](.*?)\[\/fusion_li_item\]/is', '<li>$1</li>', $c );
	$c = preg_replace( '/\[fusion_checklist\b[^\]]*\](.*?)\[\/fusion_checklist\]/is', "\n<ul>$1</ul>\n", $c );

	$c = preg_replace_callback(
		'/\[fusion_button\b([^\]]*)\](.*?)\[\/fusion_button\]/is',
		function ( $m ) {
			$href = '#';
			if ( preg_match( '/\blink=["\']([^"\']+)["\']/i', $m[1], $l ) ) {
				$href = esc_url_raw( $l[1] );
			}
			return '<p><a class="abfi-button" href="' . esc_url( $href ) . '">' . trim( wp_strip_all_tags( $m[2] ) ) . '</a></p>';
		},
		$c
	);

	$c = preg_replace( '/\[fusion_highlight\b[^\]]*\](.*?)\[\/fusion_highlight\]/is', '<mark>$1</mark>', $c );
	$c = preg_replace( '/\[fusion_dropcap\b[^\]]*\](.*?)\[\/fusion_dropcap\]/is', '$1', $c );
	$c = preg_replace( '/\[fusion_blockquote\b[^\]]*\](.*?)\[\/fusion_blockquote\]/is', "\n<blockquote>$1</blockquote>\n", $c );
	$c = preg_replace( '/\[fusion_code\b[^\]]*\](.*?)\[\/fusion_code\]/is', "\n<pre><code>$1</code></pre>\n", $c );
	$c = preg_replace( '/\[fusion_separator\b[^\]]*\](?:\[\/fusion_separator\])?/i', "\n", $c );

	$c = preg_replace( '/[ \t]+\n/', "\n", $c );
	$c = preg_replace( '/\n{3,}/', "\n\n", $c );

	return trim( $c );
}

function abfi_to_blocks( $html ) {

	if ( '' === trim( $html ) ) {
		return '';
	}

	$prev = libxml_use_internal_errors( true );
	$dom  = new DOMDocument();
	$dom->loadHTML(
		'<?xml encoding="utf-8" ?><div id="abfi-root">' . $html . '</div>',
		LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
	);
	libxml_clear_errors();
	libxml_use_internal_errors( $prev );

	// getElementById no es fiable sin DTD tras loadHTML: el wrapper es el primer
	// elemento en orden de documento, asi que lo tomamos por posicion.
	$root = $dom->getElementsByTagName( 'div' )->item( 0 );
	if ( ! $root || 'abfi-root' !== $root->getAttribute( 'id' ) ) {
		return "<!-- wp:html -->\n" . $html . "\n<!-- /wp:html -->";
	}

	$out = array();

	foreach ( iterator_to_array( $root->childNodes ) as $node ) {

		if ( XML_TEXT_NODE === $node->nodeType ) {
			$txt = trim( $node->textContent );
			if ( '' !== $txt ) {
				$out[] = "<!-- wp:paragraph -->\n<p>" . esc_html( $txt ) . "</p>\n<!-- /wp:paragraph -->";
			}
			continue;
		}

		if ( XML_COMMENT_NODE === $node->nodeType ) {
			$out[] = '<!--' . $node->textContent . '-->';
			continue;
		}

		if ( XML_ELEMENT_NODE !== $node->nodeType ) {
			continue;
		}

		$tag  = strtolower( $node->nodeName );
		$frag = $dom->saveHTML( $node );

		switch ( true ) {

			case 'p' === $tag:
				$out[] = "<!-- wp:paragraph -->\n{$frag}\n<!-- /wp:paragraph -->";
				break;

			case preg_match( '/^h([1-6])$/', $tag, $h ) > 0:
				$attr  = ( '2' === $h[1] ) ? '' : ' {"level":' . $h[1] . '}';
				$out[] = "<!-- wp:heading{$attr} -->\n{$frag}\n<!-- /wp:heading -->";
				break;

			case in_array( $tag, array( 'ul', 'ol' ), true ):
				$ordered = ( 'ol' === $tag );
				$inner   = '';
				foreach ( $node->childNodes as $li ) {
					if ( XML_ELEMENT_NODE === $li->nodeType && 'li' === strtolower( $li->nodeName ) ) {
						$inner .= "<!-- wp:list-item -->\n" . $dom->saveHTML( $li ) . "\n<!-- /wp:list-item -->\n";
					}
				}
				$open  = $ordered ? '<!-- wp:list {"ordered":true} -->' . "\n<ol>" : "<!-- wp:list -->\n<ul>";
				$close = $ordered ? "</ol>\n<!-- /wp:list -->" : "</ul>\n<!-- /wp:list -->";
				$out[] = $open . "\n" . $inner . $close;
				break;

			case 'blockquote' === $tag:
				$out[] = "<!-- wp:quote -->\n{$frag}\n<!-- /wp:quote -->";
				break;

			case 'hr' === $tag:
				$out[] = "<!-- wp:separator -->\n<hr class=\"wp-block-separator\"/>\n<!-- /wp:separator -->";
				break;

			case 'table' === $tag:
				$out[] = "<!-- wp:table -->\n<figure class=\"wp-block-table\">{$frag}</figure>\n<!-- /wp:table -->";
				break;

			case 'pre' === $tag:
				$out[] = "<!-- wp:preformatted -->\n{$frag}\n<!-- /wp:preformatted -->";
				break;

			case 'img' === $tag:
				$out[] = "<!-- wp:image -->\n<figure class=\"wp-block-image\">{$frag}</figure>\n<!-- /wp:image -->";
				break;

			case 'figure' === $tag:
				$out[] = "<!-- wp:image -->\n{$frag}\n<!-- /wp:image -->";
				break;

			default:
				$out[] = "<!-- wp:html -->\n{$frag}\n<!-- /wp:html -->";
		}
	}

	return implode( "\n\n", $out );
}

function abfi_residuals( $converted ) {
	preg_match_all( '/\[\/?(fusion_[a-z0-9_]+)/i', $converted, $m );
	return array_values( array_unique( array_map( 'strtolower', $m[1] ) ) );
}

/* -------------------------------------------------------------------------
 *  FASE 2 — ESCRITURA
 * ---------------------------------------------------------------------- */

function abfi_get_excluded_ids() {
	$raw = get_option( ABFI_OPT_EXCLUDE, array() );
	return is_array( $raw ) ? array_map( 'absint', $raw ) : array();
}

function abfi_set_excluded_ids( $ids ) {
	$ids = array_values( array_unique( array_filter( array_map( 'absint', (array) $ids ) ) ) );
	sort( $ids );
	update_option( ABFI_OPT_EXCLUDE, $ids, false );
	return $ids;
}

function abfi_is_converted( $post_id ) {
	return '' !== (string) get_post_meta( $post_id, ABFI_META_DATE, true );
}

/**
 * IDs elegibles para conversion automatica.
 * Reclasifica en vivo: nunca se fia del escaneo cacheado.
 *
 * @param bool $include_media Incluir la clase MEDIA ademas de SIMPLE.
 * @return array
 */
function abfi_build_queue( $include_media = true ) {

	global $wpdb;

	$excluded = abfi_get_excluded_ids();

	$rows = $wpdb->get_results(
		"SELECT ID, post_content FROM {$wpdb->posts}
		 WHERE post_content LIKE '%[fusion\\_%'
		   AND post_type   NOT IN ('revision','attachment','nav_menu_item','customize_changeset','oembed_cache')
		   AND post_status NOT IN ('auto-draft','trash','inherit')"
	);

	$queue = array();

	foreach ( $rows as $row ) {

		$id = (int) $row->ID;

		if ( in_array( $id, $excluded, true ) || abfi_is_converted( $id ) ) {
			continue;
		}

		$class = abfi_classify_content( $row->post_content )['class'];

		if ( 'simple' === $class || ( $include_media && 'media' === $class ) ) {
			$queue[] = $id;
		}
	}

	return $queue;
}

/**
 * Convierte una entrada. Con $dry_run = true no escribe nada.
 *
 * @return array Resultado para el log.
 */
function abfi_convert_post( $post_id, $mode = 'html', $dry_run = true ) {

	global $wpdb;

	$post_id = absint( $post_id );
	$post    = get_post( $post_id );

	if ( ! $post ) {
		return array( 'id' => $post_id, 'ok' => false, 'msg' => 'No existe' );
	}

	if ( in_array( $post_id, abfi_get_excluded_ids(), true ) ) {
		return array( 'id' => $post_id, 'title' => $post->post_title, 'ok' => false, 'msg' => 'Excluida' );
	}

	if ( abfi_is_converted( $post_id ) ) {
		return array( 'id' => $post_id, 'title' => $post->post_title, 'ok' => false, 'msg' => 'Ya convertida' );
	}

	$original = $post->post_content;
	$class    = abfi_classify_content( $original )['class'];

	// Cinturon de seguridad: jamas tocar una compleja, aunque llegue por error.
	if ( 'compleja' === $class ) {
		return array( 'id' => $post_id, 'title' => $post->post_title, 'ok' => false, 'msg' => 'COMPLEJA — omitida' );
	}

	$html      = abfi_to_html( $original );
	$new       = ( 'blocks' === $mode ) ? abfi_to_blocks( $html ) : $html;
	$residuals = abfi_residuals( $new );

	if ( ! empty( $residuals ) ) {
		return array(
			'id'    => $post_id,
			'title' => $post->post_title,
			'ok'    => false,
			'msg'   => 'Residuos: ' . implode( ' ', $residuals ),
		);
	}

	if ( '' === trim( $new ) ) {
		return array( 'id' => $post_id, 'title' => $post->post_title, 'ok' => false, 'msg' => 'Resultado vacio — omitida' );
	}

	$result = array(
		'id'     => $post_id,
		'title'  => $post->post_title,
		'ok'     => true,
		'class'  => $class,
		'before' => round( strlen( $original ) / 1024, 1 ),
		'after'  => round( strlen( $new ) / 1024, 1 ),
		'msg'    => $dry_run ? 'OK (simulacro)' : 'Convertida',
	);

	if ( $dry_run ) {
		return $result;
	}

	// Copia de seguridad ANTES de escribir. Si esto falla, no se toca el contenido.
	$saved = update_post_meta( $post_id, ABFI_META_BACKUP, $original );
	if ( false === $saved && '' === (string) get_post_meta( $post_id, ABFI_META_BACKUP, true ) ) {
		$result['ok']  = false;
		$result['msg'] = 'No se pudo guardar la copia de seguridad — abortada';
		return $result;
	}

	// Escritura directa: evita que filtros de terceros reprocesen el contenido
	// en 184 pasadas. La reversion se apoya en nuestra propia copia, no en revisiones.
	$updated = $wpdb->update(
		$wpdb->posts,
		array( 'post_content' => $new ),
		array( 'ID' => $post_id ),
		array( '%s' ),
		array( '%d' )
	);

	if ( false === $updated ) {
		delete_post_meta( $post_id, ABFI_META_BACKUP );
		$result['ok']  = false;
		$result['msg'] = 'Error de escritura en BD';
		return $result;
	}

	update_post_meta( $post_id, ABFI_META_DATE, current_time( 'mysql' ) );
	update_post_meta( $post_id, ABFI_META_MODE, $mode );
	clean_post_cache( $post_id );

	return $result;
}

/**
 * Devuelve una entrada a su contenido original.
 */
function abfi_revert_post( $post_id ) {

	global $wpdb;

	$post_id = absint( $post_id );
	$backup  = get_post_meta( $post_id, ABFI_META_BACKUP, true );

	if ( '' === (string) $backup ) {
		return array( 'id' => $post_id, 'ok' => false, 'msg' => 'Sin copia de seguridad' );
	}

	$updated = $wpdb->update(
		$wpdb->posts,
		array( 'post_content' => $backup ),
		array( 'ID' => $post_id ),
		array( '%s' ),
		array( '%d' )
	);

	if ( false === $updated ) {
		return array( 'id' => $post_id, 'ok' => false, 'msg' => 'Error de escritura en BD' );
	}

	delete_post_meta( $post_id, ABFI_META_BACKUP );
	delete_post_meta( $post_id, ABFI_META_DATE );
	delete_post_meta( $post_id, ABFI_META_MODE );
	clean_post_cache( $post_id );

	$post = get_post( $post_id );

	return array(
		'id'    => $post_id,
		'title' => $post ? $post->post_title : '',
		'ok'    => true,
		'msg'   => 'Revertida',
	);
}

function abfi_converted_ids() {
	global $wpdb;
	return array_map(
		'absint',
		$wpdb->get_col(
			$wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s", ABFI_META_DATE )
		)
	);
}

/* -------------------------------------------------------------------------
 *  LOG PERSISTENTE
 * ---------------------------------------------------------------------- */

function abfi_log_reset( $kind ) {
	update_option(
		ABFI_OPT_LOG,
		array( 'kind' => $kind, 'when' => current_time( 'mysql' ), 'rows' => array() ),
		false
	);
}

function abfi_log_append( $rows ) {
	$log = get_option( ABFI_OPT_LOG, array() );
	if ( ! is_array( $log ) || ! isset( $log['rows'] ) ) {
		$log = array( 'kind' => 'conversion', 'when' => current_time( 'mysql' ), 'rows' => array() );
	}
	$log['rows'] = array_merge( $log['rows'], $rows );
	update_option( ABFI_OPT_LOG, $log, false );
}

function abfi_log_get() {
	$log = get_option( ABFI_OPT_LOG, array() );
	return ( is_array( $log ) && ! empty( $log['rows'] ) ) ? $log : null;
}

/* -------------------------------------------------------------------------
 *  AJAX
 * ---------------------------------------------------------------------- */

add_action( 'wp_ajax_abfi_queue', 'abfi_ajax_queue' );
function abfi_ajax_queue() {

	check_ajax_referer( 'abfi_run' );
	if ( ! current_user_can( ABFI_CAP ) ) {
		wp_send_json_error( 'Permisos insuficientes' );
	}

	$mode_set = isset( $_POST['include_media'] ) && '1' === $_POST['include_media'];

	wp_send_json_success( array( 'ids' => abfi_build_queue( $mode_set ) ) );
}

add_action( 'wp_ajax_abfi_convert', 'abfi_ajax_convert' );
function abfi_ajax_convert() {

	check_ajax_referer( 'abfi_run' );
	if ( ! current_user_can( ABFI_CAP ) ) {
		wp_send_json_error( 'Permisos insuficientes' );
	}

	$ids  = isset( $_POST['ids'] ) ? array_map( 'absint', (array) $_POST['ids'] ) : array();
	$mode = ( isset( $_POST['mode'] ) && 'blocks' === $_POST['mode'] ) ? 'blocks' : 'html';
	$dry  = ! isset( $_POST['dry'] ) || '0' !== $_POST['dry'];

	if ( isset( $_POST['reset'] ) && '1' === $_POST['reset'] ) {
		abfi_log_reset( $dry ? 'simulacro' : 'conversion' );
	}

	$log = array();
	foreach ( $ids as $id ) {
		$log[] = abfi_convert_post( $id, $mode, $dry );
	}

	abfi_log_append( $log );

	if ( ! $dry ) {
		delete_transient( ABFI_CACHE_KEY );
	}

	wp_send_json_success( array( 'log' => $log ) );
}

add_action( 'wp_ajax_abfi_revert', 'abfi_ajax_revert' );
function abfi_ajax_revert() {

	check_ajax_referer( 'abfi_run' );
	if ( ! current_user_can( ABFI_CAP ) ) {
		wp_send_json_error( 'Permisos insuficientes' );
	}

	$ids = isset( $_POST['ids'] ) ? array_map( 'absint', (array) $_POST['ids'] ) : array();

	if ( isset( $_POST['reset'] ) && '1' === $_POST['reset'] ) {
		abfi_log_reset( 'reversion' );
	}

	$log = array();
	foreach ( $ids as $id ) {
		$log[] = abfi_revert_post( $id );
	}

	abfi_log_append( $log );
	delete_transient( ABFI_CACHE_KEY );

	wp_send_json_success( array( 'log' => $log ) );
}

/* -------------------------------------------------------------------------
 *  EXPORTACION CSV
 * ---------------------------------------------------------------------- */

add_action( 'admin_post_abfi_export_csv', 'abfi_export_csv' );
function abfi_export_csv() {

	if ( ! current_user_can( ABFI_CAP ) ) {
		wp_die( 'Permisos insuficientes.' );
	}
	check_admin_referer( 'abfi_export' );

	$data     = abfi_get_scan();
	$excluded = abfi_get_excluded_ids();

	nocache_headers();
	header( 'Content-Type: text/csv; charset=utf-8' );
	header( 'Content-Disposition: attachment; filename=inventario_fusion_' . gmdate( 'Ymd_Hi' ) . '.csv' );

	$fh = fopen( 'php://output', 'w' );
	fwrite( $fh, "\xEF\xBB\xBF" );

	fputcsv(
		$fh,
		array( 'ID', 'Tipo', 'Estado', 'Titulo', 'Clasificacion', 'Convertida', 'Excluida', 'Multicolumna', 'Columnas', 'Elementos mapeables', 'Elementos complejos', 'Nº shortcodes', 'KB', 'Modificado', 'URL edicion' ),
		';'
	);

	foreach ( $data['items'] as $it ) {
		fputcsv(
			$fh,
			array(
				$it['id'],
				$it['type'],
				$it['status'],
				$it['title'],
				$it['class'],
				abfi_is_converted( $it['id'] ) ? 'SI' : 'NO',
				in_array( $it['id'], $excluded, true ) ? 'SI' : 'NO',
				$it['multicol'] ? 'SI' : 'NO',
				implode( ' ', $it['cols'] ),
				implode( ' ', $it['mapped'] ),
				implode( ' ', $it['complex'] ),
				$it['n_sc'],
				$it['kb'],
				$it['modified'],
				admin_url( 'post.php?post=' . $it['id'] . '&action=edit' ),
			),
			';'
		);
	}

	fclose( $fh );
	exit;
}

/* -------------------------------------------------------------------------
 *  INTERFAZ
 * ---------------------------------------------------------------------- */

function abfi_render_page() {

	if ( ! current_user_can( ABFI_CAP ) ) {
		wp_die( 'Permisos insuficientes.' );
	}

	$tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'resumen';

	$force = false;
	if ( isset( $_POST['abfi_rescan'] ) && check_admin_referer( 'abfi_rescan' ) ) {
		$force = true;
	}

	if ( isset( $_POST['abfi_save_exclusions'] ) && check_admin_referer( 'abfi_exclusions' ) ) {
		$raw = isset( $_POST['abfi_ids'] ) ? sanitize_textarea_field( wp_unslash( $_POST['abfi_ids'] ) ) : '';
		abfi_set_excluded_ids( preg_split( '/[^0-9]+/', $raw ) );
		echo '<div class="notice notice-success is-dismissible"><p>Lista de exclusiones guardada.</p></div>';
	}

	if ( isset( $_POST['abfi_autoexclude'] ) && check_admin_referer( 'abfi_exclusions' ) ) {
		$data = abfi_get_scan( true );
		$ids  = abfi_get_excluded_ids();
		foreach ( $data['items'] as $it ) {
			if ( 'compleja' === $it['class'] ) {
				$ids[] = $it['id'];
			}
		}
		$ids = abfi_set_excluded_ids( $ids );
		echo '<div class="notice notice-success is-dismissible"><p>Añadidas las páginas complejas. Total excluidas: ' . count( $ids ) . '</p></div>';
	}

	if ( isset( $_POST['abfi_clear_log'] ) && check_admin_referer( 'abfi_exclusions' ) ) {
		delete_option( ABFI_OPT_LOG );
		echo '<div class="notice notice-success is-dismissible"><p>Log borrado.</p></div>';
	}

	$data = abfi_get_scan( $force );

	$tabs = array(
		'resumen'    => 'Resumen',
		'detalle'    => 'Detalle',
		'entorno'    => 'Entorno Avada',
		'preview'    => 'Previsualizar',
		'conversion' => 'Conversión',
	);

	echo '<div class="wrap">';
	echo '<h1>Inventario Fusion <span style="font-size:13px;font-weight:400;color:#646970;">v' . esc_html( ABFI_VERSION ) . '</span></h1>';

	echo '<h2 class="nav-tab-wrapper">';
	foreach ( $tabs as $slug => $label ) {
		printf(
			'<a href="%s" class="nav-tab %s">%s</a>',
			esc_url( admin_url( 'tools.php?page=ab-fusion-inventory&tab=' . $slug ) ),
			$tab === $slug ? 'nav-tab-active' : '',
			esc_html( $label )
		);
	}
	echo '</h2>';

	echo '<div style="margin:14px 0;">';
	echo '<form method="post" style="display:inline-block;margin-right:10px;">';
	wp_nonce_field( 'abfi_rescan' );
	echo '<button type="submit" name="abfi_rescan" value="1" class="button">Reescanear</button>';
	echo '</form>';
	printf(
		'<a class="button button-primary" href="%s">Exportar CSV</a>',
		esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=abfi_export_csv' ), 'abfi_export' ) )
	);
	echo ' <span style="color:#646970;margin-left:10px;">Escaneo: ' . esc_html( $data['generated'] ) . ' &middot; ' . (int) $data['total'] . ' entradas con Fusion &middot; ' . count( abfi_converted_ids() ) . ' convertidas &middot; ' . count( abfi_get_excluded_ids() ) . ' excluidas</span>';
	echo '</div>';

	switch ( $tab ) {
		case 'detalle':
			abfi_tab_detalle( $data );
			break;
		case 'entorno':
			abfi_tab_entorno( $data );
			break;
		case 'preview':
			abfi_tab_preview();
			break;
		case 'conversion':
			abfi_tab_conversion( $data );
			break;
		default:
			abfi_tab_resumen( $data );
	}

	echo '</div>';
}

function abfi_badge( $class ) {
	$colors = array( 'simple' => '#00a32a', 'media' => '#dba617', 'compleja' => '#d63638' );
	$color  = isset( $colors[ $class ] ) ? $colors[ $class ] : '#646970';
	return '<span style="display:inline-block;padding:2px 8px;border-radius:3px;color:#fff;font-size:11px;font-weight:600;background:' . $color . ';">' . esc_html( strtoupper( $class ) ) . '</span>';
}

function abfi_tab_resumen( $data ) {

	$c = $data['by_class'];
	$t = max( 1, $data['total'] );

	echo '<h2>Clasificación global</h2>';
	echo '<table class="widefat striped" style="max-width:760px;"><thead><tr><th>Clase</th><th>Criterio</th><th style="width:80px;">Nº</th><th style="width:80px;">%</th></tr></thead><tbody>';
	$rows = array(
		'simple'   => 'Solo contenedores + fusion_text. Columnas 1_1. Conversión automática fiable.',
		'media'    => 'Incluye elementos con equivalente HTML directo (título, botón, imagen, lista).',
		'compleja' => 'Columnas fraccionadas o elementos sin equivalente. Reconstrucción manual.',
	);
	foreach ( $rows as $k => $desc ) {
		printf(
			'<tr><td>%s</td><td>%s</td><td><strong>%d</strong></td><td>%s%%</td></tr>',
			abfi_badge( $k ),
			esc_html( $desc ),
			(int) $c[ $k ],
			esc_html( round( $c[ $k ] / $t * 100, 1 ) )
		);
	}
	echo '</tbody></table>';

	echo '<h2>Por tipo de contenido</h2>';
	echo '<table class="widefat striped" style="max-width:620px;"><thead><tr><th>post_type</th><th>Simple</th><th>Media</th><th>Compleja</th><th>Total</th></tr></thead><tbody>';
	foreach ( $data['by_type'] as $type => $n ) {
		printf(
			'<tr><td><code>%s</code></td><td>%d</td><td>%d</td><td>%d</td><td><strong>%d</strong></td></tr>',
			esc_html( $type ), (int) $n['simple'], (int) $n['media'], (int) $n['compleja'], (int) $n['total']
		);
	}
	echo '</tbody></table>';

	echo '<h2>Shortcodes Fusion encontrados</h2>';
	echo '<table class="widefat striped" style="max-width:620px;"><thead><tr><th>Shortcode</th><th style="width:100px;">Entradas</th><th style="width:140px;">Tratamiento</th></tr></thead><tbody>';
	$wrappers = abfi_wrapper_shortcodes();
	$mappable = abfi_mappable_shortcodes();
	foreach ( $data['sc_freq'] as $sc => $n ) {
		if ( in_array( $sc, $wrappers, true ) ) {
			$trat = '<span style="color:#00a32a;">se elimina</span>';
		} elseif ( in_array( $sc, $mappable, true ) ) {
			$trat = '<span style="color:#dba617;">se convierte</span>';
		} else {
			$trat = '<span style="color:#d63638;font-weight:600;">manual</span>';
		}
		printf( '<tr><td><code>%s</code></td><td>%d</td><td>%s</td></tr>', esc_html( $sc ), (int) $n, $trat );
	}
	echo '</tbody></table>';

	if ( ! empty( $data['col_freq'] ) ) {
		echo '<h2>Anchos de columna en uso</h2>';
		echo '<table class="widefat striped" style="max-width:420px;"><thead><tr><th>type</th><th>Entradas</th></tr></thead><tbody>';
		foreach ( $data['col_freq'] as $col => $n ) {
			$mark = ( '1_1' === $col ) ? '' : ' &larr; layout real';
			printf( '<tr><td><code>%s</code>%s</td><td>%d</td></tr>', esc_html( $col ), $mark, (int) $n );
		}
		echo '</tbody></table>';
	}
}

function abfi_tab_detalle( $data ) {

	$filter   = isset( $_GET['clase'] ) ? sanitize_key( $_GET['clase'] ) : '';
	$excluded = abfi_get_excluded_ids();

	echo '<h2>Detalle por entrada</h2>';
	echo '<p>Filtrar: ';
	foreach ( array( '' => 'Todas', 'simple' => 'Simple', 'media' => 'Media', 'compleja' => 'Compleja' ) as $k => $label ) {
		printf(
			'<a class="button button-small %s" href="%s">%s</a> ',
			$filter === $k ? 'button-primary' : '',
			esc_url( admin_url( 'tools.php?page=ab-fusion-inventory&tab=detalle' . ( $k ? '&clase=' . $k : '' ) ) ),
			esc_html( $label )
		);
	}
	echo '</p>';

	echo '<table class="widefat striped"><thead><tr>';
	echo '<th style="width:60px;">ID</th><th>Título</th><th style="width:90px;">Tipo</th><th style="width:90px;">Clase</th><th style="width:90px;">Estado</th><th style="width:110px;">Columnas</th><th>Elementos complejos</th><th style="width:120px;">Acciones</th>';
	echo '</tr></thead><tbody>';

	foreach ( $data['items'] as $it ) {

		if ( $filter && $it['class'] !== $filter ) {
			continue;
		}

		if ( abfi_is_converted( $it['id'] ) ) {
			$estado = '<span style="color:#00a32a;font-weight:600;">Convertida</span>';
		} elseif ( in_array( $it['id'], $excluded, true ) ) {
			$estado = '<span style="color:#d63638;">Excluida</span>';
		} else {
			$estado = '<span style="color:#646970;">Pendiente</span>';
		}

		printf(
			'<tr><td>%d</td><td><strong>%s</strong></td><td><code>%s</code></td><td>%s</td><td>%s</td><td>%s</td><td style="font-size:11px;">%s</td><td><a href="%s">Editar</a> | <a href="%s">Preview</a></td></tr>',
			(int) $it['id'],
			esc_html( $it['title'] ),
			esc_html( $it['type'] ),
			abfi_badge( $it['class'] ),
			$estado,
			esc_html( implode( ' ', $it['cols'] ) ),
			esc_html( implode( ', ', $it['complex'] ) ),
			esc_url( admin_url( 'post.php?post=' . $it['id'] . '&action=edit' ) ),
			esc_url( admin_url( 'tools.php?page=ab-fusion-inventory&tab=preview&post_id=' . $it['id'] ) )
		);
	}

	echo '</tbody></table>';
}

function abfi_tab_entorno( $data ) {

	echo '<h2>Post types de origen Avada</h2>';
	echo '<p class="description">Layouts, secciones, sliders y formularios que <strong>no viven en <code>post_content</code></strong> de páginas normales y hay que reconstruir aparte en Astra.</p>';

	if ( empty( $data['cpt_seen'] ) ) {
		echo '<p>No se han detectado.</p>';
	} else {
		echo '<table class="widefat striped" style="max-width:620px;"><thead><tr><th>post_type</th><th>Estado</th><th>Nº</th></tr></thead><tbody>';
		foreach ( $data['cpt_seen'] as $r ) {
			printf( '<tr><td><code>%s</code></td><td>%s</td><td>%d</td></tr>', esc_html( $r['post_type'] ), esc_html( $r['post_status'] ), (int) $r['n'] );
		}
		echo '</tbody></table>';
	}

	echo '<h2>Claves postmeta relacionadas</h2>';
	echo '<table class="widefat striped" style="max-width:620px;"><thead><tr><th>meta_key</th><th>Registros</th></tr></thead><tbody>';
	foreach ( $data['meta_keys'] as $r ) {
		printf( '<tr><td><code>%s</code></td><td>%d</td></tr>', esc_html( $r['meta_key'] ), (int) $r['n'] );
	}
	echo '</tbody></table>';

	global $wpdb;
	$opts = $wpdb->get_results(
		"SELECT option_name, ROUND(LENGTH(option_value)/1024,1) AS kb
		 FROM {$wpdb->options}
		 WHERE option_name LIKE '%fusion%' OR option_name LIKE '%avada%'
		 ORDER BY LENGTH(option_value) DESC LIMIT 40",
		ARRAY_A
	);

	echo '<h2>Opciones en wp_options</h2>';
	echo '<table class="widefat striped" style="max-width:620px;"><thead><tr><th>option_name</th><th>Tamaño (KB)</th></tr></thead><tbody>';
	foreach ( $opts as $r ) {
		printf( '<tr><td><code>%s</code></td><td>%s</td></tr>', esc_html( $r['option_name'] ), esc_html( $r['kb'] ) );
	}
	echo '</tbody></table>';
}

function abfi_tab_preview() {

	$post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;

	echo '<h2>Previsualizar conversión</h2>';
	echo '<form method="get" action="' . esc_url( admin_url( 'tools.php' ) ) . '">';
	echo '<input type="hidden" name="page" value="ab-fusion-inventory">';
	echo '<input type="hidden" name="tab" value="preview">';
	echo '<label>ID de entrada: <input type="number" name="post_id" value="' . esc_attr( $post_id ) . '" class="small-text"></label> ';
	echo '<button class="button button-primary">Previsualizar</button>';
	echo '</form>';

	if ( ! $post_id ) {
		return;
	}

	$post = get_post( $post_id );
	if ( ! $post ) {
		echo '<div class="notice notice-error"><p>No existe la entrada ' . (int) $post_id . '.</p></div>';
		return;
	}

	$converted = abfi_is_converted( $post_id );
	$source    = $converted ? get_post_meta( $post_id, ABFI_META_BACKUP, true ) : $post->post_content;

	$html      = abfi_to_html( $source );
	$blocks    = abfi_to_blocks( $html );
	$residuals = abfi_residuals( $html );

	printf(
		'<h3>%s <span style="font-weight:400;color:#646970;">(%s, ID %d)</span></h3>',
		esc_html( $post->post_title ), esc_html( $post->post_type ), (int) $post_id
	);

	if ( $converted ) {
		echo '<div class="notice notice-info inline"><p>Esta entrada <strong>ya está convertida</strong> (' . esc_html( get_post_meta( $post_id, ABFI_META_DATE, true ) ) . '). Se muestra la comparación contra su copia de seguridad.</p></div>';
	}

	if ( ! empty( $residuals ) ) {
		$res_html = implode( ', ', array_map( function ( $r ) {
			return '<code>' . esc_html( $r ) . '</code>';
		}, $residuals ) );
		echo '<div class="notice notice-warning inline"><p><strong>Shortcodes sin conversión:</strong> ' . $res_html . '. Requiere revisión manual.</p></div>';
	} else {
		echo '<div class="notice notice-success inline"><p>Conversión limpia: no quedan shortcodes Fusion.</p></div>';
	}

	$css = 'width:100%;height:420px;font-family:Menlo,Consolas,monospace;font-size:11px;line-height:1.5;';

	echo '<div style="display:flex;gap:16px;flex-wrap:wrap;">';
	echo '<div style="flex:1;min-width:320px;"><h4>1. Original (Fusion)</h4><textarea readonly style="' . esc_attr( $css ) . '">' . esc_textarea( $source ) . '</textarea></div>';
	echo '<div style="flex:1;min-width:320px;"><h4>2. HTML estándar</h4><textarea readonly style="' . esc_attr( $css ) . '">' . esc_textarea( $html ) . '</textarea></div>';
	echo '<div style="flex:1;min-width:320px;"><h4>3. Bloques Gutenberg</h4><textarea readonly style="' . esc_attr( $css ) . '">' . esc_textarea( $blocks ) . '</textarea></div>';
	echo '</div>';

	echo '<h4 style="margin-top:20px;">Render del HTML convertido</h4>';
	echo '<div style="border:1px solid #c3c4c7;background:#fff;padding:20px;max-width:900px;">' . wp_kses_post( $html ) . '</div>';
}

/* -------------------------------------------------------------------------
 *  PESTAÑA CONVERSION
 * ---------------------------------------------------------------------- */

function abfi_tab_conversion( $data ) {

	$excluded  = abfi_get_excluded_ids();
	$converted = abfi_converted_ids();
	$nonce     = wp_create_nonce( 'abfi_run' );

	echo '<h2>Conversión masiva</h2>';

	echo '<div class="notice notice-warning inline"><p><strong>Antes de escribir:</strong> haz siempre un simulacro. Las páginas COMPLEJAS se descartan automáticamente aunque estén en la cola, y cada entrada guarda copia de su contenido original antes de tocarse. Todo es reversible desde esta misma pantalla.</p></div>';

	// --- Estado ---
	echo '<h3>Estado</h3>';
	printf(
		'<p><strong>%d</strong> convertidas &middot; <strong>%d</strong> excluidas manualmente &middot; <strong>%d</strong> entradas con Fusion en total</p>',
		count( $converted ), count( $excluded ), (int) $data['total']
	);

	// --- Exclusiones ---
	echo '<h3>Lista de exclusión</h3>';
	echo '<p class="description">IDs que nunca se tocarán. Las clasificadas como COMPLEJA ya se descartan solas; esta lista es para las que quieras proteger además.</p>';
	echo '<form method="post">';
	wp_nonce_field( 'abfi_exclusions' );
	echo '<textarea name="abfi_ids" rows="4" style="width:100%;max-width:760px;font-family:monospace;" placeholder="577, 1234, 1288">' . esc_textarea( implode( ', ', $excluded ) ) . '</textarea><br>';
	echo '<button type="submit" name="abfi_save_exclusions" value="1" class="button button-primary">Guardar exclusiones</button> ';
	echo '<button type="submit" name="abfi_autoexclude" value="1" class="button">Añadir todas las COMPLEJAS</button>';
	echo '</form>';

	// --- Ejecucion ---
	echo '<h3 style="margin-top:26px;">Ejecución</h3>';
	echo '<p>';
	echo '<label style="margin-right:20px;"><input type="checkbox" id="abfi-media" checked> Incluir también la clase MEDIA</label>';
	echo '<label>Formato de salida: <select id="abfi-mode"><option value="html">HTML estándar (editor clásico)</option><option value="blocks">Bloques Gutenberg</option></select></label>';
	echo '</p>';

	echo '<p>';
	echo '<button class="button button-secondary" id="abfi-dry">Simulacro (no escribe)</button> ';
	echo '<button class="button button-primary" id="abfi-run">Convertir de verdad</button> ';
	echo '<button class="button" id="abfi-revert" style="color:#d63638;border-color:#d63638;">Revertir todo lo convertido</button>';
	echo '</p>';

	// Confirmacion dentro de la propia pagina: los dialogos nativos del navegador
	// bloquean la pestaña y no siempre se pueden atender.
	echo '<div id="abfi-confirm" style="display:none;border-left:4px solid #d63638;background:#fff;padding:12px 16px;margin:12px 0;max-width:760px;box-shadow:0 1px 1px rgba(0,0,0,.04);">';
	echo '<p id="abfi-confirm-text" style="margin:0 0 10px;"></p>';
	echo '<button class="button button-primary" id="abfi-confirm-yes">Sí, continuar</button> ';
	echo '<button class="button" id="abfi-confirm-no">Cancelar</button>';
	echo '</div>';

	echo '<div id="abfi-progress" style="display:none;margin:16px 0;max-width:760px;">';
	echo '<div style="background:#dcdcde;border-radius:3px;height:22px;overflow:hidden;"><div id="abfi-bar" style="background:#2271b1;height:100%;width:0;transition:width .2s;"></div></div>';
	echo '<p id="abfi-status" style="margin:8px 0;font-weight:600;"></p>';
	echo '</div>';

	echo '<div id="abfi-summary"></div>';
	echo '<div id="abfi-log" style="display:none;max-height:420px;overflow:auto;border:1px solid #c3c4c7;background:#fff;margin-top:12px;"></div>';

	// --- Log persistido de la ultima ejecucion ---
	$saved = abfi_log_get();
	if ( $saved ) {

		$ok = 0;
		$ko = 0;
		foreach ( $saved['rows'] as $r ) {
			empty( $r['ok'] ) ? $ko++ : $ok++;
		}

		printf(
			'<h3 style="margin-top:30px;">Última ejecución — %s <span style="font-weight:400;color:#646970;">(%s)</span></h3>',
			esc_html( $saved['kind'] ),
			esc_html( $saved['when'] )
		);
		printf(
			'<p><strong style="color:#00a32a;">%d</strong> correctas &middot; <strong style="color:#d63638;">%d</strong> omitidas o con aviso</p>',
			$ok,
			$ko
		);

		echo '<div style="max-height:380px;overflow:auto;border:1px solid #c3c4c7;background:#fff;">';
		echo '<table class="widefat striped"><thead><tr><th style="width:70px;">ID</th><th>Título</th><th style="width:90px;">Antes</th><th style="width:90px;">Después</th><th style="width:240px;">Resultado</th></tr></thead><tbody>';
		foreach ( $saved['rows'] as $r ) {
			printf(
				'<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td style="color:%s;">%s</td></tr>',
				isset( $r['id'] ) ? (int) $r['id'] : 0,
				isset( $r['title'] ) ? esc_html( $r['title'] ) : '',
				isset( $r['before'] ) ? esc_html( $r['before'] ) . ' KB' : '',
				isset( $r['after'] ) ? esc_html( $r['after'] ) . ' KB' : '',
				empty( $r['ok'] ) ? '#d63638' : '#00a32a',
				isset( $r['msg'] ) ? esc_html( $r['msg'] ) : ''
			);
		}
		echo '</tbody></table></div>';

		echo '<form method="post" style="margin-top:10px;">';
		wp_nonce_field( 'abfi_exclusions' );
		echo '<button type="submit" name="abfi_clear_log" value="1" class="button">Borrar este log</button>';
		echo '</form>';
	}

	$ajax = admin_url( 'admin-ajax.php' );
	?>
	<script>
	(function () {
		const AJAX  = <?php echo wp_json_encode( $ajax ); ?>;
		const NONCE = <?php echo wp_json_encode( $nonce ); ?>;
		const CHUNK = 10;

		const bar     = document.getElementById('abfi-bar');
		const status  = document.getElementById('abfi-status');
		const wrap    = document.getElementById('abfi-progress');
		const logBox  = document.getElementById('abfi-log');
		const summary = document.getElementById('abfi-summary');
		const buttons = ['abfi-dry', 'abfi-run', 'abfi-revert'].map(id => document.getElementById(id));

		function lock(on) { buttons.forEach(b => b.disabled = on); }

		function post(action, extra) {
			const body = new URLSearchParams();
			body.append('action', action);
			body.append('_ajax_nonce', NONCE);
			for (const k in extra) {
				if (Array.isArray(extra[k])) {
					extra[k].forEach(v => body.append(k + '[]', v));
				} else {
					body.append(k, extra[k]);
				}
			}
			return fetch(AJAX, { method: 'POST', credentials: 'same-origin', body })
				.then(r => r.json());
		}

		function esc(s) {
			return String(s === undefined || s === null ? '' : s)
				.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
		}

		function renderLog(rows) {
			let ok = 0, ko = 0;
			let html = '<table class="widefat striped"><thead><tr><th style="width:70px;">ID</th><th>Título</th><th style="width:90px;">Antes</th><th style="width:90px;">Después</th><th style="width:240px;">Resultado</th></tr></thead><tbody>';
			rows.forEach(r => {
				r.ok ? ok++ : ko++;
				const color = r.ok ? '#00a32a' : '#d63638';
				html += '<tr><td>' + parseInt(r.id, 10) + '</td><td>' + esc(r.title) + '</td><td>' + (r.before !== undefined ? esc(r.before) + ' KB' : '') + '</td><td>' + (r.after !== undefined ? esc(r.after) + ' KB' : '') + '</td><td style="color:' + color + ';">' + esc(r.msg) + '</td></tr>';
			});
			html += '</tbody></table>';
			logBox.innerHTML = html;
			logBox.style.display = 'block';
			summary.innerHTML = '<div class="notice notice-' + (ko ? 'warning' : 'success') + ' inline"><p><strong>' + ok + '</strong> correctas, <strong>' + ko + '</strong> omitidas o con aviso.</p></div>';
		}

		// Confirmacion en pagina. Devuelve una promesa que resuelve a true/false.
		const cBox  = document.getElementById('abfi-confirm');
		const cText = document.getElementById('abfi-confirm-text');
		const cYes  = document.getElementById('abfi-confirm-yes');
		const cNo   = document.getElementById('abfi-confirm-no');

		function ask(message) {
			return new Promise(resolve => {
				cText.innerHTML = message;
				cBox.style.display = 'block';
				cBox.scrollIntoView({ behavior: 'smooth', block: 'nearest' });

				function done(answer) {
					cBox.style.display = 'none';
					cYes.removeEventListener('click', yes);
					cNo.removeEventListener('click', no);
					resolve(answer);
				}
				function yes(e) { e.preventDefault(); done(true); }
				function no(e)  { e.preventDefault(); done(false); }

				cYes.addEventListener('click', yes);
				cNo.addEventListener('click', no);
			});
		}

		async function run(dry) {
			if (!dry) {
				const go = await ask('Se va a <strong>modificar el contenido</strong> de las entradas de la cola.<br>Cada una guardará copia de seguridad y podrás revertirla. ¿Continuar?');
				if (!go) return;
			}

			lock(true);
			summary.innerHTML = '';
			logBox.style.display = 'none';
			wrap.style.display = 'block';
			bar.style.width = '0';
			status.textContent = 'Construyendo la cola…';

			const media = document.getElementById('abfi-media').checked ? '1' : '0';
			const mode  = document.getElementById('abfi-mode').value;

			const q = await post('abfi_queue', { include_media: media });
			if (!q.success) { status.textContent = 'Error: ' + q.data; lock(false); return; }

			const ids = q.data.ids;
			if (!ids.length) { status.textContent = 'No hay entradas pendientes.'; lock(false); return; }

			const all = [];
			for (let i = 0; i < ids.length; i += CHUNK) {
				const slice = ids.slice(i, i + CHUNK);
				const res = await post('abfi_convert', {
					ids: slice, mode: mode, dry: dry ? '1' : '0', reset: i === 0 ? '1' : '0'
				});
				if (res.success) all.push(...res.data.log);
				const done = Math.min(i + CHUNK, ids.length);
				bar.style.width = (done / ids.length * 100) + '%';
				status.textContent = (dry ? 'Simulacro: ' : 'Convirtiendo: ') + done + ' de ' + ids.length;
			}

			status.textContent = (dry ? 'Simulacro terminado. ' : 'Conversión terminada. ') + ids.length + ' entradas procesadas.';
			renderLog(all);
			lock(false);
			if (!dry) {
				summary.insertAdjacentHTML('beforeend',
					'<p><a class="button" href="' + location.href + '">Actualizar contadores</a> ' +
					'<span style="color:#646970;">El log queda guardado; puedes recargar sin perderlo.</span></p>');
			}
		}

		async function revertAll() {
			const go = await ask('Se devolverán <strong>todas</strong> las entradas convertidas a su contenido original. ¿Continuar?');
			if (!go) return;

			lock(true);
			wrap.style.display = 'block';
			bar.style.width = '0';
			status.textContent = 'Revirtiendo…';

			const ids = <?php echo wp_json_encode( array_values( $converted ) ); ?>;
			const all = [];
			for (let i = 0; i < ids.length; i += CHUNK) {
				const res = await post('abfi_revert', { ids: ids.slice(i, i + CHUNK), reset: i === 0 ? '1' : '0' });
				if (res.success) all.push(...res.data.log);
				const done = Math.min(i + CHUNK, ids.length);
				bar.style.width = (done / ids.length * 100) + '%';
				status.textContent = 'Revirtiendo: ' + done + ' de ' + ids.length;
			}

			status.textContent = 'Reversión terminada.';
			renderLog(all);
			lock(false);
			summary.insertAdjacentHTML('beforeend',
				'<p><a class="button" href="' + location.href + '">Actualizar contadores</a></p>');
		}

		document.getElementById('abfi-dry').addEventListener('click', e => { e.preventDefault(); run(true); });
		document.getElementById('abfi-run').addEventListener('click', e => { e.preventDefault(); run(false); });
		document.getElementById('abfi-revert').addEventListener('click', e => { e.preventDefault(); revertAll(); });
	})();
	</script>
	<?php
}

/* -------------------------------------------------------------------------
 *  LIMPIEZA
 * ---------------------------------------------------------------------- */

register_deactivation_hook( __FILE__, function () {
	delete_transient( ABFI_CACHE_KEY );
} );
