using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using Newtonsoft.Json;
using Project.WebUI;
using AR;
using arDev.DIO;

namespace Project.Dialog
{
    public partial class fWebView : Form
    {
        private WebView2 webView;
        private Timer plcTimer;
        private WebSocketServer _wsServer;

        // Machine State
        private double currX = 0, currY = 0, currZ = 0;
        private double targetX = 0, targetY = 0, targetZ = 0;
        private string systemState = "IDLE";
        private string currentRecipeId = "1"; // Default recipe

        // IO 캐시 (변경된 값만 전송하기 위함)
        private bool[] _lastInputs;
        private bool[] _lastOutputs;

        public fWebView()
        {
            InitializeComponent();
            InitializeWebView();

            // Start WebSocket Server for HMR/Dev
            try
            {
                _wsServer = new WebSocketServer("http://localhost:8081/", this);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to start WebSocket Server (Port 8081). Run as Admin or allow port.\n" + ex.Message);
            }

            // IO 캐시 초기화
            int diCount = PUB.dio?.GetDICount ?? 32;
            int doCount = PUB.dio?.GetDOCount ?? 32;
            _lastInputs = new bool[diCount];
            _lastOutputs = new bool[doCount];

            // IO 값 변경 이벤트 구독 (DIOMonitor.cs와 동일)
            if (PUB.dio != null)
            {
                PUB.dio.IOValueChanged += Dio_IOValueChanged;
            }

            // 인터락 값 변경 이벤트 구독 (DIOMonitor.cs와 동일)
            if (PUB.iLock != null)
            {
                for (int i = 0; i < PUB.iLock.Length; i++)
                {
                    PUB.iLock[i].ValueChanged += ILock_ValueChanged;
                }
            }

            // Load event handler
            this.Load += FWebView_Load;
            this.FormClosed += FWebView_FormClosed;
        }

        private void FWebView_FormClosed(object sender, FormClosedEventArgs e)
        {
            // IO 이벤트 구독 해제
            if (PUB.dio != null)
            {
                PUB.dio.IOValueChanged -= Dio_IOValueChanged;
            }

            // 인터락 이벤트 구독 해제
            if (PUB.iLock != null)
            {
                for (int i = 0; i < PUB.iLock.Length; i++)
                {
                    PUB.iLock[i].ValueChanged -= ILock_ValueChanged;
                }
            }
        }

        // 인터락 값 변경 이벤트 핸들러 (DIOMonitor.cs의 LockXF_ValueChanged와 동일)
        private void ILock_ValueChanged(object sender, AR.InterfaceValueEventArgs e)
        {
            try
            {
                var item = sender as CInterLock;
                if (item == null) return;

                var axisIndex = item.idx;

                var ilockUpdate = new
                {
                    type = "INTERLOCK_CHANGED",
                    data = new
                    {
                        axisIndex = axisIndex,
                        lockIndex = (int)e.ArrIDX,
                        state = e.NewValue,
                        hexValue = item.Value().HexString()
                    }
                };

                string json = JsonConvert.SerializeObject(ilockUpdate);

                // WebView2로 전송
                if (webView != null && webView.CoreWebView2 != null)
                {
                    this.BeginInvoke(new Action(() =>
                    {
                        try
                        {
                            webView.CoreWebView2.PostWebMessageAsJson(json);
                        }
                        catch { }
                    }));
                }

                // WebSocket으로 전송
                _wsServer?.Broadcast(json);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[fWebView] Interlock change broadcast error: {ex.Message}");
            }
        }

        // IO 값 변경 이벤트 핸들러 (DIOMonitor.cs의 dio_IOValueChanged와 동일)
        private void Dio_IOValueChanged(object sender, IOValueEventArgs e)
        {
            try
            {
                // 변경된 IO만 즉시 전송
                var ioUpdate = new
                {
                    type = "IO_CHANGED",
                    data = new
                    {
                        id = e.ArrIDX,
                        ioType = e.Direction == eIOPINDIR.INPUT ? "input" : "output",
                        state = e.NewValue
                    }
                };

                string json = JsonConvert.SerializeObject(ioUpdate);

                // WebView2로 전송
                if (webView != null && webView.CoreWebView2 != null)
                {
                    this.BeginInvoke(new Action(() =>
                    {
                        try
                        {
                            webView.CoreWebView2.PostWebMessageAsJson(json);
                        }
                        catch { }
                    }));
                }

                // WebSocket으로 전송
                _wsServer?.Broadcast(json);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[fWebView] IO change broadcast error: {ex.Message}");
            }
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // fWebView
            // 
            this.ClientSize = new System.Drawing.Size(1784, 961);
            this.Name = "fWebView";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "STD Label Attach - Web UI";
            this.ResumeLayout(false);

        }

        private async void InitializeWebView()
        {
            // 1. Setup Virtual Host (http://hmi.local) pointing to FrontEnd/dist folder
            // Navigate up from bin/debug/ to project root, then to FrontEnd/dist
            string projectRoot = Path.GetFullPath(Path.Combine(Application.StartupPath, @"..\..\..\.."));
            string wwwroot = Path.Combine(projectRoot, @"FrontEnd\dist");

            this.Text = $"STD Label Attach - {wwwroot}";

            webView = new WebView2();
            webView.Dock = DockStyle.Fill;
            this.Controls.Add(webView);

            try
            {
                await webView.EnsureCoreWebView2Async();

                if (!Directory.Exists(wwwroot))
                {
                    MessageBox.Show($"Could not find frontend build at:\n{wwwroot}\n\nPlease run 'npm run build' in the FrontEnd folder.",
                        "Frontend Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    // Fallback to local wwwroot if needed, or just allow it to fail gracefully
                    Directory.CreateDirectory(wwwroot);
                }

                webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
                    "hmi.local",
                    wwwroot,
                    CoreWebView2HostResourceAccessKind.Allow
                );

                // 2. Inject Native Object
                webView.CoreWebView2.AddHostObjectToScript("machine", new MachineBridge(this));

                // 3. Load UI
                webView.Source = new Uri("http://hmi.local/index.html");

                // Disable default browser keys (F5, F12 etc) if needed for kiosk mode
                webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"WebView2 초기화 실패: {ex.Message}", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // HW 상태 업데이트 카운터 (250ms 주기 = 50ms * 5)
        private int _hwUpdateCounter = 0;

        // --- Logic Loop ---
        private void PlcTimer_Tick(object sender, EventArgs e)
        {
            // 1. Simulate Motion (Move Current towards Target)
            currX = Lerp(currX, targetX, 0.1);
            currY = Lerp(currY, targetY, 0.1);
            currZ = Lerp(currZ, targetZ, 0.1);

            // 2. 시스템 상태 업데이트
            if (PUB.sm != null)
            {
                systemState = PUB.sm.Step.ToString();
            }

            // 3. Prepare Data Packet
            var payload = new
            {
                type = "STATUS_UPDATE",
                sysState = systemState,
                position = new { x = currX, y = currY, z = currZ },
                ioState = GetChangedIOs() // 변경된 IO만 전송
            };

            string json = JsonConvert.SerializeObject(payload);

            // 4. Send to React via PostMessage (WebView2)
            if (webView != null && webView.CoreWebView2 != null)
            {
                webView.CoreWebView2.PostWebMessageAsJson(json);
            }

            // 5. Broadcast to WebSocket (Dev/HMR)
            _wsServer?.Broadcast(json);

            // 6. HW 상태 업데이트 (250ms 주기 - 윈폼의 _Display_Interval_250ms와 동일)
            _hwUpdateCounter++;
            if (_hwUpdateCounter >= 5) // 50ms * 5 = 250ms
            {
                _hwUpdateCounter = 0;
                BroadcastHWStatus();
                BroadcastVisionData();
            }
        }

        // H/W 상태 브로드캐스트 (윈폼의 HWState 업데이트와 동일)
        private void BroadcastHWStatus()
        {
            try
            {
                var bridge = new WebUI.MachineBridge(this);
                string hwStatusJson = bridge.GetHWStatus();

                var payload = new
                {
                    type = "HW_STATUS_UPDATE",
                    data = JsonConvert.DeserializeObject(hwStatusJson)
                };

                string json = JsonConvert.SerializeObject(payload);

                // WebView2로 전송
                if (webView != null && webView.CoreWebView2 != null)
                {
                    webView.CoreWebView2.PostWebMessageAsJson(json);
                }

                // WebSocket으로 전송
                _wsServer?.Broadcast(json);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[fWebView] HW status broadcast error: {ex.Message}");
            }
        }

        // Vision Data 브로드캐스트 (listView21과 동일한 데이터)
        private void BroadcastVisionData()
        {
            try
            {
                var bridge = new WebUI.MachineBridge(this);
                string visionDataJson = bridge.GetVisionData();

                var payload = new
                {
                    type = "VISION_DATA_UPDATE",
                    data = JsonConvert.DeserializeObject(visionDataJson)
                };

                string json = JsonConvert.SerializeObject(payload);

                // WebView2로 전송
                if (webView != null && webView.CoreWebView2 != null)
                {
                    webView.CoreWebView2.PostWebMessageAsJson(json);
                }

                // WebSocket으로 전송
                _wsServer?.Broadcast(json);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[fWebView] Vision data broadcast error: {ex.Message}");
            }
        }

        private List<object> GetChangedIOs()
        {
            var list = new List<object>();

            // 실제 DIO에서 값 읽기
            if (PUB.dio != null)
            {
                int diCount = PUB.dio.GetDICount;
                int doCount = PUB.dio.GetDOCount;

                // DI (Digital Input) - 변경된 값만 추가
                for (int i = 0; i < diCount && i < _lastInputs.Length; i++)
                {
                    bool currentValue = PUB.dio.GetDIValue(i);
                    if (currentValue != _lastInputs[i])
                    {
                        list.Add(new { id = i, type = "input", state = currentValue });
                        _lastInputs[i] = currentValue;
                    }
                }

                // DO (Digital Output) - 변경된 값만 추가
                for (int i = 0; i < doCount && i < _lastOutputs.Length; i++)
                {
                    bool currentValue = PUB.dio.GetDOValue(i);
                    if (currentValue != _lastOutputs[i])
                    {
                        list.Add(new { id = i, type = "output", state = currentValue });
                        _lastOutputs[i] = currentValue;
                    }
                }
            }

            return list;
        }

        private void FWebView_Load(object sender, EventArgs e)
        {
            // Setup Simulation Timer (50ms)
            plcTimer = new Timer();
            plcTimer.Interval = 50;
            plcTimer.Tick += PlcTimer_Tick;
            plcTimer.Start();
        }

        // --- Helper Methods called by Bridge ---
        public void SetTargetPosition(string axis, double val)
        {
            if (axis == "X") targetX = val;
            if (axis == "Y") targetY = val;
            if (axis == "Z") targetZ = val;
        }

        // DO 출력 제어 (DIOMonitor.cs의 tblDO_ItemClick과 동일한 로직)
        public bool SetOutput(int id, bool state)
        {
            try
            {
                if (PUB.dio == null)
                {
                    Console.WriteLine($"[fWebView] SetOutput failed: DIO not initialized");
                    return false;
                }

                if (PUB.dio.IsInit == false)
                {
                    // DIO가 초기화되지 않은 경우 가상 신호 생성 (디버그 모드)
                    PUB.dio.RaiseEvent(eIOPINDIR.OUTPUT, id, state);
                    PUB.log.Add($"[Web] Fake DO: idx={id}, val={state}");
                    return true;
                }
                else
                {
                    // 실제 출력 제어
                    PUB.dio.SetOutput(id, state);
                    PUB.log.Add($"[Web] Set output: idx={id}, val={state}");
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[fWebView] SetOutput error: {ex.Message}");
                return false;
            }
        }

        public void HandleCommand(string cmd)
        {
            systemState = (cmd == "START") ? "RUNNING" : (cmd == "STOP") ? "PAUSED" : "IDLE";
        }

        public void SetCurrentRecipe(string recipeId)
        {
            currentRecipeId = recipeId;
            Console.WriteLine($"[fWebView] Current recipe set to: {recipeId}");
            // In real app, load recipe parameters here
        }

        public string GetCurrentRecipe()
        {
            return currentRecipeId;
        }

        private double Lerp(double a, double b, double t) => a + (b - a) * t;

        // JavaScript 실행 메서드
        public async void ExecuteScriptAsync(string script)
        {
            try
            {
                string result = await webView.CoreWebView2.ExecuteScriptAsync(script);
                Console.WriteLine($"Script result: {result}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Script execution failed: {ex.Message}");
            }
        }

        // C#에서 JavaScript로 메시지 전송
        public void PostMessageToWeb(string message)
        {
            try
            {
                webView.CoreWebView2.PostWebMessageAsString(message);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"PostMessage failed: {ex.Message}");
            }
        }

        // 이벤트 브로드캐스트 (WebView2 + WebSocket)
        public void BroadcastEvent(string eventType, object data = null)
        {
            try
            {
                var payload = new
                {
                    type = eventType,
                    data = data
                };
                string json = JsonConvert.SerializeObject(payload);

                // WebView2로 전송
                if (webView != null && webView.CoreWebView2 != null)
                {
                    webView.CoreWebView2.PostWebMessageAsJson(json);
                }

                // WebSocket으로 전송
                _wsServer?.Broadcast(json);

                Console.WriteLine($"[fWebView] BroadcastEvent: {eventType}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"BroadcastEvent failed: {ex.Message}");
            }
        }
    }
}
