using bessw.Unity.WebView;
using bessw.Unity.WebView.ChromeDevTools;
using bessw.Unity.WebView.ChromeDevTools.Protocol.DOM;
using MoreLinq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;

public class WebViewController : MonoBehaviour
{


    // TODO: onFactAssignment event shoud hav a dropzone id and a fact id as parameters
    public UnityEvent<DomNodeWrapper, string> onFactAssignment;
    // TODO: implement onHintRequested event
    public UnityEvent<DomNodeWrapper, string> onHintRequested;
    // TODO: click events (option for custom buttons for a scroll)
    public UnityEvent<DomNodeWrapper> onClick;
    private WebViewComponent webViewComponent;
    private DomNodeWrapper[] dropzones;

    private void Awake()
    {
        Debug.LogWarning("awake Webview Controller");
        webViewComponent = GetComponent<WebViewComponent>();
        WebViewComponent.serializerSettings.Converters.Add(new FactObjectUIConverter());
        webViewComponent.targetUrl = Path.Join(Application.dataPath, "scrollView.html");

        // TODO: handle webViewComponent.onDomDocumentUpdated

        // TODO: remove this debug code
        onFactAssignment.AddListener((DomNodeWrapper node, string factId) => {
            Debug.LogWarning($"onFactAssignment: '{string.Join(", ", GetFactAssignments())}'");
        });
    }

    /// <summary>
    /// sets or updates the content of the scroll container dom element
    /// </summary>
    /// <param name="scrollHTML"></param>
    public async void SetScrollContent(string scrollHTML)
    {
        // update scroll container content
        DomNodeWrapper scrollContainer = await DomNodeWrapper.requestNodeAsync(webViewComponent.tab, "scrollContainer");
        await scrollContainer.setNodeValueAsync(scrollHTML);

        // get the dropzones from the scroll and add the event handler to track when something is dropped
        dropzones = await scrollContainer.querySelectorAllAsync("[dropzone='copy']");
        dropzones.ForEach(dropzone => dropzone.OnAttributeModified += DropzoneAttributeModifiedHandler);
    }

    private void DropzoneAttributeModifiedHandler(attributeModifiedEvent attributeModifiedEvent)
    {
        Debug.LogWarning($"dropzoneAtrributeModifiedHandler: '{attributeModifiedEvent.name}'");

        // call the onFactAssignment event if the data-fact-id attribute was modified
        if (attributeModifiedEvent.name == "data-fact-id")
        {
            var node = webViewComponent.tab.domNodes.GetValueOrDefault(attributeModifiedEvent.nodeId);
            onFactAssignment.Invoke(node, attributeModifiedEvent.value);
        }
    }

    public string[] GetFactAssignments()
    {
        return dropzones.Select(dropzone => dropzone.Node.attributes.GetValueOrDefault("data-fact-id", null)).ToArray();
    }

    private async void GetDropzoneStateAsync()
    {
        // alternative way to get the dropzone state
        DomNodeWrapper document = await DomNodeWrapper.getDocumentAsync(webViewComponent.tab);
        Debug.LogWarning($"dropzone 1: '{document}'");
        var dropzones = await document.querySelectorAllAsync("[dropzone='copy']");

        Debug.LogWarning($"dropzone 2: '{dropzones.Index().Aggregate("", (currentString, dropzone) => $"{currentString}, {dropzone.Key}: {dropzone.Value.Node.attributes.GetValueOrDefault("data-fact-id")}")}'");

        string[] factIDs = new string[dropzones.Length];

        // get attributes for each dropzone
        //for (int i = 0; i < dropzones.Length; i++)
        //{
        //    factIDs[i] = ( await dropzones[i].getAttributesAsync() ).GetValueOrDefault("data-fact-id", null);
        //}
        //Debug.LogWarning($"dropzone 3: '{string.Join(", ", factIDs)}'");
    }


    /// <summary>
    /// First try to get the dropzone state using coroutines.
    /// Doesn't work because coroutines can't access method local temporary variables.
    /// </summary>
    /// <returns></returns>
    [Obsolete("Use getDropzoneStateAsync instead")]
    private IEnumerator GetDropzoneState()
    {
        Debug.LogWarning($"dropzone pre");

        DomNodeWrapper doc = null;
        yield return DomNodeWrapper.getDocument(webViewComponent.tab, (document) => {
            Debug.LogWarning($"dropzone 1: '{document}'");
            doc = document;

            StartCoroutine(document.querySelectorAll("[dropzone='copy']", (dropzones) => {
                foreach (var dropzone in dropzones)
                {
                    Debug.LogWarning($"dropzone 2: Node is Null?: '{dropzone.Node == null}'");
                    StartCoroutine(dropzone.getAttributes((attributes) =>
                    {
                        Debug.LogWarning($"dropzone 3 getAttributes: '{string.Join(", ", attributes.Values)}'");
                    }));
                }
            }));
        });
        Debug.LogWarning($"dropzone post: '{doc}'");
    }
}

public class FactObjectUIConverter : JsonConverter<FactObjectUI>
{
    public override void WriteJson(JsonWriter writer, FactObjectUI value, JsonSerializer serializer)
    {
        //serializer.Serialize(writer, value.Fact);
        JObject o = JObject.FromObject(value.Fact, WebViewComponent.serializer);
        o.AddFirst(new JProperty("id", value.Fact.Id));

        o.WriteTo(writer);
    }
    public override FactObjectUI ReadJson(JsonReader reader, Type objectType, FactObjectUI existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var factObject = new FactObjectUI();
        factObject.Fact = serializer.Deserialize<Fact>(reader);
        return factObject;
    }
}