import type { PropertyValues } from 'lit';
import { html, LitElement, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { cache } from 'lit/directives/cache.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import type { GitCommitStats } from '@gitlens/git/models/commit.js';
import type { GitFileChangeShape } from '@gitlens/git/models/fileChange.js';
import type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';
import type { GitCommitSearchContext } from '@gitlens/git/models/search.js';
import { shortenRevision } from '@gitlens/git/utils/revision.utils.js';
import { pluralize } from '@gitlens/utils/string.js';
import type { Autolink } from '../../../../../autolinks/models/autolinks.js';
import { serializeWebviewItemContext } from '../../../../../system/webview.js';
import type {
	CommitDetails,
	CommitSignatureShape,
	DetailsItemTypedContext,
	Preferences,
	State,
} from '../../../../plus/graph/detailsProtocol.js';
import { buildFolderContext } from '../../../../plus/graph/detailsProtocol.js';
import type { AiModelInfo } from '../../../../rpc/services/types.js';
import type { OpenMultipleChangesArgs } from '../../../shared/actions/file.js';
import { renderLearnAboutAutolinks } from '../../../shared/components/chips/learn-about-autolinks.js';
import { redispatch } from '../../../shared/components/element.js';
import {
	elementBase,
	metadataBarVarsBase,
	scrollableBase,
	subPanelEnterStyles,
} from '../../../shared/components/styles/lit/base.css.js';
import type { TreeItemAction } from '../../../shared/components/tree/base.js';
import { renderCopyChangesAction, renderOpenChangesAction } from '../../../shared/components/tree/file-tree-utils.js';
import type { FileChangeListItemDetail } from '../../../shared/components/tree/gl-file-tree-pane.js';
import type { RunningOperationExecState } from './detailsState.js';
import { multiCommitPanelStyles, panelActionInputStyles, panelHostStyles } from './gl-details-multicommit-panel.css.js';
import '../../../shared/components/code-icon.js';
import './gl-compare-ai-actions.js';
import '../../../shared/components/commit-sha.js';
import '../../../shared/components/progress.js';
import '../../../shared/components/commit/commit-stats.js';
import '../../../shared/components/chips/action-chip.js';
import '../../../shared/components/chips/autolink-chip.js';
import '../../../shared/components/chips/chip-overflow.js';
import '../../../shared/components/button.js';
import '../../../shared/components/menu/menu-divider.js';
import '../../../shared/components/menu/menu-item.js';
import '../../../shared/components/menu/menu-label.js';
import '../../../shared/components/overlays/tooltip.js';
import '../../../shared/components/panes/pane-group.js';
import '../../../shared/components/tree/gl-file-tree-pane.js';
import '../../../shared/components/details-header/gl-details-header.js';
import './gl-commit-row-item.js';

@customElement('gl-details-multicommit-panel')
export class GlDetailsMultiCommitPanel extends LitElement {
	static override styles = [
		elementBase,
		metadataBarVarsBase,
		panelHostStyles,
		panelActionInputStyles,
		multiCommitPanelStyles,
		subPanelEnterStyles,
		scrollableBase,
	];

	@property({ type: Object })
	commitFrom?: CommitDetails;

	@property({ type: Object })
	commitTo?: CommitDetails;

	/** Persisted preference threaded through to the inner `gl-file-tree-pane`. */
	@property({ type: Boolean, attribute: 'show-search-box' })
	showSearchBox?: boolean;

	/** Persisted preference threaded through to the inner `gl-file-tree-pane`. */
	@property({ type: Boolean, attribute: 'search-box-filter' })
	searchBoxFilter?: boolean;

	@property({ type: Array })
	files?: readonly GitFileChangeShape[];

	@property({ type: Object })
	stats?: GitCommitStats;

	@property({ type: Boolean })
	swapped = false;

	@property({ type: Array })
	autolinks?: Autolink[];

	@property({ type: Boolean })
	autolinksLoading = false;

	@property({ type: Boolean })
	loading = false;

	@property({ type: Number })
	betweenCount = 0;

	@property({ type: Object })
	signatureFrom?: CommitSignatureShape;

	@property({ type: Object })
	signatureTo?: CommitSignatureShape;

	@property({ type: Boolean })
	autolinksEnabled = false;

	@property({ type: Array })
	enrichedItems?: IssueOrPullRequest[];

	@property({ type: Boolean })
	enrichmentLoading = false;

	@property({ type: Boolean })
	explainBusy = false;

	@property({ type: Boolean })
	generateChangelogBusy = false;

	@property({ type: Boolean })
	aiEnabled = false;

	@property()
	activeMode?: 'review' | 'compose' | null;

	@property({ attribute: false })
	modeStatus?: Partial<Record<'review' | 'compose', { execState: RunningOperationExecState; hasResult: boolean }>>;

	/** Pre-computed snippet shown in the metadata bar while in mode — mirrors WIP / commit. */
	@property({ attribute: false }) modeStatusText?: string | import('lit').TemplateResult;

	/** Forwarded to `gl-details-header` — when true, close becomes a back arrow. */
	@property({ type: Boolean }) inResultsView = false;

	@property({ attribute: false })
	subPanelContent?: ReturnType<typeof html> | typeof nothing;

	@property({ type: Object })
	preferences?: Preferences;

	@property({ type: Object })
	orgSettings?: State['orgSettings'];

	@property({ type: Object })
	aiModel?: AiModelInfo;

	@property({ type: Boolean, attribute: 'file-icons' })
	fileIcons = false;

	@property({ type: Boolean, attribute: 'files-collapsable' })
	filesCollapsable = true;

	@property({ type: Boolean })
	hasAccount = false;

	@property({ type: Boolean })
	hasIntegrationsConnected = false;

	@property({ type: Object })
	searchContext?: GitCommitSearchContext;

	@state() private _enrichmentNoneFound = false;

	/** Mirrors the pane's multi-selection so the "Open Changes" chip can swap to "Open Selected". */
	@state() private _selectedFiles: readonly GitFileChangeShape[] = [];
	private _enrichmentNoneFoundTimer?: ReturnType<typeof setTimeout>;

	/**
	 * True when the comparison identity (which commits are being compared) just changed and the
	 * new fetch hasn't returned yet. Distinct from `loading`, which fires for any refresh —
	 * this only fires when the *content* is genuinely going to change.
	 */
	@state() private _comparisonChanging = false;

	override connectedCallback(): void {
		super.connectedCallback?.();
		this.setAttribute('role', 'region');
		this.setAttribute('aria-label', 'Comparing commits');
	}

	override disconnectedCallback(): void {
		super.disconnectedCallback?.();
		clearTimeout(this._enrichmentNoneFoundTimer);
	}

	protected override willUpdate(changedProperties: PropertyValues): void {
		super.willUpdate(changedProperties);

		const identityChanged =
			changedProperties.has('commitFrom') ||
			changedProperties.has('commitTo') ||
			changedProperties.has('swapped');
		if (identityChanged && this.loading) {
			this._comparisonChanging = true;
		} else if (changedProperties.has('loading') && !this.loading) {
			this._comparisonChanging = false;
		}

		if (changedProperties.has('enrichedItems')) {
			clearTimeout(this._enrichmentNoneFoundTimer);

			const prev = changedProperties.get('enrichedItems') as IssueOrPullRequest[] | undefined;
			// Transition from undefined (not yet loaded) to empty array (none found)
			if (prev === undefined && this.enrichedItems?.length === 0) {
				this._enrichmentNoneFound = true;
				this._enrichmentNoneFoundTimer = setTimeout(() => {
					this._enrichmentNoneFound = false;
				}, 3000);
			} else {
				this._enrichmentNoneFound = false;
			}
		}
	}

	override render() {
		const isInitialLoad = this.loading && !this.commitFrom && !this.commitTo;
		// Once the commit poles paint from lite/cached shells, `isInitialLoad` is false even though
		// the comparison diff is still in flight — show a spinner in the file tree rather than the
		// "No Files" empty text. Mirrors gl-details-compare-mode-panel's files-section loading.
		const filesLoadingEmpty = this._comparisonChanging && !this.files?.length;
		// Use a single template so the header element persists across mode toggles,
		// allowing the CSS transition on .mode-header to animate. `cache` keeps both
		// body sub-trees alive across sub-panel toggles so the explain input and file
		// tree retain focus, scroll position, and expansion state.
		const hasSubPanel = this.subPanelContent != null && this.subPanelContent !== nothing;

		// Compose mode replaces the comparison metadata strip with its own plan UI. Review mode
		// and the implicit no-mode default share the same comparison context as the host and
		// keep the metadata bar visible above the sub-panel.
		const showMetadataBar = this.activeMode !== 'compose';

		return html`
			${isInitialLoad
				? html`<div class="details-loading" aria-busy="true" aria-live="polite">Loading...</div>`
				: html`
						${this.renderCompareHeader()} ${showMetadataBar ? this.renderMetadataBar() : nothing}
						${cache(
							hasSubPanel
								? html`<div class="sub-panel-enter">${this.subPanelContent}</div>`
								: html`<div class="compare-section">
											${this.renderPoles()} ${this.renderAutolinksRow()} ${this.renderAIActions()}
										</div>
										<div class="compare-files">
											<webview-pane-group flexible>
												<gl-file-tree-pane
													.files=${this.files}
													.filesLayout=${this.preferences?.files}
													.showIndentGuides=${this.preferences?.indentGuides}
													.collapsable=${this.filesCollapsable}
													?show-file-icons=${this.fileIcons}
													.fileActions=${GlDetailsMultiCommitPanel._fileActions}
													.fileContext=${this.getFileContext}
													.folderContext=${(folder: { relativePath: string }) =>
														buildFolderContext(this.commitTo?.repoPath, folder)}
													.searchContext=${this.searchContext}
													.showSearchBox=${this.showSearchBox}
													.searchBoxFilter=${this.searchBoxFilter}
													empty-text=${filesLoadingEmpty ? '' : 'No Files'}
													?multi-selectable=${true}
													@file-compare-previous=${this.handleFileCompareBetween}
													@file-open=${this.redispatch}
													@file-compare-working=${this.redispatch}
													@file-more-actions=${this.redispatch}
													@change-files-layout=${this.redispatch}
													@file-selection-changed=${this.handleFileSelectionChanged}
												>
													${this.getMultiDiffRefs() != null
														? renderOpenChangesAction({
																selectedCount: this._selectedFiles.length,
																slot: 'leading-actions',
																onOpenAll: () => this.handleOpenMultiDiff(),
																onOpenSelected: () => this.handleOpenSelectedChanges(),
															})
														: nothing}
													${(() => {
														const refs = this.getMultiDiffRefs();
														return refs != null
															? renderCopyChangesAction({
																	repoPath: refs.repoPath,
																	to: refs.rhs,
																	from: refs.lhs || undefined,
																	slot: 'leading-actions',
																})
															: nothing;
													})()}
													${filesLoadingEmpty
														? html`<div
																slot="before-tree"
																class="compare-files--loading"
																aria-busy="true"
															>
																<code-icon icon="loading" modifier="spin"></code-icon>
																<span>Loading changes…</span>
															</div>`
														: nothing}
												</gl-file-tree-pane>
											</webview-pane-group>
										</div>`,
						)}
					`}
		`;
	}

	private static readonly _fileActions: TreeItemAction[] = [
		{
			icon: 'go-to-file',
			label: 'Open File',
			action: 'file-open',
		},
		{
			icon: 'git-compare',
			label: 'Open Changes with Working File',
			action: 'file-compare-working',
		},
	];

	private getFileContext = (file: GitFileChangeShape): string | undefined => {
		const sha = this.commitTo?.sha;
		const repoPath = this.commitTo?.repoPath;
		if (!sha || !repoPath) return undefined;

		const context: DetailsItemTypedContext = {
			webviewItem: 'gitlens:file:comparison',
			webviewItemValue: {
				type: 'file',
				path: file.path,
				repoPath: repoPath,
				sha: sha,
				comparisonSha: this.commitFrom?.sha,
				status: file.status,
			},
		};

		return serializeWebviewItemContext(context);
	};

	private handleFileCompareBetween(e: CustomEvent<FileChangeListItemDetail>) {
		this.dispatchEvent(
			new CustomEvent('file-compare-between', { detail: e.detail, bubbles: true, composed: true }),
		);
	}

	private getMultiDiffRefs(): { repoPath: string; lhs: string; rhs: string; title?: string } | undefined {
		const files = this.files;
		if (!files?.length) return undefined;

		const repoPath = this.commitFrom?.repoPath ?? this.commitTo?.repoPath;
		const lhs = this.swapped ? this.commitTo?.sha : this.commitFrom?.sha;
		const rhs = this.swapped ? this.commitFrom?.sha : this.commitTo?.sha;
		if (!repoPath || !lhs || !rhs) return undefined;

		return {
			repoPath: repoPath,
			lhs: lhs,
			rhs: rhs,
			title: `Changes between ${shortenRevision(lhs)} and ${shortenRevision(rhs)}`,
		};
	}

	private handleOpenMultiDiff = (): void => {
		const refs = this.getMultiDiffRefs();
		const files = this.files;
		if (!refs || !files?.length) return;

		this.dispatchEvent(
			new CustomEvent('open-multiple-changes', {
				detail: {
					files: files,
					repoPath: refs.repoPath,
					lhs: refs.lhs,
					rhs: refs.rhs,
					title: refs.title,
				} satisfies OpenMultipleChangesArgs,
				bubbles: true,
				composed: true,
			}),
		);
	};

	private handleFileSelectionChanged = (e: CustomEvent<{ files: readonly GitFileChangeShape[] }>): void => {
		this._selectedFiles = e.detail?.files ?? [];
	};

	private handleOpenSelectedChanges = (): void => {
		const refs = this.getMultiDiffRefs();
		const selectedPaths = new Set(this._selectedFiles.map(f => f.path));
		const files = (this.files ?? []).filter(f => selectedPaths.has(f.path));
		if (!refs || !files.length) return;

		this.dispatchEvent(
			new CustomEvent('open-multiple-changes', {
				detail: {
					files: files,
					repoPath: refs.repoPath,
					lhs: refs.lhs,
					rhs: refs.rhs,
					title: refs.title,
				} satisfies OpenMultipleChangesArgs,
				bubbles: true,
				composed: true,
			}),
		);
	};

	private redispatch = redispatch.bind(this);

	private renderCompareHeader() {
		// Review is the only mode here. Compare is invoked separately as a sheet — the header
		// renders a grouped Compare entry-point chip (via `compareEnabled`) when no mode is
		// active so the user can pivot the existing comparison through the compare-refs picker.
		const modes = this.aiEnabled ? (['review'] as const) : ([] as const);
		return html`<gl-details-header
			.activeMode=${this.activeMode}
			.modeStatus=${this.modeStatus}
			.loading=${this.loading}
			.modes=${modes}
			.compareEnabled=${true}
			?in-results-view=${this.inResultsView}
		>
			<span class="compare-header__title">
				${this.activeMode === 'review'
					? html`<span>
							<code-icon class="compare-header__mode-icon" icon="checklist"></code-icon>
							Reviewing Comparison
						</span>`
					: html`Comparing References`}
			</span>
		</gl-details-header>`;
	}

	private renderMetadataBar() {
		const fromSha = this.commitFrom?.sha;
		const toSha = this.commitTo?.sha;
		if (!fromSha || !toSha) return nothing;

		return html`<div class="compare-metadata">
			<div class="compare-metadata__left">
				<gl-commit-sha-copy
					class="compare-metadata__sha"
					appearance="toolbar"
					tooltip-placement="bottom"
					copy-label="Copy SHA"
					copied-label="Copied!"
					.sha=${fromSha}
					icon="git-commit"
				></gl-commit-sha-copy>
				<span class="compare-metadata__dots">..</span>
				<gl-commit-sha-copy
					class="compare-metadata__sha"
					appearance="toolbar"
					tooltip-placement="bottom"
					copy-label="Copy SHA"
					copied-label="Copied!"
					.sha=${toSha}
					icon="git-commit"
				></gl-commit-sha-copy>
			</div>
			<div class="compare-metadata__right">
				${this.modeStatusText
					? html`<span class="mode-status">${this.modeStatusText}</span>`
					: this.renderCommitStats(this.stats)}
			</div>
		</div>`;
	}

	private renderPoles() {
		return html`<div class="compare-poles">
			${this.renderPoleCard(this.commitFrom, this.signatureFrom)}
			<div class="compare-middle">
				<div class="compare-middle__line">
					<div class="compare-middle__rule"></div>
					<gl-tooltip content="Swap Direction" placement="bottom">
						<button
							class="compare-middle__swap"
							aria-label="Swap comparison direction"
							@click=${this.handleSwap}
						>
							<code-icon icon="arrow-swap"></code-icon>
						</button>
					</gl-tooltip>
					<div class="compare-middle__rule"></div>
				</div>
				${this.betweenCount > 0
					? html`<span class="compare-middle__count"
							>${pluralize('commit', this.betweenCount)} in between</span
						>`
					: nothing}
			</div>
			${this.renderPoleCard(this.commitTo, this.signatureTo)}
		</div>`;
	}

	private renderPoleCard(commit: CommitDetails | undefined, signature?: CommitSignatureShape) {
		if (!commit) return html`<div class="pole-card pole-card--loading">Loading...</div>`;

		const showSignature = this.preferences?.showSignatureBadges && signature != null;

		// Overlay the committer avatar only when the committer differs from the author (the
		// gl-commit-author convention), so ordinary author==committer commits stay un-cluttered.
		const committerEmail = commit.committer?.email;
		// Distinct committer = different name OR email (mirrors gl-commit-author.hasDistinctCommitter).
		const hasDistinctCommitter =
			(commit.committer?.name != null && commit.committer.name !== commit.author.name) ||
			(committerEmail != null && committerEmail.toLowerCase() !== commit.author.email?.toLowerCase());

		// Map CommitDetails into the shared CommitRowData shape consumed by gl-commit-row-item.
		const rowData = {
			sha: commit.sha,
			shortSha: commit.shortSha,
			message: commit.message,
			author: commit.author.name,
			authorEmail: commit.author.email,
			avatarUrl: commit.author.avatar ?? undefined,
			committerAvatarUrl: hasDistinctCommitter ? (commit.committer?.avatar ?? undefined) : undefined,
			committerName: hasDistinctCommitter ? commit.committer?.name : undefined,
			committerEmail: hasDistinctCommitter ? committerEmail : undefined,
			committerDate: hasDistinctCommitter
				? typeof commit.committer?.date === 'string'
					? commit.committer.date
					: commit.committer?.date?.toISOString?.()
				: undefined,
			date:
				typeof commit.author.date === 'string'
					? commit.author.date
					: (commit.author.date.toISOString?.() ?? ''),
		};

		return html`<gl-commit-row-item
			class="pole-card"
			.commit=${rowData}
			.preferences=${this.preferences}
			.signature=${showSignature ? signature : undefined}
			committer-email=${ifDefined(commit.committer?.email)}
			@gl-commit-row-item-select=${() => this.handlePoleClick(commit.sha)}
		></gl-commit-row-item>`;
	}

	private _cachedMergedAutolinks?: {
		autolinksRef: Autolink[] | undefined;
		enrichedRef: IssueOrPullRequest[] | undefined;
		out: { autolinks: Autolink[]; enriched: IssueOrPullRequest[] };
	};

	private getMergedAutolinks() {
		const autolinks = this.autolinks;
		const enriched = this.enrichedItems;

		const cached = this._cachedMergedAutolinks;
		if (cached != null && cached.autolinksRef === autolinks && cached.enrichedRef === enriched) {
			return cached.out;
		}

		let out: { autolinks: Autolink[]; enriched: IssueOrPullRequest[] };
		if (!enriched?.length) {
			out = { autolinks: autolinks ?? [], enriched: [] };
		} else {
			const enrichedIds = new Set(enriched.map(i => i.id));
			const remaining = autolinks?.filter(a => !enrichedIds.has(a.id)) ?? [];
			out = { autolinks: remaining, enriched: enriched };
		}
		this._cachedMergedAutolinks = { autolinksRef: autolinks, enrichedRef: enriched, out: out };
		return out;
	}

	private renderAutolinksRow() {
		const { autolinks, enriched } = this.getMergedAutolinks();
		const hasAutolinks = autolinks.length > 0;
		const hasEnriched = enriched.length > 0;
		const hasChips = hasAutolinks || hasEnriched;
		// Show the loading state until BOTH the comparison fetch AND the autolinks fetch settle —
		// the autolinks request is fired after `compare` resolves, so there's a window where
		// `_comparisonChanging` is false but chips haven't arrived yet. Without `autolinksLoading`
		// the strip flashes "Learn about autolinks" before the chips populate.
		const isLoadingEmpty = (this._comparisonChanging || this.autolinksLoading) && !hasChips;

		return html`<div class="compare-enrichment">
			<gl-chip-overflow max-rows="1">
				${hasChips
					? nothing
					: isLoadingEmpty
						? html`<span slot="prefix" class="compare-enrichment__loading" aria-busy="true">
								<code-icon icon="loading" modifier="spin"></code-icon>
								<span>Loading autolinks…</span>
							</span>`
						: renderLearnAboutAutolinks({
								hasIntegrationsConnected: this.hasIntegrationsConnected,
								hasAccount: this.hasAccount,
								showLabel: true,
								slotName: 'prefix',
							})}
				${hasAutolinks
					? autolinks.map(autolink => {
							const name = autolink.description ?? autolink.title ?? `${autolink.prefix}${autolink.id}`;
							return html`<gl-autolink-chip
								type="autolink"
								name=${name}
								url=${autolink.url}
								identifier="${autolink.prefix}${autolink.id}"
								openOnRemote
							></gl-autolink-chip>`;
						})
					: nothing}
				${hasEnriched
					? enriched.map(
							item =>
								html`<gl-autolink-chip
									type=${item.type === 'pullrequest' ? 'pr' : 'issue'}
									name=${item.title}
									url=${item.url}
									identifier="#${item.id}"
									status=${item.state}
									.date=${item.closed ? item.closedDate : item.createdDate}
									.dateFormat=${this.preferences?.dateFormat}
									.dateStyle=${this.preferences?.dateStyle}
									.itemId=${item.id}
									.providerId=${item.provider?.id}
									?details=${item.type === 'pullrequest'}
									openOnRemote
								></gl-autolink-chip>`,
						)
					: nothing}
				${this.renderAutolinksPopover(autolinks, enriched)} ${this.renderEnrichButton()}
				${hasChips
					? renderLearnAboutAutolinks({
							hasIntegrationsConnected: this.hasIntegrationsConnected,
							hasAccount: this.hasAccount,
							slotName: 'suffix',
						})
					: nothing}
			</gl-chip-overflow>
		</div>`;
	}

	private renderAutolinksPopover(autolinks: Autolink[], enriched: IssueOrPullRequest[]) {
		if (!autolinks.length && !enriched.length) return nothing;

		const enrichedPrs = enriched.filter(i => i.type === 'pullrequest');
		const enrichedIssues = enriched.filter(i => i.type !== 'pullrequest');
		let needsDivider = false;

		return html`<div slot="popover">
			${enrichedPrs.length > 0
				? html`<menu-label>Pull Requests</menu-label> ${enrichedPrs.map(
							pr =>
								html`<menu-item href=${pr.url}>
									<code-icon icon="git-pull-request"></code-icon> #${pr.id}
									${pr.title ? ` — ${pr.title}` : ''}
								</menu-item>`,
						)}${((needsDivider = true), nothing)}`
				: nothing}
			${enrichedIssues.length > 0
				? html`${needsDivider ? html`<menu-divider></menu-divider>` : nothing}
						<menu-label>Issues</menu-label>
						${enrichedIssues.map(
							issue =>
								html`<menu-item href=${issue.url}>
									<code-icon icon="issues"></code-icon> #${issue.id}
									${issue.title ? ` — ${issue.title}` : ''}
								</menu-item>`,
						)}${((needsDivider = true), nothing)}`
				: nothing}
			${autolinks.length > 0
				? html`${needsDivider ? html`<menu-divider></menu-divider>` : nothing}
						<menu-label>Autolinks</menu-label>
						${autolinks.map(
							a =>
								html`<menu-item href=${a.url}>
									<code-icon icon="link"></code-icon> ${a.prefix}${a.id}${a.provider?.name
										? ` on ${a.provider.name}`
										: ''}
								</menu-item>`,
						)}`
				: nothing}
		</div>`;
	}

	private renderEnrichButton() {
		if (!this.hasIntegrationsConnected) return nothing;

		if (this._enrichmentNoneFound) {
			return html`<gl-action-chip
				slot="suffix"
				icon="info"
				label="No Additional Issues or Pull Requests Found"
				overlay="tooltip"
			></gl-action-chip>`;
		}

		if (this.enrichedItems != null) return nothing;

		if (this.enrichmentLoading) {
			return html`<gl-action-chip
				slot="suffix"
				icon="loading"
				label="Loading Issues and Pull Requests..."
				overlay="tooltip"
				disabled
			></gl-action-chip>`;
		}

		return html`<gl-action-chip
			slot="suffix"
			icon="sync"
			label="Load Associated Issues and Pull Requests"
			overlay="tooltip"
			@click=${this.handleEnrichAutolinks}
		></gl-action-chip>`;
	}

	private handleEnrichAutolinks() {
		this.dispatchEvent(new CustomEvent('enrich-autolinks', { bubbles: true, composed: true }));
	}

	private renderAIActions() {
		if (this.orgSettings?.ai === false) return nothing;

		return html`<gl-compare-ai-actions
			.explainBusy=${this.explainBusy}
			.generateChangelogBusy=${this.generateChangelogBusy}
			.orgSettings=${this.orgSettings}
			.aiModel=${this.aiModel}
		></gl-compare-ai-actions>`;
	}

	private renderCommitStats(stats?: GitCommitStats, appearance?: 'pill') {
		if (stats?.files == null) return undefined;

		if (typeof stats.files === 'number') {
			return html`<commit-stats
				modified="${stats.files}"
				additions="${stats.additions ?? nothing}"
				deletions="${stats.deletions ?? nothing}"
				symbol="icons"
				appearance="${appearance ?? nothing}"
			></commit-stats>`;
		}

		const { added, deleted, changed } = stats.files;
		return html`<commit-stats
			added="${added}"
			modified="${changed}"
			removed="${deleted}"
			additions="${stats.additions ?? nothing}"
			deletions="${stats.deletions ?? nothing}"
			symbol="icons"
			appearance="${appearance ?? nothing}"
		></commit-stats>`;
	}

	private handlePoleClick(sha: string) {
		this.dispatchEvent(new CustomEvent('select-commit', { detail: { sha: sha }, bubbles: true, composed: true }));
	}

	private handleSwap() {
		// Swaps the multi-commit compare selection order (from/to). Distinct from
		// `swap-refs` in the compare panel, which swaps ahead/behind refs.
		this.dispatchEvent(new CustomEvent('swap-selection', { bubbles: true, composed: true }));
	}
}
