import type { AIModel } from '@gitlens/ai/models/model.js';
import type { GitCommitIdentityShape } from '@gitlens/git/models/commit.js';
import type { Source, Sources } from '../../../constants.telemetry.js';
import type { RepositoryShape } from '../../../git/models/repositoryShape.js';
import type { IpcScope } from '../../ipc/models/ipc.js';
import { IpcCommand, IpcNotification } from '../../ipc/models/ipc.js';
import type { WebviewState } from '../../protocol.js';

export const scope: IpcScope = 'composer';

export const currentOnboardingVersion = '1.0.0'; // Update this when onboarding changes

export interface ComposerHunk extends ComposerHunkBase {
	diffHeader: string; // Git diff header (e.g., "diff --git a/file.ts b/file.ts")
	hunkHeader: string; // Hunk header (e.g., "@@ -1,5 +1,7 @@") or "rename" for rename hunks
	content: string; // The actual diff content (lines starting with +, -, or space) or rename info
}

export interface ComposerHunkBase {
	index: number; // Unique hunk index (1-based to match hunkMap)
	fileName: string;
	additions: number;
	deletions: number;
	// Known values:
	//   - 'staged' / 'unstaged' / 'commits' — diff-collection layer (used by the webview composer's
	//     own `createHunksFromDiffs`)
	//   - 'unknown' — set when the hunk came from compose-tools, which no longer attaches per-hunk
	//     source-layer info (combined-diff collection collapses staged/unstaged/untracked/branch
	//     into one bucket). UI categorizers should treat 'unknown' as "we don't know which layer"
	//     and avoid asserting staged/unstaged badges off of it.
	//   - any other string — historically a commit SHA for branch-source hunks. Reserved for the
	//     non-compose-tools paths; the compose-tools path uses 'unknown' instead.
	source: 'staged' | 'unstaged' | 'commits' | 'unknown' | string;
	assigned?: boolean; // True when this hunk's index is in any commit's hunkIndices array
	isRename?: boolean; // True for rename-only hunks
	originalFileName?: string; // Original filename for renames
	author?: GitCommitIdentityShape; // Author of the commit this hunk belongs to, if any
	coAuthors?: GitCommitIdentityShape[]; // Co-authors of the commit this hunk belongs to, if any
}

export interface ComposerCommitMessage {
	content: string;
	isGenerated: boolean;
}

export interface ComposerCommit {
	id: string;
	message: ComposerCommitMessage;
	sha?: string; // Optional SHA for existing commits
	aiExplanation?: string;
	hunkIndices: number[]; // References to hunk indices in the hunk map
	locked?: boolean; // True if this commit is fully locked (cannot be reordered, edited, or have hunks reassigned)
}

// Remove callbacks - use IPC instead

export interface ComposerBaseCommit {
	sha: string;
	message: string;
	repoName: string;
	branchName?: string;
}

export interface ComposerSafetyState {
	repoPath: string;
	branchName?: string;
	headSha: string | null;
	baseSha: string | null;
	// branchName: string;
	// branchRefSha: string;
	// worktreeName: string;
	hashes: {
		staged: string | null;
		unstaged: string | null;
		unified: string | null;
		commits?: string | null; // Combined diff hash for branch mode
	};

	// stagedDiff: string | null; // null if no staged changes when composer opened
	// unstagedDiff: string | null; // null if no unstaged changes when composer opened
	// unifiedDiff: string | null; // null if no changes when composer opened
	// timestamp: number;
}

export interface State extends WebviewState<'gitlens.composer'> {
	// data model
	hunks: ComposerHunk[];

	commits: ComposerCommit[];
	baseCommit: ComposerBaseCommit | null;

	// UI state
	detailsSectionExpanded: {
		commitMessage: boolean;
		aiExplanation: boolean;
		filesChanged: boolean;
	};
	generatingCommits: boolean;
	/**
	 * Human-readable status of the currently running compose phase (e.g. "Analyzing changes…").
	 * Emitted by the library route's onProgress hook. Null when no granular progress is available
	 * (legacy AI path, or before the first phase event arrives).
	 */
	generatingCommitsStatus: string | null;
	generatingCommitMessage: string | null; // commitId of the commit currently generating a message, or null
	committing: boolean; // true when finish and commit is in progress
	safetyError: string | null; // error message if safety validation failed, or null
	loadingError: string | null; // error message if there was an error loading the webview, or null
	aiOperationError: { operation: string; error?: string } | null; // error message if AI operation failed, or null

	// AI composition state
	hasUsedAutoCompose: boolean; // true if auto-compose has been successfully used at least once
	autoComposeInstructions?: string; // optional instructions to trigger auto-compose on load

	// Content state
	hasChanges: boolean; // true if there are working directory changes to compose
	workingDirectoryHasChanged: boolean; // true if working directory has changed since composer opened
	indexHasChanged: boolean; // true if index has changed since composer opened

	// Mode controls
	mode: 'experimental' | 'preview'; // experimental = normal mode, preview = locked AI preview mode
	recompose: {
		enabled: boolean; // true if composer is in recompose mode
		branchName?: string; // name of the branch being recomposed
		locked: boolean; // true if commits are locked (cannot be reordered/edited)
		commitShas?: string[]; // Optional: specific commit SHAs selected for recompose (if not all commits)
	} | null;

	// AI settings
	aiEnabled: {
		org: boolean;
		config: boolean;
	};
	ai: {
		model: AIModel | undefined;
	};
	onboardingDismissed: boolean;
	// only needed when multiple repositories are open
	repositoryState?: {
		current: RepositoryShape;
		hasMultipleRepositories: boolean;
	};
}

export const initialState: Omit<State, keyof WebviewState<'gitlens.composer'>> = {
	hunks: [],
	commits: [],
	baseCommit: null,
	detailsSectionExpanded: {
		commitMessage: true,
		aiExplanation: true,
		filesChanged: true,
	},
	generatingCommits: false,
	generatingCommitsStatus: null,
	generatingCommitMessage: null,
	committing: false,
	safetyError: null,
	loadingError: null,
	aiOperationError: null,
	hasUsedAutoCompose: false,
	hasChanges: true,
	workingDirectoryHasChanged: false,
	indexHasChanged: false,
	mode: 'preview',
	recompose: null,
	aiEnabled: {
		org: false,
		config: false,
	},
	ai: {
		model: undefined,
	},
	onboardingDismissed: false,
};

export interface ComposerContext {
	sessionStart: string; // timestamp when the session started
	sessionDuration: number | undefined; // Only populated as user is exiting composer session
	diff: {
		files: number;
		hunks: number;
		lines: number;
		hash: string;
		staged: boolean;
		unstaged: boolean;
		commits: boolean;
		unstagedIncluded: boolean;
	};
	commits: {
		initialCount: number;
		autoComposedCount: number | undefined; // What the auto-compose did
		composedCount: number | undefined; // Anything the user did outside of auto-compose
		finalCount: number | undefined;
	};
	ai: {
		enabled: {
			org: boolean;
			config: boolean;
		};
		model: AIModel | undefined;
	};
	onboarding: {
		stepReached: number | undefined;
		dismissed: boolean;
	};
	operations: {
		generateCommits: {
			count: number;
			cancelledCount: number;
			errorCount: number;
			feedback: {
				upvoteCount: number;
				downvoteCount: number;
			};
		};
		generateCommitMessage: {
			count: number;
			cancelledCount: number;
			errorCount: number;
		};
		finishAndCommit: {
			errorCount: number;
		};
		undo: {
			count: number;
		};
		redo: {
			count: number;
		};
		reset: {
			count: number;
		};
	};
	source: Source | undefined;
	mode: 'experimental' | 'preview';
	errors: {
		safety: {
			count: number;
		};
		operation: {
			count: number;
		};
	};
	warnings: {
		workingDirectoryChanged: boolean;
		indexChanged: boolean;
	};
}

export const baseContext: ComposerContext = {
	sessionStart: '',
	sessionDuration: undefined,
	diff: {
		files: 0,
		hunks: 0,
		lines: 0,
		hash: '',
		staged: false,
		unstaged: false,
		commits: false,
		unstagedIncluded: false,
	},
	commits: {
		initialCount: 0,
		autoComposedCount: undefined,
		composedCount: undefined,
		finalCount: undefined,
	},
	ai: {
		enabled: {
			org: false,
			config: false,
		},
		model: undefined,
	},
	onboarding: {
		dismissed: false,
		stepReached: undefined,
	},
	operations: {
		generateCommits: {
			count: 0,
			cancelledCount: 0,
			errorCount: 0,
			feedback: {
				upvoteCount: 0,
				downvoteCount: 0,
			},
		},
		generateCommitMessage: {
			count: 0,
			cancelledCount: 0,
			errorCount: 0,
		},
		finishAndCommit: {
			errorCount: 0,
		},
		undo: {
			count: 0,
		},
		redo: {
			count: 0,
		},
		reset: {
			count: 0,
		},
	},
	source: undefined,
	mode: 'preview',
	errors: {
		safety: { count: 0 },
		operation: { count: 0 },
	},
	warnings: {
		workingDirectoryChanged: false,
		indexChanged: false,
	},
};

// Commands that can be sent from the webview to the host

// Notifications that can be sent from the host to the webview
export interface DidChangeComposerDataParams {
	hunks: ComposerHunk[];
	commits: ComposerCommit[];
	baseCommit: ComposerBaseCommit | null;
}

// IPC Commands and Notifications
const ipcScope = 'composer';

// Commands sent from webview to host
export const GenerateCommitsCommand = new IpcCommand<GenerateCommitsParams>(ipcScope, 'generateCommits');
export const GenerateCommitMessageCommand = new IpcCommand<GenerateCommitMessageParams>(
	ipcScope,
	'generateCommitMessage',
);
export const FinishAndCommitCommand = new IpcCommand<FinishAndCommitParams>(ipcScope, 'finishAndCommit');
export const CloseComposerCommand = new IpcCommand(ipcScope, 'close');
export const ReloadComposerCommand = new IpcCommand<ReloadComposerParams>(ipcScope, 'reload');
export const CancelGenerateCommitsCommand = new IpcCommand(ipcScope, 'cancelGenerateCommits');
export const CancelGenerateCommitMessageCommand = new IpcCommand(ipcScope, 'cancelGenerateCommitMessage');
export const CancelFinishAndCommitCommand = new IpcCommand(ipcScope, 'cancelFinishAndCommit');
export const ClearAIOperationErrorCommand = new IpcCommand(ipcScope, 'clearAIOperationError');
export const OnSelectAIModelCommand = new IpcCommand(ipcScope, 'selectAIModel');
export interface AIFeedbackParams {
	sessionId: string | null;
}

export const AIFeedbackHelpfulCommand = new IpcCommand<AIFeedbackParams>(ipcScope, 'aiFeedbackHelpful');
export const AIFeedbackUnhelpfulCommand = new IpcCommand<AIFeedbackParams>(ipcScope, 'aiFeedbackUnhelpful');

export const OpenOnboardingCommand = new IpcCommand(ipcScope, 'openOnboarding');
export const DismissOnboardingCommand = new IpcCommand(ipcScope, 'dismissOnboarding');
export const AdvanceOnboardingCommand = new IpcCommand<{ stepNumber: number }>(ipcScope, 'advanceOnboarding');

export const ChooseRepositoryCommand = new IpcCommand(scope, 'chooseRepository');

// Commands intended only to update context/telemetry
export interface OnAddHunksToCommitParams {
	source: string;
}
export const OnAddHunksToCommitCommand = new IpcCommand<OnAddHunksToCommitParams>(ipcScope, 'onAddHunksToCommit');
export const OnUndoCommand = new IpcCommand(ipcScope, 'onUndo');
export const OnRedoCommand = new IpcCommand(ipcScope, 'onRedo');
export const OnResetCommand = new IpcCommand(ipcScope, 'onReset');

// Notifications sent from host to webview
export const DidStartGeneratingNotification = new IpcNotification(ipcScope, 'didStartGenerating');
export interface DidProgressGeneratingCommitsParams {
	/** Machine-identifier for the phase (e.g. 'analyzing', 'grouping', 'ordering'). */
	phase: string;
	/** Human-readable label to show in the loading dialog (e.g. 'Analyzing changes…'). */
	message: string;
}
export const DidProgressGeneratingCommitsNotification = new IpcNotification<DidProgressGeneratingCommitsParams>(
	ipcScope,
	'didProgressGeneratingCommits',
);
export const DidStartGeneratingCommitMessageNotification = new IpcNotification<{ commitId: string }>(
	ipcScope,
	'didStartGeneratingCommitMessage',
);
export const DidGenerateCommitsNotification = new IpcNotification<DidGenerateCommitsParams>(
	ipcScope,
	'didGenerateCommits',
);
export const DidGenerateCommitMessageNotification = new IpcNotification<DidGenerateCommitMessageParams>(
	ipcScope,
	'didGenerateCommitMessage',
);
export const DidStartCommittingNotification = new IpcNotification(ipcScope, 'didStartCommitting');
export const DidFinishCommittingNotification = new IpcNotification(ipcScope, 'didFinishCommitting');
export const DidSafetyErrorNotification = new IpcNotification<DidSafetyErrorParams>(ipcScope, 'didSafetyError');
export const DidReloadComposerNotification = new IpcNotification<DidReloadComposerParams>(
	ipcScope,
	'didReloadComposer',
);
export const DidLoadingErrorNotification = new IpcNotification<DidLoadingErrorParams>(ipcScope, 'didLoadingError');
export const DidWorkingDirectoryChangeNotification = new IpcNotification(ipcScope, 'didWorkingDirectoryChange');
export const DidIndexChangeNotification = new IpcNotification(ipcScope, 'didIndexChange');
export const DidCancelGenerateCommitsNotification = new IpcNotification(ipcScope, 'didCancelGenerateCommits');
export const DidCancelGenerateCommitMessageNotification = new IpcNotification(
	ipcScope,
	'didCancelGenerateCommitMessage',
);
export interface DidErrorAIOperationParams {
	operation: string;
	error?: string;
}
export const DidErrorAIOperationNotification = new IpcNotification<DidErrorAIOperationParams>(
	ipcScope,
	'didErrorAIOperation',
);
export const DidClearAIOperationErrorNotification = new IpcNotification(ipcScope, 'didClearAIOperationError');
export const DidChangeAiEnabledNotification = new IpcNotification<DidChangeAiEnabledParams>(
	ipcScope,
	'didChangeAiEnabled',
);
export const DidChangeAiModelNotification = new IpcNotification<DidChangeAiModelParams>(ipcScope, 'didChangeAiModel');

// Parameters for IPC messages
export interface GenerateCommitsParams {
	hunkIndices: number[];
	commits: ComposerCommit[];
	baseCommit: ComposerBaseCommit | null;
	commitsToReplace?: {
		commits: { id: string; sha?: string; hunkIndices: number[] }[];
		baseShaForNewDiff?: string;
	};
	customInstructions?: string;
	isRecompose?: boolean;
}

export interface GenerateCommitMessageParams {
	commitId: string;
	commitHunkIndices: number[];
	overwriteExistingMessage?: boolean;
}

export interface FinishAndCommitParams {
	commits: ComposerCommit[];
	baseCommit: ComposerBaseCommit | null;
}

export interface ReloadComposerParams {
	repoPath?: string;
	mode?: 'experimental' | 'preview';
	source?: Sources;
}

export interface DidGenerateCommitsParams {
	commits: ComposerCommit[];
	replacedCommitIds?: string[];
	hunks?: ComposerHunk[];
}

export interface DidGenerateCommitMessageParams {
	commitId: string;
	message: string;
}

export interface DidChangeAiEnabledParams {
	org?: boolean;
	config?: boolean;
}

export interface DidChangeAiModelParams {
	model: AIModel | undefined;
}

export interface DidSafetyErrorParams {
	error: string;
}

export interface DidReloadComposerParams {
	hunks: ComposerHunk[];
	commits: ComposerCommit[];
	baseCommit: ComposerBaseCommit | null;
	loadingError: string | null;
	hasChanges: boolean;
	repositoryState?: {
		current: RepositoryShape;
		hasMultipleRepositories: boolean;
	};
}

export interface DidLoadingErrorParams {
	error: string;
}
