/*
  ==============================================================================

	PluginEditor.cpp
	Created: 4 Nov 2025 6:20:46pm
	Author:  mickl

  ==============================================================================
*/

#include "PluginEditor.h"
#include "DemoUtilities.h"



static ZipFile* getZipFile()
{
	static auto stream = createAssetInputStream("webviewplugin-gui_1.0.0.zip", AssertAssetExists::no);

	if (stream == nullptr)
		return nullptr;

	static ZipFile f{ stream.get(), false };
	return &f;
}

static const char* getMimeForExtension(const String& extension)
{
	static const std::unordered_map<String, const char*> mimeMap =
	{
		{ { "htm"   },  "text/html"                },
		{ { "html"  },  "text/html"                },
		{ { "txt"   },  "text/plain"               },
		{ { "jpg"   },  "image/jpeg"               },
		{ { "jpeg"  },  "image/jpeg"               },
		{ { "svg"   },  "image/svg+xml"            },
		{ { "ico"   },  "image/vnd.microsoft.icon" },
		{ { "json"  },  "application/json"         },
		{ { "png"   },  "image/png"                },
		{ { "css"   },  "text/css"                 },
		{ { "map"   },  "application/json"         },
		{ { "js"    },  "text/javascript"          },
		{ { "woff2" },  "font/woff2"               }
	};

	if (const auto it = mimeMap.find(extension.toLowerCase()); it != mimeMap.end())
		return it->second;

	jassertfalse;
	return "";
}

static String getExtension(String filename)
{
	return filename.fromLastOccurrenceOf(".", false, false);
}

static auto streamToVector(InputStream& stream)
{
	std::vector<std::byte> result((size_t)stream.getTotalLength());
	stream.setPosition(0);
	[[maybe_unused]] const auto bytesRead = stream.read(result.data(), result.size());
	jassert(bytesRead == (ssize_t)result.size());
	return result;
}

std::optional<WebBrowserComponent::Resource> WebViewPluginAudioProcessorEditor::getResource(const String& url)
{
	const auto urlToRetrive = url == "/" ? String{ "index.html" }
	: url.fromFirstOccurrenceOf("/", false, false);

	if (auto* archive = getZipFile())
	{
		if (auto* entry = archive->getEntry(urlToRetrive))
		{
			auto stream = rawToUniquePtr(archive->createStreamForEntry(*entry));
			auto v = streamToVector(*stream);
			auto mime = getMimeForExtension(getExtension(entry->filename).toLowerCase());
			return WebBrowserComponent::Resource{ std::move(v),
												   std::move(mime) };
		}
	}

	if (urlToRetrive == "index.html")
	{
		auto fallbackIndexHtml = createAssetInputStream("webviewplugin-gui-fallback.html");
		return WebBrowserComponent::Resource{ streamToVector(*fallbackIndexHtml),
											   String { "text/html" } };
	}

	if (urlToRetrive == "data.txt")
	{
		WebBrowserComponent::Resource resource;
		static constexpr char testData[] = "testdata";
		MemoryInputStream stream{ testData, numElementsInArray(testData) - 1, false };
		return WebBrowserComponent::Resource{ streamToVector(stream), String { "text/html" } };
	}

	if (urlToRetrive == "midNoteData.json")
	{

		juce::Array<var> notes;
		int voice_num = 0;
		for (auto& voice : processorRef.shifter.voices) {
			if (voice.onoff_) {
				auto obj = new DynamicObject();
				obj->setProperty("voice", voice_num);
				obj->setProperty("midi", voice.GetMidiNote());
				notes.add(var(obj));
			}
			voice_num++;
		}

		DynamicObject::Ptr d(new DynamicObject());
		d->setProperty("notes", notes);

		const auto s = JSON::toString(d.get());
		MemoryInputStream stream{ s.getCharPointer(), s.getNumBytesAsUTF8(), false };
		return WebBrowserComponent::Resource{ streamToVector(stream), String { "application/json" } };
	}

	return std::nullopt;
}

#if JUCE_ANDROID
// The localhost is available on this address to the emulator
const String localDevServerAddress = "http://10.0.2.2:3000/";
#else
const String localDevServerAddress = "http://localhost:3000/";
#endif

bool SinglePageBrowser::pageAboutToLoad(const String& newURL)
{
	return newURL == localDevServerAddress || newURL == getResourceProviderRoot();
}


WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPluginAudioProcessor& p)
	: AudioProcessorEditor(&p), processorRef(p)
{
	auto options = WebBrowserComponent::Options{}
		.withBackend(WebBrowserComponent::Options::Backend::webview2)
		.withWinWebView2Options(WebBrowserComponent::Options::WinWebView2{}
			.withUserDataFolder(File::getSpecialLocation(File::SpecialLocationType::tempDirectory)))
		.withNativeIntegrationEnabled()
		.withOptionsFrom(controlParameterIndexReceiver)
		.withResourceProvider([this](const auto& url)
			{
				return getResource(url);
			},
			URL{ localDevServerAddress }.getOrigin());


	for (auto& sliderId : p.parameters.sliderIds) {
		slider_relays.push_back(new WebSliderRelay{ sliderId });
		slider_attatchments.push_back(new 
			WebSliderParameterAttachment(
				*processorRef.state.getParameter(sliderId),
				*slider_relays.back(),
				processorRef.state.undoManager));
		options = options.withOptionsFrom(*slider_relays.back());
	}

	for (auto& toggleId : p.parameters.toggleIds) {
		toggle_relays.push_back(new WebToggleButtonRelay{ toggleId });
		toggle_attatchments.push_back(new
			WebToggleButtonParameterAttachment(
				*processorRef.state.getParameter(toggleId),
				*toggle_relays.back(),
				processorRef.state.undoManager));
		options = options.withOptionsFrom(*toggle_relays.back());
	}

	webComponent = new SinglePageBrowser(options);
	addAndMakeVisible(*webComponent);

	webComponent->goToURL(localDevServerAddress);
	//webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());

	setSize(800, 390);

	startTimerHz(60);
}

void WebViewPluginAudioProcessorEditor::paint(Graphics& g)
{
	// (Our component is opaque, so we must completely fill the background with a solid colour)
	g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId));
}

void WebViewPluginAudioProcessorEditor::resized()
{
	if (webComponent == nullptr) return;
	webComponent->setBounds(getLocalBounds());
}
