using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Newtonsoft.Json;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using AR;
using System.Threading.Tasks;

namespace Project.WebUI
{
    // Important: Allows JavaScript to see this class
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class MachineBridge
    {
        // Reference to the main form to update logic
        private Project.Dialog.fWebView _host;

        // Data folder paths
        private readonly string _dataFolder;
        private readonly string _settingsPath;
        private readonly string _recipeFolder;

        public MachineBridge(Project.Dialog.fWebView host)
        {
            _host = host;

            // Initialize data folder paths - use existing Data folder
            _dataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
            _settingsPath = Path.Combine(_dataFolder, "Setting.json");
            _recipeFolder = Path.Combine(_dataFolder, "recipe");

            // Ensure folders exist
            EnsureDataFoldersExist();
        }

        private void EnsureDataFoldersExist()
        {
            try
            {
                if (!Directory.Exists(_dataFolder))
                    Directory.CreateDirectory(_dataFolder);

                if (!Directory.Exists(_recipeFolder))
                    Directory.CreateDirectory(_recipeFolder);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to create data folders: {ex.Message}");
            }
        }

        // --- Methods called from React ---

        public void MoveAxis(string axis, double value)
        {
            Console.WriteLine($"[C#] Moving {axis} to {value}");
            _host.SetTargetPosition(axis, value);
        }

        public void SetIO(int id, bool isInput, bool state)
        {
            Console.WriteLine($"[C#] Set IO {id} to {state}");
            _host.SetOutput(id, state);
        }

        public void SystemControl(string command)
        {
            Console.WriteLine($"[C#] CMD: {command}");
            _host.HandleCommand(command);
        }

        public string InitializeDevice()
        {
            Console.WriteLine($"[C#] Initialize Device Request");

            try
            {
                // Check if motion is initialized
                if (PUB.flag.get(eVarBool.FG_INIT_MOTIO) == false)
                {
                    var response = new { success = false, message = "Motion not initialized\nPlease try again later" };
                    return JsonConvert.SerializeObject(response);
                }

                var errors = new List<string>();

                // Check if system is running
                if (PUB.sm.isRunning == true)
                    errors.Add("AUTO-RUN MODE - Cannot be used during automatic execution. Please stop and try again");

                // Check AIR supply
                if (DIO.GetIOOutput(eDOName.SOL_AIR) == false)
                    errors.Add("Press the AIR button on the front to supply AIR");

                // Check printer pickers
                if (DIO.GetIOInput(eDIName.L_PICK_BW) == false)
                    errors.Add("Left printer picker is not in reverse position");

                if (DIO.GetIOInput(eDIName.R_PICK_BW) == false)
                    errors.Add("Right printer picker is not in reverse position");

                // Check picker cylinders
                if (SETTING.Data.Enable_PickerCylinder)
                {
                    if (DIO.GetIOInput(eDIName.L_CYLUP) == false)
                        errors.Add("Left printer picker cylinder is not in UP position");

                    if (DIO.GetIOInput(eDIName.R_CYLUP) == false)
                        errors.Add("Right printer picker cylinder is not in UP position");
                }

                // Check emergency stop
                if (DIO.IsEmergencyOn() == true)
                    errors.Add("Cannot move when in emergency stop state");

                // Check if running
                if (PUB.sm.Step == eSMStep.RUN)
                    errors.Add("Cannot initialize while in operation");

                if (errors.Count > 0)
                {
                    var response = new { success = false, message = string.Join("\n", errors) };
                    return JsonConvert.SerializeObject(response);
                }

                // Start initialization
                PUB.log.Add("User Request: Initialize Device", false);
                PUB.sm.SetNewStep(eSMStep.HOME_FULL);

                var response2 = new { success = true, message = "Device initialization started" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to initialize device: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string GetInitializeStatus()
        {
            try
            {
                var status = new
                {
                    isInitializing = PUB.sm.Step == eSMStep.HOME_FULL,
                    currentStep = PUB.sm.Step.ToString(),
                    axes = new List<object>
                    {
                        new { name = "PZ_PICK", axisIndex = (int)eAxis.PZ_PICK, isHomeSet = PUB.mot.IsHomeSet((short)eAxis.PZ_PICK), progress = GetAxisProgress(eAxis.PZ_PICK) },
                        new { name = "PL_UPDN", axisIndex = (int)eAxis.PL_UPDN, isHomeSet = PUB.mot.IsHomeSet((short)eAxis.PL_UPDN), progress = GetAxisProgress(eAxis.PL_UPDN) },
                        new { name = "PR_UPDN", axisIndex = (int)eAxis.PR_UPDN, isHomeSet = PUB.mot.IsHomeSet((short)eAxis.PR_UPDN), progress = GetAxisProgress(eAxis.PR_UPDN) },
                        new { name = "PL_MOVE", axisIndex = (int)eAxis.PL_MOVE, isHomeSet = PUB.mot.IsHomeSet((short)eAxis.PL_MOVE), progress = GetAxisProgress(eAxis.PL_MOVE) },
                        new { name = "PR_MOVE", axisIndex = (int)eAxis.PR_MOVE, isHomeSet = PUB.mot.IsHomeSet((short)eAxis.PR_MOVE), progress = GetAxisProgress(eAxis.PR_MOVE) },
                        new { name = "Z_THETA", axisIndex = (int)eAxis.Z_THETA, isHomeSet = PUB.mot.IsHomeSet((short)eAxis.Z_THETA), progress = GetAxisProgress(eAxis.Z_THETA) },
                        new { name = "PX_PICK", axisIndex = (int)eAxis.PX_PICK, isHomeSet = PUB.mot.IsHomeSet((short)eAxis.PX_PICK), progress = GetAxisProgress(eAxis.PX_PICK) }
                    }
                };

                return JsonConvert.SerializeObject(status);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get initialize status: {ex.Message}");
                return JsonConvert.SerializeObject(new { isInitializing = false, currentStep = "ERROR", axes = new List<object>() });
            }
        }

        private int GetAxisProgress(eAxis axis)
        {
            // Return 100 if home is set, otherwise check motion status
            if (PUB.mot.IsHomeSet((short)axis))
                return 100;

            // Check if axis is in motion
            if (PUB.mot.IsMotion((short)axis))
                return 50; // In progress

            // Check if home search started
            if (PUB.mot.IsOrg((short)axis) || PUB.mot.IsLimitN((short)axis))
                return 75; // Near completion

            return 0; // Not started
        }

        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <param name="recipeTitle"></param>
        /// <returns></returns>
        public string SelectRecipe(string recipeTitle)
        {
            Console.WriteLine($"[C#] Selecting Recipe: {recipeTitle}");

            try
            {
                // Check if system is running
                if (PUB.sm.Step == eSMStep.RUN || PUB.sm.Step == eSMStep.WAITSTART || PUB.sm.Step == eSMStep.PAUSE)
                {
                    var response = new { success = false, message = "Cannot change model while system is currently running (waiting)" };
                    return JsonConvert.SerializeObject(response);
                }

                // Select model using PUB.SelectModelV
                bool result = PUB.SelectModelV(recipeTitle, true);

                if (result)
                {
                    // Select motion model based on conveyor usage
                    var motionmode = VAR.BOOL[eVarBool.Use_Conveyor] ? "Conveyor" : "Default";
                    bool changeMot = PUB.SelectModelM(motionmode);
                    if (changeMot == false)
                    {
                        PUB.log.AddE($"No Motion model ({motionmode})");
                    }

                    Console.WriteLine($"[INFO] Recipe {recipeTitle} selected successfully with motion mode: {motionmode}");

                    // Get the selected model data
                    var dr = PUB.mdm.GetDataV(recipeTitle);
                    if (dr == null)
                    {
                        var response = new { success = false, message = "Recipe data not found" };
                        return JsonConvert.SerializeObject(response);
                    }

                    // Convert DataRow to Dictionary
                    var recipeData = new Dictionary<string, object>();
                    foreach (System.Data.DataColumn col in dr.Table.Columns)
                    {
                        string colName = col.ColumnName;
                        object value = dr[colName];

                        // DBNull을 null로 변환
                        if (value == DBNull.Value)
                            value = null;

                        recipeData[colName] = value;
                    }

                    var response2 = new { success = true, message = "Recipe selected successfully", recipeId = recipeTitle, recipe = recipeData };
                    return JsonConvert.SerializeObject(response2);
                }
                else
                {
                    var response = new { success = false, message = "Failed to select recipe" };
                    return JsonConvert.SerializeObject(response);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to select recipe: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        /// <summary>
        /// ���̱׷��̼� ����
        /// </summary>
        /// <returns></returns>
        public string GetConfig()
        {
            try
            {
                // Use SETTING.Data (CommonSetting) from the project
                if (SETTING.Data == null)
                {
                    Console.WriteLine($"[WARN] SETTING.Data is not initialized");
                    return "[]";
                }

                var settingsArray = new List<object>();
                var properties = SETTING.Data.GetType().GetProperties();

                foreach (var prop in properties)
                {
                    // Skip non-browsable properties
                    var browsable = prop.GetCustomAttributes(typeof(System.ComponentModel.BrowsableAttribute), false)
                        .FirstOrDefault() as System.ComponentModel.BrowsableAttribute;
                    if (browsable != null && !browsable.Browsable)
                        continue;

                    // Skip filename property
                    if (prop.Name == "filename")
                        continue;

                    // Get property info
                    string key = prop.Name;
                    object value = prop.GetValue(SETTING.Data, null);
                    string type = "String";
                    string group = "General";
                    string description = key.Replace("_", " ");

                    // Get Category attribute
                    var categoryAttr = prop.GetCustomAttributes(typeof(System.ComponentModel.CategoryAttribute), false)
                        .FirstOrDefault() as System.ComponentModel.CategoryAttribute;
                    if (categoryAttr != null)
                        group = categoryAttr.Category;

                    // Get DisplayName attribute
                    var displayNameAttr = prop.GetCustomAttributes(typeof(System.ComponentModel.DisplayNameAttribute), false)
                        .FirstOrDefault() as System.ComponentModel.DisplayNameAttribute;
                    if (displayNameAttr != null)
                        description = displayNameAttr.DisplayName;

                    // Get Description attribute
                    var descAttr = prop.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false)
                        .FirstOrDefault() as System.ComponentModel.DescriptionAttribute;
                    if (descAttr != null)
                        description = descAttr.Description;

                    // Determine type based on property type
                    if (prop.PropertyType == typeof(bool))
                    {
                        type = "Boolean";
                        value = value?.ToString().ToLower() ?? "false";
                    }
                    else if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(long) ||
                             prop.PropertyType == typeof(double) || prop.PropertyType == typeof(float))
                    {
                        type = "Number";
                        value = value?.ToString() ?? "0";
                    }
                    else
                    {
                        value = value?.ToString() ?? "";
                    }

                    settingsArray.Add(new
                    {
                        Key = key,
                        Value = value,
                        Group = group,
                        Type = type,
                        Description = description
                    });
                }

                Console.WriteLine($"[INFO] Loaded {settingsArray.Count} settings from SETTING.Data");
                return JsonConvert.SerializeObject(settingsArray);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to load settings: {ex.Message}");
                return "[]";
            }
        }

        /// <summary>
        /// ���̱׷��̼Ǽ���
        /// </summary>
        /// <param name="configJson"></param>
        public void SaveConfig(string configJson)
        {
            try
            {
                Console.WriteLine($"[Backend] SAVE CONFIG REQUEST RECEIVED");

                // Parse array format from React
                var settingsArray = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(configJson);

                // Update SETTING.Data properties
                var properties = SETTING.Data.GetType().GetProperties();

                foreach (var item in settingsArray)
                {
                    string key = item["Key"].ToString();
                    string value = item["Value"].ToString();
                    string type = item["Type"].ToString();

                    var prop = properties.FirstOrDefault(p => p.Name == key);
                    if (prop == null || !prop.CanWrite)
                        continue;

                    // Convert value based on type
                    try
                    {
                        if (type == "Boolean")
                        {
                            prop.SetValue(SETTING.Data, bool.Parse(value), null);
                        }
                        else if (type == "Number")
                        {
                            if (prop.PropertyType == typeof(int))
                                prop.SetValue(SETTING.Data, int.Parse(value), null);
                            else if (prop.PropertyType == typeof(long))
                                prop.SetValue(SETTING.Data, long.Parse(value), null);
                            else if (prop.PropertyType == typeof(double))
                                prop.SetValue(SETTING.Data, double.Parse(value), null);
                            else if (prop.PropertyType == typeof(float))
                                prop.SetValue(SETTING.Data, float.Parse(value), null);
                        }
                        else
                        {
                            prop.SetValue(SETTING.Data, value, null);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"[WARN] Failed to set property {key}: {ex.Message}");
                    }
                }

                // Save to file
                SETTING.Data.Save();

                Console.WriteLine($"[INFO] Settings saved successfully");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to save settings: {ex.Message}");
            }
        }

        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <returns></returns>
        public string GetIOList()
        {
            try
            {
                var result = new
                {
                    inputs = GetDigitalInputs(),
                    outputs = GetDigitalOutputs(),
                    interlocks = GetInterlocks()
                };

                return JsonConvert.SerializeObject(result);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get IO list: {ex.Message}");
                return JsonConvert.SerializeObject(new { inputs = new List<object>(), outputs = new List<object>(), interlocks = new List<object>() });
            }
        }
        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <returns></returns>
        private List<object> GetDigitalInputs()
        {
            var inputs = new List<object>();

            try
            {
                var diNames = DIO.Pin.GetDIName;
                var diPinNames = DIO.Pin.GetDIPinName;
                int diCount = PUB.dio.GetDICount;

                for (int i = 0; i < diCount; i++)
                {
                    bool state = PUB.dio.GetDIValue(i);
                    string name = i < diNames.Length ? diNames[i] : $"DIN_{i:D2}";
                    string pinName = i < diPinNames.Length ? diPinNames[i] : $"X{i:D2}";

                    inputs.Add(new
                    {
                        id = i,
                        name = name,
                        pinName = pinName,
                        type = "input",
                        state = state
                    });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get digital inputs: {ex.Message}");
            }

            return inputs;
        }
        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <returns></returns>
        private List<object> GetDigitalOutputs()
        {
            var outputs = new List<object>();

            try
            {
                var doNames = DIO.Pin.GetDOName;
                var doPinNames = DIO.Pin.GetDOPinName;
                int doCount = PUB.dio.GetDOCount;

                for (int i = 0; i < doCount; i++)
                {
                    bool state = PUB.dio.GetDOValue(i);
                    string name = i < doNames.Length ? doNames[i] : $"DOUT_{i:D2}";
                    string pinName = i < doPinNames.Length ? doPinNames[i] : $"Y{i:D2}";

                    outputs.Add(new
                    {
                        id = i,
                        name = name,
                        pinName = pinName,
                        type = "output",
                        state = state
                    });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get digital outputs: {ex.Message}");
            }

            return outputs;
        }
        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <returns></returns>
        private List<object> GetInterlocks()
        {
            var interlocks = new List<object>();

            try
            {
                for (int i = 0; i < PUB.iLock.Length; i++)
                {
                    var axisName = ((AR.eAxis)i).ToString();
                    var nonAxis = false;

                    // Check if this is a non-axis interlock (i >= 7)
                    if (i >= 7)
                    {
                        axisName = PUB.iLock[i].Tag.ToString();
                        nonAxis = true;
                    }

                    // Get enum names based on axis index
                    string[] ilockNames;
                    if (i == 7) ilockNames = Enum.GetNames(typeof(eILockPRL));
                    else if (i == 8) ilockNames = Enum.GetNames(typeof(eILockPRR));
                    else if (i == 9) ilockNames = Enum.GetNames(typeof(eILockVS0));
                    else if (i == 10) ilockNames = Enum.GetNames(typeof(eILockVS1));
                    else if (i == 11) ilockNames = Enum.GetNames(typeof(eILockVS2));
                    else if (i == 12 || i == 13) ilockNames = Enum.GetNames(typeof(eILockCV));
                    else ilockNames = Enum.GetNames(typeof(eILock));

                    // Get interlock values (up to 64 bits)
                    var lockValues = new List<object>();
                    for (int j = 0; j < ilockNames.Length && j < 64; j++)
                    {
                        bool state = PUB.iLock[i].get(j);
                        lockValues.Add(new
                        {
                            id = j,
                            name = ilockNames[j],
                            state = state
                        });
                    }

                    interlocks.Add(new
                    {
                        axisIndex = i,
                        axisName = axisName,
                        nonAxis = nonAxis,
                        locks = lockValues,
                        hexValue = PUB.iLock[i].Value().HexString()
                    });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get interlocks: {ex.Message}");
            }

            return interlocks;
        }


        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <returns></returns>
        public string GetRecipeList()
        {
            //PUB.mdm.dataSet.OPModel에 등록된 데이터가 모델 목록이다

            try
            {
                var recipes = new List<object>();

                if (PUB.mdm == null || PUB.mdm.dataSet == null || PUB.mdm.dataSet.OPModel == null)
                {
                    Console.WriteLine($"[WARN] OPModel is not initialized");
                    return JsonConvert.SerializeObject(recipes);
                }

                // OPModel 테이블에서 Title이 비어있지 않은 항목만 가져오기
                foreach (var row in PUB.mdm.dataSet.OPModel)
                {
                    var modelRow = row as DataSet1.OPModelRow;
                    if (modelRow == null) continue;

                    // Title이 비어있으면 스킵
                    if (string.IsNullOrEmpty(modelRow.Title))
                        continue;

                    recipes.Add(new
                    {
                        id = modelRow.idx.ToString(),
                        name = modelRow.Title,
                        lastModified = DateTime.Now.ToString("yyyy-MM-dd")
                    });
                }

                Console.WriteLine($"[INFO] Loaded {recipes.Count} recipes from OPModel");
                return JsonConvert.SerializeObject(recipes);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get recipe list: {ex.Message}");
                return "[]";
            }
        }

        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <param name="recipeTitle"></param>
        /// <returns></returns>
        public string GetRecipe(string recipeTitle)
        {
            try
            {
                var dr = PUB.mdm.GetDataV(recipeTitle);

                if (dr == null)
                {
                    var response = new { success = false, message = "Recipe not found" };
                    return JsonConvert.SerializeObject(response);
                }

                // DataRow의 모든 컬럼을 Dictionary로 변환
                var recipeData = new Dictionary<string, object>();
                foreach (System.Data.DataColumn col in dr.Table.Columns)
                {
                    string colName = col.ColumnName;
                    object value = dr[colName];

                    // DBNull을 null로 변환
                    if (value == DBNull.Value)
                        value = null;

                    recipeData[colName] = value;
                }

                Console.WriteLine($"[INFO] Loaded recipe {recipeTitle}");
                return JsonConvert.SerializeObject(recipeData);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get recipe {recipeTitle}: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        /// <summary>
        /// 마이그레이션완료
        /// </summary>
        /// <param name="recipeTitle"></param>
        /// <param name="recipeData"></param>
        /// <returns></returns>
        public string SaveRecipe(string recipeTitle, string recipeData)
        {
            try
            {
                var dr = PUB.mdm.GetDataV(recipeTitle);

                if (dr == null)
                {
                    var response = new { success = false, message = "Recipe not found" };
                    return JsonConvert.SerializeObject(response);
                }

                // JSON 데이터를 Dictionary로 변환
                var recipeDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(recipeData);

                // DataRow의 각 컬럼 업데이트
                foreach (var kvp in recipeDict)
                {
                    string colName = kvp.Key;

                    // 컬럼이 테이블에 존재하는지 확인
                    if (!dr.Table.Columns.Contains(colName))
                        continue;

                    // idx나 Midx 같은 자동 생성 컬럼은 스킵
                    if (colName == "idx" || colName == "Midx")
                        continue;

                    try
                    {
                        var col = dr.Table.Columns[colName];
                        object value = kvp.Value;

                        // null 처리
                        if (value == null)
                        {
                            dr[colName] = DBNull.Value;
                        }
                        else
                        {
                            // 타입 변환
                            if (col.DataType == typeof(bool))
                                dr[colName] = Convert.ToBoolean(value);
                            else if (col.DataType == typeof(int))
                                dr[colName] = Convert.ToInt32(value);
                            else if (col.DataType == typeof(double))
                                dr[colName] = Convert.ToDouble(value);
                            else if (col.DataType == typeof(float))
                                dr[colName] = Convert.ToSingle(value);
                            else
                                dr[colName] = value.ToString();
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"[WARN] Failed to set column {colName}: {ex.Message}");
                    }
                }

                // 변경사항 저장
                PUB.mdm.dataSet.OPModel.AcceptChanges();
                PUB.mdm.SaveModelV();

                Console.WriteLine($"[INFO] Recipe {recipeTitle} saved successfully to OPModel");

                var response2 = new { success = true, message = "Recipe saved successfully", recipeId = recipeTitle };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to save recipe {recipeTitle}: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string CopyRecipe(string recipeId, string newName)
        {
            Console.WriteLine($"[C#] Copying Recipe: {recipeId} as {newName}");

            try
            {
                string sourcePath = Path.Combine(_recipeFolder, $"{recipeId}.json");

                if (!File.Exists(sourcePath))
                {
                    var response = new { success = false, message = "Source recipe not found" };
                    return JsonConvert.SerializeObject(response);
                }

                string newId = Guid.NewGuid().ToString().Substring(0, 8);
                string destPath = Path.Combine(_recipeFolder, $"{newId}.json");

                string json = File.ReadAllText(sourcePath);
                var recipeData = JsonConvert.DeserializeObject<dynamic>(json);

                recipeData.name = newName;

                File.WriteAllText(destPath, JsonConvert.SerializeObject(recipeData, Formatting.Indented));

                string timestamp = DateTime.Now.ToString("yyyy-MM-dd");

                Console.WriteLine($"[INFO] Recipe copied from {recipeId} to {newId}");

                var response2 = new {
                    success = true,
                    message = "Recipe copied successfully",
                    newRecipe = new {
                        id = newId,
                        name = newName,
                        lastModified = timestamp
                    }
                };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to copy recipe: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string DeleteRecipe(string recipeId)
        {
            Console.WriteLine($"[C#] Deleting Recipe: {recipeId}");

            try
            {
                if (recipeId == _host.GetCurrentRecipe())
                {
                    var response1 = new { success = false, message = "Cannot delete currently selected recipe" };
                    return JsonConvert.SerializeObject(response1);
                }

                string recipePath = Path.Combine(_recipeFolder, $"{recipeId}.json");

                if (!File.Exists(recipePath))
                {
                    var response = new { success = false, message = "Recipe not found" };
                    return JsonConvert.SerializeObject(response);
                }

                File.Delete(recipePath);

                Console.WriteLine($"[INFO] Recipe {recipeId} deleted successfully");

                var response2 = new { success = true, message = "Recipe deleted successfully", recipeId = recipeId };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to delete recipe: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string GetProcessedData()
        {
            try
            {
                // TODO: Connect to actual data source (FMain.dataSet1.K4EE_Component_Reel_Result)
                // For now, return sample data structure
                var processedData = new List<object>();

                // Sample data for testing - replace with actual data from FMain or database
                var sampleData = new[] {
                    new {
                        target = "1",
                        JTYPE = "MODEL_A",
                        STIME = DateTime.Now.AddMinutes(-30).ToString("HH:mm:ss"),
                        BATCH = DateTime.Now.AddMinutes(-30).ToString("HH:mm:ss"),
                        SID = "SID123456",
                        RID = "RID789012",
                        VNAME = "VENDOR_A",
                        LOC = "L",
                        QTY = 1250,
                        qtymax = 2500,
                        MFGDATE = "2025-11-20",
                        VLOT = "LOT001",
                        PARTNO = "PN12345",
                        MCN = "CPN001",
                        REMARK = "",
                        PRNATTACH = true,
                        PRNVALID = true
                    },
                    new {
                        target = "2",
                        JTYPE = "MODEL_B",
                        STIME = DateTime.Now.AddMinutes(-25).ToString("HH:mm:ss"),
                        BATCH = DateTime.Now.AddMinutes(-25).ToString("HH:mm:ss"),
                        SID = "SID234567",
                        RID = "RID890123",
                        VNAME = "VENDOR_B",
                        LOC = "R",
                        QTY = 3500,
                        qtymax = 5000,
                        MFGDATE = "2025-11-21",
                        VLOT = "LOT002",
                        PARTNO = "PN23456",
                        MCN = "CPN002",
                        REMARK = "",
                        PRNATTACH = true,
                        PRNVALID = false
                    },
                    new {
                        target = "3",
                        JTYPE = "MODEL_A",
                        STIME = DateTime.Now.AddMinutes(-20).ToString("HH:mm:ss"),
                        BATCH = DateTime.Now.AddMinutes(-20).ToString("HH:mm:ss"),
                        SID = "SID345678",
                        RID = "RID901234",
                        VNAME = "VENDOR_A",
                        LOC = "L",
                        QTY = 800,
                        qtymax = 2500,
                        MFGDATE = "2025-11-22",
                        VLOT = "LOT003",
                        PARTNO = "PN34567",
                        MCN = "CPN003",
                        REMARK = "(BYPASS)",
                        PRNATTACH = false,
                        PRNVALID = true
                    },
                    new {
                        target = "4",
                        JTYPE = "MODEL_C",
                        STIME = DateTime.Now.AddMinutes(-15).ToString("HH:mm:ss"),
                        BATCH = DateTime.Now.AddMinutes(-15).ToString("HH:mm:ss"),
                        SID = "SID456789",
                        RID = "RID012345",
                        VNAME = "VENDOR_C",
                        LOC = "R",
                        QTY = 4200,
                        qtymax = 6000,
                        MFGDATE = "2025-11-23",
                        VLOT = "LOT004",
                        PARTNO = "PN45678",
                        MCN = "CPN004",
                        REMARK = "",
                        PRNATTACH = true,
                        PRNVALID = true
                    },
                    new {
                        target = "5",
                        JTYPE = "MODEL_B",
                        STIME = DateTime.Now.AddMinutes(-10).ToString("HH:mm:ss"),
                        BATCH = DateTime.Now.AddMinutes(-10).ToString("HH:mm:ss"),
                        SID = "SID567890",
                        RID = "RID123456",
                        VNAME = "VENDOR_B",
                        LOC = "L",
                        QTY = 1800,
                        qtymax = 5000,
                        MFGDATE = "2025-11-24",
                        VLOT = "LOT005",
                        PARTNO = "PN56789",
                        MCN = "CPN005",
                        REMARK = "",
                        PRNATTACH = true,
                        PRNVALID = true
                    }
                };

                foreach (var item in sampleData)
                {
                    processedData.Add(item);
                }

                return JsonConvert.SerializeObject(processedData);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get processed data: {ex.Message}");
                return JsonConvert.SerializeObject(new List<object>());
            }
        }

        // ===== VISION CONTROL METHODS =====

        public string CameraConnect()
        {
            try
            {
                if (PUB.wsL != null && PUB.wsL.Connected)
                {
                    var response = new { success = false, message = "Camera is already connected" };
                    return JsonConvert.SerializeObject(response);
                }

                if (string.IsNullOrEmpty(SETTING.Data.CameraLFile))
                {
                    var response = new { success = false, message = "Camera program filename not specified" };
                    return JsonConvert.SerializeObject(response);
                }

                var fi = new System.IO.FileInfo(SETTING.Data.CameraLFile);
                if (!fi.Exists)
                {
                    var response = new { success = false, message = $"Camera program file does not exist\n{fi.FullName}" };
                    return JsonConvert.SerializeObject(response);
                }

                PUB.log.Add("User Request: Connect Camera (QRCode)", false);
                // Camera connection logic would be implemented here
                // For now, return success
                var response2 = new { success = true, message = "Camera connection initiated" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to connect camera: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string CameraDisconnect()
        {
            try
            {
                PUB.log.Add("User Request: Disconnect Camera (QRCode)", false);
                // Camera disconnection logic
                var response = new { success = true, message = "Camera disconnected" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to disconnect camera: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string CameraGetImage()
        {
            try
            {
                PUB.log.Add("User Request: Get Camera Image", false);
                // Get image logic
                var response = new { success = true, message = "Image captured" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get camera image: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string CameraLiveView()
        {
            try
            {
                PUB.log.Add("User Request: Camera Live View", false);
                // Live view logic
                var response = new { success = true, message = "Live view started" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to start live view: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string CameraReadTest()
        {
            try
            {
                PUB.log.Add("User Request: Camera Read Test", false);
                // Read test logic
                var response = new { success = true, message = "Read test completed" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to run read test: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string KeyenceTriggerOn()
        {
            try
            {
                PUB.log.Add("User Request: Keyence Trigger ON", false);
                PUB.keyenceF.Trigger(true);
                PUB.keyenceR.Trigger(true);
                var response = new { success = true, message = "Keyence trigger ON" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to trigger keyence on: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string KeyenceTriggerOff()
        {
            try
            {
                PUB.log.Add("User Request: Keyence Trigger OFF", false);
                PUB.keyenceF.Trigger(false);
                PUB.keyenceR.Trigger(false);
                var response = new { success = true, message = "Keyence trigger OFF" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to trigger keyence off: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string KeyenceGetImage()
        {
            try
            {
                PUB.log.Add("User Request: Get Keyence Image", false);
                var curimageF = PUB.keyenceF.GetImage();
                var curimageR = PUB.keyenceR.GetImage();
                var response = new { success = true, message = "Keyence images captured" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get keyence image: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string KeyenceSaveImage()
        {
            try
            {
                PUB.log.Add("User Request: Save Keyence Image", false);
                var fn = System.IO.Path.Combine(AR.UTIL.CurrentPath, "Images", "keyence.bmp");
                var dir = System.IO.Path.GetDirectoryName(fn);
                if (!System.IO.Directory.Exists(dir))
                {
                    System.IO.Directory.CreateDirectory(dir);
                }
                // SaveImage logic would go here
                var response = new { success = true, message = $"Image saved to {fn}" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to save keyence image: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string ToggleLight()
        {
            try
            {
                if (PUB.flag.get(AR.eVarBool.FG_INIT_MOTIO) == false)
                {
                    var response = new { success = false, message = "Motion not initialized. Please try again later" };
                    return JsonConvert.SerializeObject(response);
                }

                var cur = DIO.GetIOOutput(AR.eDOName.ROOMLIGHT);
                var success = DIO.SetRoomLight(!cur, true);

                if (success == false)
                {
                    var response = new { success = false, message = "Failed to control room light" };
                    return JsonConvert.SerializeObject(response);
                }

                PUB.log.Add($"User Request: Room Light {(!cur ? "ON" : "OFF")}", false);
                var response2 = new { success = true, message = $"Light turned {(!cur ? "ON" : "OFF")}" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to toggle light: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string ExecuteManualPrint(string sid, string venderLot, string qty, string mfg, string rid, string spy, string partNo, string printer, int count)
        {
            try
            {
                // Parse quantity
                if (!int.TryParse(qty, out int vqty))
                {
                    var response = new { success = false, message = "Please enter quantity as a number" };
                    return JsonConvert.SerializeObject(response);
                }

                // Select printer
                var selectedPrinter = printer.ToLower() == "left" ? PUB.PrinterL : PUB.PrinterR;

                // Create ZPL
                string zpl = selectedPrinter.makeZPL_210908(new Class.Reel
                {
                    SID = sid,
                    venderLot = venderLot,
                    venderName = spy,
                    qty = vqty,
                    id = rid,
                    mfg = mfg,
                    PartNo = partNo,
                }, SETTING.Data.DrawOutbox, out string qrdata);

                // Print
                for (int i = 0; i < count; i++)
                {
                    var prn = selectedPrinter.Print(zpl);
                    if (prn.result == false)
                    {
                        PUB.log.AddE(prn.errmessage);
                        var response = new { success = false, message = $"Cannot proceed further due to printing failure: {prn.errmessage}" };
                        return JsonConvert.SerializeObject(response);
                    }
                    else
                    {
                        if (i == 0) // Log only first print
                            PUB.log.Add($"User print completed: {string.Join("|", new string[] { sid, venderLot, spy, qty, rid, mfg, partNo })}", false);
                    }
                    System.Threading.Thread.Sleep(100);
                }

                var response2 = new { success = true, message = $"Successfully printed {count} label(s)" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to execute manual print: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string CancelJob()
        {
            try
            {
                if (PUB.flag.get(AR.eVarBool.FG_INIT_MOTIO) == false)
                {
                    var response = new { success = false, message = "Motion not initialized. Please try again later" };
                    return JsonConvert.SerializeObject(response);
                }

                var msg = new System.Text.StringBuilder();

                if (PUB.mot.HasHomeSetOff)
                {
                    msg.AppendLine("Device initialization is not complete. Execute device initialization");
                }

                if (PUB.sm.isRunning == true)
                {
                    msg.AppendLine("AUTO-RUN MODE. Cannot be used during automatic execution. Please stop and try again");
                }

                if (DIO.IsEmergencyOn() == true)
                {
                    msg.AppendLine("Release emergency stop");
                }

                if (DIO.isSaftyDoorF() == false)
                {
                    msg.AppendLine("Front door is open");
                }

                if (DIO.isSaftyDoorR() == false)
                {
                    msg.AppendLine("Rear door is open");
                }

                if (msg.Length > 0)
                {
                    PUB.log.AddE(msg.ToString());
                    var response = new { success = false, message = msg.ToString() };
                    return JsonConvert.SerializeObject(response);
                }

                PUB.log.Add("User Click : tray out & clear position", false);
                PUB.flag.set(eVarBool.FG_USERSTEP, false, "Run_MotionPositionReset");
                PUB.log.AddAT("Starting discharge and home movement");
                PUB.AddSystemLog(System.Windows.Forms.Application.ProductVersion, "MAIN", "Cancel Work");
                PUB.sm.SetNewStep(eSMStep.HOME_QUICK);

                var response2 = new { success = true, message = "Job cancelled. Executing motion position reset" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to cancel job: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string OpenManage()
        {
            try
            {
                if (PUB.sm.isRunning)
                {
                    var response = new { success = false, message = "Cannot use during operation" };
                    return JsonConvert.SerializeObject(response);
                }

                if (PUB.flag.get(AR.eVarBool.FG_MOVE_PICKER))
                {
                    var response = new { success = false, message = "The window is already open" };
                    return JsonConvert.SerializeObject(response);
                }

                // Set flag to indicate picker move dialog is open
                PUB.flag.set(eVarBool.FG_MOVE_PICKER, true, "PICKERMOVE_WEB");
                PUB.log.Add("User Request: Manage (Web UI)", false);

                var response2 = new { success = true, message = "Manage dialog opened" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to open manage: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string CloseManage()
        {
            try
            {
                // Clear the flag
                PUB.flag.set(eVarBool.FG_MOVE_PICKER, false, "PICKERMOVE_WEB");

                // Check if auto-init is needed: home not set AND picker is in center position
                bool shouldAutoInit = PUB.mot.HasHomeSetOff == true && DIO.GetIOInput(eDIName.PICKER_SAFE);

                PUB.log.Add($"User Request: Close Manage (Web UI), shouldAutoInit={shouldAutoInit}", false);

                var response = new { shouldAutoInit = shouldAutoInit };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to close manage: {ex.Message}");
                var response = new { shouldAutoInit = false };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string OpenManual()
        {
            try
            {
                string file = System.IO.Path.Combine(AR.UTIL.CurrentPath, "Manual", "Manual.pdf");
                if (System.IO.File.Exists(file) == false)
                {
                    var response = new { success = false, message = "User manual file does not exist\nFile name: " + file };
                    return JsonConvert.SerializeObject(response);
                }

                AR.UTIL.RunExplorer(file);
                PUB.log.Add("User Request: Open Manual", false);
                var response2 = new { success = true, message = "Manual opened" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to open manual: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string OpenLogViewer()
        {
            try
            {
                var exename = AR.UTIL.MakePath("LogView.exe");
                if (System.IO.File.Exists(exename) == false)
                {
                    var response = new { success = false, message = $"Log viewer file not found\n{exename}\nPlease contact support" };
                    return JsonConvert.SerializeObject(response);
                }

                AR.UTIL.RunProcess(exename);
                PUB.log.Add("User Request: Open Log Viewer", false);
                var response2 = new { success = true, message = "Log viewer opened" };
                return JsonConvert.SerializeObject(response2);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to open log viewer: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string OpenProgramFolder()
        {
            try
            {
                AR.UTIL.RunExplorer(AR.UTIL.CurrentPath);
                PUB.log.Add("User Request: Open Program Folder", false);
                var response = new { success = true, message = "Program folder opened" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to open program folder: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string OpenLogFolder()
        {
            try
            {
                PUB.LogFlush();
                var fi = new System.IO.FileInfo(PUB.log.FileName);
                AR.UTIL.RunExplorer(fi.Directory.FullName);
                PUB.log.Add("User Request: Open Log Folder", false);
                var response = new { success = true, message = "Log folder opened" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to open log folder: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string OpenScreenshotFolder()
        {
            try
            {
                string savefile = System.IO.Path.Combine(AR.UTIL.CurrentPath, "ScreenShot", DateTime.Now.ToString("yyyyMMddHHmmss") + ".png");
                var grpath = new System.IO.FileInfo(savefile);
                AR.UTIL.RunExplorer(grpath.Directory.FullName);
                PUB.log.Add("User Request: Open Screenshot Folder", false);
                var response = new { success = true, message = "Screenshot folder opened" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to open screenshot folder: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        public string OpenSavedDataFolder()
        {
            try
            {
                var basepath = SETTING.Data.GetDataPath();
                var path = System.IO.Path.Combine(
                    basepath, "History",
                    DateTime.Now.Year.ToString("0000"),
                    DateTime.Now.Month.ToString("00"),
                    DateTime.Now.Day.ToString("00"));
                if (System.IO.Directory.Exists(path))
                    AR.UTIL.RunExplorer(path);
                else
                    AR.UTIL.RunExplorer(System.IO.Path.Combine(SETTING.Data.GetDataPath(), "History"));

                PUB.log.Add("User Request: Open Saved Data Folder", false);
                var response = new { success = true, message = "Saved data folder opened" };
                return JsonConvert.SerializeObject(response);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to open saved data folder: {ex.Message}");
                var response = new { success = false, message = ex.Message };
                return JsonConvert.SerializeObject(response);
            }
        }

        // ===== PICKER MOVE METHODS =====

        private static bool _manPosL = false;
        private static bool _manPosR = false;

        public string GetPickerStatus()
        {
            try
            {
                // X축 피커 이동 가능 여부 체크
                bool xEnabled = false;
                if (PUB.mot.IsHomeSet((int)eAxis.PZ_PICK) == false)
                {
                    if (PUB.mot.IsOrg((int)eAxis.PZ_PICK) || PUB.mot.IsLimitN((int)eAxis.PZ_PICK))
                        xEnabled = true;
                }
                else
                {
                    var PosZ = MOT.GetPZPos(ePZLoc.READY);
                    var OffZ = MOT.getPositionOffset(PosZ);
                    xEnabled = OffZ < 1;
                }

                var doorsafef = DIO.isSaftyDoorF();
                var managementEnabled = PUB.mot.HasHomeSetOff == false && doorsafef == true;

                var status = new
                {
                    xEnabled = xEnabled,
                    zEnabled = xEnabled, // Z jog uses same condition
                    pickerSafe = DIO.GetIOInput(eDIName.PICKER_SAFE),
                    managementEnabled = managementEnabled,
                    manPosL = _manPosL,
                    manPosR = _manPosR
                };

                return JsonConvert.SerializeObject(status);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get picker status: {ex.Message}");
                return JsonConvert.SerializeObject(new { xEnabled = false, zEnabled = false, pickerSafe = false, managementEnabled = false, manPosL = false, manPosR = false });
            }
        }

        private bool CheckPickerSafety(out string errorMessage)
        {
            errorMessage = null;

            if (DIO.isSaftyDoorF() == false)
            {
                errorMessage = "Front door is open";
                return false;
            }

            if (PUB.mot.HasHomeSetOff)
            {
                errorMessage = "Motion home operation is not completed";
                return false;
            }

            return true;
        }

        private bool CheckZAxisReady(out string errorMessage)
        {
            errorMessage = null;
            var z = MOT.GetPZPos(ePZLoc.READY);
            var zpos = MOT.getPositionOffset(z);
            if (zpos >= 0.5)
            {
                errorMessage = "Raise the Z axis and try again";
                return false;
            }
            return true;
        }

        public string PickerMoveLeft()
        {
            try
            {
                if (!CheckPickerSafety(out string safetyError))
                    return JsonConvert.SerializeObject(new { success = false, message = safetyError });

                if (!CheckZAxisReady(out string zError))
                    return JsonConvert.SerializeObject(new { success = false, message = zError });

                var m1 = MOT.GetLMPos(eLMLoc.READY);
                if (MOT.getPositionMatch(m1) == false)
                    return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });

                var p1 = MOT.GetPXPos(ePXLoc.PICKOFFL);
                MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);

                return JsonConvert.SerializeObject(new { success = true, message = "Moving to left position" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerMoveLeftWait()
        {
            try
            {
                if (!CheckPickerSafety(out string safetyError))
                    return JsonConvert.SerializeObject(new { success = false, message = safetyError });

                if (!CheckZAxisReady(out string zError))
                    return JsonConvert.SerializeObject(new { success = false, message = zError });

                var m1 = MOT.GetLMPos(eLMLoc.READY);
                if (MOT.getPositionMatch(m1) == false)
                    return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });

                var p1 = MOT.GetPXPos(ePXLoc.READYL);
                MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);

                return JsonConvert.SerializeObject(new { success = true, message = "Moving to left wait position" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerMoveCenter()
        {
            try
            {
                if (!CheckPickerSafety(out string safetyError))
                    return JsonConvert.SerializeObject(new { success = false, message = safetyError });

                if (!CheckZAxisReady(out string zError))
                    return JsonConvert.SerializeObject(new { success = false, message = zError });

                var p1 = MOT.GetPXPos(ePXLoc.PICKON);
                MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);

                return JsonConvert.SerializeObject(new { success = true, message = "Moving to center position" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerMoveRightWait()
        {
            try
            {
                if (!CheckPickerSafety(out string safetyError))
                    return JsonConvert.SerializeObject(new { success = false, message = safetyError });

                if (!CheckZAxisReady(out string zError))
                    return JsonConvert.SerializeObject(new { success = false, message = zError });

                var m1 = MOT.GetRMPos(eRMLoc.READY);
                if (MOT.getPositionMatch(m1) == false)
                    return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });

                var p1 = MOT.GetPXPos(ePXLoc.READYR);
                MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);

                return JsonConvert.SerializeObject(new { success = true, message = "Moving to right wait position" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerMoveRight()
        {
            try
            {
                if (!CheckPickerSafety(out string safetyError))
                    return JsonConvert.SerializeObject(new { success = false, message = safetyError });

                if (!CheckZAxisReady(out string zError))
                    return JsonConvert.SerializeObject(new { success = false, message = zError });

                var m1 = MOT.GetRMPos(eRMLoc.READY);
                if (MOT.getPositionMatch(m1) == false)
                    return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });

                var p1 = MOT.GetPXPos(ePXLoc.PICKOFFR);
                MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);

                return JsonConvert.SerializeObject(new { success = true, message = "Moving to right position" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerJogStart(string direction)
        {
            try
            {
                switch (direction)
                {
                    case "up":
                        PUB.mot.JOG((int)eAxis.PZ_PICK, arDev.MOT.MOTION_DIRECTION.Negative, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
                        break;
                    case "down":
                        PUB.mot.JOG((int)eAxis.PZ_PICK, arDev.MOT.MOTION_DIRECTION.Positive, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
                        break;
                    case "left":
                        PUB.mot.JOG((int)eAxis.PX_PICK, arDev.MOT.MOTION_DIRECTION.Negative, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
                        break;
                    case "right":
                        PUB.mot.JOG((int)eAxis.PX_PICK, arDev.MOT.MOTION_DIRECTION.Positive, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
                        break;
                }
                return JsonConvert.SerializeObject(new { success = true, message = $"Jog {direction} started" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerJogStop()
        {
            try
            {
                PUB.mot.MoveStop("pmove_web", (short)eAxis.PX_PICK);
                PUB.mot.MoveStop("pmove_web", (short)eAxis.PZ_PICK);
                return JsonConvert.SerializeObject(new { success = true, message = "Jog stopped" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerStop()
        {
            try
            {
                PUB.mot.MoveStop("pmove_web", (int)eAxis.PX_PICK);
                return JsonConvert.SerializeObject(new { success = true, message = "Picker stopped" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string CancelVisionValidation(string side)
        {
            try
            {
                if (side == "left")
                {
                    PUB.flag.set(eVarBool.FG_PRC_VISIONL, false, "CANCEL_WEB");
                    PUB.flag.set(eVarBool.FG_PORTL_ITEMON, false, "CANCEL_WEB");
                    PUB.log.Add($"LEFT-QR verification cancelled JGUID={PUB.Result.ItemDataL.guid}");
                }
                else
                {
                    PUB.flag.set(eVarBool.FG_PRC_VISIONR, false, "CANCEL_WEB");
                    PUB.flag.set(eVarBool.FG_PORTR_ITEMON, false, "CANCEL_WEB");
                    PUB.log.Add($"RIGHT-QR verification cancelled JGUID={PUB.Result.ItemDataR.guid}");
                }

                return JsonConvert.SerializeObject(new { success = true, message = $"{side.ToUpper()}-QR verification cancelled" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerManagePosition(string side)
        {
            try
            {
                if (PUB.sm.Step != eSMStep.IDLE)
                    return JsonConvert.SerializeObject(new { success = false, message = "Available only in standby state" });

                var Xpos = DIO.GetIOInput(eDIName.PICKER_SAFE);
                if (Xpos == false)
                    return JsonConvert.SerializeObject(new { success = false, message = "Available only when picker is in center position" });

                int vidx = side == "left" ? 0 : 2;

                Task.Run(() =>
                {
                    DateTime dt;
                    if (vidx == 0)
                    {
                        while (DIO.GetIOInput(eDIName.L_CYLUP) == false)
                        {
                            var dorlt = DIO.checkDigitalO(eDOName.L_CYLDN, new TimeSpan(1), false);
                            if (dorlt == eNormalResult.False)
                                System.Threading.Thread.Sleep(100);
                            else if (dorlt == eNormalResult.Error)
                            {
                                PUB.log.AddE("l_cylup check error");
                                return;
                            }
                        }

                        var zPos = MOT.GetLZPos(eLZLoc.READY).Clone();
                        zPos.Speed = 100;
                        MOT.Move(zPos);
                        dt = DateTime.Now;
                        while (MOT.getPositionMatch(zPos) == false)
                        {
                            if ((DateTime.Now - dt).TotalSeconds > 30) break;
                            System.Threading.Thread.Sleep(10);
                        }

                        var mPos = MOT.GetLMPos(eLMLoc.PRINTL07).Clone();
                        mPos.Speed = 100;
                        MOT.Move(mPos);
                        dt = DateTime.Now;
                        while (MOT.getPositionMatch(mPos) == false)
                        {
                            if ((DateTime.Now - dt).TotalSeconds > 30) break;
                            System.Threading.Thread.Sleep(10);
                        }

                        var zPos2 = MOT.GetLZPos(eLZLoc.PICKOFF);
                        var tPos = (zPos2.Position / 2f);
                        MOT.Move(eAxis.PL_UPDN, tPos, 100, zPos.Acc);
                        _manPosL = true;
                    }
                    else
                    {
                        while (DIO.GetIOInput(eDIName.R_CYLUP) == false)
                        {
                            var dorlt = DIO.checkDigitalO(eDOName.R_CYLDN, new TimeSpan(1), false);
                            if (dorlt == eNormalResult.False)
                                System.Threading.Thread.Sleep(100);
                            else if (dorlt == eNormalResult.Error)
                            {
                                PUB.log.AddE("r_cylup check error");
                                return;
                            }
                        }

                        var zPos = MOT.GetRZPos(eRZLoc.READY).Clone();
                        zPos.Speed = 100;
                        MOT.Move(zPos);
                        dt = DateTime.Now;
                        while (MOT.getPositionMatch(zPos) == false)
                        {
                            if ((DateTime.Now - dt).TotalSeconds > 30) break;
                            System.Threading.Thread.Sleep(10);
                        }

                        var mPos = MOT.GetRMPos(eRMLoc.PRINTL07).Clone();
                        mPos.Speed = 100;
                        MOT.Move(mPos);
                        dt = DateTime.Now;
                        while (MOT.getPositionMatch(mPos) == false)
                        {
                            if ((DateTime.Now - dt).TotalSeconds > 30) break;
                            System.Threading.Thread.Sleep(10);
                        }

                        var zPos2 = MOT.GetRZPos(eRZLoc.PICKOFF);
                        var tPos = (zPos2.Position / 2f);
                        MOT.Move(eAxis.PR_UPDN, tPos, 100, zPos.Acc);
                        _manPosR = true;
                    }
                });

                return JsonConvert.SerializeObject(new { success = true, message = $"Moving to {side} management position" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerManageReturn()
        {
            try
            {
                var Xpos = DIO.GetIOInput(eDIName.PICKER_SAFE);
                if (Xpos == false)
                    return JsonConvert.SerializeObject(new { success = false, message = "Available only when picker is in center position" });

                // Recover both positions
                Task.Run(() => PosRecover(0));
                Task.Run(() => PosRecover(2));

                return JsonConvert.SerializeObject(new { success = true, message = "Returning to ready position" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        private void PosRecover(int vidx)
        {
            DateTime dt;
            if (vidx == 0)
            {
                while (DIO.GetIOInput(eDIName.L_CYLUP) == false)
                {
                    var dorlt = DIO.checkDigitalO(eDOName.L_CYLDN, new TimeSpan(1), false);
                    if (dorlt == eNormalResult.False)
                        System.Threading.Thread.Sleep(100);
                    else if (dorlt == eNormalResult.Error)
                    {
                        PUB.log.AddE("l_cylup check error");
                        return;
                    }
                }

                var zPos = MOT.GetLZPos(eLZLoc.READY).Clone();
                zPos.Speed = 100;
                MOT.Move(zPos);
                dt = DateTime.Now;
                while (MOT.getPositionMatch(zPos) == false)
                {
                    if ((DateTime.Now - dt).TotalSeconds > 30) break;
                    System.Threading.Thread.Sleep(10);
                }

                var mPos = MOT.GetLMPos(eLMLoc.READY).Clone();
                mPos.Speed = 100;
                MOT.Move(mPos);
                dt = DateTime.Now;
                while (MOT.getPositionMatch(mPos) == false)
                {
                    if ((DateTime.Now - dt).TotalSeconds > 30) break;
                    System.Threading.Thread.Sleep(10);
                }
                _manPosL = false;
            }
            else
            {
                while (DIO.GetIOInput(eDIName.R_CYLUP) == false)
                {
                    var dorlt = DIO.checkDigitalO(eDOName.R_CYLDN, new TimeSpan(1), false);
                    if (dorlt == eNormalResult.False)
                        System.Threading.Thread.Sleep(100);
                    else if (dorlt == eNormalResult.Error)
                    {
                        PUB.log.AddE("R_cylup check error");
                        return;
                    }
                }

                var zPos = MOT.GetRZPos(eRZLoc.READY).Clone();
                zPos.Speed = 100;
                MOT.Move(zPos);
                dt = DateTime.Now;
                while (MOT.getPositionMatch(zPos) == false)
                {
                    if ((DateTime.Now - dt).TotalSeconds > 30) break;
                    System.Threading.Thread.Sleep(10);
                }

                var mPos = MOT.GetRMPos(eRMLoc.READY).Clone();
                mPos.Speed = 100;
                MOT.Move(mPos);
                dt = DateTime.Now;
                while (MOT.getPositionMatch(mPos) == false)
                {
                    if ((DateTime.Now - dt).TotalSeconds > 30) break;
                    System.Threading.Thread.Sleep(10);
                }

                _manPosR = false;
            }
        }

        public string PickerZHome()
        {
            try
            {
                MOT.Home("Management_Web", eAxis.PZ_PICK, false);
                return JsonConvert.SerializeObject(new { success = true, message = "Z-axis home search started" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerZZero()
        {
            try
            {
                if (PUB.mot.IsHomeSet((int)eAxis.PZ_PICK) == false)
                    return JsonConvert.SerializeObject(new { success = false, message = "Z home operation is not completed. Please perform HOME first" });

                MOT.Move(eAxis.PZ_PICK, 0, 500, 1000, false, false, false);
                return JsonConvert.SerializeObject(new { success = true, message = "Moving Z-axis to zero" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string PickerTestPrint(string side)
        {
            try
            {
                if (side == "left")
                {
                    PUB.PrinterL.TestPrint(AR.SETTING.Data.DrawOutbox, "", "");
                    PUB.log.Add("Temporary print L:" + PUB.PrinterL.LastPrintZPL);
                }
                else
                {
                    PUB.PrinterR.TestPrint(AR.SETTING.Data.DrawOutbox, "", "");
                    PUB.log.Add("Temporary print R:" + PUB.PrinterR.LastPrintZPL);
                }

                return JsonConvert.SerializeObject(new { success = true, message = $"{side.ToUpper()} test print completed" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        public string CanCloseManage()
        {
            try
            {
                if (_manPosL || _manPosR)
                {
                    return JsonConvert.SerializeObject(new { canClose = false, message = "Printer motion is in management position.\nReturn to position and try again" });
                }
                return JsonConvert.SerializeObject(new { canClose = true, message = "" });
            }
            catch (Exception ex)
            {
                return JsonConvert.SerializeObject(new { canClose = false, message = ex.Message });
            }
        }

        // ===== INTERLOCK METHODS =====

        /// <summary>
        /// 인터락 토글 (DIOMonitor.cs의 gvILXF_ItemClick과 동일)
        /// </summary>
        public string ToggleInterlock(int axisIndex, int lockIndex)
        {
            try
            {
                if (axisIndex < 0 || axisIndex >= PUB.iLock.Length)
                {
                    return JsonConvert.SerializeObject(new { success = false, message = "Invalid axis index" });
                }

                var curValue = PUB.iLock[axisIndex].get(lockIndex);
                PUB.iLock[axisIndex].set(lockIndex, !curValue, "IOMONITOR_WEB");

                PUB.log.Add($"[Web] Interlock toggle: axis={axisIndex}, lock={lockIndex}, newVal={!curValue}");

                return JsonConvert.SerializeObject(new { success = true, newState = !curValue });
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to toggle interlock: {ex.Message}");
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
            }
        }

        // ===== HISTORY DATA =====

        /// <summary>
        /// 작업 이력 데이터 조회 (fHistory.cs의 refreshList와 동일)
        /// </summary>
        public string GetHistoryData(string startDate, string endDate, string search)
        {
            try
            {
                var results = new List<object>();

                using (var ta = new DataSet1TableAdapters.K4EE_Component_Reel_ResultTableAdapter())
                {
                    var ds = new DataSet1();

                    // 검색어 처리
                    var searchPattern = string.IsNullOrEmpty(search) ? "%" : "%" + search + "%";

                    // DB에서 조회
                    ta.FillBySearch(ds.K4EE_Component_Reel_Result, AR.SETTING.Data.McName, startDate, endDate, searchPattern);

                    foreach (DataSet1.K4EE_Component_Reel_ResultRow row in ds.K4EE_Component_Reel_Result.Rows)
                    {
                        results.Add(new
                        {
                            idx = row.idx,
                            jtype = row.IsJTYPENull() ? "" : row.JTYPE,
                            rid = row.IsRIDNull() ? "" : row.RID,
                            sid = row.IsSIDNull() ? "" : row.SID,
                            qty = row.IsQTYNull() ? 0 : row.QTY,
                            vname = row.IsVNAMENull() ? "" : row.VNAME,
                            vlot = row.IsVLOTNull() ? "" : row.VLOT,
                            loc = row.IsLOCNull() ? "" : row.LOC,
                            qr = row.IsQRNull() ? "" : row.QR,
                            stime = row.STIME.ToString("yyyy-MM-dd HH:mm:ss"),
                            etime = row.IsETIMENull() ? "" : row.ETIME.ToString("yyyy-MM-dd HH:mm:ss"),
                            prnattach = !row.IsPRNATTACHNull() && row.PRNATTACH,
                            prnvalid = !row.IsPRNVALIDNull() && row.PRNVALID,
                            rid0 = row.IsRID0Null() ? "" : row.RID0,
                            sid0 = row.IsSID0Null() ? "" : row.SID0,
                            qtymax = row.IsqtymaxNull() ? 0 : row.qtymax
                        });
                    }
                }

                return JsonConvert.SerializeObject(new {
                    success = true,
                    data = results,
                    mcName = AR.SETTING.Data.McName
                });
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get history data: {ex.Message}");
                return JsonConvert.SerializeObject(new { success = false, message = ex.Message, data = new List<object>() });
            }
        }

        // ===== HARDWARE STATUS METHODS =====

        /// <summary>
        /// H/W 상태 조회 (_Interval_250ms.cs의 HWState 업데이트 로직과 동일)
        /// </summary>
        public string GetHWStatus()
        {
            try
            {
                var hwList = new List<object>();

                // 1. KeyenceF (BCD Front) - 바코드 리더
                if (PUB.keyenceF != null)
                {
                    hwList.Add(new
                    {
                        name = "BCD-F",
                        title = PUB.keyenceF.IsConnect ? (PUB.keyenceF.IsTriggerOn ? "TRIG" : "ON") : "OFF",
                        status = PUB.keyenceF.IsConnect ? (PUB.keyenceF.IsTriggerOn ? 2 : 1) : 3
                    });
                }
                else
                {
                    hwList.Add(new { name = "BCD-F", title = "SET", status = 0 });
                }

                // 2. KeyenceR (BCD Rear) - 바코드 리더
                if (PUB.keyenceR != null)
                {
                    hwList.Add(new
                    {
                        name = "BCD-R",
                        title = PUB.keyenceR.IsConnect ? (PUB.keyenceR.IsTriggerOn ? "TRIG" : "ON") : "OFF",
                        status = PUB.keyenceR.IsConnect ? (PUB.keyenceR.IsTriggerOn ? 2 : 1) : 3
                    });
                }
                else
                {
                    hwList.Add(new { name = "BCD-R", title = "SET", status = 0 });
                }

                // 3. Vision WebSocket Left
                if (PUB.wsL != null)
                {
                    hwList.Add(new
                    {
                        name = "VIS-L",
                        title = PUB.wsL.Connected ? "ON" : "OFF",
                        status = PUB.wsL.Connected ? 1 : 3
                    });
                }
                else
                {
                    hwList.Add(new { name = "VIS-L", title = "SET", status = 0 });
                }

                // 4. Vision WebSocket Right
                if (PUB.wsR != null)
                {
                    hwList.Add(new
                    {
                        name = "VIS-R",
                        title = PUB.wsR.Connected ? "ON" : "OFF",
                        status = PUB.wsR.Connected ? 1 : 3
                    });
                }
                else
                {
                    hwList.Add(new { name = "VIS-R", title = "SET", status = 0 });
                }

                // 5. BarcodeFix (Fixed Barcode Reader)
                hwList.Add(new
                {
                    name = "FIX",
                    title = PUB.BarcodeFix.IsOpen() ? AR.SETTING.Data.Barcode_Port : "OFF",
                    status = PUB.BarcodeFix.IsOpen() ? 1 : 3
                });

                // 6. PrinterL
                if (PUB.PrinterL != null)
                {
                    hwList.Add(new
                    {
                        name = "PRT-L",
                        title = PUB.PrinterL.IsOpen ? AR.SETTING.Data.PrintL_Port : "OFF",
                        status = PUB.PrinterL.IsOpen ? 1 : 3
                    });
                }
                else
                {
                    hwList.Add(new { name = "PRT-L", title = "SET", status = 0 });
                }

                // 7. PrinterR
                if (PUB.PrinterR != null)
                {
                    hwList.Add(new
                    {
                        name = "PRT-R",
                        title = PUB.PrinterR.IsOpen ? AR.SETTING.Data.PrintR_Port : "OFF",
                        status = PUB.PrinterR.IsOpen ? 1 : 3
                    });
                }
                else
                {
                    hwList.Add(new { name = "PRT-R", title = "SET", status = 0 });
                }

                // 8. PLC
                if (PUB.plc != null)
                {
                    hwList.Add(new
                    {
                        name = "PLC",
                        title = PUB.plc.Init ? AR.SETTING.Data.swplc_name : "OFF",
                        status = PUB.plc.Init ? 1 : 3
                    });
                }
                else
                {
                    hwList.Add(new { name = "PLC", title = "SET", status = 0 });
                }

                // 9. DIO (Digital I/O)
                hwList.Add(new
                {
                    name = "DIO",
                    title = PUB.dio?.IsInit == true ? "ON" : "OFF",
                    status = PUB.dio?.IsInit == true ? 1 : 3
                });

                // 10. Motion
                hwList.Add(new
                {
                    name = "MOT",
                    title = PUB.mot?.IsInit == true ? "ON" : "OFF",
                    status = PUB.mot?.IsInit == true ? 1 : 3
                });

                return JsonConvert.SerializeObject(hwList);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get HW status: {ex.Message}");
                return JsonConvert.SerializeObject(new List<object>());
            }
        }

        /// <summary>
        /// 실시간 인식 데이터 조회 (listView21과 동일한 데이터)
        /// Left/Center/Right 포트의 VisionData 정보
        /// </summary>
        public string GetVisionData()
        {
            try
            {
                var visdataL = PUB.Result.ItemDataL?.VisionData;
                var visdataC = PUB.Result.ItemDataC?.VisionData;
                var visdataR = PUB.Result.ItemDataR?.VisionData;

                var result = new
                {
                    left = new
                    {
                        cartSize = DIO.getCartSize(0).ToString(),
                        enabled = PUB.flag.get(eVarBool.FG_ENABLE_LEFT),
                        rid = visdataL?.RID ?? "",
                        sid = visdataL?.SID ?? "",
                        qty = visdataL?.QTY ?? "",
                        vname = visdataL?.VNAME ?? "",
                        vlot = visdataL?.VLOT ?? "",
                        mfgdate = visdataL?.MFGDATE ?? "",
                        partno = visdataL?.PARTNO ?? "",
                        reelSize = visdataL?.ReelSize.ToString() ?? "--",
                        batch = visdataL?.BATCH ?? "",
                        rid2 = visdataL?.RID2 ?? "",
                        sid2 = visdataL?.SID2 ?? "",
                        qty2 = visdataL?.QTY2 ?? "",
                        vname2 = visdataL?.VNAME2 ?? "",
                        vlot2 = visdataL?.VLOT2 ?? "",
                        mfgdate2 = visdataL?.MFGDATE2 ?? "",
                        partno2 = visdataL?.PARTNO2 ?? ""
                    },
                    center = new
                    {
                        cartSize = DIO.getCartSize(1).ToString(),
                        confirm = visdataC?.Confirm ?? false,
                        rid = visdataC?.RID ?? "",
                        rid_trust = visdataC?.RID_Trust ?? false,
                        rid_new = visdataC?.RIDNew ?? false,
                        sid = visdataC?.SID ?? "",
                        sid0 = visdataC?.SID0 ?? "",
                        sid_trust = visdataC?.SID_Trust ?? false,
                        qty = visdataC?.QTY ?? "",
                        qty_trust = visdataC?.QTY_Trust ?? false,
                        qty_rq = visdataC?.QTYRQ ?? false,
                        vname = visdataC?.VNAME ?? "",
                        vname_trust = visdataC?.VNAME_Trust ?? false,
                        vlot = visdataC?.VLOT ?? "",
                        vlot_trust = visdataC?.VLOT_Trust ?? false,
                        mfgdate = visdataC?.MFGDATE ?? "",
                        mfgdate_trust = visdataC?.MFGDATE_Trust ?? false,
                        partno = visdataC?.PARTNO ?? "",
                        partno_trust = visdataC?.PARTNO_Trust ?? false,
                        reelSize = visdataC?.ReelSize.ToString() ?? "--",
                        batch = visdataC?.BATCH ?? "",
                        qtymax = visdataC?.QTYMAX ?? "",
                        barcodeCount = visdataC?.barcodelist?.Count ?? 0,
                        regexCount = PUB.Result.BCDPattern?.Count ?? 0
                    },
                    right = new
                    {
                        cartSize = DIO.getCartSize(2).ToString(),
                        enabled = PUB.flag.get(eVarBool.FG_ENABLE_RIGHT),
                        rid = visdataR?.RID ?? "",
                        sid = visdataR?.SID ?? "",
                        qty = visdataR?.QTY ?? "",
                        vname = visdataR?.VNAME ?? "",
                        vlot = visdataR?.VLOT ?? "",
                        mfgdate = visdataR?.MFGDATE ?? "",
                        partno = visdataR?.PARTNO ?? "",
                        reelSize = visdataR?.ReelSize.ToString() ?? "--",
                        batch = visdataR?.BATCH ?? "",
                        rid2 = visdataR?.RID2 ?? "",
                        sid2 = visdataR?.SID2 ?? "",
                        qty2 = visdataR?.QTY2 ?? "",
                        vname2 = visdataR?.VNAME2 ?? "",
                        vlot2 = visdataR?.VLOT2 ?? "",
                        mfgdate2 = visdataR?.MFGDATE2 ?? "",
                        partno2 = visdataR?.PARTNO2 ?? ""
                    }
                };

                return JsonConvert.SerializeObject(result);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get vision data: {ex.Message}");
                return JsonConvert.SerializeObject(new { left = new { }, center = new { }, right = new { } });
            }
        }

        /// <summary>
        /// 인터락 목록 조회 (실시간 값 포함)
        /// </summary>
        public string GetInterlockList()
        {
            try
            {
                var interlocks = new List<object>();

                for (int i = 0; i < PUB.iLock.Length; i++)
                {
                    var axisName = ((AR.eAxis)i).ToString();
                    var nonAxis = false;

                    if (i >= 7)
                    {
                        axisName = PUB.iLock[i].Tag.ToString();
                        nonAxis = true;
                    }

                    string[] ilockNames;
                    if (i == 7) ilockNames = Enum.GetNames(typeof(eILockPRL));
                    else if (i == 8) ilockNames = Enum.GetNames(typeof(eILockPRR));
                    else if (i == 9) ilockNames = Enum.GetNames(typeof(eILockVS0));
                    else if (i == 10) ilockNames = Enum.GetNames(typeof(eILockVS1));
                    else if (i == 11) ilockNames = Enum.GetNames(typeof(eILockVS2));
                    else if (i == 12 || i == 13) ilockNames = Enum.GetNames(typeof(eILockCV));
                    else ilockNames = Enum.GetNames(typeof(eILock));

                    var lockValues = new List<object>();
                    for (int j = 0; j < ilockNames.Length && j < 64; j++)
                    {
                        bool state = PUB.iLock[i].get(j);
                        lockValues.Add(new
                        {
                            id = j,
                            name = ilockNames[j],
                            state = state
                        });
                    }

                    interlocks.Add(new
                    {
                        axisIndex = i,
                        axisName = axisName,
                        nonAxis = nonAxis,
                        locks = lockValues,
                        hexValue = PUB.iLock[i].Value().HexString()
                    });
                }

                return JsonConvert.SerializeObject(interlocks);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ERROR] Failed to get interlock list: {ex.Message}");
                return JsonConvert.SerializeObject(new List<object>());
            }
        }
    }
}
