import { WeBWorkspace, WeBWorkspaceSave } from '../../buttercup/workspace';
import {
	WeBWireCoordinate,
	WeBWireFilter,
	WeBWireMap,
	WeBWireSave,
} from '../fetch/types';

/**
 * The other place that knows both vocabularies — the reverse direction.
 *
 * `WorkspaceAdapter.ts` turns a payload into engine types; this turns the
 * engine's `WeBWorkspaceSave` into the one wire type that travels *to* the
 * server, `WeBWireSave`. Together they are the only two files allowed to
 * name both an engine field and a wire field, so a rename on either side has
 * exactly two places to look.
 *
 * The save is read through `WeBWorkspace.saveGet()`, never off the graph
 * directly: the engine already decided what a save is made of, and repeating
 * that here would be a second list to keep in step.
 *
 * Every field is copied **by name**, even where the spellings agree today, for
 * the same reason the inbound adapter does: the two sides are close enough
 * that a spread would hide a drift until the server started dropping a key.
 * Nothing is validated — `WeBWPSaveState` (PHP) does that per field and falls
 * back rather than refusing — and nothing is derived: what the browser knows
 * is what it sends.
 *
 * @param workspace - the workspace to serialise
 */
export function weBSerialiseWorkspace( workspace: WeBWorkspace ): WeBWireSave {
	return weBSerialiseSave( workspace.saveGet() );
}

/**
 * The mapping itself, separated so a harness can feed it a save without
 * building a graph.
 *
 * @param save - `WeBWorkspace.saveGet()`
 */
export function weBSerialiseSave( save: WeBWorkspaceSave ): WeBWireSave {
	const coordinates: WeBWireSave[ 'layout' ][ 'coordinates' ] = {};
	const options: WeBWireSave[ 'layout' ][ 'options' ] = {};
	const recordTypeColours: WeBWireSave[ 'recordTypeColours' ] = {};
	const filters: WeBWireFilter[] = [];

	/*
	 * Every layout's cells, keyed by layout id — a cell address means nothing
	 * without the grid it is addressed in, so the layouts cannot share one map.
	 *
	 * Copied cell by cell: the registries' entries are the engine's, not ours
	 * to hand on.
	 */
	for ( const layoutId in save.layout.coordinates ) {
		const cells = save.layout.coordinates[ layoutId ];
		const copied: WeBWireMap< WeBWireCoordinate > = {};

		for ( const recordId in cells ) {
			const { x, y } = cells[ recordId ];

			copied[ recordId ] = { x, y };
		}

		coordinates[ layoutId ] = copied;
	}

	/*
	 * And what the reader set on each, with the nulls dropped.
	 *
	 * A null is the engine's "none" — no row above, say — and is also what
	 * every option of a layout that does not use it reads as. The wire carries
	 * the keys that say something and leaves the rest out, which is what the
	 * PHP's per-key validation expects.
	 *
	 * **A number travels as its decimal.** Every option was a type id until the
	 * timeline gained a record count, and the wire, the PHP whitelist and the
	 * cloud's `layout.options.*.*` rule are all `string` — the cloud faults a
	 * whole save over an integer there, and it is not this repo's to change.
	 * `weBAdaptLayout` reads it back as a number, the two adapters being the
	 * only files that know both vocabularies.
	 */
	for ( const layoutId in save.layout.options ) {
		const layoutOptions = save.layout.options[ layoutId ];
		const copied: { [ key: string ]: string } = {};

		for ( const key in layoutOptions ) {
			const value = layoutOptions[ key as keyof typeof layoutOptions ];

			if ( typeof value === 'number' && Number.isFinite( value ) ) {
				copied[ key ] = String( value );

				continue;
			}

			if ( typeof value !== 'string' || value === '' ) {
				continue;
			}

			copied[ key ] = value;
		}

		options[ layoutId ] = copied;
	}

	for ( const typeId in save.recordTypeColours ) {
		recordTypeColours[ typeId ] = save.recordTypeColours[ typeId ];
	}

	//filters stack in order, so the list order is part of the save
	for ( const filter of save.funnel.filters ) {
		filters.push( {
			mode: filter.mode,
			recordIds: [ ...filter.recordIds ],
			recordTypeId: filter.recordTypeId,
			related: filter.related,
		} );
	}

	return {
		config: {
			colourSchemeKey: save.config.colourSchemeKey,
			coordinateRadius: save.config.coordinateRadius,
			//the reader's detail level, which is theirs and not the product's
			nodeDetail: save.config.nodeDetail,
			nodeRadius: save.config.nodeRadius,
		},
		layout: {
			activeLayoutId: save.layout.activeLayoutId,
			coordinates,
			options,
		},
		funnel: { filters },
		label: save.label,
		recordTypeColours,
		visibleRecordIds: [ ...save.visibleRecordIds ],
	};
}
