import { a as StringifiedObject, c as isNotSubsetOf, d as parseJsArg, f as stringifyAttributes, g as __toESM, h as __exportAll, i as htmlSafe, l as isSubsetOf, m as require_classnames, n as Template, o as asyncEach, p as unallowedExpression, r as escape, s as each, t as require_js_stringify, u as nanoid } from "./js-stringify-CbllxJUy.js";
import { fileURLToPath } from "node:url";
import string from "@poppinss/utils/string";
import { isAbsolute, join, relative } from "node:path";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { EdgeBuffer, Parser, Stack, expressions } from "edge-parser";
import { EdgeError, EdgeError as EdgeError$1 } from "edge-error";
import lodash from "@poppinss/utils/lodash";
import * as lexerUtils from "edge-lexer/utils";
import inspect from "@poppinss/inspect";
//#region src/loader.ts
/**
* edge
*
* (c) EdgeJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* The job of a loader is to load the template from a given path.
* The base loader (shipped with edge) looks for files on the
* file-system and reads them synchronously.
*
* You are free to define your own loaders that implements the [[LoaderContract]] interface.
*/
var Loader = class {
	/**
	* List of mounted directories
	*/
	#mountedDirs = /* @__PURE__ */ new Map();
	/**
	* List of pre-registered (in-memory) templates
	*/
	#preRegistered = /* @__PURE__ */ new Map();
	/**
	* Reads the content of a template from the disk. An exception is raised
	* when file is missing or if `readFileSync` returns an error.
	*/
	#readTemplateContents(absPath) {
		try {
			return readFileSync(absPath, "utf-8");
		} catch (error) {
			if (error.code === "ENOENT") throw new Error(`Cannot resolve "${absPath}". Make sure the file exists`);
			else throw error;
		}
	}
	/**
	* Returns a list of components for a given disk
	*/
	#getDiskComponents(diskName) {
		const componentsDirName = "components";
		const diskBasePath = this.#mountedDirs.get(diskName);
		let files = diskName === "default" ? Array.from(this.#preRegistered.keys()).map((template) => {
			return {
				fileName: template,
				componentPath: template
			};
		}) : [];
		/**
		* Read disk files
		*/
		const componentsPath = join(diskBasePath, componentsDirName);
		if (existsSync(componentsPath)) files = files.concat(readdirSync(join(componentsPath), {
			recursive: true,
			withFileTypes: true
		}).filter((file) => {
			return file.isFile() && file.name.endsWith(".edge");
		}).map((file) => {
			const fileName = string.toUnixSlash(relative(componentsPath, join(file.parentPath, file.name))).replace(/\.edge$/, "");
			return {
				fileName,
				componentPath: `${componentsDirName}/${fileName}`
			};
		}));
		return files.map(({ fileName, componentPath }) => {
			const tagName = fileName.split("/").filter((segment, index) => {
				return index === 0 || segment !== "index";
			}).map((segment) => string.camelCase(segment)).join(".");
			return {
				componentName: diskName !== "default" ? `${diskName}::${componentPath}` : componentPath,
				tagName: diskName !== "default" ? `${diskName}.${tagName}` : tagName
			};
		});
	}
	/**
	* Returns a list of templates for a given disk
	*/
	#getDiskTemplates(diskName) {
		const diskBasePath = this.#mountedDirs.get(diskName);
		let files = diskName === "default" ? Array.from(this.#preRegistered.keys()) : [];
		if (existsSync(diskBasePath)) files = files.concat(readdirSync(diskBasePath, {
			recursive: true,
			withFileTypes: true
		}).filter((file) => {
			return file.isFile() && file.name.endsWith(".edge");
		}).map((file) => {
			return relative(diskBasePath, join(file.parentPath, file.name)).replace(/\.edge$/, "");
		}));
		return files.map((file) => {
			const fileName = string.toUnixSlash(file).replace(/\.edge$/, "");
			return diskName !== "default" ? `${diskName}::${fileName}` : fileName;
		});
	}
	/**
	* Extracts the disk name and the template name from the template
	* path expression.
	*
	* If `diskName` is missing, it will be set to `default`.
	*
	* ```
	* extractDiskAndTemplateName('users::list')
	* // returns ['users', 'list.edge']
	*
	* extractDiskAndTemplateName('list')
	* // returns ['default', 'list.edge']
	* ```
	*/
	#extractDiskAndTemplateName(templatePath) {
		let [disk, ...rest] = templatePath.split("::");
		if (!rest.length) {
			rest = [disk];
			disk = "default";
		}
		let [template, ext] = rest.join("::").split(".edge");
		return [disk, `${template}.${ext || "edge"}`];
	}
	/**
	* Returns an object of mounted directories with their public
	* names.
	*
	* ```js
	* loader.mounted
	* // output
	*
	* {
	*   default: '/users/virk/code/app/views',
	*   foo: '/users/virk/code/app/foo',
	* }
	* ```
	*/
	get mounted() {
		return Array.from(this.#mountedDirs).reduce((obj, [key, value]) => {
			obj[key] = value;
			return obj;
		}, {});
	}
	/**
	* Returns an object of templates registered as a raw string
	*
	* ```js
	* loader.templates
	* // output
	*
	* {
	*   'form.label': { template: 'Template contents' }
	* }
	* ```
	*/
	get templates() {
		return Array.from(this.#preRegistered).reduce((obj, [key, value]) => {
			obj[key] = value;
			return obj;
		}, {});
	}
	/**
	* Mount a directory with a name for resolving views. If name is set
	* to `default`, then you can resolve views without prefixing the
	* disk name.
	*
	* ```js
	* loader.mount('default', join(__dirname, 'views'))
	*
	* // mount a named disk
	* loader.mount('admin', join(__dirname, 'admin/views'))
	* ```
	*/
	mount(diskName, dirPath) {
		this.#mountedDirs.set(diskName, typeof dirPath === "string" ? dirPath : fileURLToPath(dirPath));
	}
	/**
	* Remove the previously mounted dir.
	*
	* ```js
	* loader.unmount('default')
	* ```
	*/
	unmount(diskName) {
		this.#mountedDirs.delete(diskName);
	}
	/**
	* Make path to a given template. The paths are resolved from the root
	* of the mounted directory.
	*
	* ```js
	* loader.makePath('welcome') // returns {diskRootPath}/welcome.edge
	* loader.makePath('admin::welcome') // returns {adminRootPath}/welcome.edge
	* loader.makePath('users.list') // returns {diskRootPath}/users/list.edge
	* ```
	*
	* @throws Error if disk is not mounted and attempting to make path for it.
	*/
	makePath(templatePath) {
		/**
		* Return the template path as it is, when it is registered
		* dynamically
		*/
		if (this.#preRegistered.has(templatePath)) return templatePath;
		/**
		* Return absolute path as it is
		*/
		if (isAbsolute(templatePath)) return templatePath;
		/**
		* Extract disk name and template path from the expression
		*/
		const [diskName, template] = this.#extractDiskAndTemplateName(templatePath);
		/**
		* Raise exception when disk name is not registered
		*/
		const mountedDir = this.#mountedDirs.get(diskName);
		if (!mountedDir) throw new Error(`"${diskName}" namespace is not mounted`);
		return join(mountedDir, template);
	}
	/**
	* Resolves the template by reading its contents from the disk
	*
	* ```js
	* loader.resolve('welcome', true)
	*
	* // output
	* {
	*   template: `<h1> Template content </h1>`,
	* }
	* ```
	*/
	resolve(templatePath) {
		/**
		* Return from pre-registered one's if exists
		*/
		if (this.#preRegistered.has(templatePath)) return this.#preRegistered.get(templatePath);
		/**
		* Make absolute to the file on the disk
		*/
		templatePath = isAbsolute(templatePath) ? templatePath : this.makePath(templatePath);
		return { template: this.#readTemplateContents(templatePath) };
	}
	/**
	* Register in memory template for a given path. This is super helpful
	* when distributing components.
	*
	* ```js
	* loader.register('welcome', {
	*   template: '<h1> Template content </h1>',
	*   Presenter: class Presenter {
	*     constructor (state) {
	*       this.state = state
	*     }
	*   }
	* })
	* ```
	*
	* @throws Error if template content is empty.
	*/
	register(templatePath, contents) {
		/**
		* Ensure template content is defined as a string
		*/
		if (typeof contents.template !== "string") throw new Error("Make sure to define the template content as a string");
		/**
		* Do not overwrite existing template with same template path
		*/
		if (this.#preRegistered.has(templatePath)) throw new Error(`Cannot override previously registered "${templatePath}" template`);
		this.#preRegistered.set(templatePath, contents);
	}
	/**
	* Remove registered template
	*/
	remove(templatePath) {
		this.#preRegistered.delete(templatePath);
	}
	/**
	* Returns a list of components from all the disks. We assume
	* the components are stored within the components directory.
	*
	* Also, we treat all in-memory templates as components.
	*
	* The return path is same the path you will pass to the `@component`
	* tag.
	*/
	listComponents() {
		return [...this.#mountedDirs.keys()].map((diskName) => {
			return {
				diskName,
				components: this.#getDiskComponents(diskName)
			};
		});
	}
	/**
	* Returns a list of templates from all the disks and in-memory
	* templates as well
	*/
	listTemplates() {
		return [...this.#mountedDirs.keys()].map((diskName) => {
			return {
				diskName,
				templates: this.#getDiskTemplates(diskName)
			};
		});
	}
};
//#endregion
//#region src/tags/if.ts
/**
* If tag is used to define conditional blocks.
*/
const ifTag = {
	block: true,
	seekable: true,
	tagName: "if",
	/**
	* Compiles the if block node to a Javascript if statement
	*/
	compile(parser, buffer, token) {
		const parsed = parseJsArg(parser, token);
		/**
		* Disallow sequence expressions
		*/
		isNotSubsetOf(parsed, [expressions.SequenceExpression], () => {
			unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @if tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		/**
		* Start if block
		*/
		buffer.writeStatement(`if (${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
		/**
		* Process of all children recursively
		*/
		token.children.forEach((child) => parser.processToken(child, buffer));
		/**
		* Close if block
		*/
		buffer.writeStatement("}", token.filename, -1);
	}
};
//#endregion
//#region src/tags/let.ts
/**
* The let tag is used to set runtime values within the template. The value
* is set inside the current scope of the template.
*/
const letTag = {
	block: false,
	seekable: true,
	tagName: "let",
	noNewLine: true,
	/**
	* Compiles else block node to Javascript else statement
	*/
	compile(parser, buffer, token) {
		const parsed = parser.utils.generateAST(`let ${token.properties.jsArg}`, token.loc, token.filename).declarations[0];
		const key = parsed.id;
		const value = parsed.init;
		/**
		* The variable name has to be an identifier or the destructuring
		* operator.
		*/
		isSubsetOf(key, [
			"ObjectPattern",
			expressions.Identifier,
			"ArrayPattern"
		], () => {
			throw unallowedExpression(`Invalid variable name for the @let tag`, token.filename, parser.utils.getExpressionLoc(key));
		});
		/**
		* Define local variables based upon the expression
		*/
		if (key.type === "Identifier") parser.stack.defineVariable(key.name);
		else if (key.type === "ObjectPattern") key.properties.forEach((property) => {
			parser.stack.defineVariable(property.argument ? property.argument.name : property.value.name);
		});
		else if (key.type === "ArrayPattern") key.elements.forEach((element) => {
			parser.stack.defineVariable(element.argument ? element.argument.name : element.name);
		});
		/**
		* Declare let variable
		*/
		const expression = `let ${parser.utils.stringify(key)} = ${parser.utils.stringify(parser.utils.transformAst(value, token.filename, parser))}`;
		buffer.writeExpression(expression, token.filename, token.loc.start.line);
	},
	/**
	* Add methods to the template for running the loop
	*/
	boot(template) {
		template.macro("setValue", lodash.set);
	}
};
//#endregion
//#region src/tags/each.ts
/**
* Returns the list to loop over for the each binary expression
*/
function getLoopList(rhsExpression, parser, filename) {
	return parser.utils.stringify(parser.utils.transformAst(rhsExpression, filename, parser));
}
/**
* Returns loop item and the index for the each binary expression
*/
function getLoopItemAndIndex(lhsExpression, parser, filename) {
	/**
	* Ensure the LHS content inside `@each()` curly braces is a `SequenceExpression` or
	* `Identifier`. Anything else is not allowed.
	*
	* For example:
	*
	* - In `@each(user in users)`, `user` is an indentifier
	* - In `@each((user, index) in users)`, `(user, index)` is a sequence expression
	*/
	isSubsetOf(lhsExpression, [expressions.SequenceExpression, expressions.Identifier], () => {
		unallowedExpression(`invalid left hand side "${lhsExpression.type}" expression for the @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression));
	});
	/**
	* Return list index from the sequence expression
	*/
	if (lhsExpression.type === "SequenceExpression") {
		/**
		* First item of the sequence expression must be an idenifier
		*/
		isSubsetOf(lhsExpression.expressions[0], [expressions.Identifier], () => {
			unallowedExpression(`"${lhsExpression.expressions[0]}.type" is not allowed as value identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[0]));
		});
		/**
		* Second item of the sequence expression must be an idenifier
		*/
		isSubsetOf(lhsExpression.expressions[1], [expressions.Identifier], () => {
			unallowedExpression(`"${lhsExpression.expressions[1]}.type" is not allowed as key identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[1]));
		});
		return [lhsExpression.expressions[0].name, lhsExpression.expressions[1].name];
	}
	/**
	* There is no key, just the value
	*/
	return [lhsExpression.name];
}
/**
* Each tag is used to run a foreach loop on arrays and even objects.
*
* ```edge
* @each((user, index) in users)
*   {{ user }} {{ index }}
* @endeach
* ```
*/
const eachTag = {
	block: true,
	seekable: true,
	tagName: "each",
	/**
	* Compile the template
	*/
	compile(parser, buffer, token) {
		const awaitKeyword = parser.asyncMode ? "await " : "";
		const loopFunctionName = parser.asyncMode ? "loopAsync" : "loop";
		const asyncKeyword = parser.asyncMode ? "async " : "";
		/**
		* We just generate the AST and do not transform it, since the transform
		* function attempts to resolve identifiers and we don't want that
		*/
		const { expression } = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename);
		/**
		* Each tag only accepts the binary expression or sequence expression. ie `user in users`
		*/
		isSubsetOf(expression, [expressions.BinaryExpression], () => {
			unallowedExpression(`"${token.properties.jsArg}" is not valid expression for the @each tag`, token.filename, parser.utils.getExpressionLoc(expression));
		});
		/**
		* Finding if an else child exists inside the each tag
		*/
		const elseIndex = token.children.findIndex((child) => lexerUtils.isTag(child, "else"));
		const elseChildren = elseIndex > -1 ? token.children.splice(elseIndex) : [];
		/**
		* Fetching the item,index and list for the each loop
		*/
		const list = getLoopList(expression.right, parser, token.filename);
		const [item, index] = getLoopItemAndIndex(expression.left, parser, token.filename);
		/**
		* If there is an else statement, then wrap the loop inside the `if` statement first
		*/
		if (elseIndex > -1) buffer.writeStatement(`if(template.size(${list})) {`, token.filename, token.loc.start.line);
		/**
		* Write the loop statement to the template
		*/
		const loopCallbackArgs = (index ? [item, index] : [item]).join(",");
		buffer.writeStatement(`${awaitKeyword}template.${loopFunctionName}(${list}, ${asyncKeyword}function (${loopCallbackArgs}) {`, token.filename, token.loc.start.line);
		/**
		* Start a new parser scope. So that all variable resolutions for the `item`
		* are pointing to the local variable and not the template `state`.
		*/
		parser.stack.defineScope();
		parser.stack.defineVariable(item);
		index && parser.stack.defineVariable(index);
		/**
		* Process all children
		*/
		token.children.forEach((child) => parser.processToken(child, buffer));
		/**
		* Clear scope
		*/
		parser.stack.clearScope();
		/**
		* Close each loop
		*/
		buffer.writeExpression("})", token.filename, -1);
		/**
		* If there is an else statement, then process
		* else childs and close the if block
		*/
		if (elseIndex > -1) {
			elseChildren.forEach((elseChild) => parser.processToken(elseChild, buffer));
			buffer.writeStatement("}", token.filename, -1);
		}
	},
	/**
	* Add methods to the template for running the loop
	*/
	boot(template) {
		template.macro("loopAsync", asyncEach);
		template.macro("loop", each);
		template.macro("size", lodash.size);
	}
};
//#endregion
//#region src/tags/slot.ts
/**
* Slot tag is used to define the slots of a given component. Slots cannot be
* nested and must appear as top level children inside a component.
*/
const slotTag = {
	block: true,
	seekable: true,
	tagName: "slot",
	noNewLine: true,
	compile(_, __, token) {
		throw new EdgeError$1("@slot tag must appear as top level tag inside the @component tag", "E_ORPHAN_SLOT_TAG", {
			line: token.loc.start.line,
			col: token.loc.start.col,
			filename: token.filename
		});
	}
};
//#endregion
//#region src/tags/else.ts
const elseTag = {
	block: false,
	seekable: false,
	tagName: "else",
	/**
	* Compiles else block node to Javascript else statement
	*/
	compile(_, buffer, token) {
		buffer.writeStatement("} else {", token.filename, -1);
	}
};
//#endregion
//#region src/tags/eval.ts
/**
* The eval tag accepts expressions similar to double curly
* braces. However, it does not write anything to the
* output.
*/
const evalTag = {
	block: false,
	seekable: true,
	tagName: "eval",
	noNewLine: true,
	/**
	* Compiles the tag AST
	*/
	compile(parser, buffer, token) {
		const parsed = parseJsArg(parser, token);
		buffer.writeExpression(parser.utils.stringify(parsed), token.filename, token.loc.start.line);
	}
};
//#endregion
//#region src/tags/stack.ts
/**
* Stack tag to define stack placeholders
*/
const stackTag = {
	tagName: "stack",
	block: false,
	seekable: true,
	noNewLine: false,
	compile(parser, buffer, token) {
		const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
		/**
		* Disallow sequence expressions
		*/
		if (expressions.SequenceExpression.includes(parsed.type)) throw new EdgeError$1(`"${token.properties.jsArg}" is not a valid argument type for the "@stack" tag`, "E_UNALLOWED_EXPRESSION", {
			...parser.utils.getExpressionLoc(parsed),
			filename: token.filename
		});
		/**
		* Create stack
		*/
		buffer.outputExpression(`template.stacks.create(${parser.utils.stringify(parsed)})`, token.filename, token.loc.start.line, false);
	}
};
//#endregion
//#region src/tags/assign.ts
/**
* The assign tag is used to re-assign value to an existing variable
*/
const assignTag = {
	block: false,
	seekable: true,
	tagName: "assign",
	noNewLine: true,
	/**
	* Compiles else block node to Javascript else statement
	*/
	compile(parser, buffer, token) {
		const parsed = parseJsArg(parser, token);
		isSubsetOf(parsed, [expressions.AssignmentExpression], () => {
			throw unallowedExpression(`Invalid expression for the @assign tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		buffer.writeExpression(parser.utils.stringify(parsed), token.filename, token.loc.start.line);
	},
	/**
	* Add methods to the template for running the loop
	*/
	boot(template) {
		template.macro("setValue", lodash.set);
	}
};
//#endregion
//#region src/tags/inject.ts
/**
* The inject tag is used within the components to share values with the
* component caller.
*/
const injectTag = {
	block: false,
	seekable: true,
	tagName: "inject",
	noNewLine: true,
	compile(parser, buffer, token) {
		token.properties.jsArg = `(${token.properties.jsArg})`;
		const parsed = parseJsArg(parser, token);
		/**
		* The inject tag only accepts an object expression.
		*/
		isSubsetOf(parsed, [
			expressions.ObjectExpression,
			expressions.Identifier,
			expressions.CallExpression
		], () => {
			throw unallowedExpression(`"${token.properties.jsArg}" is not a valid key-value pair for the @inject tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		/**
		* Ensure $slots are defined before merging shared state
		*/
		buffer.writeStatement("if (!state.$slots || !state.$slots.$context) {", token.filename, token.loc.start.line);
		buffer.writeExpression(`throw new Error('Cannot use "@inject" outside of a component scope')`, token.filename, token.loc.start.line);
		buffer.writeStatement("}", token.filename, token.loc.start.line);
		buffer.writeExpression(`Object.assign(state.$slots.$context, ${parser.utils.stringify(parsed)})`, token.filename, token.loc.start.line);
	}
};
//#endregion
//#region src/tags/unless.ts
/**
* Inverse of the `if` condition. The term `unless` is more readable and logical
* vs using `@if(!expression)`.
*
* ```edge
* @unless(auth.user)
*   <a href="/login"> Login </a>
* @endunless
* ```
*/
const unlessTag = {
	block: true,
	seekable: true,
	tagName: "unless",
	/**
	* Compiles the if block node to a Javascript if statement
	*/
	compile(parser, buffer, token) {
		const parsed = parseJsArg(parser, token);
		/**
		* Disallow sequence expressions
		*/
		isNotSubsetOf(parsed, [expressions.SequenceExpression], () => {
			unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @unless tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		/**
		* Start if block
		*/
		buffer.writeStatement(`if (!${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
		/**
		* Process of all children recursively
		*/
		token.children.forEach((child) => parser.processToken(child, buffer));
		/**
		* Close if block
		*/
		buffer.writeStatement("}", token.filename, -1);
	}
};
//#endregion
//#region src/tags/else_if.ts
/**
* Else if tag is used to define conditional blocks. We keep `@elseif` tag
* is a inline tag, so that everything between the `if` and the `elseif`
* comes `if` children.
*/
const elseIfTag = {
	block: false,
	seekable: true,
	tagName: "elseif",
	/**
	* Compiles the else if block node to a Javascript if statement
	*/
	compile(parser, buffer, token) {
		const parsed = parseJsArg(parser, token);
		/**
		* Disallow sequence expressions
		*/
		isNotSubsetOf(parsed, [expressions.SequenceExpression], () => {
			unallowedExpression(`{${token.properties.jsArg}} is not a valid argument type for the @elseif tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		/**
		* Start else if block
		*/
		buffer.writeStatement(`} else if (${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
	}
};
//#endregion
//#region src/tags/push_to.ts
/**
* Stack tag to define stack placeholders
*/
const pushToTag = {
	tagName: "pushTo",
	block: true,
	seekable: true,
	noNewLine: true,
	generateId() {
		return `stack_${nanoid()}`;
	},
	compile(parser, buffer, token) {
		const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
		/**
		* Disallow sequence expressions
		*/
		if (expressions.SequenceExpression.includes(parsed.type)) throw new EdgeError$1(`"${token.properties.jsArg}" is not a valid argument type for the "@pushTo" tag`, "E_UNALLOWED_EXPRESSION", {
			...parser.utils.getExpressionLoc(parsed),
			filename: token.filename
		});
		/**
		* Each stack must be unique
		*/
		const stackId = this.generateId();
		/**
		* Create a custom buffer for the stack. Since we do not want to the write
		* to the main buffer
		*/
		const stackBuffer = buffer.create(token.filename, { outputVar: stackId });
		/**
		* Process children
		*/
		for (let child of token.children) parser.processToken(child, stackBuffer);
		/**
		* Flush the stack buffer content to the main buffer
		*/
		buffer.writeStatement(stackBuffer.disableFileAndLineVariables().disableReturnStatement().disableTryCatchBlock().flush(), token.filename, token.loc.start.line);
		buffer.writeExpression(`template.stacks.pushTo(${parser.utils.stringify(parsed)}, ${stackId})`, token.filename, token.loc.start.line);
	}
};
//#endregion
//#region src/tags/include.ts
/**
* List of expressions allowed for the include tag
*/
const ALLOWED_EXPRESSION = [
	expressions.Literal,
	expressions.Identifier,
	expressions.CallExpression,
	expressions.TemplateLiteral,
	expressions.MemberExpression,
	expressions.LogicalExpression,
	expressions.ConditionalExpression
];
/**
* Returns the expression for rendering the partial
*/
function getRenderExpression(parser, parsedExpression) {
	/**
	* We need to pass the local variables to the partial render function
	*/
	const localVariables = parser.stack.list();
	/**
	* Arguments for the `renderInline` method
	*/
	const renderArgs = localVariables.length ? [parser.utils.stringify(parsedExpression), localVariables.map((localVar) => `"${localVar}"`).join(",")] : [parser.utils.stringify(parsedExpression)];
	/**
	* Arguments for invoking the output function of `renderInline`
	*/
	const callFnArgs = localVariables.length ? [
		"template",
		"state",
		"$context",
		localVariables.map((localVar) => localVar).join(",")
	] : [
		"template",
		"state",
		"$context"
	];
	return `template.compilePartial(${renderArgs.join(",")})(${callFnArgs.join(",")})`;
}
/**
* Include tag is used to include partials in the same scope of the parent
* template.
*
* ```edge
* @include('partials.header')
* ```
*/
const includeTag = {
	block: false,
	seekable: true,
	tagName: "include",
	/**
	* Compiles else block node to Javascript else statement
	*/
	compile(parser, buffer, token) {
		const awaitKeyword = parser.asyncMode ? "await " : "";
		const parsed = parseJsArg(parser, token);
		/**
		* Only mentioned expressions are allowed inside `@include` tag
		*/
		isSubsetOf(parsed, ALLOWED_EXPRESSION, () => {
			unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @include tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		buffer.outputExpression(`${awaitKeyword}${getRenderExpression(parser, parsed)}`, token.filename, token.loc.start.line, false);
	}
};
//#endregion
//#region src/tags/debugger.ts
/**
* Add debugger break point to the compiled template
*/
const debuggerTag = {
	block: false,
	seekable: false,
	tagName: "debugger",
	noNewLine: true,
	/**
	* Compiles `@debugger` tags
	*/
	compile(_, buffer, token) {
		buffer.writeExpression("debugger", token.filename, token.loc.start.line);
	}
};
//#endregion
//#region src/tags/new_error.ts
/**
* newError tag to raise exceptions inside your templates. They will point
* back to the exact line:col in the template
*/
const newErrorTag = {
	block: false,
	seekable: true,
	tagName: "newError",
	noNewLine: true,
	compile(parser, buffer, token) {
		const parsed = parseJsArg(parser, token);
		let message = "";
		let line = token.loc.start.line;
		let col = token.loc.start.col;
		let filename = "$filename";
		if (parsed.type === expressions.SequenceExpression) {
			message = parser.utils.stringify(parsed.expressions[0]);
			filename = parsed.expressions[1] ? parser.utils.stringify(parsed.expressions[1]) : "$filename";
			line = parsed.expressions[2] ? parser.utils.stringify(parsed.expressions[2]) : token.loc.start.line;
			col = parsed.expressions[3] ? parser.utils.stringify(parsed.expressions[3]) : token.loc.start.col;
		} else message = parser.utils.stringify(parsed);
		/**
		* Raise the exception with the correct filename and the line number
		*/
		buffer.writeStatement(`template.newError(${message}, ${filename}, ${line}, ${col})`, token.filename, token.loc.start.line);
	}
};
//#endregion
//#region src/tags/component.ts
/**
* A list of allowed expressions for the component name
*/
const ALLOWED_EXPRESSION_FOR_COMPONENT_NAME = [
	expressions.Identifier,
	expressions.Literal,
	expressions.LogicalExpression,
	expressions.MemberExpression,
	expressions.ConditionalExpression,
	expressions.CallExpression,
	expressions.TemplateLiteral
];
/**
* Returns the component name and props by parsing the component jsArg expression
*/
function getComponentNameAndProps(expression, parser, filename) {
	let name;
	/**
	* Use the first expression inside the sequence expression as the name
	* of the component
	*/
	if (expression.type === expressions.SequenceExpression) name = expression.expressions.shift();
	else name = expression;
	/**
	* Ensure the component name is a literal value or an expression that
	* outputs a literal value
	*/
	isSubsetOf(name, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME, () => {
		unallowedExpression(`"${parser.utils.stringify(name)}" is not a valid argument for component name`, filename, parser.utils.getExpressionLoc(name));
	});
	/**
	* Parse rest of sequence expressions as an objectified string.
	*/
	if (expression.type === expressions.SequenceExpression) {
		/**
		* We only need to entertain the first expression of the sequence
		* expression, as components allows a max of two arguments
		*/
		const firstSequenceExpression = expression.expressions[0];
		return [parser.utils.stringify(name), parser.utils.stringify(firstSequenceExpression)];
	}
	/**
	* When top level expression is not a sequence expression, then we assume props
	* as empty stringified object.
	*/
	return [parser.utils.stringify(name), "{}"];
}
/**
* Parses the slot component to fetch it's name and props
*/
function getSlotNameAndProps(token, parser) {
	/**
	* We just generate the acorn AST only, since we don't want parser to transform
	* ast to edge statements for a `@slot` tag.
	*/
	const parsed = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename).expression;
	isSubsetOf(parsed, [expressions.Literal, expressions.SequenceExpression], () => {
		unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed));
	});
	/**
	* Fetch the slot name
	*/
	let name;
	if (parsed.type === expressions.SequenceExpression) name = parsed.expressions[0];
	else name = parsed;
	/**
	* Validating the slot name to be a literal value, since slot names cannot be dynamic
	*/
	isSubsetOf(name, [expressions.Literal], () => {
		unallowedExpression("slot name must be a valid string literal", token.filename, parser.utils.getExpressionLoc(name));
	});
	/**
	* Return the slot name with empty props, when the expression is a literal
	* value.
	*/
	if (parsed.type === expressions.Literal) return [name.raw, null];
	/**
	* Make sure the sequence expression has only 2 arguments in it. Though it doesn't hurt
	* the rendering of component, we must not run code with false expectations.
	*/
	if (parsed.expressions.length > 2) throw new EdgeError$1("maximum of 2 arguments are allowed for @slot tag", "E_MAX_ARGUMENTS", {
		line: parsed.loc.start.line,
		col: parsed.loc.start.column,
		filename: token.filename
	});
	isSubsetOf(parsed.expressions[1], [expressions.Identifier], () => {
		unallowedExpression(`"${parser.utils.stringify(parsed.expressions[1])}" is not valid prop identifier for @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed.expressions[1]));
	});
	/**
	* Returning the slot name and slot props name
	*/
	return [name.raw, parsed.expressions[1].name];
}
/**
* The component tag implementation. It is one of the most complex tags and
* can be used as a reference for creating other tags.
*/
const componentTag = {
	block: true,
	seekable: true,
	tagName: "component",
	compile(parser, buffer, token) {
		const asyncKeyword = parser.asyncMode ? "async " : "";
		const awaitKeyword = parser.asyncMode ? "await " : "";
		const parsed = parseJsArg(parser, token);
		/**
		* Check component jsProps for allowed expressions
		*/
		isSubsetOf(parsed, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME.concat(expressions.SequenceExpression), () => {
			unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @component tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		/**
		* Pulling the name and props for the component. The underlying method will
		* ensure that the arguments passed to component tag are valid
		*/
		const [name, props] = getComponentNameAndProps(parsed, parser, token.filename);
		/**
		* Loop over all the children and set them as part of slots. If no slot
		* is defined, then the content will be part of the main slot
		*/
		const slots = {};
		/**
		* Main slot collects everything that is out of the named slots
		* inside a component
		*/
		const mainSlot = {
			outputVar: "slot_main",
			props: {},
			buffer: buffer.create(token.filename, { outputVar: "slot_main" }),
			line: -1,
			filename: token.filename
		};
		let slotsCounter = 0;
		/**
		* Loop over all the component children
		*/
		token.children.forEach((child) => {
			/**
			* If children is not a slot, then add it to the main slot
			*/
			if (!lexerUtils.isTag(child, "slot")) {
				/**
				* Ignore first newline inside the unnamed main slot
				*/
				if (mainSlot.buffer.size === 0 && child.type === "newline") return;
				parser.processToken(child, mainSlot.buffer);
				return;
			}
			/**
			* Fetch slot and props
			*/
			const [slotName, slotProps] = getSlotNameAndProps(child, parser);
			slotsCounter++;
			/**
			* Create a new slot with buffer to process the children
			*/
			if (!slots[slotName]) {
				/**
				* Slot buffer points to the component file name, since slots doesn't
				* have their own file names.
				*/
				slots[slotName] = {
					outputVar: `slot_${slotsCounter}`,
					buffer: buffer.create(token.filename, { outputVar: `slot_${slotsCounter}` }),
					props: slotProps,
					line: -1,
					filename: token.filename
				};
				/**
				* Only start the frame, when there are props in use for a given slot.
				*/
				if (slotProps) {
					parser.stack.defineScope();
					parser.stack.defineVariable(slotProps);
				}
			}
			/**
			* Self process the slot children.
			*/
			child.children.forEach((grandChildren) => {
				parser.processToken(grandChildren, slots[slotName].buffer);
			});
			/**
			* Close the frame after process the slot children
			*/
			if (slotProps) parser.stack.clearScope();
		});
		const obj = new StringifiedObject();
		/**
		* Creating a shallow copy of context for the component slots and its children
		*/
		obj.add("$context", "Object.assign({}, $context)");
		/**
		* Add main slot to the stringified object, when main slot
		* is not defined otherwise.
		*/
		if (!slots["main"]) if (mainSlot.buffer.size) {
			mainSlot.buffer.wrap(`${asyncKeyword}function () { const $context = this.$context;`, "}");
			obj.add("main", mainSlot.buffer.disableFileAndLineVariables().flush());
		} else obj.add("main", "function () { return \"\" }");
		/**
		* We convert the slots to an objectified string, that is passed to `template.renderWithState`,
		* which will pass it to the component as it's local state.
		*/
		Object.keys(slots).forEach((slotName) => {
			if (slots[slotName].buffer.size) {
				const fnCall = slots[slotName].props ? `${asyncKeyword}function (${slots[slotName].props}) { const $context = this.$context;` : `${asyncKeyword}function () { const $context = this.$context;`;
				slots[slotName].buffer.wrap(fnCall, "}");
				obj.add(slotName, slots[slotName].buffer.disableFileAndLineVariables().flush());
			} else obj.add(slotName, "function () { return \"\" }");
		});
		const caller = new StringifiedObject();
		caller.add("filename", "$filename");
		caller.add("line", "$lineNumber");
		caller.add("col", 0);
		/**
		* Write the line to render the component with it's own state
		*/
		buffer.outputExpression(`${awaitKeyword}template.compileComponent(${name})(template, template.getComponentState(${props}, ${obj.flush()}, ${caller.flush()}), $context)`, token.filename, token.loc.start.line, false);
	}
};
//#endregion
//#region src/tags/include_if.ts
/**
* Include tag is used to include partials in the same scope of the parent
* template.
*
* ```edge
* @include('partials.header')
* ```
*/
const includeIfTag = {
	block: false,
	seekable: true,
	tagName: "includeIf",
	/**
	* Compiles else block node to Javascript else statement
	*/
	compile(parser, buffer, token) {
		const awaitKeyword = parser.asyncMode ? "await " : "";
		const parsed = parseJsArg(parser, token);
		/**
		* The include if only accepts the sequence expression
		*/
		isSubsetOf(parsed, [expressions.SequenceExpression], () => {
			unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(parsed));
		});
		/**
		* Disallow more than or less than 2 values for the sequence expression
		*/
		if (parsed.expressions.length !== 2) throw new EdgeError$1("@includeIf expects a total of 2 arguments", "E_ARGUMENTS_MIS_MATCH", {
			line: parsed.loc.start.line,
			col: parsed.loc.start.column,
			filename: token.filename
		});
		const [conditional, include] = parsed.expressions;
		isNotSubsetOf(conditional, [expressions.SequenceExpression], () => {
			unallowedExpression(`"${conditional.type}" is not a valid 1st argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(conditional));
		});
		isSubsetOf(include, ALLOWED_EXPRESSION, () => {
			unallowedExpression(`"${include.type}" is not a valid 2nd argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(include));
		});
		buffer.writeStatement(`if (${parser.utils.stringify(conditional)}) {`, token.filename, token.loc.start.line);
		buffer.outputExpression(`${awaitKeyword}${getRenderExpression(parser, include)}`, token.filename, token.loc.start.line, false);
		buffer.writeStatement("}", token.filename, -1);
	}
};
//#endregion
//#region src/tags/push_once_to.ts
/**
* Stack tag to define stack placeholders
*/
const pushOnceToTag = {
	tagName: "pushOnceTo",
	block: true,
	seekable: true,
	noNewLine: true,
	generateId() {
		return `stack_${nanoid()}`;
	},
	compile(parser, buffer, token) {
		const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
		/**
		* Disallow sequence expressions
		*/
		if (expressions.SequenceExpression.includes(parsed.type)) throw new EdgeError$1(`"${token.properties.jsArg}" is not a valid argument type for the "@pushOnceTo" tag`, "E_UNALLOWED_EXPRESSION", {
			...parser.utils.getExpressionLoc(parsed),
			filename: token.filename
		});
		/**
		* Each stack must be unique
		*/
		const stackId = this.generateId();
		/**
		* Create a custom buffer for the stack. Since we do not want to the write
		* to the main buffer
		*/
		const stackBuffer = buffer.create(token.filename, { outputVar: stackId });
		/**
		* Process children
		*/
		for (let child of token.children) parser.processToken(child, stackBuffer);
		/**
		* Flush the stack buffer content to the main buffer
		*/
		buffer.writeStatement(stackBuffer.disableFileAndLineVariables().disableReturnStatement().disableTryCatchBlock().flush(), token.filename, token.loc.start.line);
		const { line, col } = token.loc.start;
		const sourceId = `${token.filename.replace(/\\|\//g, "_")}-${line}-${col}`;
		buffer.writeExpression(`template.stacks.pushOnceTo(${parser.utils.stringify(parsed)}, '${sourceId}', ${stackId})`, token.filename, line);
	}
};
//#endregion
//#region src/tags/main.ts
var main_exports = /* @__PURE__ */ __exportAll({
	assign: () => assignTag,
	component: () => componentTag,
	debugger: () => debuggerTag,
	each: () => eachTag,
	else: () => elseTag,
	elseif: () => elseIfTag,
	eval: () => evalTag,
	if: () => ifTag,
	include: () => includeTag,
	includeIf: () => includeIfTag,
	inject: () => injectTag,
	let: () => letTag,
	newError: () => newErrorTag,
	pushOnceTo: () => pushOnceToTag,
	pushTo: () => pushToTag,
	slot: () => slotTag,
	stack: () => stackTag,
	unless: () => unlessTag
});
//#endregion
//#region src/cache_manager.ts
/**
* In memory cache manager to cache pre-compiled templates.
*/
var CacheManager = class {
	enabled;
	#cacheStore = /* @__PURE__ */ new Map();
	constructor(enabled) {
		this.enabled = enabled;
	}
	/**
	* Returns a boolean to tell if a template has already been cached
	* or not.
	*/
	has(absPath) {
		return this.#cacheStore.has(absPath);
	}
	/**
	* Returns the template from the cache. If caching is disabled,
	* then it will return undefined.
	*/
	get(absPath) {
		if (!this.enabled) return;
		return this.#cacheStore.get(absPath);
	}
	/**
	* Set's the template path and the payload to the cache. If
	* cache is disabled, then this function results in a noop.
	*/
	set(absPath, payload) {
		if (!this.enabled) return;
		this.#cacheStore.set(absPath, payload);
	}
	/**
	* Delete template from the compiled cache
	*/
	delete(absPath) {
		if (!this.enabled) return;
		this.#cacheStore.delete(absPath);
	}
};
//#endregion
//#region src/compiler.ts
const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor;
/**
* Compiler is to used to compile templates using the `edge-parser`. Along with that
* it natively merges the contents of a layout with a parent template.
*/
var Compiler = class {
	/**
	* The variables someone can access inside templates. All other
	* variables will get prefixed with `state` property name
	*/
	#inlineVariables = [
		"$filename",
		"state",
		"$context"
	];
	/**
	* A fixed set of params to pass to the template every time.
	*/
	#templateParams = [
		"template",
		"state",
		"$context"
	];
	#claimTagFn;
	#loader;
	#tags;
	#processor;
	/**
	* Caches compiled templates
	*/
	cacheManager;
	/**
	* A boolean to know if compat mode is enabled
	*/
	compat;
	/**
	* Know if compiler is compiling in the async mode or not
	*/
	async;
	constructor(loader, tags, processor, options = {
		cache: true,
		async: false,
		compat: false
	}) {
		this.#processor = processor;
		this.#loader = loader;
		this.#tags = tags;
		this.async = !!options.async;
		this.compat = options.compat === true;
		this.cacheManager = new CacheManager(!!options.cache);
	}
	/**
	* Merges sections of base template and parent template tokens
	*/
	#mergeSections(base, extended) {
		/**
		* Collection of all sections from the extended tokens
		*/
		const extendedSections = {};
		/**
		* Collection of extended set calls as top level nodes. The set
		* calls are hoisted just like `var` statements in Javascript.
		*/
		const extendedSetCalls = [];
		extended.forEach((node) => {
			/**
			* Ignore new lines, comments, layout tag and empty raw nodes inside the parent
			* template
			*/
			if (lexerUtils.isTag(node, "layout") || node.type === "newline" || node.type === "raw" && !node.value.trim() || node.type === "comment") return;
			/**
			* Collect parent template sections
			*/
			if (lexerUtils.isTag(node, "section")) {
				extendedSections[node.properties.jsArg.trim()] = node;
				return;
			}
			/**
			* Collect set calls inside parent templates
			*/
			if (lexerUtils.isTag(node, "set")) {
				extendedSetCalls.push(node);
				return;
			}
			/**
			* Everything else is not allowed as top level nodes
			*/
			const [line, col] = lexerUtils.getLineAndColumn(node);
			throw new EdgeError$1("Template extending a layout can only use \"@section\" or \"@set\" tags as top level nodes", "E_UNALLOWED_EXPRESSION", {
				line,
				col,
				filename: node.filename
			});
		});
		/**
		* Replace/extend sections inside base tokens list
		*/
		const finalNodes = base.map((node) => {
			if (!lexerUtils.isTag(node, "section")) return node;
			const extendedNode = extendedSections[node.properties.jsArg.trim()];
			if (!extendedNode) return node;
			/**
			* Concat children when super was called
			*/
			if (extendedNode.children.length) {
				if (lexerUtils.isTag(extendedNode.children[0], "super")) {
					extendedNode.children.shift();
					extendedNode.children = node.children.concat(extendedNode.children);
				} else if (lexerUtils.isTag(extendedNode.children[1], "super")) {
					extendedNode.children.shift();
					extendedNode.children.shift();
					extendedNode.children = node.children.concat(extendedNode.children);
				}
			}
			return extendedNode;
		});
		/**
		* Set calls are hoisted to the top
		*/
		return [].concat(extendedSetCalls).concat(finalNodes);
	}
	/**
	* Generates an array of lexer tokens from the template string. Further tokens
	* are checked for layouts and if layouts are used, their sections will be
	* merged together.
	*/
	#templateContentToTokens(content, parser, absPath) {
		let templateTokens = parser.tokenize(content, { filename: absPath });
		/**
		* Parse layout and section in compat mode only
		*/
		if (this.compat) {
			const firstToken = templateTokens[0];
			/**
			* The `layout` is inbuilt feature from core, where we merge the layout
			* and parent template sections together
			*/
			if (lexerUtils.isTag(firstToken, "layout")) {
				const layoutName = firstToken.properties.jsArg.replace(/'|"/g, "");
				templateTokens = this.#mergeSections(this.tokenize(layoutName, parser), templateTokens);
			}
		}
		return templateTokens;
	}
	/**
	* Returns the parser instance for a given template
	*/
	#getParserFor(templatePath, localVariables) {
		const parser = new Parser(this.#tags, new Stack(), {
			claimTag: this.#claimTagFn,
			async: this.async,
			statePropertyName: "state",
			escapeCallPath: ["template", "escape"],
			localVariables: this.#inlineVariables,
			onTag: (tag) => this.#processor.executeTag({
				tag,
				path: templatePath
			})
		});
		/**
		* Define local variables on the parser. This is helpful when trying to compile
		* a partail and we want to share the local state of the parent template
		* with it
		*/
		if (localVariables) localVariables.forEach((localVariable) => parser.stack.defineVariable(localVariable));
		return parser;
	}
	/**
	* Returns the parser instance for a given template
	*/
	#getBufferFor(templatePath) {
		return new EdgeBuffer(templatePath, {
			outputVar: "out",
			rethrowCallPath: ["template", "reThrow"]
		});
	}
	/**
	* Wraps template output to a function along with local variables
	*/
	#wrapToFunction(template, localVariables) {
		const args = localVariables ? this.#templateParams.concat(localVariables) : this.#templateParams;
		if (this.async) return new AsyncFunction(...args, template);
		return new Function(...args, template);
	}
	/**
	* Define a function to claim tags
	*/
	claimTag(fn) {
		this.#claimTagFn = fn;
		return this;
	}
	/**
	* Converts the template content to an array of lexer tokens. The method is
	* same as the `parser.tokenize`, but it also handles layouts natively.
	*
	* ```
	* compiler.tokenize('<template-path>')
	* ```
	*/
	tokenize(templatePath, parser) {
		const absPath = this.#loader.makePath(templatePath);
		let { template } = this.#loader.resolve(absPath);
		return this.tokenizeRaw(template, absPath, parser);
	}
	/**
	* Tokenize a raw template
	*/
	tokenizeRaw(contents, templatePath = "eval.edge", parser) {
		contents = this.#processor.executeRaw({
			path: templatePath,
			raw: contents
		});
		return this.#templateContentToTokens(contents, parser || this.#getParserFor(templatePath), templatePath);
	}
	/**
	* Compiles the template contents to string. The output is same as the `edge-parser`,
	* it's just that the compiler uses the loader to load the templates and also
	* handles layouts.
	*
	* ```js
	* compiler.compile('welcome')
	* ```
	*/
	compile(templatePath, localVariables) {
		const absPath = this.#loader.makePath(templatePath);
		let cachedResponse = localVariables ? null : this.cacheManager.get(absPath);
		/**
		* Process the template and cache it
		*/
		if (!cachedResponse) {
			const parser = this.#getParserFor(absPath, localVariables);
			const buffer = this.#getBufferFor(absPath);
			this.tokenize(absPath, parser).forEach((token) => parser.processToken(token, buffer));
			/**
			* Processing template via hook
			*/
			const template = this.#processor.executeCompiled({
				path: absPath,
				compiled: buffer.flush()
			});
			const compiledTemplate = this.#wrapToFunction(template, localVariables);
			if (!localVariables) this.cacheManager.set(absPath, compiledTemplate);
			cachedResponse = compiledTemplate;
		}
		return cachedResponse;
	}
	/**
	* Compiles the template contents to string. The output is same as the `edge-parser`,
	* it's just that the compiler uses the loader to load the templates and also
	* handles layouts.
	*
	* ```js
	* compiler.compileRaw('welcome')
	* ```
	*/
	compileRaw(contents, templatePath = "eval.edge") {
		const parser = this.#getParserFor(templatePath);
		const buffer = this.#getBufferFor(templatePath);
		this.tokenizeRaw(contents, templatePath, parser).forEach((token) => parser.processToken(token, buffer));
		const template = this.#processor.executeCompiled({
			path: templatePath,
			compiled: buffer.flush()
		});
		return this.#wrapToFunction(template);
	}
};
//#endregion
//#region src/edge/globals.ts
var import_js_stringify = /* @__PURE__ */ __toESM(require_js_stringify(), 1);
/**
* Inbuilt globals
*/
const edgeGlobals = {
	/**
	* Converts new lines to break
	*/
	nl2br: (value) => {
		if (!value) return;
		return String(value).replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, "$1<br>");
	},
	/**
	* Inspect state
	*/
	inspect: (value) => {
		return htmlSafe(inspect.string.html(value));
	},
	/**
	* Truncate a sentence
	*/
	truncate: (value, length = 20, options) => {
		options = options || {};
		return string.truncate(value, length, {
			completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict,
			suffix: options.suffix
		});
	},
	/**
	* Generate an excerpt
	*/
	excerpt: (value, length = 20, options) => {
		options = options || {};
		return string.excerpt(value, length, {
			completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict,
			suffix: options.suffix
		});
	},
	/**
	* Helpers related to HTML
	*/
	html: {
		escape,
		safe: htmlSafe,
		classNames: (/* @__PURE__ */ __toESM(require_classnames(), 1)).default,
		attrs: (values) => {
			return htmlSafe(stringifyAttributes(values));
		}
	},
	/**
	* Helpers related to JavaScript
	*/
	js: { stringify: import_js_stringify.default },
	camelCase: string.camelCase,
	snakeCase: string.snakeCase,
	dashCase: string.dashCase,
	pascalCase: string.pascalCase,
	capitalCase: string.capitalCase,
	sentenceCase: string.sentenceCase,
	dotCase: string.dotCase,
	noCase: string.noCase,
	titleCase: string.titleCase,
	pluralize: string.pluralize,
	sentence: string.sentence,
	prettyMs: string.milliseconds.format,
	toMs: string.milliseconds.parse,
	prettyBytes: string.bytes.format,
	toBytes: string.bytes.parse,
	ordinal: string.ordinal
};
//#endregion
//#region src/processor.ts
/**
* Exposes the API to register a set of handlers to process the
* templates output at different stages
*/
var Processor = class {
	#handlers = /* @__PURE__ */ new Map();
	/**
	* Execute tag handler
	*/
	executeTag(data) {
		const handlers = this.#handlers.get("tag");
		if (!handlers) return;
		handlers.forEach((handler) => {
			handler(data);
		});
	}
	/**
	* Execute raw handlers
	*/
	executeRaw(data) {
		const handlers = this.#handlers.get("raw");
		if (!handlers) return data.raw;
		handlers.forEach((handler) => {
			const output = handler(data);
			if (output !== void 0) data.raw = output;
		});
		return data.raw;
	}
	/**
	* Execute compiled handlers
	*/
	executeCompiled(data) {
		const handlers = this.#handlers.get("compiled");
		if (!handlers) return data.compiled;
		handlers.forEach((handler) => {
			const output = handler(data);
			if (output !== void 0) data.compiled = output;
		});
		return data.compiled;
	}
	/**
	* Execute output handlers
	*/
	executeOutput(data) {
		const handlers = this.#handlers.get("output");
		if (!handlers) return data.output;
		handlers.forEach((handler) => {
			const output = handler(data);
			if (output !== void 0) data.output = output;
		});
		return data.output;
	}
	process(event, handler) {
		if (!this.#handlers.has(event)) this.#handlers.set(event, /* @__PURE__ */ new Set());
		this.#handlers.get(event).add(handler);
		return this;
	}
};
//#endregion
//#region src/edge/renderer.ts
/**
* Renders a given template with it's shared state
*/
var EdgeRenderer = class EdgeRenderer {
	#compiler;
	#processor;
	#asyncCompiler;
	/**
	* Global state
	*/
	#locals = {};
	#globals;
	constructor(compiler, asyncCompiler, processor, globals) {
		this.#compiler = compiler;
		this.#asyncCompiler = asyncCompiler;
		this.#processor = processor;
		this.#globals = globals;
	}
	/**
	* Clone renderer instance with shared data
	*/
	clone() {
		return new EdgeRenderer(this.#compiler, this.#asyncCompiler, this.#processor, this.#globals).share(this.#locals);
	}
	/**
	* Share local variables with the template. They will overwrite the
	* globals
	*/
	share(data) {
		lodash.merge(this.#locals, data);
		return this;
	}
	/**
	* Retrieves the local and global variables, used for testing things that
	* share variables
	*/
	getState() {
		return {
			...this.#globals,
			...this.#locals
		};
	}
	/**
	* Render the template
	*/
	async render(templatePath, state = {}) {
		return new Template(this.#asyncCompiler, this.#globals, this.#locals, this.#processor).render(templatePath, state);
	}
	/**
	* Render the template
	*/
	renderSync(templatePath, state = {}) {
		return new Template(this.#compiler, this.#globals, this.#locals, this.#processor).render(templatePath, state);
	}
	/**
	* Render the template from a raw string
	*/
	async renderRaw(contents, state = {}, templatePath) {
		return new Template(this.#asyncCompiler, this.#globals, this.#locals, this.#processor).renderRaw(contents, state, templatePath);
	}
	/**
	* Render the template from a raw string
	*/
	renderRawSync(contents, state = {}, templatePath) {
		return new Template(this.#compiler, this.#globals, this.#locals, this.#processor).renderRaw(contents, state, templatePath);
	}
};
//#endregion
//#region src/plugins/supercharged.ts
/**
* Hooks into the compiler phase of Edge and converts
* tags to components.
*
* The components are discovered from the components directory
* inside every registered disk.
*/
var SuperChargedComponents = class {
	#edge;
	#components = {};
	constructor(edge) {
		this.#edge = edge;
		this.#claimTags();
		this.#transformTags();
	}
	/**
	* Refreshes the list of components
	*/
	refreshComponents() {
		this.#components = this.#edge.loader.listComponents().reduce((result, { components }) => {
			components.forEach((component) => {
				result[component.tagName] = component.componentName;
			});
			return result;
		}, {});
	}
	/**
	* Registers hook to claim self processing of tags that
	* are references to components
	*/
	#claimTags() {
		this.#edge.compiler.claimTag((name) => {
			if (this.#components[name]) return {
				seekable: true,
				block: true
			};
			return null;
		});
		this.#edge.asyncCompiler.claimTag((name) => {
			if (this.#components[name]) return {
				seekable: true,
				block: true
			};
			return null;
		});
	}
	/**
	* Transforms tags to component calls
	*/
	#transformTags() {
		this.#edge.processor.process("tag", ({ tag }) => {
			const component = this.#components[tag.properties.name];
			if (!component) return;
			tag.properties.name = "component";
			if (tag.properties.jsArg.trim() === "") tag.properties.jsArg = `'${component}'`;
			else tag.properties.jsArg = `'${component}',${tag.properties.jsArg}`;
		});
	}
};
/**
* The superCharged plugin converts components stored within the
* components directory of all the disk to Edge tags.
*/
let superCharged;
const pluginSuperCharged = (edge, firstRun) => {
	if (firstRun) superCharged = new SuperChargedComponents(edge);
	superCharged.refreshComponents();
};
//#endregion
//#region src/edge/main.ts
/**
* Exposes the API to render templates, register custom tags and globals
*/
var Edge = class Edge {
	/**
	* Create an instance of edge with given options
	*/
	static create(options = {}) {
		return new Edge(options);
	}
	/**
	* An array of bundled plugins
	*/
	#bundledPlugins = [];
	/**
	* An array of registered plugins
	*/
	#plugins = [];
	/**
	* Array of registered renderer hooks
	*/
	#renderCallbacks = [];
	/**
	* Reference to the registered processor handlers
	*/
	processor = new Processor();
	/**
	* A flag to know if using compat mode
	*/
	compat = false;
	/**
	* Globals are shared with all rendered templates
	*/
	globals = { ...edgeGlobals };
	/**
	* List of registered tags. Adding new tags will only impact
	* this list
	*/
	tags = {};
	constructor(options = {}) {
		this.configure(options);
		/**
		* Registering bundled set of tags
		*/
		Object.keys(main_exports).forEach((name) => {
			this.registerTag(main_exports[name]);
		});
		this.#bundledPlugins.push({
			fn: pluginSuperCharged,
			executed: false,
			options: { recurring: !options.cache }
		});
	}
	/**
	* Re-configure an existing edge instance
	*/
	configure(options) {
		if (options.loader) this.loader = options.loader;
		else if (!this.loader) this.loader = new Loader();
		this.compiler = new Compiler(this.loader, this.tags, this.processor, {
			cache: !!options.cache,
			async: false
		});
		this.asyncCompiler = new Compiler(this.loader, this.tags, this.processor, {
			cache: !!options.cache,
			async: true
		});
	}
	/**
	* Execute plugins
	*/
	#executePlugins() {
		/**
		* Running user-land plugins
		*/
		this.#plugins.filter(({ options, executed }) => {
			if (options && options.recurring) return true;
			return !executed;
		}).forEach((plugin) => {
			plugin.fn(this, !plugin.executed, plugin.options);
			plugin.executed = true;
		});
		/**
		* Running bundled plugins after the user-land
		* plugins
		*/
		this.#bundledPlugins.filter(({ options, executed }) => {
			if (options && options.recurring) return true;
			return !executed;
		}).forEach((plugin) => {
			plugin.fn(this, !plugin.executed, plugin.options);
			plugin.executed = true;
		});
	}
	/**
	* Register a plugin. Plugins are called only once just before
	* a rendering a view.
	*
	* You can invoke a plugin multiple times by marking it as a
	* recurring plugin
	*/
	use(pluginFn, options) {
		this.#plugins.push({
			fn: pluginFn,
			executed: false,
			options
		});
		return this;
	}
	mount(diskName, viewsDirectory) {
		if (!viewsDirectory) {
			viewsDirectory = diskName;
			diskName = "default";
		}
		this.loader.mount(diskName, viewsDirectory);
		return this;
	}
	/**
	* Un Mount a disk from the loader.
	*
	* ```js
	* edge.unmount('admin')
	* ```
	*/
	unmount(diskName) {
		this.loader.unmount(diskName);
		return this;
	}
	/**
	* Add a new global to the edge globals. The globals are available
	* to all the templates.
	*
	* ```js
	* edge.global('username', 'virk')
	* edge.global('time', () => new Date().getTime())
	* ```
	*/
	global(name, value) {
		this.globals[name] = value;
		return this;
	}
	/**
	* Add a new tag to the tags list.
	*
	* ```ts
	* edge.registerTag('svg', {
	*   block: false,
	*   seekable: true,
	*
	*   compile (parser, buffer, token) {
	*     const fileName = token.properties.jsArg.trim()
	*     buffer.writeRaw(fs.readFileSync(__dirname, 'assets', `${fileName}.svg`), 'utf-8')
	*   }
	* })
	* ```
	*/
	registerTag(tag) {
		if (typeof tag.boot === "function") tag.boot(Template);
		this.tags[tag.tagName] = tag;
		return this;
	}
	/**
	* Register an in-memory template.
	*
	* ```ts
	* edge.registerTemplate('button', {
	*   template: `<button class="{{ this.type || 'primary' }}">
	*     @!yield($slots.main())
	*   </button>`,
	* })
	* ```
	*
	* Later you can use this template
	*
	* ```edge
	* @component('button', type = 'primary')
	*   Get started
	* @endcomponent
	* ```
	*/
	registerTemplate(templatePath, contents) {
		this.loader.register(templatePath, contents);
		return this;
	}
	/**
	* Remove the template registered using the "registerTemplate" method
	*/
	removeTemplate(templatePath) {
		this.loader.remove(templatePath);
		this.compiler.cacheManager.delete(templatePath);
		this.asyncCompiler.cacheManager.delete(templatePath);
		return this;
	}
	/**
	* Get access to the underlying template renderer. Each render call
	* to edge results in creating an isolated renderer instance.
	*/
	onRender(callback) {
		this.#renderCallbacks.push(callback);
		return this;
	}
	/**
	* Returns a new instance of edge. The instance
	* can be used to define locals.
	*/
	createRenderer() {
		this.#executePlugins();
		const renderer = new EdgeRenderer(this.compiler, this.asyncCompiler, this.processor, this.globals);
		this.#renderCallbacks.forEach((callback) => callback(renderer));
		return renderer;
	}
	/**
	* Render a template with optional state
	*
	* ```ts
	* edge.render('welcome', { greeting: 'Hello world' })
	* ```
	*/
	render(templatePath, state) {
		return this.createRenderer().render(templatePath, state);
	}
	/**
	* Render a template asynchronously with optional state
	*
	* ```ts
	* edge.render('welcome', { greeting: 'Hello world' })
	* ```
	*/
	renderSync(templatePath, state) {
		return this.createRenderer().renderSync(templatePath, state);
	}
	/**
	* Render a template with optional state
	*
	* ```ts
	* edge.render('welcome', { greeting: 'Hello world' })
	* ```
	*/
	renderRaw(contents, state, templatePath) {
		return this.createRenderer().renderRaw(contents, state, templatePath);
	}
	/**
	* Render a template asynchronously with optional state
	*
	* ```ts
	* edge.render('welcome', { greeting: 'Hello world' })
	* ```
	*/
	renderRawSync(templatePath, state) {
		return this.createRenderer().renderRawSync(templatePath, state);
	}
	/**
	* Share locals with the current view context.
	*
	* ```js
	* const view = edge.createRenderer()
	*
	* // local state for the current render
	* view.share({ foo: 'bar' })
	*
	* view.render('welcome')
	* ```
	*/
	share(data) {
		return this.createRenderer().share(data);
	}
};
//#endregion
//#region index.ts
const edge = Edge.create();
//#endregion
export { Edge, EdgeError, Template, edge as default, edgeGlobals };
