import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import type { GitBranch } from '@gitlens/git/models/branch.js';
import { GitCommit } from '@gitlens/git/models/commit.js';
import type { GitFileWithCommit } from '@gitlens/git/models/file.js';
import { createRevisionRange } from '@gitlens/git/utils/revision.utils.js';
import { makeHierarchical } from '@gitlens/utils/array.js';
import { filter, flatMap, groupByMap, map } from '@gitlens/utils/iterable.js';
import { joinPaths, normalizePath } from '@gitlens/utils/path.js';
import { pluralize, sortCompare } from '@gitlens/utils/string.js';
import type { FilesComparison } from '../../git/actions/commit.js';
import { GitUri } from '../../git/gitUri.js';
import { getCommitDate } from '../../git/utils/-webview/commit.utils.js';
import type { ViewsWithCommits } from '../viewBase.js';
import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode.js';
import type { BranchTrackingStatus } from './branchTrackingStatusNode.js';
import type { FileNode } from './folderNode.js';
import { FolderNode } from './folderNode.js';
import { StatusFileNode } from './statusFileNode.js';

export class BranchTrackingStatusFilesNode extends ViewNode<'tracking-status-files', ViewsWithCommits> {
	constructor(
		view: ViewsWithCommits,
		protected override readonly parent: ViewNode,
		public readonly branch: GitBranch,
		public readonly status: Required<BranchTrackingStatus>,
		public readonly direction: 'ahead' | 'behind',
	) {
		super('tracking-status-files', GitUri.fromRepoPath(status.repoPath), view, parent);

		this.updateContext({ branch: branch, branchStatus: status, branchStatusUpstreamType: direction });
		this._uniqueId = getViewNodeId(this.type, this.context);
	}

	get ref1(): string {
		return this.branch.ref;
	}

	get ref2(): string {
		return this.status.upstream?.name;
	}

	get repoPath(): string {
		return this.status.repoPath;
	}

	async getFilesComparison(): Promise<FilesComparison> {
		const grouped = await this.getGroupedFiles();
		return {
			files: [...map(grouped, ([, files]) => files.at(-1)!)],
			repoPath: this.repoPath,
			ref1: this.ref1,
			ref2: this.ref2,
			title: this.direction === 'ahead' ? `Changes to push to ${this.ref2}` : `Changes to pull from ${this.ref2}`,
		};
	}

	private async getGroupedFiles(): Promise<Map<string, GitFileWithCommit[]>> {
		const log = await this.view.container.git
			.getRepositoryService(this.repoPath)
			.commits.getLog(
				this.direction === 'behind'
					? createRevisionRange(this.ref1, this.ref2, '..')
					: createRevisionRange(this.ref2, this.ref1, '..'),
				{ limit: 0 },
			);
		if (log == null) return new Map();

		await Promise.allSettled(
			map(
				filter(log.commits.values(), c => c.fileset?.files == null),

				c => GitCommit.ensureFullDetails(c),
			),
		);

		const files = [
			...flatMap(log.commits.values(), c => c.anyFiles?.map<GitFileWithCommit>(f => ({ ...f, commit: c })) ?? []),
		];

		files.sort((a, b) => getCommitDate(b.commit).getTime() - getCommitDate(a.commit).getTime());

		const groups = groupByMap(files, s => s.path);
		return groups;
	}

	async getChildren(): Promise<ViewNode[]> {
		const files = await this.getGroupedFiles();

		let children: FileNode[] = [
			...map(files.values(), files => new StatusFileNode(this.view, this, this.repoPath, files, this.direction)),
		];

		if (this.view.config.files.layout !== 'list') {
			const hierarchy = makeHierarchical(
				children,
				n => n.uri.relativePath.split('/'),
				(...parts: string[]) => normalizePath(joinPaths(...parts)),
				this.view.config.files.compact,
			);

			const root = new FolderNode(this.view, this, hierarchy, this.repoPath, '', undefined, false);
			children = root.getChildren() as FileNode[];
		} else {
			children.sort((a, b) => a.priority - b.priority || sortCompare(a.label!, b.label!));
		}

		return children;
	}

	async getTreeItem(): Promise<TreeItem> {
		const stats = await this.view.container.git
			.getRepositoryService(this.repoPath)
			.diff.getChangedFilesCount(this.direction === 'behind' ? `${this.ref1}...${this.ref2}` : `${this.ref2}...`);
		const files = stats?.files ?? 0;

		const label = `${pluralize('file', files)} changed`;
		const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
		item.id = this.id;
		item.contextValue = ContextValues.BranchStatusFiles;

		return item;
	}
}
