import { n as __name } from "./chunk-BVHe6Par.js";
import { c as TreeNode, d as _defineProperty, g as createFile, h as _classPrivateFieldInitSpec, m as _classPrivateFieldGet2, p as _classPrivateFieldSet2, t as Root } from "./Root-BQX3eHqb.js";
import { t as getRelativePath } from "./getRelativePath-C4Au07ON.js";
import path, { relative, resolve } from "node:path";
import fs from "fs-extra";
import { onExit } from "signal-exit";
import http from "node:http";
import handler from "serve-handler";
import { spawn } from "node:child_process";
import * as clack from "@clack/prompts";
import pc from "picocolors";
import { WebSocket, WebSocketServer } from "ws";

//#region src/plugins/definePlugin.ts
/**
* Defines a Fabric plugin with type safety.
*
* Use this function to create plugins that hook into Fabric's lifecycle
* events and extend its functionality.
*
* @param plugin - The plugin configuration object
* @returns A typed plugin ready to use with Fabric
*
* @example
* ```ts
* import { definePlugin } from '@kubb/fabric-core'
*
* export const myPlugin = definePlugin({
*   name: 'my-plugin',
*   async setup(fabric) {
*     fabric.context.on('write:start', (files) => {
*       console.log(`Writing ${files.length} files`)
*     })
*   }
* })
* ```
*/
function definePlugin(plugin) {
	return {
		type: "plugin",
		...plugin
	};
}

//#endregion
//#region src/plugins/barrelPlugin.ts
/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
function getBarrelFiles({ files, root, mode }) {
	if (mode === "propagate" || mode === false) return [];
	const indexableSourcesMap = /* @__PURE__ */ new Map();
	for (const file of files) {
		const indexableSources = [];
		for (const source of file.sources || []) if (source.isIndexable && source.name) indexableSources.push(source);
		if (indexableSources.length > 0) indexableSourcesMap.set(file, indexableSources);
	}
	const cachedFiles = /* @__PURE__ */ new Map();
	const dedupe = /* @__PURE__ */ new Map();
	const treeNode = TreeNode.fromFiles(files, root);
	if (!treeNode) return [];
	treeNode.forEach((node) => {
		var _node$parent;
		if (!(node === null || node === void 0 ? void 0 : node.children) || !((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.data.path)) return;
		const parentPath = node.parent.data.path;
		const barrelPath = path.join(parentPath, "index.ts");
		let barrelFile = cachedFiles.get(barrelPath);
		if (!barrelFile) {
			barrelFile = createFile({
				path: barrelPath,
				baseName: "index.ts",
				imports: [],
				exports: [],
				sources: []
			});
			cachedFiles.set(barrelPath, barrelFile);
			dedupe.set(barrelPath, /* @__PURE__ */ new Set());
		}
		const seen = dedupe.get(barrelPath);
		for (const leaf of node.leaves) {
			const file = leaf.data.file;
			if (!file || !file.path) continue;
			const indexableSources = indexableSourcesMap.get(file);
			if (!indexableSources) continue;
			for (const source of indexableSources) {
				const key = `${source.name}|${source.isTypeOnly ? "1" : "0"}`;
				if (seen.has(key)) continue;
				seen.add(key);
				barrelFile.exports.push({
					name: [source.name],
					path: getRelativePath(parentPath, file.path),
					isTypeOnly: source.isTypeOnly
				});
				barrelFile.sources.push({
					name: source.name,
					isTypeOnly: source.isTypeOnly,
					value: "",
					isExportable: mode === "all" || mode === "named",
					isIndexable: mode === "all" || mode === "named"
				});
			}
		}
	});
	const result = [...cachedFiles.values()];
	if (mode === "all") return result.map((file) => {
		var _file$exports;
		return {
			...file,
			exports: (_file$exports = file.exports) === null || _file$exports === void 0 ? void 0 : _file$exports.map((e) => ({
				...e,
				name: void 0
			}))
		};
	});
	return result;
}
const barrelPlugin = definePlugin({
	name: "barrel",
	install(ctx, options) {
		if (!options) throw new Error("Barrel plugin requires options.root and options.mode");
		if (!options.mode) return;
		ctx.on("files:writing:start", async (files) => {
			const root = options.root;
			const barrelFiles = getBarrelFiles({
				files,
				root,
				mode: options.mode
			});
			await ctx.fileManager.add(...barrelFiles);
		});
	},
	inject(ctx, options) {
		if (!options) throw new Error("Barrel plugin requires options.root and options.mode");
		return { async writeEntry({ root, mode }) {
			if (!mode || mode === "propagate") return;
			const rootPath = path.resolve(root, "index.ts");
			const barrelFiles = [];
			for (const file of ctx.files) for (const source of file.sources) if (source.isIndexable) {
				barrelFiles.push(file);
				break;
			}
			const fileTypeCache = /* @__PURE__ */ new Map();
			for (const file of barrelFiles) fileTypeCache.set(file, file.sources.every((source) => source.isTypeOnly));
			const exports = [];
			for (const file of barrelFiles) {
				var _fileTypeCache$get;
				const containsOnlyTypes = (_fileTypeCache$get = fileTypeCache.get(file)) !== null && _fileTypeCache$get !== void 0 ? _fileTypeCache$get : false;
				for (const source of file.sources) {
					if (!file.path || !source.isIndexable) continue;
					exports.push({
						name: mode === "all" ? void 0 : [source.name],
						path: getRelativePath(rootPath, file.path),
						isTypeOnly: mode === "all" ? containsOnlyTypes : source.isTypeOnly
					});
				}
			}
			const entryFile = createFile({
				path: rootPath,
				baseName: "index.ts",
				imports: [],
				exports,
				sources: []
			});
			await ctx.addFile(entryFile);
			await ctx.fileManager.write({
				mode: ctx.config.mode,
				dryRun: options.dryRun,
				parsers: ctx.installedParsers
			});
		} };
	}
});

//#endregion
//#region src/plugins/fsPlugin.ts
async function write(path, data, options = {}) {
	if (typeof Bun !== "undefined") {
		if (!data || (data === null || data === void 0 ? void 0 : data.trim()) === "") return;
		await Bun.write(resolve(path), data.trim());
		if (options === null || options === void 0 ? void 0 : options.sanity) {
			const savedData = await Bun.file(resolve(path)).text();
			if ((savedData === null || savedData === void 0 ? void 0 : savedData.toString()) !== (data === null || data === void 0 ? void 0 : data.toString())) throw new Error(`Sanity check failed for ${path}\n\nData[${data.length}]:\n${data}\n\nSaved[${savedData.length}]:\n${savedData}\n`);
			return savedData;
		}
		return data;
	}
	if (!data || (data === null || data === void 0 ? void 0 : data.trim()) === "") return;
	try {
		const oldContent = await fs.readFile(resolve(path), { encoding: "utf-8" });
		if ((oldContent === null || oldContent === void 0 ? void 0 : oldContent.toString()) === (data === null || data === void 0 ? void 0 : data.toString())) return;
	} catch (_err) {}
	await fs.outputFile(resolve(path), data.trim(), { encoding: "utf-8" });
	if (options === null || options === void 0 ? void 0 : options.sanity) {
		const savedData = await fs.readFile(resolve(path), { encoding: "utf-8" });
		if ((savedData === null || savedData === void 0 ? void 0 : savedData.toString()) !== (data === null || data === void 0 ? void 0 : data.toString())) throw new Error(`Sanity check failed for ${path}\n\nData[${data.length}]:\n${data}\n\nSaved[${savedData.length}]:\n${savedData}\n`);
		return savedData;
	}
	return data;
}
const fsPlugin = definePlugin({
	name: "fs",
	install(ctx, options = {}) {
		if (options.clean) fs.removeSync(options.clean.path);
		ctx.on("file:processing:update", async ({ file, source }) => {
			if (options.onBeforeWrite) await options.onBeforeWrite(file.path, source);
			await write(file.path, source, { sanity: false });
		});
	},
	inject(ctx, { dryRun } = {}) {
		return { async write(options = { extension: { ".ts": ".ts" } }) {
			await ctx.fileManager.write({
				mode: ctx.config.mode,
				extension: options.extension,
				dryRun,
				parsers: ctx.installedParsers
			});
			await ctx.emit("lifecycle:end");
		} };
	}
});

//#endregion
//#region src/plugins/fsxPlugin/Runtime.ts
var _options = /* @__PURE__ */ new WeakMap();
var _renderPromise = /* @__PURE__ */ new WeakMap();
var Runtime = class {
	constructor(options) {
		_classPrivateFieldInitSpec(this, _options, void 0);
		_defineProperty(this, "exitPromise", void 0);
		_classPrivateFieldInitSpec(this, _renderPromise, Promise.resolve());
		_defineProperty(this, "resolveExitPromise", () => {});
		_defineProperty(this, "rejectExitPromise", () => {});
		_defineProperty(this, "unsubscribeExit", () => {});
		_classPrivateFieldSet2(_options, this, options);
		this.unsubscribeExit = onExit((code) => {
			this.unmount(code);
		}, { alwaysLast: false }).bind(this);
	}
	get fileManager() {
		return _classPrivateFieldGet2(_options, this).fileManager;
	}
	onError(error) {
		throw error;
	}
	onExit(error) {
		setTimeout(() => {
			this.unmount(error);
		}, 0);
	}
	async render(node) {
		const treeNode = _classPrivateFieldGet2(_options, this).treeNode || new TreeNode({
			type: "Root",
			props: {}
		});
		const props = {
			fileManager: this.fileManager,
			treeNode,
			onExit: this.onExit.bind(this),
			onError: this.onError.bind(this)
		};
		try {
			var _element;
			treeNode.data.props = props;
			const element = Root({
				...props,
				children: node
			});
			await _classPrivateFieldGet2(_renderPromise, this);
			return ((_element = element()) === null || _element === void 0 ? void 0 : _element.toString()) || "";
		} catch (e) {
			props.onError(e);
			return "";
		}
	}
	unmount(error) {
		var _classPrivateFieldGet2$1;
		if ((_classPrivateFieldGet2$1 = _classPrivateFieldGet2(_options, this)) === null || _classPrivateFieldGet2$1 === void 0 ? void 0 : _classPrivateFieldGet2$1.debug) console.log("Unmount", error);
		this.unsubscribeExit();
		if (error instanceof Error) {
			this.rejectExitPromise(error);
			return;
		}
		this.resolveExitPromise();
	}
	async waitUntilExit() {
		if (!this.exitPromise) this.exitPromise = new Promise((resolve, reject) => {
			this.resolveExitPromise = resolve;
			this.rejectExitPromise = reject;
		});
		return this.exitPromise;
	}
};

//#endregion
//#region src/plugins/fsxPlugin/fsxPlugin.ts
const fsxPlugin = definePlugin({
	name: "fsx",
	install() {},
	inject(ctx, options = {}) {
		const runtime = new Runtime({
			fileManager: ctx.fileManager,
			...options
		});
		return {
			async render(App) {
				await ctx.emit("lifecycle:start");
				return runtime.render(App);
			},
			async waitUntilExit() {
				await runtime.waitUntilExit();
			}
		};
	}
});

//#endregion
//#region src/utils/open.ts
const spawnBin = (bin, args) => {
	return new Promise((resolve) => {
		spawn(bin, args, {
			detached: true,
			shell: false,
			windowsHide: true
		}).on("close", (code) => {
			resolve(!code);
		});
	});
};
async function open(path, options) {
	const app = options === null || options === void 0 ? void 0 : options.app;
	if (process.platform === "win32") return spawnBin("cmd.exe", [
		"/c",
		"start",
		app || "",
		path.replace(/[&^]/g, "^$&")
	]);
	if (process.platform === "linux") return spawnBin(app || "xdg-open", [path]);
	if (process.platform === "darwin") return spawnBin("open", app ? [
		"-a",
		app,
		path
	] : [path]);
	throw new Error(`Unsupported platform, could not open "${path}"`);
}

//#endregion
//#region src/plugins/graphPlugin.ts
function getGraph({ files, root }) {
	const treeNode = TreeNode.fromFiles(files, root);
	if (!treeNode) return;
	return TreeNode.toGraph(treeNode);
}
const html = `
      <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>File Graph</title>
    <script type="module">
      import { Network } from 'https://cdn.jsdelivr.net/npm/vis-network/standalone/esm/vis-network.min.js'

      async function main() {
        const res = await fetch('./graph.json')
        const { nodes, edges } = await res.json()
        const container = document.getElementById('graph')

        const network = new Network(
          container,
          { nodes, edges },
          {
            layout: { hierarchical: { direction: 'UD', sortMethod: 'directed' } },
            nodes: { shape: 'box', font: { face: 'monospace' } },
            edges: { arrows: 'to' },
            physics: false,
          },
        )
      }

      main()
    <\/script>
    <style>
      html, body, #graph { height: 100%; margin: 0; }
    </style>
  </head>
  <body>
    <div id="graph"></div>
  </body>
</html>
`;
async function serve(root) {
	const server = http.createServer((req, res) => {
		return handler(req, res, {
			public: root,
			cleanUrls: true
		});
	});
	server.listen(0, async () => {
		const { port } = server.address();
		console.log(`Running on http://localhost:${port}/graph.html`);
		await open(`http://localhost:${port}/graph.html`);
	});
}
const graphPlugin = definePlugin({
	name: "graph",
	install(ctx, options) {
		if (!options) throw new Error("Graph plugin requires options.root and options.mode");
		ctx.on("files:writing:start", async (files) => {
			const root = options.root;
			const graph = getGraph({
				files,
				root
			});
			if (!graph) return;
			const graphFile = createFile({
				baseName: "graph.json",
				path: path.join(root, "graph.json"),
				sources: [{
					name: "graph",
					value: JSON.stringify(graph, null, 2)
				}],
				imports: [],
				exports: []
			});
			const graphHtmlFile = createFile({
				baseName: "graph.html",
				path: path.join(root, "graph.html"),
				sources: [{
					name: "graph",
					value: html
				}],
				imports: [],
				exports: []
			});
			await ctx.addFile(graphFile, graphHtmlFile);
			if (options.open) await serve(root);
		});
	}
});

//#endregion
//#region src/plugins/loggerPlugin.ts
function normalizeAddress(address) {
	return {
		host: address.address === "::" ? "127.0.0.1" : address.address,
		port: address.port
	};
}
function serializeFile(file) {
	return {
		path: file.path,
		baseName: file.baseName,
		name: "name" in file ? file.name : void 0,
		extname: "extname" in file ? file.extname : void 0
	};
}
function pluralize(word, count) {
	return `${count} ${word}${count === 1 ? "" : "s"}`;
}
const DEFAULT_PROGRESS_BAR_SIZE = 30;
const loggerPlugin = definePlugin({
	name: "logger",
	install(ctx, options = {}) {
		const { websocket = true, progress = true } = options;
		const state = {
			spinner: clack.spinner(),
			isSpinning: false,
			progressBar: void 0
		};
		function formatPath(path) {
			return relative(process.cwd(), path);
		}
		let server;
		let wss;
		const broadcast = (event, payload) => {
			if (!wss) return;
			const message = JSON.stringify({
				event,
				payload
			});
			for (const client of wss.clients) if (client.readyState === WebSocket.OPEN) client.send(message);
		};
		if (websocket) {
			const { host = "127.0.0.1", port = 0 } = typeof websocket === "boolean" ? {} : websocket;
			server = http.createServer();
			wss = new WebSocketServer({ server });
			server.listen(port, host, () => {
				const addressInfo = server === null || server === void 0 ? void 0 : server.address();
				if (addressInfo && typeof addressInfo === "object") {
					const { host: resolvedHost, port: resolvedPort } = normalizeAddress(addressInfo);
					const url = `ws://${resolvedHost}:${resolvedPort}`;
					clack.log.info(`${pc.blue("ℹ")} Logger websocket listening on ${url}`);
					broadcast("websocket:ready", { url });
				}
			});
			wss.on("connection", (socket) => {
				clack.log.info(`${pc.blue("ℹ")} Logger websocket client connected`);
				socket.send(JSON.stringify({
					event: "welcome",
					payload: {
						message: "Connected to Fabric log stream",
						timestamp: Date.now()
					}
				}));
			});
			wss.on("error", (error) => {
				clack.log.error(`${pc.red("✗")} Logger websocket error: ${error.message}`);
			});
		}
		ctx.on("lifecycle:start", async () => {
			clack.intro(`${pc.blue("Fabric")} ${pc.dim("Starting run")}`);
			broadcast("lifecycle:start", { timestamp: Date.now() });
		});
		ctx.on("lifecycle:render", async () => {
			clack.log.info(`${pc.blue("ℹ")} Rendering application graph`);
			broadcast("lifecycle:render", { timestamp: Date.now() });
		});
		ctx.on("files:added", async (files) => {
			if (!files.length) return;
			clack.log.info(`${pc.blue("ℹ")} Queued ${pluralize("file", files.length)}`);
			broadcast("files:added", { files: files.map(serializeFile) });
		});
		ctx.on("file:resolve:path", async (file) => {
			clack.log.step(`Resolving path for ${pc.dim(formatPath(file.path))}`);
			broadcast("file:resolve:path", { file: serializeFile(file) });
		});
		ctx.on("file:resolve:name", async (file) => {
			clack.log.step(`Resolving name for ${pc.dim(formatPath(file.path))}`);
			broadcast("file:resolve:name", { file: serializeFile(file) });
		});
		ctx.on("files:processing:start", async (files) => {
			clack.log.step(`Processing ${pc.green(pluralize("file", files.length))}`);
			broadcast("files:processing:start", {
				total: files.length,
				timestamp: Date.now()
			});
			if (progress) {
				state.progressBar = clack.progress({
					style: "block",
					max: files.length,
					size: DEFAULT_PROGRESS_BAR_SIZE
				});
				state.progressBar.start(`Processing ${files.length} files`);
			}
		});
		ctx.on("file:processing:start", async (file, index, total) => {
			if (!state.progressBar) clack.log.step(`Processing ${pc.dim(`[${index + 1}/${total}]`)} ${formatPath(file.path)}`);
			broadcast("file:processing:start", {
				index,
				total,
				file: serializeFile(file)
			});
		});
		ctx.on("file:processing:update", async ({ processed, total, percentage, file }) => {
			broadcast("file:processing:update", {
				processed,
				total,
				percentage,
				file: serializeFile(file)
			});
			if (state.progressBar) state.progressBar.advance(void 0, `Writing ${formatPath(file.path)}`);
			else {
				const formattedPercentage = Number.isFinite(percentage) ? percentage.toFixed(1) : "0.0";
				clack.log.step(`Progress ${pc.green(`${formattedPercentage}%`)} ${pc.dim(`(${processed}/${total})`)} → ${formatPath(file.path)}`);
			}
		});
		ctx.on("file:processing:end", async (file, index, total) => {
			if (state.progressBar) state.progressBar.message(`${pc.green("✓")} Finished ${pc.dim(`[${index + 1}/${total}]`)} ${formatPath(file.path)}`);
			else clack.log.success(`${pc.green("✓")} Finished ${pc.dim(`[${index + 1}/${total}]`)} ${formatPath(file.path)}`);
			broadcast("file:processing:end", {
				index,
				total,
				file: serializeFile(file)
			});
		});
		ctx.on("files:writing:start", async (files) => {
			broadcast("files:writing:start", { files: files.map(serializeFile) });
		});
		ctx.on("files:writing:end", async (files) => {
			broadcast("files:writing:end", { files: files.map(serializeFile) });
		});
		ctx.on("files:processing:end", async (files) => {
			if (state.progressBar) {
				state.progressBar.stop(`${pc.green("✓")} Processed ${pluralize("file", files.length)}`);
				state.progressBar = void 0;
			} else clack.log.success(`${pc.green("✓")} Processed ${pluralize("file", files.length)}`);
			broadcast("files:processing:end", {
				total: files.length,
				timestamp: Date.now()
			});
		});
		ctx.on("lifecycle:end", async () => {
			if (state.progressBar) {
				state.progressBar.stop();
				state.progressBar = void 0;
			}
			clack.outro(`${pc.blue("Fabric")} ${pc.dim("completed")}`);
			broadcast("lifecycle:end", { timestamp: Date.now() });
			const closures = [];
			if (wss) {
				const wsServer = wss;
				closures.push(new Promise((resolve) => {
					for (const client of wsServer.clients) client.close();
					wsServer.close(() => resolve());
				}));
			}
			if (server) {
				const httpServer = server;
				closures.push(new Promise((resolve) => {
					httpServer.close(() => resolve());
				}));
			}
			if (closures.length) {
				await Promise.allSettled(closures);
				clack.log.info(`${pc.blue("ℹ")} Logger websocket closed`);
			}
		});
	}
});

//#endregion
export { barrelPlugin, definePlugin, fsPlugin, fsxPlugin, graphPlugin, loggerPlugin };
//# sourceMappingURL=plugins.js.map