// Vendored from claudekit-cli (MIT). Source: src/commands/portable/reconcile-state-builders.ts
// Path-resolution and target-checksum helpers extracted to reconcile-state-builder-helpers.ts.
import { existsSync, readdirSync, statSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { computeManagedSectionChecksums } from "../config-merger/merge-single-sections.js";
import { convertItem } from "../converters/index.js";
import { providers } from "../provider-registry.js";
import type { PortableItem, PortableType, ProviderType } from "../types.js";
import { computeContentChecksum } from "./checksum-utils.js";
import type { PortableInstallationV3 } from "./portable-registry.js";
import {
	buildTargetChecksum,
	getProviderPathConfig,
	getProviderPathKeyForPortableType,
	usesMergeSingleChecksums,
} from "./reconcile-state-builder-helpers.js";
import type { SourceItemState, TargetDirectoryState, TargetFileState } from "./reconcile-types.js";

export interface ConversionFallbackWarning {
	item: string;
	type: PortableType;
	provider: ProviderType;
	format: string;
	error: string;
}

export function buildConvertedChecksums(
	item: PortableItem,
	type: PortableType,
	selectedProviders: ProviderType[],
	options?: { onConversionFallback?: (warning: ConversionFallbackWarning) => void },
): Record<string, string> {
	const rawChecksum = computeContentChecksum(item.body);
	const convertedChecksums: Record<string, string> = {};

	for (const provider of selectedProviders) {
		const pathConfig = getProviderPathConfig(provider, type);
		if (!pathConfig) {
			convertedChecksums[provider] = rawChecksum;
			continue;
		}

		const result = convertItem(item, pathConfig.format, provider);
		if (result.error) {
			options?.onConversionFallback?.({
				item: item.name,
				type,
				provider,
				format: pathConfig.format,
				error: result.error,
			});
			convertedChecksums[provider] = rawChecksum;
			continue;
		}

		convertedChecksums[provider] = computeContentChecksum(result.content);
	}

	return convertedChecksums;
}

export function buildSourceItemState(
	item: PortableItem,
	type: PortableType,
	selectedProviders: ProviderType[],
	options?: { onConversionFallback?: (warning: ConversionFallbackWarning) => void },
): SourceItemState {
	const rawChecksum = computeContentChecksum(item.body);
	const convertedChecksums = buildConvertedChecksums(item, type, selectedProviders, options);
	const targetChecksums: Record<string, string> = {};

	for (const provider of selectedProviders) {
		const pathConfig = getProviderPathConfig(provider, type);
		if (!pathConfig) {
			targetChecksums[provider] = rawChecksum;
			continue;
		}

		const result = convertItem(item, pathConfig.format, provider);
		if (result.error) {
			targetChecksums[provider] = rawChecksum;
			continue;
		}

		const targetChecksum = buildTargetChecksum(item, type, provider, result.content);
		if (targetChecksum) targetChecksums[provider] = targetChecksum;
	}

	return {
		item: item.name,
		type,
		sourceChecksum: rawChecksum,
		convertedChecksums,
		targetChecksums,
	};
}

export function buildTypeDirectoryStates(
	providerConfigs: Array<{ provider: ProviderType; global: boolean }>,
	types: PortableType[],
): TargetDirectoryState[] {
	const results: TargetDirectoryState[] = [];

	for (const { provider, global: isGlobal } of providerConfigs) {
		const providerConfig = providers[provider];
		if (!providerConfig) continue;

		for (const type of types) {
			const pathKey = getProviderPathKeyForPortableType(type);
			const pathConfig = providerConfig[pathKey];
			if (!pathConfig) continue;

			if (pathConfig.writeStrategy === "merge-single" || pathConfig.writeStrategy === "single-file") {
				continue;
			}

			const dirPath = isGlobal ? pathConfig.globalPath : pathConfig.projectPath;
			if (!dirPath) continue;

			const exists = existsSync(dirPath);

			if (!exists) {
				results.push({
					provider,
					type,
					global: isGlobal,
					path: dirPath,
					exists: false,
					isEmpty: true,
					fileCount: 0,
				});
				continue;
			}

			let stat: ReturnType<typeof statSync> | null = null;
			try {
				stat = statSync(dirPath);
			} catch {
				results.push({
					provider,
					type,
					global: isGlobal,
					path: dirPath,
					exists: false,
					isEmpty: true,
					fileCount: 0,
				});
				continue;
			}

			if (!stat.isDirectory()) {
				results.push({
					provider,
					type,
					global: isGlobal,
					path: dirPath,
					exists: true,
					isEmpty: false,
					fileCount: 1,
				});
				continue;
			}

			const ext = pathConfig.fileExtension ?? "";
			let entries: string[] = [];
			try {
				entries = readdirSync(dirPath);
			} catch {
				results.push({
					provider,
					type,
					global: isGlobal,
					path: dirPath,
					exists: true,
					isEmpty: true,
					fileCount: 0,
				});
				continue;
			}

			const managedFiles =
				ext === ""
					? entries.filter((f) => f.endsWith(".json") || f.endsWith(".sh") || f.endsWith(".js"))
					: entries.filter((f) => f.endsWith(ext));

			results.push({
				provider,
				type,
				global: isGlobal,
				path: dirPath,
				exists: true,
				isEmpty: managedFiles.length === 0,
				fileCount: managedFiles.length,
			});
		}
	}

	return results;
}

export async function buildTargetStates(
	entries: PortableInstallationV3[],
	options?: { onReadFailure?: (path: string, error: unknown) => void },
): Promise<Map<string, TargetFileState>> {
	const targetStates = new Map<string, TargetFileState>();
	const entriesByPath = new Map<string, PortableInstallationV3[]>();

	for (const entry of entries) {
		if (entry.type === "skill") continue;
		const group = entriesByPath.get(entry.path) ?? [];
		group.push(entry);
		entriesByPath.set(entry.path, group);
	}

	for (const [entryPath, groupedEntries] of entriesByPath) {
		const exists = existsSync(entryPath);
		const state: TargetFileState = { path: entryPath, exists };

		if (exists) {
			try {
				const content = await readFile(entryPath, "utf-8");
				state.currentChecksum = computeContentChecksum(content);
				if (groupedEntries.some((entry) => usesMergeSingleChecksums(entry))) {
					state.sectionChecksums = computeManagedSectionChecksums(content);
				}
			} catch (error) {
				options?.onReadFailure?.(entryPath, error);
			}
		}

		targetStates.set(entryPath, state);
	}

	return targetStates;
}
