import { existsSync, readdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import {
	Key,
	matchesKey,
	truncateToWidth,
	visibleWidth,
} from "@singularity-forge/tui";
import { getExperimentalFlag } from "../experimental.js";
import { sfHome } from "../sf-home.js";

const CATEGORIES = ["all", "extension", "skill", "theme"];
const FEATURED = [
	{
		id: "agents-filter-output",
		name: "Filter Output",
		source: "featured",
		category: "extension",
		description: "Redact secrets from tool results",
	},
	{
		id: "agents-security",
		name: "Security",
		source: "featured",
		category: "extension",
		description: "Block dangerous commands and protected paths",
	},
	{
		id: "pi-hooks-permission",
		name: "Permission",
		source: "featured",
		category: "extension",
		description: "4-level permission control for bash/write/edit",
	},
	{
		id: "shitty-usage-bar",
		name: "Usage Bar",
		source: "featured",
		category: "extension",
		description: "Live AI provider quota & status",
	},
	{
		id: "rhubarb-bg-notify",
		name: "Background Notify",
		source: "featured",
		category: "extension",
		description: "Notify when background tasks complete",
	},
	{
		id: "pi-dcp",
		name: "Dynamic Context Pruning",
		source: "featured",
		category: "extension",
		description: "Intelligent conversation context pruning",
	},
	{
		id: "pi-powerline-footer",
		name: "Powerline Footer",
		source: "featured",
		category: "extension",
		description: "Git-integrated status bar components",
	},
];
function scanInstalledExtensions(dir, sourceLabel) {
	if (!existsSync(dir)) return [];
	const items = [];
	for (const entry of readdirSync(dir, { withFileTypes: true })) {
		if (!entry.isDirectory()) continue;
		const extPath = join(dir, entry.name);
		const pkgPath = join(extPath, "package.json");
		let name = entry.name;
		let description = "";
		try {
			if (existsSync(pkgPath)) {
				const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
				name = pkg.name || name;
				description = pkg.description || "";
			}
		} catch {
			/* ignore */
		}
		items.push({
			id: entry.name,
			name,
			source: sourceLabel,
			category: "extension",
			description,
			path: extPath,
		});
	}
	return items;
}
function buildCatalog() {
	const installed = scanInstalledExtensions(
		join(sfHome(), "agent", "extensions"),
		"installed",
	);
	const piCompat = scanInstalledExtensions(
		join(homedir(), ".pi", "agent", "extensions"),
		"pi-compat",
	);
	const piLegacy = scanInstalledExtensions(
		join(homedir(), ".pi", "extensions"),
		"pi-compat",
	);
	const all = [...installed, ...piCompat, ...piLegacy];
	const seen = new Set(all.map((i) => i.id));
	for (const f of FEATURED) {
		if (!seen.has(f.id)) all.push(f);
	}
	return all.sort((a, b) => {
		if (a.source === "installed" && b.source !== "installed") return -1;
		if (b.source === "installed" && a.source !== "installed") return 1;
		return a.name.localeCompare(b.name);
	});
}
class MarketplaceOverlay {
	tui;
	theme;
	onClose;
	items;
	filtered;
	sel = 0;
	catIdx = 0;
	scroll = 0;
	cacheW = 0;
	cacheL = [];
	constructor(tui, theme, items, onClose) {
		this.tui = tui;
		this.theme = theme;
		this.items = items;
		this.onClose = onClose;
		this.filtered = this.applyFilter();
	}
	get category() {
		return CATEGORIES[this.catIdx];
	}
	applyFilter() {
		if (this.category === "all") return this.items;
		return this.items.filter((i) => i.category === this.category);
	}
	handleInput(data) {
		if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
			this.onClose();
			return;
		}
		if (matchesKey(data, Key.down) || data === "j") {
			this.sel = Math.min(this.filtered.length - 1, this.sel + 1);
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (matchesKey(data, Key.up) || data === "k") {
			this.sel = Math.max(0, this.sel - 1);
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (data === "f") {
			this.catIdx = (this.catIdx + 1) % CATEGORIES.length;
			this.sel = 0;
			this.scroll = 0;
			this.filtered = this.applyFilter();
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (matchesKey(data, Key.return) || matchesKey(data, Key.enter)) {
			const item = this.filtered[this.sel];
			if (item) {
				if (item.source === "installed") {
					// Already installed — close
					this.onClose();
				} else {
					// Trigger install via npm
					this.onClose();
					installExtensionNpm(item.id, item.name);
				}
			} else {
				this.onClose();
			}
		}
	}
	invalidate() {
		this.cacheW = 0;
	}
	render(width) {
		if (this.cacheW === width) return this.cacheL;
		const th = this.theme;
		const bw = Math.min(90, width - 4);
		const iw = bw - 4;
		const maxRows = Math.max(6, (process.stdout.rows || 24) - 10);
		const pad = (s) => s + " ".repeat(Math.max(0, width - visibleWidth(s)));
		const box = (s) => {
			const len = visibleWidth(s);
			return (
				th.fg("dim", "│ ") +
				s +
				" ".repeat(Math.max(0, bw - 2 - len)) +
				th.fg("dim", " │")
			);
		};
		const lines = [];
		lines.push(pad(th.fg("dim", "╭" + "─".repeat(bw) + "╮")));
		lines.push(pad(box(th.bold(th.fg("accent", "📦 Marketplace")))));
		lines.push(pad(th.fg("dim", "├" + "─".repeat(bw) + "┤")));
		const filterLabel =
			this.category === "all"
				? th.fg("dim", "all")
				: th.fg("accent", this.category);
		lines.push(
			pad(
				box(
					`${th.fg("dim", "filter:")} ${filterLabel}  ${th.fg("dim", "↑/jk navigate • f filter • Enter install • Esc close")}`,
				),
			),
		);
		lines.push(pad(box("")));
		const visibleItems = this.filtered;
		if (!visibleItems.length) {
			lines.push(pad(box(th.fg("dim", "No packages found."))));
		} else {
			this.scroll = Math.min(
				this.scroll,
				Math.max(0, visibleItems.length - maxRows),
			);
			this.sel = Math.min(this.sel, visibleItems.length - 1);
			if (this.sel < this.scroll) this.scroll = this.sel;
			if (this.sel >= this.scroll + maxRows)
				this.scroll = this.sel - maxRows + 1;
			for (
				let i = this.scroll;
				i < Math.min(visibleItems.length, this.scroll + maxRows);
				i++
			) {
				const item = visibleItems[i];
				const ptr = i === this.sel ? th.fg("accent", "❯ ") : "  ";
				const srcIcon =
					item.source === "installed"
						? th.fg("success", "● ")
						: item.source === "pi-compat"
							? th.fg("warning", "◐ ")
							: th.fg("dim", "○ ");
				const name =
					i === this.sel
						? th.fg("accent", item.name)
						: th.fg("text", item.name);
				const desc = th.fg(
					"dim",
					truncateToWidth(
						item.description,
						Math.max(10, iw - visibleWidth(`${ptr}${srcIcon}${item.name}  `)),
					),
				);
				lines.push(pad(box(`${ptr}${srcIcon}${name}  ${desc}`)));
			}
		}
		lines.push(pad(box("")));
		lines.push(pad(th.fg("dim", "├" + "─".repeat(bw) + "┤")));
		lines.push(
			pad(
				box(
					th.fg(
						"dim",
						`${visibleItems.length} packages • ${this.items.filter((i) => i.source === "installed").length} installed`,
					),
				),
			),
		);
		lines.push(pad(th.fg("dim", "╰" + "─".repeat(bw) + "╯")));
		lines.push("");
		this.cacheL = lines;
		this.cacheW = width;
		return lines;
	}
}
export async function openMarketplaceOverlay(ctx) {
	if (!ctx.hasUI) {
		ctx.ui.notify("Marketplace requires interactive mode", "error");
		return;
	}
	const items = buildCatalog();
	await ctx.ui.custom(
		(tui, theme, _kb, done) => {
			const overlay = new MarketplaceOverlay(tui, theme, items, () =>
				done(true),
			);
			return {
				render: (w) => overlay.render(w),
				invalidate: () => overlay.invalidate(),
				handleInput: (d) => overlay.handleInput(d),
			};
		},
		{
			overlay: true,
			overlayOptions: {
				width: "92%",
				minWidth: 70,
				maxHeight: "88%",
				anchor: "center",
				backdrop: true,
			},
		},
	);
}

/**
 * Install a marketplace extension via npm into ~/.sf/agent/extensions/.
 *
 * Purpose: wire the EXTENSIONS experimental flag — press Enter on a featured
 * package to trigger `npm install` into the user extensions directory.
 * Consumer: MarketplaceOverlay.handleInput on Enter for non-installed items.
 */
export function installExtensionNpm(packageId, displayName) {
	if (!getExperimentalFlag("extensions")) {
		// Silently skip if EXTENSIONS flag is off; overlay already closed
		return;
	}
	import("node:child_process").then(({ spawn }) => {
		const target = join(sfHome(), "agent", "extensions");
		const proc = spawn("npm", ["install", "--prefix", target, packageId], {
			stdio: ["ignore", "pipe", "pipe"],
			detached: false,
		});
		let stderr = "";
		proc.stderr?.on("data", (d) => {
			stderr += d.toString();
		});
		proc.on("close", (code) => {
			if (code === 0) {
				process.stdout.write(
					`\r\n\x1b[32m✓ Installed ${displayName || packageId}\x1b[0m\r\n`,
				);
			} else {
				process.stdout.write(
					`\r\n\x1b[31m✗ Install failed for ${displayName || packageId}: ${stderr.slice(0, 200)}\x1b[0m\r\n`,
				);
			}
		});
		proc.on("error", () => {
			process.stdout.write(
				`\r\n\x1b[33m⚠ npm not found — install npm to enable extension installs\x1b[0m\r\n`,
			);
		});
	});
}
