/*
 * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <algorithm>

#include <ox/model/typenamecatcher.hpp>

#include <keel/media.hpp>
#include <turbine/context.hpp>

#include "context.hpp"

namespace studio {

class ItemTemplate {
	private:
		ox::String const m_name{"Default"};

	public:
		explicit ItemTemplate() noexcept = default;

		explicit ItemTemplate(ox::StringParam name) noexcept: m_name{std::move(name)} {}

		virtual ~ItemTemplate() = default;

		[[nodiscard]]
		constexpr ox::String const &name() const noexcept {
			return m_name;
		}

		constexpr bool operator<=>(ItemTemplate const &other) const noexcept {
			return m_name != other.name() ? (m_name < other.name() ? -1 : 1) : 0;
		}

		[[nodiscard]]
		ox::CStringView displayName() const noexcept {
			return m_name;
		}

		[[nodiscard]]
		virtual ox::CStringView typeName() const noexcept = 0;

		[[nodiscard]]
		virtual int typeVersion() const noexcept = 0;

		virtual ox::Result<ox::UAnyPtr> getItem(
			keel::Context &ctx, ox::StringViewCR name, int version) noexcept = 0;

};

template<typename T>
class ItemTemplateT: public ItemTemplate {
	private:
		T const m_item;

	public:
		constexpr ItemTemplateT() noexcept = default;

		explicit ItemTemplateT(ox::StringParam name, T item) noexcept:
			ItemTemplate{std::move(name)}, m_item{std::move(item)} {}

		[[nodiscard]]
		ox::CStringView typeName() const noexcept final {
			return ox::ModelTypeName_v<T>;
		}

		[[nodiscard]]
		int typeVersion() const noexcept final {
			return ox::ModelTypeVersion_v<T>;
		}

		ox::Result<ox::UAnyPtr> getItem(
				keel::Context &kctx, ox::StringViewCR name, int const version) noexcept final {
			if (ox::ModelTypeVersion_v<T> != version || ox::ModelTypeName_v<T> != name) {
				OX_REQUIRE_M(item, keel::convert(kctx, m_item, name, version));
				auto out = ox::Result<ox::UAnyPtr>{item->moveToCopy()};
				ox::safeDelete(item.release());
				return out;
			}
			return ox::UAnyPtr{new T{m_item}};
		}

};

class ItemMaker {
	private:
		ox::Vector<ox::UPtr<ItemTemplate>> m_templates;
		ox::String const m_parentDir;
		ox::String const m_fileExt;
		ox::String const m_typeDisplayName;

	public:
		constexpr ItemMaker(
				ox::StringParam pName,
				ox::StringViewCR pParentDir,
				ox::StringParam pFileExt) noexcept:
			m_parentDir{sfmt("/{}", pParentDir)},
			m_fileExt{std::move(pFileExt)},
			m_typeDisplayName{std::move(pName)} {
		}

		virtual ~ItemMaker() noexcept = default;

		[[nodiscard]]
		ox::String const &typeDisplayName() const noexcept {
			return m_typeDisplayName;
		}

		bool installTemplate(ox::UPtr<ItemTemplate> &&tmpl) {
			if (typeName() == tmpl->typeName() &&
			    typeVersion() <= tmpl->typeVersion()) {
                m_templates.emplace_back(std::move(tmpl));
				// begin() + 1 because 'Default' should always be first
                std::sort(m_templates.begin() + 1, m_templates.end());
				return true;
            }
			return false;
		}

		constexpr ox::Vector<ox::UPtr<ItemTemplate>> const &itemTemplates() const noexcept {
			return m_templates;
		}

		[[nodiscard]]
		ox::String const &fileExt() const noexcept {
			return m_fileExt;
		}

		[[nodiscard]]
		ox::String const &defaultPath() const noexcept {
			return m_parentDir;
		}

		[[nodiscard]]
		ox::String itemPath(ox::StringViewCR pName, ox::StringViewCR pPath) const noexcept {
			return ox::sfmt("/{}/{}.{}", pPath, pName, m_fileExt);
		}

		[[nodiscard]]
		ox::String itemPath(ox::StringViewCR pName) const noexcept {
			return ox::sfmt("{}/{}.{}", m_parentDir, pName, m_fileExt);
		}

		[[nodiscard]]
		virtual ox::StringView typeName() const noexcept = 0;

		[[nodiscard]]
		virtual int typeVersion() const noexcept = 0;

		/**
		 * Returns path of the file created.
		 * @param ctx
		 * @param pPath
		 * @param pTemplateIdx
		 * @return path of file or error in Result
		 */
		ox::Error write(
			Context &ctx,
			ox::StringViewCR pPath,
			size_t pTemplateIdx) const noexcept {
			return writeItem(ctx, pPath, pTemplateIdx);
		}

		/**
		 * Returns path of the file created.
		 * @param ctx
		 * @param pPath
		 * @param pName
		 * @param pTemplateIdx
		 * @return path of file or error in Result
		 */
		ox::Error write(
			Context &ctx,
			ox::StringViewCR pPath,
			ox::StringViewCR pName,
			size_t pTemplateIdx) const noexcept {
			auto const path = itemPath(pName, pPath);
			return writeItem(ctx, path, pTemplateIdx);
		}

	protected:
		virtual ox::Error writeItem(
			Context &ctx,
			ox::StringViewCR pPath,
			size_t pTemplateIdx) const noexcept = 0;

};

template<typename T>
class ItemMakerT final: public ItemMaker {
	private:
		ox::ClawFormat const m_fmt;

	public:
		constexpr ItemMakerT(
				ox::StringParam pDisplayName,
				ox::StringViewCR pParentDir,
				ox::StringParam fileExt,
				ox::ClawFormat const pFmt = ox::ClawFormat::Metal) noexcept:
				ItemMaker(
					std::move(pDisplayName),
					pParentDir,
					std::move(fileExt)),
				m_fmt{pFmt} {
			installTemplate(ox::make_unique<ItemTemplateT<T>>());
		}

		constexpr ItemMakerT(
				ox::StringParam pDisplayName,
				ox::StringViewCR pParentDir,
				ox::StringParam fileExt,
				T const &pItem,
				ox::ClawFormat const pFmt) noexcept:
				ItemMaker(
					std::move(pDisplayName),
					pParentDir,
					std::move(fileExt)),
				m_fmt{pFmt} {
			installTemplate(ox::make_unique<ItemTemplateT<T>>("Default", std::move(pItem)));
		}

		constexpr ItemMakerT(
				ox::StringParam pDisplayName,
				ox::StringViewCR pParentDir,
				ox::StringParam fileExt,
				T &&pItem,
				ox::ClawFormat const pFmt) noexcept:
				ItemMaker(
					std::move(pDisplayName),
					pParentDir,
					std::move(fileExt)),
				m_fmt{pFmt} {
			installTemplate(ox::make_unique<ItemTemplateT<T>>("Default", std::move(pItem)));
		}

		ox::StringView typeName() const noexcept override {
			return ox::ModelTypeName_v<T>;
		}

		int typeVersion() const noexcept override {
			return ox::ModelTypeVersion_v<T>;
		}

		ox::Error writeItem(
			Context &ctx,
			ox::StringViewCR pPath,
			size_t const pTemplateIdx) const noexcept override {
			createUuidMapping(keelCtx(ctx.tctx), pPath, ox::UUID::generate().unwrap());
			auto const &templates = itemTemplates();
			auto const tmplIdx = pTemplateIdx < templates.size() ? pTemplateIdx : 0;
			OX_REQUIRE_M(tmpl, templates[tmplIdx]->getItem(
				keelCtx(ctx), typeName(), typeVersion()));
			auto item = tmpl.template get<T>();
			OX_RETURN_ERROR(ctx.project->writeObj(pPath, *item, m_fmt));
			tmpl.free();
			return {};
		}

};

}
