import { loggable } from '@gitlens/utils/decorators/log.js';
import type { Uri } from '@gitlens/utils/uri.js';
import type { GitFile } from './file.js';
import type { GitFileStatus } from './fileStatus.js';
import { GitFileConflictStatus, GitFileIndexStatus, GitFileWorkingTreeStatus } from './fileStatus.js';

@loggable(i => i.path)
export class GitStatusFile implements GitFile {
	public readonly conflictStatus: GitFileConflictStatus | undefined;
	public readonly indexStatus: GitFileIndexStatus | undefined;
	public readonly workingTreeStatus: GitFileWorkingTreeStatus | undefined;
	private readonly _uri: Uri;

	constructor(
		public readonly repoPath: string,
		x: string | undefined,
		y: string | undefined,
		public readonly path: string,
		uri: Uri,
		public readonly originalPath?: string,
		public readonly submodule?: { readonly oid: string; readonly previousOid?: string } | undefined,
	) {
		this._uri = uri;
		if (x != null && y != null) {
			switch (x + y) {
				case '??':
					this.workingTreeStatus = GitFileWorkingTreeStatus.Untracked;
					break;
				case '!!':
					this.workingTreeStatus = GitFileWorkingTreeStatus.Ignored;
					break;
				case 'AA':
					this.conflictStatus = GitFileConflictStatus.AddedByBoth;
					break;
				case 'AU':
					this.conflictStatus = GitFileConflictStatus.AddedByUs;
					break;
				case 'UA':
					this.conflictStatus = GitFileConflictStatus.AddedByThem;
					break;
				case 'DD':
					this.conflictStatus = GitFileConflictStatus.DeletedByBoth;
					break;
				case 'DU':
					this.conflictStatus = GitFileConflictStatus.DeletedByUs;
					break;
				case 'UD':
					this.conflictStatus = GitFileConflictStatus.DeletedByThem;
					break;
				case 'UU':
					this.conflictStatus = GitFileConflictStatus.ModifiedByBoth;
					break;
			}
		}

		if (this.conflictStatus == null) {
			switch (x) {
				case 'A':
					this.indexStatus = GitFileIndexStatus.Added;
					break;
				case 'D':
					this.indexStatus = GitFileIndexStatus.Deleted;
					break;
				case 'M':
					this.indexStatus = GitFileIndexStatus.Modified;
					break;
				case 'T':
					this.indexStatus = GitFileIndexStatus.TypeChanged;
					break;
				case 'R':
					this.indexStatus = GitFileIndexStatus.Renamed;
					break;
				case 'C':
					this.indexStatus = GitFileIndexStatus.Copied;
					break;
			}

			switch (y) {
				case 'A':
					// case '?':
					this.workingTreeStatus = GitFileWorkingTreeStatus.Added;
					break;
				case 'D':
					this.workingTreeStatus = GitFileWorkingTreeStatus.Deleted;
					break;
				case 'M':
					this.workingTreeStatus = GitFileWorkingTreeStatus.Modified;
					break;
			}
		}
	}

	get conflicted(): boolean {
		return this.conflictStatus != null;
	}

	/**
	 * True when the file has both staged (index) and unstaged (working-tree) changes and is not
	 * conflicted. Used by the host-side discard flows to apply partial-discard semantics — drop the
	 * working-tree delta while preserving the staged portion. Centralized here so those host paths
	 * agree on what "mixed" means. (The webview button predicate can't consume this getter — it
	 * runs against the wire `GitFileChangeShape` and infers the same state from the duplicate
	 * staged/unstaged rows the WIP feed emits per mixed path.)
	 */
	get mixed(): boolean {
		return this.indexStatus != null && this.workingTreeStatus != null && this.conflictStatus == null;
	}

	get staged(): boolean {
		return this.indexStatus != null;
	}

	get status(): GitFileStatus {
		return (this.conflictStatus ?? this.indexStatus ?? this.workingTreeStatus)!;
	}

	get uri(): Uri {
		return this._uri;
	}

	get wip(): boolean {
		return this.workingTreeStatus != null;
	}

	static is(file: unknown): file is GitStatusFile {
		return file instanceof GitStatusFile;
	}
}
