import deindent from '../../../utils/deindent.js';
import getGlobals, { Globals } from './getGlobals';

export type ModuleFormat = "es" | "amd" | "cjs" | "iife" | "umd" | "eval";

export interface Options {
	name: string;
	amd?: {
		id?: string;
	};
	globals: Globals | object;
	onerror: (err: Error) => void;
	onwarn: (obj: Error | { message: string }) => void;
}

export interface Declaration {
	name: string;
	source: {
		value: string;
	};
}

export default function getIntro ( format: ModuleFormat, options: Options, imports: Declaration[] ) {
	if ( format === 'es' ) return '';
	if ( format === 'amd' ) return getAmdIntro( options, imports );
	if ( format === 'cjs' ) return getCjsIntro( options, imports );
	if ( format === 'iife' ) return getIifeIntro( options, imports );
	if ( format === 'umd' ) return getUmdIntro( options, imports );
	if ( format === 'eval' ) return getEvalIntro( options, imports );

	throw new Error( `Not implemented: ${format}` );
}

function getAmdIntro ( options: Options, imports: Declaration[] ) {
	const sourceString = imports.length ?
		`[ ${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ' )} ], ` :
		'';

	const id = options.amd && options.amd.id;

	return `define(${id ? ` '${id}', ` : ''}${sourceString}function (${paramString( imports )}) { 'use strict';\n\n`;
}

function getCjsIntro ( options: Options, imports: Declaration[] ) {
	const requireBlock = imports
		.map( declaration => `var ${declaration.name} = require( '${declaration.source.value}' );` )
		.join( '\n\n' );

	if ( requireBlock ) {
		return `'use strict';\n\n${requireBlock}\n\n`;
	}

	return `'use strict';\n\n`;
}

function getIifeIntro ( options: Options, imports: Declaration[] ) {
	if ( !options.name ) {
		throw new Error( `Missing required 'name' option for IIFE export` );
	}

	return `var ${options.name} = (function (${paramString( imports )}) { 'use strict';\n\n`;
}

function getUmdIntro ( options: Options, imports: Declaration[] ) {
	if ( !options.name ) {
		throw new Error( `Missing required 'name' option for UMD export` );
	}

	const amdId = options.amd && options.amd.id ? `'${options.amd.id}', ` : '';

	const amdDeps = imports.length ? `[${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ')}], ` : '';
	const cjsDeps = imports.map( declaration => `require('${declaration.source.value}')` ).join( ', ' );
	const globalDeps = getGlobals( imports, options );

	return deindent`
		(function ( global, factory ) {
			typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(${cjsDeps}) :
			typeof define === 'function' && define.amd ? define(${amdId}${amdDeps}factory) :
			(global.${options.name} = factory(${globalDeps}));
		}(this, (function (${paramString( imports )}) { 'use strict';` + '\n\n';
}

function getEvalIntro ( options: Options, imports: Declaration[] ) {
	return `(function (${paramString( imports )}) { 'use strict';\n\n`;
}

function paramString ( imports: Declaration[] ) {
	return imports.length ? ` ${imports.map( dep => dep.name ).join( ', ' )} ` : '';
}

function removeExtension ( file: string ) {
	const index = file.lastIndexOf( '.' );
	return ~index ? file.slice( 0, index ) : file;
}