<?php
/**
 * Brentwood Timetable — front-end render.
 *
 * Draws the weekly grid from the developer-maintained schedule in grid.php:
 * one column per day, each block absolutely positioned on a shared time axis
 * (so the same start times line up across days), coloured by its category.
 *
 * Clicking a category block filters the grid to that category and reveals its
 * content panel below (photo + text; the Academic Block Rotation table for
 * Academics) — the filtering/animation is driven by view.js, which reads the
 * data-cat / data-top / data-h attributes emitted here. All schedule + panel
 * content is static (developer-maintained in grid.php).
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Unused (dynamic block).
 * @var WP_Block $block      Block instance.
 */

defined( 'ABSPATH' ) || exit;

require_once __DIR__ . '/grid.php';

$grid = bw_timetable_grid();
if ( empty( $grid['days'] ) ) {
	return;
}

$scale         = (float) ( $attributes['scale'] ?? 1.1 );
$scale         = max( 0.6, min( 2.0, $scale ) );
$show_rotation = ! isset( $attributes['showRotation'] ) || ! empty( $attributes['showRotation'] );

$cats   = $grid['categories'];
$panels = function_exists( 'bw_timetable_panels' ) ? bw_timetable_panels() : array();

// Initial view: which category the timetable opens filtered to ('' = full week).
// Only a category that has a panel is valid. When set, the grid is pre-filtered
// server-side below (so the editor preview and the first paint show it directly).
$initial = isset( $attributes['initialCategory'] ) ? sanitize_title( $attributes['initialCategory'] ) : '';
if ( '' !== $initial && ! isset( $panels[ $initial ] ) ) {
	$initial = '';
}

// Per-category background photos — shown faintly behind a category's blocks once
// it's filtered (like the live site). Resolved once and cycled across the blocks
// of that category so adjacent blocks show different photos.
$cat_imgs = array();
foreach ( $panels as $pslug => $pp ) {
	if ( empty( $pp['images'] ) ) {
		continue;
	}
	$urls = array();
	foreach ( (array) $pp['images'] as $iid ) {
		$u = wp_get_attachment_image_url( (int) $iid, 'large' );
		if ( $u ) {
			$urls[] = $u;
		}
	}
	if ( $urls ) {
		$cat_imgs[ $pslug ] = $urls;
	}
}
$cat_bg_cursor = array();

// ── shared time axis: earliest start → latest end across all blocks ───────────
$t0 = PHP_INT_MAX;
$t1 = 0;
foreach ( $grid['days'] as $day ) {
	foreach ( $day['blocks'] as $blk ) {
		$s  = bw_timetable_minutes( $blk['start'] );
		$e  = bw_timetable_minutes( $blk['end'] );
		$t0 = min( $t0, $s );
		$t1 = max( $t1, $e );
	}
}
if ( $t0 >= $t1 ) {
	return;
}
// Full time-axis height — every day's track gets this min-height so all columns
// end at the same bottom (empty time shows as the grey track), like the live site.
$axis_h = (int) round( ( $t1 - $t0 ) * $scale );

// ── filtered-view precompute (mirrors view.js filter()) ──────────────────────
// When an initial category is set, work out the filtered geometry once: blocks
// grow ×1.25, a lone short/Activities block floors at 300px, and tops repack to
// the earliest matching start. $flt drives the per-block overrides in the loop.
$flt = null;
if ( '' !== $initial ) {
	$exp      = 1.25;
	$short_px = 105;
	$earliest = PHP_INT_MAX;
	$counts   = array();
	foreach ( $grid['days'] as $di => $day ) {
		$prev_bottom = 0;
		$c           = 0;
		foreach ( $day['blocks'] as $blk ) {
			$s   = bw_timetable_minutes( $blk['start'] );
			$e   = bw_timetable_minutes( $blk['end'] );
			$h   = max( 16, (int) round( max( 1, $e - $s ) * $scale ) );
			$top = max( (int) round( ( $s - $t0 ) * $scale ), $prev_bottom );
			if ( sanitize_title( $blk['cat'] ?? '' ) === $initial ) {
				$earliest = min( $earliest, $top );
				$c++;
			}
			$prev_bottom = $top + $h;
		}
		$counts[ $di ] = $c;
	}
	if ( PHP_INT_MAX === $earliest ) {
		$earliest = 0;
	}
	$maxb = 0;
	foreach ( $grid['days'] as $di => $day ) {
		$prev_bottom = 0;
		$single      = ( 1 === $counts[ $di ] );
		foreach ( $day['blocks'] as $blk ) {
			$s   = bw_timetable_minutes( $blk['start'] );
			$e   = bw_timetable_minutes( $blk['end'] );
			$h   = max( 16, (int) round( max( 1, $e - $s ) * $scale ) );
			$top = max( (int) round( ( $s - $t0 ) * $scale ), $prev_bottom );
			if ( sanitize_title( $blk['cat'] ?? '' ) === $initial ) {
				$top2 = (int) round( ( $top - $earliest ) * $exp );
				$effh = (int) round( $h * $exp );
				if ( $single && ( $h <= $short_px || 'activities' === $initial ) ) {
					$effh = max( $effh, 300 );
				}
				$maxb = max( $maxb, $top2 + $effh );
			}
			$prev_bottom = $top + $h;
		}
	}
	$flt = array(
		'exp'      => $exp,
		'short'    => $short_px,
		'earliest' => $earliest,
		'counts'   => $counts,
		'axis'     => max( 1, $maxb ),
		'inline'   => ! empty( $panels[ $initial ]['inline'] ),
	);
}

// ── helper: format "H:i" → "8:15" (drop leading zero) ────────────────────────
$fmt = function ( $hi ) {
	$m = bw_timetable_minutes( $hi );
	return intdiv( $m, 60 ) . ':' . str_pad( (string) ( $m % 60 ), 2, '0', STR_PAD_LEFT );
};

// Chevron shown on the right of each block (clickable affordance; wired in Phase 2).
$chev = '<svg class="bw-tt__chev" viewBox="0 0 24 24" aria-hidden="true" focusable="false">'
	. '<path fill="currentColor" d="M9 6 7.6 7.4 12.2 12l-4.6 4.6L9 18l6-6z"/></svg>';

// ── days ──────────────────────────────────────────────────────────────────────
// Each block is positioned at its TRUE time (top = minutes since the global
// start × scale), so same-time blocks line up across every column (Dinner,
// Academic Prep, …). If a block would overlap the previous one (the source data
// has intentional overlaps, e.g. Arts Block 4 vs Dinner), it's pushed down just
// past it — and since that overlap pattern is identical on every day, those
// blocks stay aligned too.
$days_html = '';
foreach ( $grid['days'] as $di => $day ) {
	$blocks_html = '';
	$prev_bottom = 0;
	foreach ( $day['blocks'] as $blk ) {
		$s   = bw_timetable_minutes( $blk['start'] );
		$e   = bw_timetable_minutes( $blk['end'] );
		$dur = max( 1, $e - $s );

		$true_top = (int) round( ( $s - $t0 ) * $scale );
		$h        = max( 16, (int) round( $dur * $scale ) );
		$top      = max( $true_top, $prev_bottom );
		$vis_h    = $h; // contiguous blocks touch (one continuous bar, like the live site)

		$slug  = sanitize_title( $blk['cat'] ?? '' );
		$color = isset( $cats[ $slug ]['color'] ) ? $cats[ $slug ]['color'] : '#9aa0a6';
		$short = $h < 42; // thin blocks (Break, Advisor…) render on one line

		$style = 'top:' . $top . 'px;height:' . $vis_h . 'px;background-color:' . esc_attr( $color ) . ';';
		$time  = $fmt( $blk['start'] ) . '–' . $fmt( $blk['end'] );

		// Every block carries its category + its default geometry (data-top/data-h)
		// so the view script can filter the grid: on click it hides non-matching
		// blocks, collapses empty day columns, and repacks the matching blocks to
		// the top. A block is *clickable* only if its category has a content panel
		// (that's what gets the chevron + pointer — the live "cp" blocks).
		$has_panel = isset( $panels[ $slug ] );
		$cls       = 'bw-tt__block bw-tt__block--' . esc_attr( $slug )
			. ( $short ? ' bw-tt__block--short' : '' )
			. ( $has_panel ? ' bw-tt__block--clickable' : '' );

		// Initial filtered view, pre-rendered server-side (mirror of view.js).
		// data-top/data-h stay at the default geometry so view.js can restore.
		if ( $flt ) {
			if ( $slug === $initial ) {
				$single = ( 1 === $flt['counts'][ $di ] );
				$top2   = (int) round( ( $top - $flt['earliest'] ) * $flt['exp'] );
				$effh   = (int) round( $vis_h * $flt['exp'] );
				if ( $single && ( $vis_h <= $flt['short'] || 'activities' === $initial ) ) {
					$effh = max( $effh, 300 );
				}
				$style = 'top:' . $top2 . 'px;height:' . $effh . 'px;background-color:' . esc_attr( $color ) . ';';
				$cls  .= ' is-active';
			} else {
				$style .= 'display:none;';
			}
		}

		// Faint background photo (cycled per category); hidden until filtered.
		$bg = '';
		if ( isset( $cat_imgs[ $slug ] ) ) {
			$idx = isset( $cat_bg_cursor[ $slug ] ) ? $cat_bg_cursor[ $slug ] : 0;
			$url = $cat_imgs[ $slug ][ $idx % count( $cat_imgs[ $slug ] ) ];
			$cat_bg_cursor[ $slug ] = $idx + 1;
			$bg = '<span class="bw-tt__block-bg" aria-hidden="true" style="background-image:url(' . esc_url( $url ) . ')"></span>';
		}

		$blocks_html .= '<div class="' . $cls . '" data-cat="' . esc_attr( $slug ) . '"'
			. ' data-top="' . (int) $top . '" data-h="' . (int) $vis_h . '" style="' . $style . '">'
			. $bg
			. '<span class="bw-tt__block-name">' . esc_html( $blk['name'] ) . '</span>'
			. '<span class="bw-tt__block-time">' . esc_html( $time ) . '</span>'
			. ( $has_panel ? $chev : '' )
			. '</div>';

		$prev_bottom = $top + $h;
	}

	// Day column classes + track height for the pre-rendered filtered view.
	$day_cls = 'bw-tt__day';
	$track_h = $axis_h;
	if ( $flt ) {
		$track_h = $flt['axis'];
		if ( 0 === $flt['counts'][ $di ] ) {
			$day_cls .= ' bw-tt__day--empty';
		} elseif ( 1 === $flt['counts'][ $di ] ) {
			$day_cls .= ' bw-tt__day--single';
		}
	}

	$days_html .= '<div class="' . $day_cls . '" data-day="' . esc_attr( sanitize_title( $day['name'] ) ) . '">'
		. '<div class="bw-tt__day-name">' . esc_html( $day['name'] ) . '</div>'
		. '<div class="bw-tt__track" data-axis="' . (int) $axis_h . '" style="height:' . (int) $track_h . 'px">' . $blocks_html . '</div>'
		. '</div>';
}

// ── academic block rotation ──────────────────────────────────────────────────
$rotation_html = '';
if ( $show_rotation && ! empty( $grid['rotation']['periods'] ) ) {
	$rot     = $grid['rotation'];
	$cols    = count( $rot['days'] ) + 1;
	$head    = '<div class="bw-tt-rot__cell bw-tt-rot__corner"></div>';
	foreach ( $rot['days'] as $d ) {
		$head .= '<div class="bw-tt-rot__cell bw-tt-rot__head">' . esc_html( $d ) . '</div>';
	}
	$rows = '';
	foreach ( $rot['periods'] as $period => $letters ) {
		$rows .= '<div class="bw-tt-rot__cell bw-tt-rot__head bw-tt-rot__rowhead">' . esc_html( $period ) . '</div>';
		foreach ( $letters as $L ) {
			$c = isset( $rot['colors'][ $L ] ) ? $rot['colors'][ $L ] : '#dddddd';
			$rows .= '<div class="bw-tt-rot__cell bw-tt-rot__block" style="background-color:' . esc_attr( $c ) . '">' . esc_html( $L ) . '</div>';
		}
	}
	$rot_title = ! empty( $rot['title'] ) ? $rot['title'] : 'Academic Block Rotation';
	$rotation_html = '<div class="bw-tt-rot">'
		. '<h3 class="bw-tt-rot__title">' . esc_html( $rot_title ) . '</h3>'
		. '<div class="bw-tt-rot__grid" style="--bw-tt-rot-cols:' . (int) $cols . '">' . $head . $rows . '</div>'
		. '</div>';
}

// ── category content panels ──────────────────────────────────────────────────
// Static, developer-maintained (bw_timetable_panels()). Hidden by default; the
// view script reveals a panel when its category block is clicked (and filters
// the grid to that category), mirroring the live click-to-expand behaviour.
//
// Two layouts, exactly like the live site, driven by each category's `inline`
// flag:
//   • inline = 0 (Academics, Prep, Food): a "full" panel BELOW the grid — photo
//     column + text card (+ the rotation table for Academics).
//   • inline = 1 (Athletics, Arts, …): an "inline" panel BESIDE the grid —
//     text only (no photo); the filtered grid narrows to the right.
// `style` (blue/white/…) tints the text card.
$panels_html = '';
foreach ( $panels as $slug => $p ) {
	if ( ! isset( $cats[ $slug ] ) ) {
		continue; // panel for an unknown category — skip
	}
	$color      = $cats[ $slug ]['color'];
	$style_name = ! empty( $p['style'] ) ? sanitize_html_class( $p['style'] ) : 'default';
	$is_inline  = ! empty( $p['inline'] );

	$cta = '';
	if ( ! empty( $p['link'] ) && ! empty( $p['link_text'] ) ) {
		$cta = '<a class="bw-tt__panel-cta" href="' . esc_url( $p['link'] ) . '">'
			. esc_html( $p['link_text'] ) . ' &rsaquo;</a>';
	}

	// Shared text card (title + body + links).
	$text = '<div class="bw-tt__panel-text">'
		. '<h3 class="bw-tt__panel-title">' . esc_html( $p['header'] ) . '</h3>'
		. '<div class="bw-tt__panel-body">' . wp_kses_post( $p['body'] ) . '</div>'
		. '<div class="bw-tt__panel-links">'
		. '<button type="button" class="bw-tt__panel-back">&lsaquo;&nbsp;View timetable</button>'
		. $cta
		. '</div>'
		. '</div>';

	$layout = $is_inline ? 'inline' : 'full';
	// The initial category's panel is shown server-side (no `hidden`); the rest start hidden.
	$hidden = ( $slug === $initial ) ? '' : ' hidden';
	$open   = '<section class="bw-tt__panel bw-tt__panel--' . $layout . ' bw-tt__panel--' . $style_name . '"'
		. ' id="bw-tt-panel-' . esc_attr( $slug ) . '" data-cat="' . esc_attr( $slug ) . '"'
		. ' data-inline="' . ( $is_inline ? '1' : '0' ) . '"' . $hidden . ' style="--bw-tt-accent:' . esc_attr( $color ) . '">';

	if ( $is_inline ) {
		// Text-only, sits beside the narrowed grid.
		$panels_html .= $open . $text . '</section>';
	} else {
		// Photo column + text card below the grid; Academics also gets rotation.
		$media = '';
		if ( ! empty( $p['image'] ) ) {
			$media = wp_get_attachment_image(
				(int) $p['image'],
				'large',
				false,
				array(
					'class'   => 'bw-tt__panel-img',
					'loading' => 'lazy',
					'alt'     => $p['header'],
				)
			);
		}
		$extra = ( 'academics' === $slug ) ? $rotation_html : '';

		$panels_html .= $open
			. '<div class="bw-tt__panel-row">'
			. '<div class="bw-tt__panel-media" aria-hidden="true">' . $media . '</div>'
			. $text
			. '</div>'
			. $extra
			. '</section>';
	}
}
if ( '' !== $panels_html ) {
	$panels_html = '<div class="bw-tt__panels">' . $panels_html . '</div>';
}

// Wrapper. When pre-filtered, mark it open (data-open + data-mode) so the CSS
// renders the filtered layout immediately — in the editor SSR preview AND on the
// first front-end paint. data-initial lets view.js know the intended view (it
// skips re-applying when data-open already matches).
$wrapper = get_block_wrapper_attributes( array( 'class' => 'bw-tt' ) );
if ( $flt ) {
	$wrapper .= ' data-initial="' . esc_attr( $initial ) . '"'
		. ' data-open="' . esc_attr( $initial ) . '"'
		. ' data-mode="' . ( $flt['inline'] ? 'inline' : 'full' ) . '"';
}

printf(
	'<div %s><div class="bw-tt__scroll"><div class="bw-tt__grid">%s</div></div>%s</div>',
	$wrapper,    // phpcs:ignore WordPress.Security.EscapeOutput -- wrapper attrs + escaped values
	$days_html,  // phpcs:ignore WordPress.Security.EscapeOutput -- assembled from escaped parts
	$panels_html // phpcs:ignore WordPress.Security.EscapeOutput -- assembled from escaped parts
);
