/**
 * @license Copyright (c) 2017-2025, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md.
 */

'use strict';

const cheerio = require( 'cheerio' );
const keyNameMap = {
	tagname: {
		name: 'kind',
		adapter: tagnameAdapter
	},
	doc: {
		name: 'description',
		adapter: docAdapter
	},
	extends: {
		name: 'augments',
		adapter: item => toArray( item.extends )
	},
	mixins: {
		name: 'mixes',
		adapter: item => toArray( item.mixins )
	},
	// TODO: 'implements' property?
	default: {
		name: 'defaultvalue'
	},
	params: {
		name: 'params',
		adapter: paramsAdapter
	},
	private: {
		name: 'access',
		adapter: item => item.private ? 'private' : 'public'
	},
	protected: {
		name: 'access',
		adapter: item => item.protected ? 'protected' : 'public'
	},
	return: {
		name: 'returns',
		adapter: returnAdapter
	},
	type: {
		name: 'type',
		adapter: typeAdapter
	},
	static: {
		name: 'scope',
		adapter: item => item.static ? 'static' : ''
	},
	overrides: {
		name: 'overrides',
		adapter: overridesAdapter
	},
	files: {
		name: 'file',
		adapter: fileAdapter
	}
};

/**
 * Creates doclets in umberto format from JSDuck generated API data.
 * Prepared for CKEditor 4.
 *
 * @param data
 * @returns {Array}
 */
module.exports = data => {
	let parsed = {};
	const doclets = [];

	try {
		parsed = JSON.parse( data );
	} catch ( err ) {
		throw new Error( `Failed to convert JSDuck docs to Umberto format. ${ err.stack }` );
	}

	const mainDoclet = parsed;
	const members = mainDoclet.members || [];

	delete mainDoclet.members;

	addDoclet( mainDoclet, doclets );

	for ( const item of members ) {
		addDoclet( item, doclets, mainDoclet );
	}

	return doclets;
};

function addDoclet( item, resultDoclets, parent ) {
	const adapted = adapt( item );

	adapted.longname = createLongname( item, parent );
	adapted.memberof = createMemberOf( item, parent );
	adapted.extraId = item.id;

	resultDoclets.push( adapted );
}

// Properties of each item (which are defined in keyNameMap) are transformed by respective functions.
function adapt( item ) {
	for ( const key of Object.keys( item ) ) {
		const keyConf = keyNameMap[ key ];

		if ( keyConf ) {
			item[ keyConf.name ] = keyConf.adapter ? keyConf.adapter.call( null, item ) : item[ key ];

			if ( keyConf.name !== key ) {
				delete item[ key ];
			}
		}
	}

	return item;
}

function createLongname( item, parent ) {
	const name = item.name.replace( '$', 's-' );

	if ( !parent ) {
		return name;
	} else {
		return `${ parent.name }#${ name }`;
	}
}

function createMemberOf( item, parent ) {
	if ( parent ) {
		return parent.name;
	} else {
		const parts = item.name.split( '.' );

		if ( parts.length > 1 ) {
			return item.name.substring( 0, item.name.lastIndexOf( '.' ) );
		} else {
			return '';
		}
	}
}

function tagnameAdapter( item ) {
	let result = item.tagname;

	switch ( item.tagname ) {
		case 'property':
			result = 'member';
			break;

		case 'method':
			result = 'function';
			break;
	}

	return result;
}

function docAdapter( item ) {
	const description = item.doc;
	const $ = cheerio.load( description, null, false );

	$( 'a' ).each( function() {
		const href = $( this ).attr( 'href' );
		const linkText = $( this ).text();

		$( this ).replaceWith( fixLink( href, linkText, $( this ).toString() ) );
	} );

	return $.html();
}

function fixLink( href, linkText, original ) {
	href = href
		.replace( '-property-S-', '#s-' )
		.replace( '-static-property-', '#' )
		.replace( '-property-', '#' )
		.replace( '-static-method-', '#' )
		.replace( '-method-', '#' )
		.replace( 'method-', '#' )
		.replace( '-event-', '#' )
		.replace( '-cfg-', '#' )
		.replace( '-section-', '#' );

	if ( href.indexOf( '#!/api/' ) !== -1 ) {
		const longname = href.replace( '#!/api/', '' );

		return `{@link ${ longname } ${ linkText }}`;
	} else if ( href.indexOf( '#!/guide/' ) !== -1 ) {
		const url = href.replace( '#!/', '' );

		return `{@glink ${ url } ${ linkText }}`;
	} else {
		return original;
	}
}

function paramsAdapter( item ) {
	const params = item.params || item.properties || [];

	return params.map( p => {
		p.defaultvalue = p.default;

		if ( p.doc ) {
			p.description = docAdapter( p );
		}

		if ( p.type ) {
			p.type = typeAdapter( p );
		}

		if ( p.properties ) {
			p.params = paramsAdapter( p );
		}

		return p;
	} );
}

function returnAdapter( item ) {
	if ( !item.return ) {
		return [];
	}

	const returns = item.return;

	if ( returns.doc ) {
		returns.description = docAdapter( returns );
	}

	if ( returns.type ) {
		returns.type = typeAdapter( returns );
	}

	if ( returns.properties ) {
		returns.params = paramsAdapter( returns );
	}

	return [ returns ];
}

function typeAdapter( item ) {
	if ( !item.type ) {
		return null;
	}

	let names = item.type.split( '/' );

	// Don't split complex types, e.g. Object.<String, CKEDITOR.ui.button/CKEDITOR.ui.richCombo>.
	if ( /^([\w]+\.<\(*)([^)>]+)([)>]+)$/.exec( item.type ) !== null ) {
		names = [ item.type.replace( '/', '|' ) ];
	}

	return {
		names
	};
}

function overridesAdapter( item ) {
	const overrides = item.overrides || [];

	return overrides.map( o => {
		const $ = cheerio.load( o.link, null, false );

		$( 'a' ).each( function() {
			const href = $( this ).attr( 'href' );
			const linkText = $( this ).text();

			$( this ).replaceWith( fixLink( href, linkText, $( this ).toString() ) );
		} );

		o.link = $.html();

		return o;
	} );
}

function fileAdapter( item ) {
	const ret = {};
	if ( item.files[ 0 ].filename.length === 0 ) {
		return;
	}
	ret.filePath = item.files[ 0 ].filename;
	ret.lineNr = item.files[ 0 ].linenr;
	ret.len = ( item.doc.match( /\n/g ) || [] ).length;
	return ret;
}

function toArray( item ) {
	if ( Array.isArray( item ) ) {
		return item;
	}

	if ( item ) {
		return [ item ];
	}

	return [];
}
