import {
	field,
	numberValue,
	safeArray,
	safeRecord,
	statusLabel,
	type AdminRecord,
} from '../admin-data';
import { decodeEntities } from '@wordpress/html-entities';
import type { DesignSystemViewModel } from './types';
import { formatFriendlyDateTime, formatFriendlyFieldLabel } from './format';
import { launchStatusView, type BadgeTone } from './status';

const num = ( record: AdminRecord, key: string, fallback = 0 ): number =>
	key in record ? numberValue( record[ key ] ) : fallback;

const displayText = ( value: string ): string => {
	let decoded = value;

	for ( let pass = 0; pass < 3; pass += 1 ) {
		const next = decodeEntities( decoded );
		if ( next === decoded ) {
			break;
		}
		decoded = next;
	}

	return decoded;
};

// ---------------------------------------------------------------------------
// Dashboard metrics
// ---------------------------------------------------------------------------

export interface MetricCardData {
	key: string;
	label: string;
	value: number;
	colorVar: string;
}

// Top-row metric cards from the real dashboard summary, falling back to the
// reference example values when the summary is empty so the example renders.
export const selectDashboardMetrics = (
	model: DesignSystemViewModel
): MetricCardData[] => {
	const summary = model.dashboard;
	const has = Object.keys( summary ).length > 0;

	return [
		{
			key: 'scheduled',
			label: 'Scheduled',
			value: has ? num( summary, 'scheduledCount' ) : 12,
			colorVar: 'var(--status-scheduled)',
		},
		{
			key: 'successful',
			label: 'Successful',
			value: has ? num( summary, 'successfulCount' ) : 34,
			colorVar: 'var(--status-success)',
		},
		{
			key: 'failed',
			label: 'Failed',
			value: has ? num( summary, 'failedCount' ) : 1,
			colorVar: 'var(--status-failed)',
		},
		{
			key: 'targets',
			label: 'Items',
			value: has ? num( summary, 'affectedTargets' ) : 9,
			colorVar: 'var(--status-running)',
		},
	];
};

// ---------------------------------------------------------------------------
// Launch rows + next change
// ---------------------------------------------------------------------------

export interface LaunchActionAvailability {
	canCancel: boolean;
	canReschedule: boolean;
	canRunNow: boolean;
	canRollback: boolean;
}

export interface LaunchRow {
	uuid: string;
	name: string;
	reference: string;
	statusValue: string;
	statusTone: BadgeTone;
	statusLabel: string;
	goLive: string;
	completedLabel: string;
	targetCount: number;
	timezone: string;
	availability: LaunchActionAvailability;
}

const launchAvailability = (
	model: DesignSystemViewModel,
	launch: AdminRecord
): LaunchActionAvailability => {
	const status = field( launch, 'status' );
	const actions = safeArray< string >( launch.availableActions );
	const perms = model.permissions;
	const isFinished = [ 'successful', 'partially_failed', 'failed' ].includes(
		status
	);

	return {
		canCancel:
			true === launch.canCancel &&
			true === perms.canSchedule &&
			'scheduled' === status,
		canReschedule:
			true === launch.canReschedule && true === perms.canSchedule,
		canRunNow:
			true === perms.canExecute &&
			( actions.includes( 'run-now' ) ||
				[ 'scheduled', 'failed', 'ready' ].includes( status ) ),
		canRollback:
			true === perms.canRollback &&
			( 'successful' === status ||
				'partially_failed' === status ||
				( isFinished && true === launch.rollbackAvailable ) ),
	};
};

export const toLaunchRow = (
	model: DesignSystemViewModel,
	launch: AdminRecord
): LaunchRow => {
	const status = field( launch, 'health', field( launch, 'status' ) );
	const view = launchStatusView( status );
	const ref =
		field( launch, 'primaryItemName' ) ||
		`${ field( launch, 'targetCount', '0' ) } items`;

	return {
		uuid: field(
			launch,
			'uuid',
			field(
				launch,
				'detailUuid',
				field( launch, 'launchUuid', field( launch, 'id' ) )
			)
		),
		name: displayText( field( launch, 'name', 'Scheduled change' ) ),
		reference: displayText( ref ),
		statusValue: status,
		statusTone: view.badgeTone,
		statusLabel: view.label,
		goLive: formatFriendlyDateTime( field( launch, 'startAt' ) ),
		completedLabel: formatFriendlyDateTime(
			field( launch, 'completedAt' ),
			''
		),
		targetCount: num( launch, 'targetCount' ),
		timezone: field( launch, 'timezone' ),
		availability: launchAvailability( model, launch ),
	};
};

export const selectLaunchRows = ( model: DesignSystemViewModel ): LaunchRow[] =>
	model.launches.map( ( launch ) => toLaunchRow( model, launch ) );

export const selectNextChange = (
	model: DesignSystemViewModel
): AdminRecord | null => {
	const fromSummary = safeRecord( model.dashboard.nextChange );
	if ( Object.keys( fromSummary ).length ) {
		return fromSummary;
	}

	const scheduled = model.launches.find(
		( launch ) => 'scheduled' === field( launch, 'status' )
	);

	return scheduled ?? model.launches[ 0 ] ?? null;
};

// ---------------------------------------------------------------------------
// Status breakdown (charts, segmented bar, ranked list)
// ---------------------------------------------------------------------------

export interface StatusSlice {
	key: string;
	label: string;
	tone: BadgeTone;
	colorVar: string;
	count: number;
}

const TONE_COLOR: Record< BadgeTone, string > = {
	scheduled: 'var(--status-scheduled)',
	running: 'var(--status-running)',
	success: 'var(--status-success)',
	failed: 'var(--status-failed)',
	canceled: 'var(--status-canceled)',
};

export const selectStatusBreakdown = (
	model: DesignSystemViewModel
): StatusSlice[] => {
	const buckets: Record< string, StatusSlice > = {
		scheduled: {
			key: 'scheduled',
			label: 'Scheduled',
			tone: 'scheduled',
			colorVar: TONE_COLOR.scheduled,
			count: 0,
		},
		running: {
			key: 'running',
			label: 'Running',
			tone: 'running',
			colorVar: TONE_COLOR.running,
			count: 0,
		},
		success: {
			key: 'success',
			label: 'Completed',
			tone: 'success',
			colorVar: TONE_COLOR.success,
			count: 0,
		},
		failed: {
			key: 'failed',
			label: 'Failed',
			tone: 'failed',
			colorVar: TONE_COLOR.failed,
			count: 0,
		},
		canceled: {
			key: 'canceled',
			label: 'Cancelled',
			tone: 'canceled',
			colorVar: TONE_COLOR.canceled,
			count: 0,
		},
	};

	for ( const launch of model.launches ) {
		const status = field( launch, 'status' );
		if ( 'scheduled' === status || 'ready' === status ) {
			buckets.scheduled.count += 1;
		} else if ( 'running' === status || 'restoring' === status ) {
			buckets.running.count += 1;
		} else if (
			[ 'successful', 'rolled_back', 'restored' ].includes( status )
		) {
			buckets.success.count += 1;
		} else if (
			[ 'failed', 'partially_failed', 'rollback_failed' ].includes(
				status
			)
		) {
			buckets.failed.count += 1;
		} else if (
			[ 'cancelled', 'canceled', 'superseded' ].includes( status )
		) {
			buckets.canceled.count += 1;
		} else {
			buckets.scheduled.count += 1;
		}
	}

	return Object.values( buckets );
};

// ---------------------------------------------------------------------------
// Field usage (most-changed fields)
// ---------------------------------------------------------------------------

export interface FieldUsage {
	label: string;
	count: number;
}

export const selectFieldUsage = (
	model: DesignSystemViewModel
): FieldUsage[] => {
	const counts = new Map< string, number >();

	for ( const recording of model.recordings ) {
		for ( const change of safeArray< AdminRecord >( recording.changes ) ) {
			const label = field( change, 'field' );
			if ( label ) {
				counts.set( label, ( counts.get( label ) || 0 ) + 1 );
			}
		}
	}

	const rows = Array.from( counts.entries() )
		.map( ( [ label, count ] ) => ( { label, count } ) )
		.sort( ( a, b ) => b.count - a.count )
		.slice( 0, 5 );

	if ( rows.length ) {
		return rows;
	}

	return [
		{ label: 'Title', count: 28 },
		{ label: 'Status', count: 19 },
		{ label: 'Featured image', count: 14 },
		{ label: 'Categories', count: 11 },
		{ label: 'Price', count: 7 },
	];
};

// ---------------------------------------------------------------------------
// Scheduler health
// ---------------------------------------------------------------------------

export interface HealthRow {
	led: 'led-ok' | 'led-warn' | 'led-bad';
	label: string;
	value: string;
}

export const selectSchedulerHealth = (
	model: DesignSystemViewModel
): HealthRow[] => {
	const diag = model.schedulerDiagnostics;

	if ( ! Object.keys( diag ).length ) {
		return [
			{ led: 'led-ok', label: 'Scheduler', value: 'Healthy' },
			{ led: 'led-ok', label: 'WordPress content', value: 'Ready' },
			{ led: 'led-warn', label: 'Overdue', value: '2' },
			{ led: 'led-bad', label: 'Failed', value: '1' },
		];
	}

	const counts = safeRecord( diag.counts );
	const timing = safeRecord( diag.timing );
	const overdue = num( counts, 'overdue' );
	const failed = num( counts, 'failed' );
	const status = field( diag, 'status' );
	const healthy = ! status || status.toLowerCase().includes( 'healthy' );

	return [
		{
			led: healthy ? 'led-ok' : 'led-warn',
			label: 'Scheduler',
			value: healthy ? 'Healthy' : 'Needs review',
		},
		{
			led: true === timing.wpCronDisabled ? 'led-warn' : 'led-ok',
			label: 'WP-Cron',
			value: true === timing.wpCronDisabled ? 'Disabled' : 'Enabled',
		},
		{
			led: overdue > 0 ? 'led-warn' : 'led-ok',
			label: 'Overdue',
			value: String( overdue ),
		},
		{
			led: failed > 0 ? 'led-bad' : 'led-ok',
			label: 'Failed',
			value: String( failed ),
		},
	];
};

export interface SchedulerMeter {
	label: string;
	count: number;
	max: number;
	colorVar: string;
}

export const selectSchedulerMeters = (
	model: DesignSystemViewModel
): SchedulerMeter[] => {
	const counts = safeRecord( model.schedulerDiagnostics.counts );
	const has = Object.keys( counts ).length > 0;
	const pending = has ? num( counts, 'pending' ) : 12;
	const overdue = has ? num( counts, 'overdue' ) : 2;
	const failed = has ? num( counts, 'failed' ) : 1;
	const stale = has ? num( counts, 'staleLocks' ) : 0;
	const max = Math.max( pending, overdue, failed, stale, 1 );

	return [
		{
			label: 'Pending',
			count: pending,
			max,
			colorVar: 'var(--status-scheduled)',
		},
		{
			label: 'Overdue',
			count: overdue,
			max,
			colorVar: 'var(--status-running)',
		},
		{
			label: 'Failed',
			count: failed,
			max,
			colorVar: 'var(--status-failed)',
		},
		{
			label: 'Stale locks',
			count: stale,
			max,
			colorVar: 'var(--status-canceled)',
		},
	];
};

// ---------------------------------------------------------------------------
// System status rows
// ---------------------------------------------------------------------------

export interface SystemStatusRow {
	led: 'led-ok' | 'led-warn' | 'led-bad';
	label: string;
	value: string;
}

const ledForValue = ( value: string ): 'led-ok' | 'led-warn' | 'led-bad' => {
	const v = value.toLowerCase();
	if ( /healthy|ready|loaded|enabled|ok|active|yes|available/.test( v ) ) {
		return 'led-ok';
	}
	if ( /fail|error|missing|unavailable|critical/.test( v ) ) {
		return 'led-bad';
	}
	if ( /disabled|warn|overdue|delayed|review|limited/.test( v ) ) {
		return 'led-warn';
	}
	return 'led-ok';
};

export const selectSystemStatusRows = (
	model: DesignSystemViewModel
): SystemStatusRow[] => {
	if ( ! model.system.length ) {
		return [
			{ led: 'led-ok', label: 'Database schema', value: 'Healthy' },
			{ led: 'led-ok', label: 'Queue health', value: 'Healthy' },
			{
				led: 'led-ok',
				label: 'wordpress-core provider',
				value: 'Loaded',
			},
			{ led: 'led-warn', label: 'Overdue actions', value: '2' },
			{ led: 'led-warn', label: 'WP-Cron disabled', value: 'Yes' },
			{ led: 'led-bad', label: 'Failed actions', value: '1' },
		];
	}

	return model.system.slice( 0, 8 ).map( ( row ) => {
		const value = field( row, 'value' );
		return {
			led: ledForValue( value ),
			label: field( row, 'label', 'Check' ),
			value: value || '—',
		};
	} );
};

// ---------------------------------------------------------------------------
// Notifications
// ---------------------------------------------------------------------------

export interface NotificationRow {
	id: string;
	title: string;
	summary: string;
	statusTone: BadgeTone;
	statusLabel: string;
	channel: string;
	time: string;
}

export const selectNotificationRows = (
	model: DesignSystemViewModel
): NotificationRow[] =>
	model.notifications.map( ( note ) => {
		const view = launchStatusView( field( note, 'status' ) );
		const channel = field( note, 'channel', 'email' );
		const created = formatFriendlyDateTime( field( note, 'createdAt' ) );

		return {
			id: field( note, 'id' ),
			title: field( note, 'title', 'Notification' ),
			summary: field( note, 'summary' ),
			statusTone: view.badgeTone,
			statusLabel: statusLabel( field( note, 'status' ) ),
			channel: `${ channel.charAt( 0 ).toUpperCase() }${ channel.slice(
				1
			) } · ${ created }`,
			time: created,
		};
	} );

// ---------------------------------------------------------------------------
// Activity feed + history (runs)
// ---------------------------------------------------------------------------

export interface ActivityRow {
	id: string;
	name: string;
	rest: string;
	tone: BadgeTone;
	colorVar: string;
	when: string;
}

export const selectActivityRows = (
	model: DesignSystemViewModel
): ActivityRow[] =>
	model.runs.map( ( run ) => {
		const view = launchStatusView( field( run, 'status' ) );
		return {
			id: field( run, 'uuid', field( run, 'id' ) ),
			name: displayText( field( run, 'launchName', 'Scheduled change' ) ),
			rest: ` ${ displayText( field( run, 'summary', view.label ) ) }`,
			tone: view.badgeTone,
			colorVar: TONE_COLOR[ view.badgeTone ],
			when: formatFriendlyDateTime(
				field( run, 'startedAt', field( run, 'scheduledAt' ) )
			),
		};
	} );

export interface HistoryRow {
	id: string;
	when: string;
	what: string;
	tone: BadgeTone;
	statusLabel: string;
	detail: string;
	open: boolean;
}

export const selectHistoryRows = (
	model: DesignSystemViewModel
): HistoryRow[] =>
	model.runs.map( ( run, index ) => {
		const view = launchStatusView( field( run, 'status' ) );
		return {
			id: field( run, 'uuid', field( run, 'id' ) ),
			when: formatFriendlyDateTime(
				field( run, 'startedAt', field( run, 'scheduledAt' ) )
			),
			what: displayText( field( run, 'launchName', 'Scheduled change' ) ),
			tone: view.badgeTone,
			statusLabel: view.label,
			detail: displayText( field( run, 'summary' ) ),
			open: 0 === index,
		};
	} );

// ---------------------------------------------------------------------------
// Recordable targets (item picker)
// ---------------------------------------------------------------------------

export interface RecordableTargetRow {
	id: string;
	title: string;
	meta: string;
	type: string;
	recordUrl: string;
	canEdit: boolean;
}

export const selectRecordableTargets = (
	model: DesignSystemViewModel
): RecordableTargetRow[] =>
	model.recordableTargets.map( ( target ) => ( {
		id: field( target, 'reference', field( target, 'id' ) ),
		title: displayText( field( target, 'name', 'Item' ) ),
		meta: displayText(
			field( target, 'typeLabel', field( target, 'providerLabel' ) )
		),
		type: field( target, 'typeLabel', 'Item' ),
		recordUrl: field( target, 'recordUrl' ),
		canEdit: true === target.canEdit,
	} ) );

// ---------------------------------------------------------------------------
// Change groups (recording diff)
// ---------------------------------------------------------------------------

export interface ChangeRow {
	id: string;
	field: string;
	before: string;
	after: string;
	sensitive: boolean;
	supported: boolean;
}

export interface ChangeGroupData {
	group: string;
	rows: ChangeRow[];
}

const cleanValue = ( raw: string ): string =>
	raw.replace( /^"|"$/g, '' ).replace( /^\[redacted\]$/, '' );

export const selectChangeGroups = (
	model: DesignSystemViewModel
): ChangeGroupData[] => {
	const recording = model.recordings.find(
		( record ) => safeArray( record.changes ).length > 0
	);
	if ( ! recording ) {
		return [];
	}

	const groups = new Map< string, ChangeRow[] >();
	for ( const change of safeArray< AdminRecord >( recording.changes ) ) {
		const group = field( change, 'group', 'Changes' );
		const sensitive = true === change.sensitive;
		const row: ChangeRow = {
			id: field( change, 'id' ),
			field: formatFriendlyFieldLabel(
				field(
					change,
					'field',
					field( change, 'label', field( change, 'fieldId' ) )
				)
			),
			before: sensitive ? '' : cleanValue( field( change, 'before' ) ),
			after: sensitive ? '' : cleanValue( field( change, 'after' ) ),
			sensitive,
			supported: 'unsupported' !== field( change, 'validation' ),
		};
		groups.set( group, [ ...( groups.get( group ) || [] ), row ] );
	}

	return Array.from( groups.entries() ).map( ( [ group, rows ] ) => ( {
		group,
		rows,
	} ) );
};

// ---------------------------------------------------------------------------
// Restore (runs with restore points)
// ---------------------------------------------------------------------------

export interface RestoreState {
	available: AdminRecord | null;
	unavailable: AdminRecord | null;
	previewRows: ChangeRow[];
}

export const selectRestoreState = (
	model: DesignSystemViewModel
): RestoreState => {
	const available =
		model.runs.find( ( run ) => true === run.rollbackAvailable ) ?? null;
	const unavailable =
		model.runs.find(
			( run ) =>
				false === run.rollbackAvailable &&
				'failed' === field( run, 'status' )
		) ?? null;
	const previewRows = selectChangeGroups( model )
		.flatMap( ( group ) => group.rows )
		.filter( ( row ) => row.supported )
		.slice( 0, 4 );

	return { available, unavailable, previewRows };
};

// ---------------------------------------------------------------------------
// Safety issues (unsupported recorded changes + needs-review launches)
// ---------------------------------------------------------------------------

export interface SafetyIssue {
	what: string;
	next: string;
	fixLabel: string;
}

export const selectSafetyIssues = (
	model: DesignSystemViewModel
): SafetyIssue[] => {
	const issues: SafetyIssue[] = [];

	for ( const recording of model.recordings ) {
		for ( const change of safeArray< AdminRecord >( recording.changes ) ) {
			if ( 'unsupported' === field( change, 'validation' ) ) {
				issues.push( {
					what: `${ field(
						change,
						'field',
						'A field'
					) } can't be scheduled on its own`,
					next: 'Schedule the supported changes, remove that field, or install a compatible addon.',
					fixLabel: 'Show field',
				} );
			}
		}
	}

	return issues.slice( 0, 3 );
};

// ---------------------------------------------------------------------------
// Compatibility / supported areas
// ---------------------------------------------------------------------------

export interface CompatibilityRow {
	name: string;
	detail: string;
	status: 'ok' | 'warn' | 'soon' | 'off';
}

export const selectSupportedAreas = (
	model: DesignSystemViewModel
): CompatibilityRow[] => {
	if ( ! model.supportedAreas.length ) {
		return [];
	}

	const toStatus = ( status: string ): CompatibilityRow[ 'status' ] => {
		if ( 'module_supported' === status ) {
			return 'ok';
		}
		if (
			'requires_extension' === status ||
			'module_lifecycle_partial' === status
		) {
			return 'warn';
		}
		return 'off';
	};

	return model.supportedAreas.map( ( area ) => ( {
		name: field(
			area,
			'area',
			field( area, 'label', field( area, 'name', 'Area' ) )
		),
		detail: field( area, 'examples', field( area, 'description' ) ),
		status: toStatus( field( area, 'status' ) ),
	} ) );
};

// ---------------------------------------------------------------------------
// Calendar month grid
// ---------------------------------------------------------------------------

export interface CalendarEvent {
	tone: string;
	label: string;
	uuid?: string;
	dateKey?: string;
}

export interface CalendarCell {
	num: number;
	dateKey?: string;
	muted?: boolean;
	today?: boolean;
	events: CalendarEvent[];
}

const eventToneClass = ( tone: BadgeTone ): string => {
	switch ( tone ) {
		case 'success':
			return 'ev-success';
		case 'failed':
			return 'ev-failed';
		default:
			return 'ev-scheduled';
	}
};

// Builds a Monday-first month grid for the month of the first calendar event
// (or the schedule default month), placing real events on their day.
export const selectCalendarMonth = (
	model: DesignSystemViewModel
): { title: string; cells: CalendarCell[] } | null => {
	if ( ! model.calendar.length ) {
		return null;
	}

	const anchor =
		field( model.calendar[ 0 ], 'startDate' ) ||
		field( model.scheduleDefaults, 'currentDate' );
	const anchorDate = anchor ? new Date( `${ anchor }T00:00:00` ) : new Date();
	const year = anchorDate.getFullYear();
	const month = anchorDate.getMonth();
	const today = field( model.scheduleDefaults, 'currentDate' );

	const eventsByDay = new Map< number, CalendarEvent[] >();
	for ( const item of model.calendar ) {
		const start = field( item, 'startDate' );
		if ( ! start ) {
			continue;
		}
		const date = new Date( `${ start }T00:00:00` );
		if ( date.getFullYear() !== year || date.getMonth() !== month ) {
			continue;
		}
		const day = date.getDate();
		const view = launchStatusView(
			field( item, 'health', field( item, 'status' ) )
		);
		const list = eventsByDay.get( day ) || [];
		list.push( {
			tone: eventToneClass( view.badgeTone ),
			label: field( item, 'primaryItemName', field( item, 'name' ) ),
			uuid: field(
				item,
				'uuid',
				field(
					item,
					'detailUuid',
					field( item, 'launchUuid', field( item, 'id' ) )
				)
			),
			dateKey: start,
		} );
		eventsByDay.set( day, list );
	}

	const firstWeekday = ( new Date( year, month, 1 ).getDay() + 6 ) % 7; // Monday=0
	const daysInMonth = new Date( year, month + 1, 0 ).getDate();
	const prevDays = new Date( year, month, 0 ).getDate();

	const cells: CalendarCell[] = [];
	for ( let i = firstWeekday; i > 0; i-- ) {
		cells.push( { num: prevDays - i + 1, muted: true, events: [] } );
	}
	for ( let day = 1; day <= daysInMonth; day++ ) {
		const iso = `${ year }-${ String( month + 1 ).padStart(
			2,
			'0'
		) }-${ String( day ).padStart( 2, '0' ) }`;
		cells.push( {
			num: day,
			dateKey: iso,
			today: iso === today,
			events: eventsByDay.get( day ) || [],
		} );
	}
	while ( cells.length % 7 !== 0 ) {
		cells.push( {
			num: cells.length - firstWeekday - daysInMonth + 1,
			muted: true,
			events: [],
		} );
	}

	const title = anchorDate.toLocaleDateString( 'en-US', {
		month: 'long',
		year: 'numeric',
	} );

	return { title, cells };
};
