/**
 * Store-locator token formats — the DOMAIN knowledge that used to live in the
 * framework renderer. cliq-ui resolves only generic content formats (text/media/
 * gallery/button); everything store-shaped (address, opening hours, distance,
 * rating, category) is supplied here as a `cliqui.token_value` filter callback.
 *
 * Imported for its side effect by BOTH the admin-extension and the frontend
 * entries, so the builder preview and the live site resolve tokens identically.
 * Mirrors includes/Frontend/DesignRenderer.php (the SSR store-detail renderer).
 */
import { __ } from '@wordpress/i18n';
import { addFilter } from '@cliqui/shared/hooks';
import { directionsAction } from './directionsAction.mjs';

// One-line address from the public store shape (location.address_line1/city/state)
// or simpler admin shapes.
const formatAddress = ( store ) => {
	const loc = store.location || ( Array.isArray( store.locations ) ? store.locations[ 0 ] : null ) || {};
	const line = loc.address_line1 || loc.address || store.address || '';
	const tail = [ loc.city || store.city, loc.state, loc.postal_code ].filter( Boolean ).join( ', ' );
	const full = [ line, tail ].filter( Boolean ).join( ', ' );
	return full || store.city || '—';
};

// "Open now" / "Closed". Prefer the server-computed flag (site timezone — matches
// the "open now" search filter); fall back to a client-side hours compute for the
// builder preview / legacy payloads, and show nothing when there's no data.
const openState = ( store ) => {
	if ( store.open_now === true ) return true;
	if ( store.open_now === false ) return false;
	const hours = Array.isArray( store.hours ) ? store.hours : null;
	if ( ! hours || ! hours.length ) return null;
	const now = new Date();
	const today = hours.find( ( h ) => Number( h.day ) === now.getDay() );
	if ( ! today || today.closed ) return false;
	const hm = `${ String( now.getHours() ).padStart( 2, '0' ) }:${ String( now.getMinutes() ).padStart( 2, '0' ) }`;
	const open = ( today.open || '00:00' ).slice( 0, 5 );
	const close = ( today.close || '23:59' ).slice( 0, 5 );
	return hm >= open && hm <= close;
};

// "09:00" → "9:00 AM"; passes anything unparseable through.
const formatTime = ( hm, format = '12' ) => {
	const s = String( hm || '' ).trim();
	const m = s.match( /^(\d{1,2}):(\d{2})/ );
	if ( ! m ) return s;
	if ( format === '24' ) return `${ String( parseInt( m[ 1 ], 10 ) ).padStart( 2, '0' ) }:${ m[ 2 ] }`;
	const h = parseInt( m[ 1 ], 10 );
	const ampm = h >= 12 ? 'PM' : 'AM';
	const h12 = h % 12 === 0 ? 12 : h % 12;
	return `${ h12 }:${ m[ 2 ] } ${ ampm }`;
};

// Render an "Opening Hours" (type 'hours') value as a readable weekly schedule.
// Accepts the structured object (decoded or JSON string) OR a plain string. Returns
// an html-typed value so the renderer emits markup; mirrors DesignRenderer::formatSchedule.
const dayLabels = ( format ) => format === 'long'
	? [ [ 'mon', __( 'Monday', 'cliq-store-locator' ) ], [ 'tue', __( 'Tuesday', 'cliq-store-locator' ) ], [ 'wed', __( 'Wednesday', 'cliq-store-locator' ) ], [ 'thu', __( 'Thursday', 'cliq-store-locator' ) ], [ 'fri', __( 'Friday', 'cliq-store-locator' ) ], [ 'sat', __( 'Saturday', 'cliq-store-locator' ) ], [ 'sun', __( 'Sunday', 'cliq-store-locator' ) ] ]
	: [ [ 'mon', __( 'Mon', 'cliq-store-locator' ) ], [ 'tue', __( 'Tue', 'cliq-store-locator' ) ], [ 'wed', __( 'Wed', 'cliq-store-locator' ) ], [ 'thu', __( 'Thu', 'cliq-store-locator' ) ], [ 'fri', __( 'Fri', 'cliq-store-locator' ) ], [ 'sat', __( 'Sat', 'cliq-store-locator' ) ], [ 'sun', __( 'Sun', 'cliq-store-locator' ) ] ];
const formatSchedule = ( raw, item = {} ) => {
	let data = raw;
	if ( typeof data === 'string' ) {
		const s = data.trim();
		if ( ! s ) return '';
		if ( s[ 0 ] === '{' || s[ 0 ] === '[' ) {
			try { data = JSON.parse( s ); } catch ( e ) { return s; }
		} else {
			return s; // plain string schedule
		}
	}
	if ( ! data || typeof data !== 'object' ) return raw == null ? '' : String( raw );
	if ( Array.isArray( data ) ) {
		const keys = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
		const normalised = {};
		data.forEach( ( row ) => {
			const key = keys[ Number( row?.day ) ];
			if ( ! key ) return;
			if ( ! normalised[ key ] ) normalised[ key ] = { closed: true, open24: false, slots: [] };
			if ( row.closed ) return;
			normalised[ key ].closed = false;
			normalised[ key ].slots.push( { from: row.open || '', to: row.close || '' } );
		} );
		data = normalised;
	}

	const closedText = item?.scheduleClosedText || __( 'Closed', 'cliq-store-locator' );
	const open24Text = item?.scheduleOpen24Text || __( 'Open 24 hours', 'cliq-store-locator' );
	const dayVals = dayLabels( item?.scheduleDayFormat ).map( ( [ key, label ] ) => {
		const d = data[ key ];
		let closed = ! d || !! d.closed;
		let val;
		if ( closed ) {
			val = closedText;
		} else if ( d.open24 ) {
			val = open24Text;
		} else {
			const parts = ( Array.isArray( d.slots ) ? d.slots : [] )
				.map( ( sl ) => {
					const from = formatTime( sl.from, item?.scheduleTimeFormat );
					const to = formatTime( sl.to, item?.scheduleTimeFormat );
					return from && to ? `${ from } – ${ to }` : '';
				} )
				.filter( Boolean );
			val = parts.length ? parts.join( ', ' ) : closedText;
			closed = ! parts.length;
		}
		return { label, val, closed };
	} ).filter( ( day ) => ! item?.scheduleHideClosed || ! day.closed );

	// Collapse consecutive days that share the same hours into a single range row:
	// 1 day → "Mon", 2 → "Sat & Sun", 3+ → "Mon–Fri".
	const groups = [];
	dayVals.forEach( ( d ) => {
		const last = groups[ groups.length - 1 ];
		if ( item?.scheduleLayout !== 'daily' && last && last.val === d.val ) last.days.push( d.label );
		else groups.push( { val: d.val, days: [ d.label ] } );
	} );
	const rangeLabel = ( days ) =>
		days.length === 1
			? days[ 0 ]
			: days.length === 2
				? `${ days[ 0 ] } & ${ days[ 1 ] }`
				: `${ days[ 0 ] }–${ days[ days.length - 1 ] }`;
	const rows = groups
		.map( ( g ) => `<div class="cliq-hours__row"><span class="cliq-hours__day">${ rangeLabel( g.days ) }</span><span class="cliq-hours__val">${ g.val }</span></div>` )
		.join( '' );

	return { type: 'html', html: `<div class="cliq-hours">${ rows }</div>` };
};

addFilter( 'cliqui.token_value', 'cliq-store-locator/formats', ( value, ctx ) => {
	// The framework passes the record being rendered as `record` (generic); for the
	// store locator that record IS a store.
	const { format, record: store, raw, token, item } = ctx;
	switch ( format ) {
		case 'button': {
			// Give the resolved button (value = { type:'button', label }) a real
			// target: Details → the store detail page, Directions → maps directions.
			if ( token === 'details' ) {
				const href = store.detail_url || store.url || store.permalink || '';
				return href ? { ...value, href } : value;
			}
			if ( token === 'directions' ) {
				return directionsAction( value, store, item );
			}
			return value;
		}
		case 'address':
			return formatAddress( store );
		case 'schedule':
			return formatSchedule( raw, item );
		case 'open_status':
			return { type: 'open_status', open: openState( store ) };
		case 'distance':
			return store.distance !== undefined && store.distance !== null
				? `${ Math.round( store.distance * 10 ) / 10 } ${ store.distanceUnit || 'km' }`
				: ( raw ?? '' );
		case 'rating': {
			const r = store.rating ?? raw;
			if ( r === undefined || r === null || r === '' ) return '';
			const count = store.rating_count;
			return `★ ${ r }${ count ? ` (${ count })` : '' }`;
		}
		case 'category': {
			const cats = store.categories;
			if ( Array.isArray( cats ) && cats.length ) {
				return ( typeof cats[ 0 ] === 'object' ? cats[ 0 ].name : cats[ 0 ] ) || '—';
			}
			return raw ?? '—';
		}
		default:
			return value;
	}
} );
