/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken, SourceControlHistoryItem } from "vscode"
export { ProviderResult } from "vscode"

export interface Git {
	readonly path: string
}

export interface InputBox {
	value: string
}

export const enum ForcePushMode {
	Force,
	ForceWithLease,
	ForceWithLeaseIfIncludes,
}

export const enum RefType {
	Head,
	RemoteHead,
	Tag,
}

export interface Ref {
	readonly type: RefType
	readonly name?: string
	readonly commit?: string
	readonly commitDetails?: Commit
	readonly remote?: string
}

export interface UpstreamRef {
	readonly remote: string
	readonly name: string
	readonly commit?: string
}

export interface Branch extends Ref {
	readonly upstream?: UpstreamRef
	readonly ahead?: number
	readonly behind?: number
}

export interface CommitShortStat {
	readonly files: number
	readonly insertions: number
	readonly deletions: number
}

export interface Commit {
	readonly hash: string
	readonly message: string
	readonly parents: string[]
	readonly authorDate?: Date
	readonly authorName?: string
	readonly authorEmail?: string
	readonly commitDate?: Date
	readonly shortStat?: CommitShortStat
}

export interface Submodule {
	readonly name: string
	readonly path: string
	readonly url: string
}

export interface Remote {
	readonly name: string
	readonly fetchUrl?: string
	readonly pushUrl?: string
	readonly isReadOnly: boolean
}

export const enum Status {
	INDEX_MODIFIED,
	INDEX_ADDED,
	INDEX_DELETED,
	INDEX_RENAMED,
	INDEX_COPIED,

	MODIFIED,
	DELETED,
	UNTRACKED,
	IGNORED,
	INTENT_TO_ADD,
	INTENT_TO_RENAME,
	TYPE_CHANGED,

	ADDED_BY_US,
	ADDED_BY_THEM,
	DELETED_BY_US,
	DELETED_BY_THEM,
	BOTH_ADDED,
	BOTH_DELETED,
	BOTH_MODIFIED,
}

export interface Change {
	/**
	 * Returns either `originalUri` or `renameUri`, depending
	 * on whether this change is a rename change. When
	 * in doubt always use `uri` over the other two alternatives.
	 */
	readonly uri: Uri
	readonly originalUri: Uri
	readonly renameUri: Uri | undefined
	readonly status: Status
}

export interface RepositoryState {
	readonly HEAD: Branch | undefined
	readonly refs: Ref[]
	readonly remotes: Remote[]
	readonly submodules: Submodule[]
	readonly rebaseCommit: Commit | undefined

	readonly mergeChanges: Change[]
	readonly indexChanges: Change[]
	readonly workingTreeChanges: Change[]
	readonly untrackedChanges: Change[]

	readonly onDidChange: Event<void>
}

export interface RepositoryUIState {
	readonly selected: boolean
	readonly onDidChange: Event<void>
}

/**
 * Log options.
 */
export interface LogOptions {
	/** Max number of log entries to retrieve. If not specified, the default is 32. */
	readonly maxEntries?: number
	readonly path?: string
	/** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */
	readonly range?: string
	readonly reverse?: boolean
	readonly sortByAuthorDate?: boolean
	readonly shortStats?: boolean
	readonly author?: string
	readonly grep?: string
	readonly refNames?: string[]
	readonly maxParents?: number
	readonly skip?: number
}

export interface CommitOptions {
	all?: boolean | "tracked"
	amend?: boolean
	signoff?: boolean
	signCommit?: boolean
	empty?: boolean
	noVerify?: boolean
	requireUserConfig?: boolean
	useEditor?: boolean
	verbose?: boolean
	/**
	 * string    - execute the specified command after the commit operation
	 * undefined - execute the command specified in git.postCommitCommand
	 *             after the commit operation
	 * null      - do not execute any command after the commit operation
	 */
	postCommitCommand?: string | null
}

export interface FetchOptions {
	remote?: string
	ref?: string
	all?: boolean
	prune?: boolean
	depth?: number
}

export interface InitOptions {
	defaultBranch?: string
}

export interface CloneOptions {
	parentPath?: Uri
	/**
	 * ref is only used if the repository cache is missed.
	 */
	ref?: string
	recursive?: boolean
	/**
	 * If no postCloneAction is provided, then the users setting for git.openAfterClone is used.
	 */
	postCloneAction?: "none"
}

export interface RefQuery {
	readonly contains?: string
	readonly count?: number
	readonly pattern?: string | string[]
	readonly sort?: "alphabetically" | "committerdate"
}

export interface BranchQuery extends RefQuery {
	readonly remote?: boolean
}

export interface Repository {
	readonly rootUri: Uri
	readonly inputBox: InputBox
	readonly state: RepositoryState
	readonly ui: RepositoryUIState

	readonly onDidCommit: Event<void>
	readonly onDidCheckout: Event<void>

	getConfigs(): Promise<{ key: string; value: string }[]>
	getConfig(key: string): Promise<string>
	setConfig(key: string, value: string): Promise<string>
	unsetConfig(key: string): Promise<string>
	getGlobalConfig(key: string): Promise<string>

	getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number }>
	detectObjectType(object: string): Promise<{ mimetype: string; encoding?: string }>
	buffer(ref: string, path: string): Promise<Buffer>
	show(ref: string, path: string): Promise<string>
	getCommit(ref: string): Promise<Commit>

	add(paths: string[]): Promise<void>
	revert(paths: string[]): Promise<void>
	clean(paths: string[]): Promise<void>

	apply(patch: string, reverse?: boolean): Promise<void>
	diff(cached?: boolean): Promise<string>
	diffWithHEAD(): Promise<Change[]>
	diffWithHEAD(path: string): Promise<string>
	diffWithHEADShortStats(path?: string): Promise<CommitShortStat>
	diffWith(ref: string): Promise<Change[]>
	diffWith(ref: string, path: string): Promise<string>
	diffIndexWithHEAD(): Promise<Change[]>
	diffIndexWithHEAD(path: string): Promise<string>
	diffIndexWithHEADShortStats(path?: string): Promise<CommitShortStat>
	diffIndexWith(ref: string): Promise<Change[]>
	diffIndexWith(ref: string, path: string): Promise<string>
	diffBlobs(object1: string, object2: string): Promise<string>
	diffBetween(ref1: string, ref2: string): Promise<Change[]>
	diffBetween(ref1: string, ref2: string, path: string): Promise<string>

	hashObject(data: string): Promise<string>

	createBranch(name: string, checkout: boolean, ref?: string): Promise<void>
	deleteBranch(name: string, force?: boolean): Promise<void>
	getBranch(name: string): Promise<Branch>
	getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise<Ref[]>
	getBranchBase(name: string): Promise<Branch | undefined>
	setBranchUpstream(name: string, upstream: string): Promise<void>

	checkIgnore(paths: string[]): Promise<Set<string>>

	getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise<Ref[]>

	getMergeBase(ref1: string, ref2: string): Promise<string | undefined>

	tag(name: string, message: string, ref?: string | undefined): Promise<void>
	deleteTag(name: string): Promise<void>

	status(): Promise<void>
	checkout(treeish: string): Promise<void>

	addRemote(name: string, url: string): Promise<void>
	removeRemote(name: string): Promise<void>
	renameRemote(name: string, newName: string): Promise<void>

	fetch(options?: FetchOptions): Promise<void>
	fetch(remote?: string, ref?: string, depth?: number): Promise<void>
	pull(unshallow?: boolean): Promise<void>
	push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise<void>

	blame(path: string): Promise<string>
	log(options?: LogOptions): Promise<Commit[]>

	commit(message: string, opts?: CommitOptions): Promise<void>
	merge(ref: string): Promise<void>
	mergeAbort(): Promise<void>

	applyStash(index?: number): Promise<void>
	popStash(index?: number): Promise<void>
	dropStash(index?: number): Promise<void>
}

export interface RemoteSource {
	readonly name: string
	readonly description?: string
	readonly url: string | string[]
}

export interface RemoteSourceProvider {
	readonly name: string
	readonly icon?: string // codicon name
	readonly supportsQuery?: boolean
	getRemoteSources(query?: string): ProviderResult<RemoteSource[]>
	getBranches?(url: string): ProviderResult<string[]>
	publishRepository?(repository: Repository): Promise<void>
}

export interface RemoteSourcePublisher {
	readonly name: string
	readonly icon?: string // codicon name
	publishRepository(repository: Repository): Promise<void>
}

export interface Credentials {
	readonly username: string
	readonly password: string
}

export interface CredentialsProvider {
	getCredentials(host: Uri): ProviderResult<Credentials>
}

export interface PostCommitCommandsProvider {
	getCommands(repository: Repository): Command[]
}

export interface PushErrorHandler {
	handlePushError(
		repository: Repository,
		remote: Remote,
		refspec: string,
		error: Error & { gitErrorCode: GitErrorCodes },
	): Promise<boolean>
}

export interface BranchProtection {
	readonly remote: string
	readonly rules: BranchProtectionRule[]
}

export interface BranchProtectionRule {
	readonly include?: string[]
	readonly exclude?: string[]
}

export interface BranchProtectionProvider {
	onDidChangeBranchProtection: Event<Uri>
	provideBranchProtection(): BranchProtection[]
}

export interface AvatarQueryCommit {
	readonly hash: string
	readonly authorName?: string
	readonly authorEmail?: string
}

export interface AvatarQuery {
	readonly commits: AvatarQueryCommit[]
	readonly size: number
}

export interface SourceControlHistoryItemDetailsProvider {
	provideAvatar(repository: Repository, query: AvatarQuery): ProviderResult<Map<string, string | undefined>>
	provideHoverCommands(repository: Repository): ProviderResult<Command[]>
	provideMessageLinks(repository: Repository, message: string): ProviderResult<string>
}

export type APIState = "uninitialized" | "initialized"

export interface PublishEvent {
	repository: Repository
	branch?: string
}

export interface API {
	readonly state: APIState
	readonly onDidChangeState: Event<APIState>
	readonly onDidPublish: Event<PublishEvent>
	readonly git: Git
	readonly repositories: Repository[]
	readonly onDidOpenRepository: Event<Repository>
	readonly onDidCloseRepository: Event<Repository>

	toGitUri(uri: Uri, ref: string): Uri
	getRepository(uri: Uri): Repository | null
	getRepositoryRoot(uri: Uri): Promise<Uri | null>
	getRepositoryWorkspace(uri: Uri): Promise<Uri[] | null>
	init(root: Uri, options?: InitOptions): Promise<Repository | null>
	/**
	 * Checks the cache of known cloned repositories, and clones if the repository is not found.
	 * Make sure to pass `postCloneAction` 'none' if you want to have the uri where you can find the repository returned.
	 * @returns The URI of a folder or workspace file which, when opened, will open the cloned repository.
	 */
	clone(uri: Uri, options?: CloneOptions): Promise<Uri | null>
	openRepository(root: Uri): Promise<Repository | null>

	registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable
	registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable
	registerCredentialsProvider(provider: CredentialsProvider): Disposable
	registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable
	registerPushErrorHandler(handler: PushErrorHandler): Disposable
	registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable
	registerSourceControlHistoryItemDetailsProvider(provider: SourceControlHistoryItemDetailsProvider): Disposable
}

export interface GitExtension {
	readonly enabled: boolean
	readonly onDidChangeEnablement: Event<boolean>

	/**
	 * Returns a specific API version.
	 *
	 * Throws error if git extension is disabled. You can listen to the
	 * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event
	 * to know when the extension becomes enabled/disabled.
	 *
	 * @param version Version number.
	 * @returns API instance
	 */
	getAPI(version: 1): API
}

export const enum GitErrorCodes {
	BadConfigFile = "BadConfigFile",
	BadRevision = "BadRevision",
	AuthenticationFailed = "AuthenticationFailed",
	NoUserNameConfigured = "NoUserNameConfigured",
	NoUserEmailConfigured = "NoUserEmailConfigured",
	NoRemoteRepositorySpecified = "NoRemoteRepositorySpecified",
	NotAGitRepository = "NotAGitRepository",
	NotASafeGitRepository = "NotASafeGitRepository",
	NotAtRepositoryRoot = "NotAtRepositoryRoot",
	Conflict = "Conflict",
	StashConflict = "StashConflict",
	UnmergedChanges = "UnmergedChanges",
	PushRejected = "PushRejected",
	ForcePushWithLeaseRejected = "ForcePushWithLeaseRejected",
	ForcePushWithLeaseIfIncludesRejected = "ForcePushWithLeaseIfIncludesRejected",
	RemoteConnectionError = "RemoteConnectionError",
	DirtyWorkTree = "DirtyWorkTree",
	CantOpenResource = "CantOpenResource",
	GitNotFound = "GitNotFound",
	CantCreatePipe = "CantCreatePipe",
	PermissionDenied = "PermissionDenied",
	CantAccessRemote = "CantAccessRemote",
	RepositoryNotFound = "RepositoryNotFound",
	RepositoryIsLocked = "RepositoryIsLocked",
	BranchNotFullyMerged = "BranchNotFullyMerged",
	NoRemoteReference = "NoRemoteReference",
	InvalidBranchName = "InvalidBranchName",
	BranchAlreadyExists = "BranchAlreadyExists",
	NoLocalChanges = "NoLocalChanges",
	NoStashFound = "NoStashFound",
	LocalChangesOverwritten = "LocalChangesOverwritten",
	NoUpstreamBranch = "NoUpstreamBranch",
	IsInSubmodule = "IsInSubmodule",
	WrongCase = "WrongCase",
	CantLockRef = "CantLockRef",
	CantRebaseMultipleBranches = "CantRebaseMultipleBranches",
	PatchDoesNotApply = "PatchDoesNotApply",
	NoPathFound = "NoPathFound",
	UnknownPath = "UnknownPath",
	EmptyCommitMessage = "EmptyCommitMessage",
	BranchFastForwardRejected = "BranchFastForwardRejected",
	BranchNotYetBorn = "BranchNotYetBorn",
	TagConflict = "TagConflict",
	CherryPickEmpty = "CherryPickEmpty",
	CherryPickConflict = "CherryPickConflict",
	WorktreeContainsChanges = "WorktreeContainsChanges",
	WorktreeAlreadyExists = "WorktreeAlreadyExists",
	WorktreeBranchAlreadyUsed = "WorktreeBranchAlreadyUsed",
}
