import { Type } from "@earendil-works/pi-ai";
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
import { openInBrowser, type BrowserOpenResult } from "./browser.ts";
import { computeDiff, fetchFileContents, getReviewContext, hasWorkingTreeChanges, resolveBase, validateGitRepo, type FileContent, type ReviewContext } from "./git.ts";
import { parseDiff } from "./diff-parser.ts";
import { writeReviewHtml } from "./output.ts";
import { renderReviewHtml } from "./render.ts";

export interface ReviewDiffResult {
	ok: boolean;
	message: string;
	outputPath?: string;
}

export interface ReviewDiffDeps {
	renderHtml: (files: FileContent[], title: string, context: { repoRoot: string; repoName: string; branch: string; base: string }) => string;
	writeHtml: (context: ReviewContext, html: string) => Promise<string>;
	openBrowser: (filePath: string) => Promise<BrowserOpenResult>;
}

function notify(ctx: ExtensionContext, message: string, level: "info" | "warning" | "error") {
	if (ctx.hasUI) {
		ctx.ui.notify(message, level);
	}
}

const defaultDeps: ReviewDiffDeps = {
	renderHtml: renderReviewHtml,
	writeHtml: writeReviewHtml,
	openBrowser: openInBrowser,
};

export function createRunReviewDiff(deps: ReviewDiffDeps = defaultDeps) {
	return async function runReviewDiff(
		pi: ExtensionAPI,
		explicitBase: string | undefined,
		ctx: ExtensionContext,
	): Promise<ReviewDiffResult> {
		const cwd = ctx.cwd;

		const repoCheck = await validateGitRepo(pi, cwd);
		if (repoCheck !== true) {
			return { ok: false, message: repoCheck.error };
		}

		const baseResult = await resolveBase(pi, cwd, explicitBase);
		if ("error" in baseResult) {
			return { ok: false, message: baseResult.error };
		}
		const { base } = baseResult;

		const workingTreeStatus = await hasWorkingTreeChanges(pi, cwd);
		if (typeof workingTreeStatus === "object" && workingTreeStatus !== null && "error" in workingTreeStatus) {
			return { ok: false, message: workingTreeStatus.error };
		}
		if (workingTreeStatus) {
			notify(ctx, "Working tree has staged/unstaged changes. HTML includes only committed branch changes.", "warning");
		}

		const diffResult = await computeDiff(pi, cwd, base);
		if ("error" in diffResult) {
			return { ok: false, message: diffResult.error };
		}

		const { diff } = diffResult;
		if (!diff.trim()) {
			return { ok: true, message: `No changes between ${base} and HEAD` };
		}

		const contextResult = await getReviewContext(pi, cwd, base);
		if ("error" in contextResult) {
			return { ok: false, message: contextResult.error };
		}

		const title = `${contextResult.repoName}: ${contextResult.branch} vs ${contextResult.base}`;
		const parsedFiles = parseDiff(diff);
		const fileContents = await fetchFileContents(pi, cwd, base, parsedFiles);
		const html = deps.renderHtml(fileContents, title, {
			repoRoot: contextResult.repoRoot,
			repoName: contextResult.repoName,
			branch: contextResult.branch,
			base: contextResult.base,
		});

		try {
			const outputPath = await deps.writeHtml(contextResult, html);

			const browserResult = await deps.openBrowser(outputPath);
			if (!browserResult.ok) {
				notify(
					ctx,
					`Failed to open browser automatically (${browserResult.error}). Open manually: ${outputPath}`,
					"warning",
				);
			}

			return { ok: true, message: `HTML diff generated: ${outputPath}`, outputPath };
		} catch (error) {
			return {
				ok: false,
				message: `Failed to write HTML diff: ${error instanceof Error ? error.message : String(error)}`,
			};
		}
	};
}

export async function runShowDiffCommand(
	pi: ExtensionAPI,
	args: string,
	ctx: ExtensionCommandContext,
	runReviewDiff = createRunReviewDiff(),
): Promise<void> {
	const explicitBase = args.trim() || undefined;
	const result = await runReviewDiff(pi, explicitBase, ctx);
	notify(ctx, result.message, result.ok ? "info" : "error");
}

export default function showDiffExtension(pi: ExtensionAPI, deps: ReviewDiffDeps = defaultDeps) {
	const runReviewDiff = createRunReviewDiff(deps);

	pi.registerCommand("show-diff", {
		description: "Show a browser-based review diff for this branch against an optional base ref",
		handler: (args, ctx) => runShowDiffCommand(pi, args, ctx, runReviewDiff),
	});

	pi.registerTool({
		name: "show_review_diff",
		label: "Show Review Diff",
		description: "Generate and open an HTML diff of this branch against an optional base ref",
		parameters: Type.Object({
			base: Type.Optional(Type.String({ description: "Optional base git ref (for example: main, master, origin/main)" })),
		}),
		execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => {
			const result = await runReviewDiff(pi, params.base, ctx);
			notify(ctx, result.message, result.ok ? "info" : "error");
			return {
				content: [{ type: "text", text: result.message }],
				details: result,
			};
		},
	});
}
