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

'use strict';

const { spawnSync } = require( 'child_process' );
const upath = require( 'upath' );

const cachedGitPaths = new Set();
const gitHashCache = new Map();

const utils = {
	/**
	 * Returns a URL where a user will be redirect after clicking a "Report new issue" button.
	 *
	 * @param {Object} projectConfig
	 * @param {String} [projectConfig.issueUrl] An absolute URL for reporting an issue. If specified, it will be used.
	 * @param {String} [projectConfig.repoUrlDocs] An absolute URL where the repository is located.
	 * @returns {String}
	 */
	getIssueUrl( projectConfig ) {
		const reportIssueRepository = projectConfig.issueUrl || projectConfig.repoUrlDocs;

		if ( !reportIssueRepository ) {
			return '';
		}

		return `${ reportIssueRepository }/issues/new`;
	},

	/**
	 * Returns a URL where a user will be redirect after clicking a "Contribute to this guide" button.
	 *
	 * @param {Object} projectConfig
	 * @param {String} [projectConfig.contributeUrl] An absolute URL for contributing to the guide.
	 * @param {String} [projectConfig.repoUrlDocs] An absolute URL where the repository is located.
	 * @param {Boolean} [projectConfig.doNotAddDocsInPath=false] If set to true, `docs/` part won't be added to the URL.
	 * @param {String} sourceDirOrPathKey If `pathKey` was passed, `itemName` is not required. The `pathKey` part contains
	 * the rest of path to the page (including an extension). `sourceDir` is a directory where the entire SDK group is located.
	 * @param {String|null} [itemName=null] Name of the guide where the URL should be generated. Required if `sourceDirOrPathKey`
	 * contains the `sourceDir` for specified SDK group. In such case, `itemName` is a name from SDK item that belongs to given group.
	 * @returns {String}
	 */
	getContributeUrl( projectConfig, sourceDirOrPathKey, itemName = null ) {
		const contributeRepository = projectConfig.contributeUrl || projectConfig.repoUrlDocs;

		if ( !contributeRepository ) {
			return '';
		}

		const urlParts = [
			contributeRepository,
			'blob',
			'master'
		];

		if ( !projectConfig.doNotAddDocsInPath ) {
			urlParts.push( 'docs' );
		}

		urlParts.push( sourceDirOrPathKey.replace( /^\//, '' ) );

		if ( itemName ) {
			urlParts.push( itemName + '.html' );
		}

		return urlParts.join( '/' );
	},

	/**
	 * Method obtain URL string with repository address including hash of github repository which was used to build given docs files.
	 *
	 * @param {String} repoUrl Repository url for which we need to obtain full url to github
	 * @param {Object} file Object storing details about given documentation entry
	 * @param {String} file.filePath Path to file where documentation was read
	 * @param {Number} file.lineNr Line number where documentation starts
	 * @param {Number} file.len Length of comment
	 *
	 * @returns {String/null} String URL to github with specific hash and line numbers to highlight
	 */
	getFullGithubLink( repoUrl, file ) {
		const filePath = file.filePath;

		let projectName;

		// TODO: Couldn't be the repository/github link obtained based on the file path?
		// We know where the file is saved. Based on that, we could find the repository URL
		// and adjust values.
		if ( repoUrl.startsWith( '@ckeditor' ) ) {
			projectName = filePath.match( /([^(?:/|\\)]+)(?:\/|\\)packages/ )[ 1 ];
		} else {
			projectName = repoUrl.replace( /\/*$/, '' ).match( /[^/]*$/ )[ 0 ];
		}

		const pathSuffix = _getPathSuffix( filePath );

		if ( !pathSuffix ) {
			return null;
		}

		const gitHash = _getGitHashFromPath( filePath.replace( pathSuffix, '' ) );

		if ( !gitHash ) {
			return null;
		}

		// eslint-disable-next-line @stylistic/max-len
		return `https://github.com/ckeditor/${ projectName }/blob/${ gitHash }${ pathSuffix }#L${ file.lineNr }-L${ file.lineNr + file.len }`;
	}
};

module.exports = utils;

function _getPathSuffix( fileInGitRepositoryPath ) {
	const searchedGitPath = [ ...cachedGitPaths ].find( gitPath => fileInGitRepositoryPath.startsWith( gitPath ) );

	if ( searchedGitPath ) {
		return fileInGitRepositoryPath.replace( searchedGitPath, '' );
	}

	const gitRev = spawnSync( 'git', [ 'rev-parse', '--show-toplevel' ], { cwd: upath.parse( fileInGitRepositoryPath ).dir } );

	if ( gitRev.error ) {
		return null;
	}

	const newGitPath = gitRev.stdout.toString().trim();

	cachedGitPaths.add( newGitPath );

	return fileInGitRepositoryPath.replace( newGitPath, '' );
}

/**
 * Obtain git hash for given path where documentation was read. Obtained data are cached to not read multiple times the same file.
 * Function also walk to different location if current repository has just reference to different places
 * (e.g. its submodule inside different repository)
 *
 * @param {String} gitPath String with file system path, where documentation files was read. It should be main folder of repository.
 *
 * @returns {String/null} hash of branch which is currently active in given path.
 */
function _getGitHashFromPath( gitPath ) {
	if ( gitHashCache.has( gitPath ) ) {
		return gitHashCache.get( gitPath );
	}

	const gitRev = spawnSync( 'git', [ 'rev-parse', '--verify', '--long', 'HEAD' ], { cwd: gitPath } );

	if ( gitRev.error ) {
		return null;
	}

	const hash = gitRev.stdout.toString().trim();

	gitHashCache.set( gitPath, hash );

	return hash;
}
