UserControls/CustomerDgvControl.cs

/*
 * Copyright XXXX Co.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using ChatworkBulkSender.Daos;
using ChatworkBulkSender.Dtos;
using ChatworkBulkSender.Utils;
using ChatworkBulkSender.Forms;
using System.Diagnostics;
using System.Linq;

namespace ChatworkBulkSender.UserControls
{
    /// <summary>
    /// 顧客マスタ画面DGVコントロール。
    /// デザイナー表示のためデバッグ時は、中間クラスを経由する。
    /// </summary>
    [DesignerCategory("UserControl")] // デザイナーでユーザーコントロールとして表示することを明記する
    public partial class CustomerDgvControl : BaseCustomerDgvControl
    {
        protected override int EditButtonColumnIndex => 6;

        protected override int SortableColumnMaxIndex => 5;

        protected override string InitialSortColumnName => "SortOrder";

        EventHandler<CustomerMasterDto> _createdCustomer;


        public CustomerDgvControl()
        {
            if (!IsInDesignMode())
            {
                InitializeComponent();
                if (_dgv == null)
                {
                    base._dgv = this.customerDgv;
                }
                InitializeDgv();
                SetUpColumns();
                customerDgv.Dock = DockStyle.Fill;

            }
        }

        protected override void InitializeDgv()
        {
            base.InitializeDgv();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            if (!IsInDesignMode())
            {
                _createdCustomer = (s, args) => CreatedCustomer();
                _individualEditForm = new M_CustomerMasterIndividualEdit();
                _individualEditForm.DataCreated += _createdCustomer;
                RefreshData();
            }
        }

        public void ClearSelectionCell()
        {
            ClearSelection();
        }

        public void ShowCreateForm()
        {
            customerDgv.Enabled = false;

            try
            {
                var createForm = new M_CustomerMasterIndividualEdit(
                    M_CustomerMasterIndividualEdit.ButtonMode.Register);

                createForm.DataCreated += _createdCustomer;
                createForm.Owner = this.ParentForm;

                createForm.FormClosed += (s, args) =>
                {
                    customerDgv.Enabled = true;

                    if (this.ParentForm != null && !this.ParentForm.Visible)
                    {
                        this.ParentForm.Show();
                        this.ParentForm.BringToFront();
                    }

                    RefreshData();
                };
                this.ParentForm.Hide();
                createForm.Show();
            }
            catch(Exception e)
            {
                customerDgv.Enabled = true;

                if (this.ParentForm != null && !this.ParentForm.Visible)
                {
                    this.ParentForm.Show();
                }
                MessageBoxUtil.ShowErr($"新規作成画面の表示中にエラーが発生しました。\n\n{e.Message}");
            }
        }

        protected override void ShowEditForm(CustomerMasterDto selectedItem)
        {
            customerDgv.Enabled = false;

            try
            {
                if(_individualEditForm != null && !_individualEditForm.IsDisposed)
                {
                    var form = _individualEditForm;

                    if (form != null)
                    {
                        form.DataCreated += _createdCustomer;
                    }
                    _individualEditForm.Close();
                    _individualEditForm.Dispose();
                    // 内部メッセージ処理
                    Application.DoEvents();
                }

                // 編集前に最新のデータを取得して更新チェック
                var dao = new CustomerMasterDao();
                var latestData = dao.GetByManagementNumber(selectedItem.ManagementNumber);
                
                if (latestData == null)
                {
                    MessageBox.Show("対象のデータが削除されています。一覧を更新します。", "エラー", 
                        MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    customerDgv.Enabled = true;  // DataGridViewを再度有効化
                    RefreshData();
                    return;
                }
                
                // 元の更新日時を取得(データロード時に保存された値)
                var originalUpdateTime = GetOriginalUpdateTime(selectedItem);
                
                
                // 更新日時が異なる場合(元の値と最新値を比較)
                // DateTime精度問題対策:1秒以内の差は同じとみなす
                bool isUpdatedByOthers = false;
                if (latestData.UpdatedAt.HasValue && originalUpdateTime.HasValue)
                {
                    var timeDiff = Math.Abs((latestData.UpdatedAt.Value - originalUpdateTime.Value).TotalSeconds);
                    isUpdatedByOthers = timeDiff > 1.0;  // 1秒以上の差がある場合のみ更新とみなす
                }
                else if (latestData.UpdatedAt != originalUpdateTime)
                {
                    isUpdatedByOthers = true;
                }
                
                if (isUpdatedByOthers)
                {
                    MessageBox.Show(MessageBoxUtil.DB_011, "エラー", 
                        MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    customerDgv.Enabled = true;  // DataGridViewを再度有効化
                    RefreshData();
                    return;
                }
                
                // フォームを作成(更新ボタン配置の設定にする)
                _individualEditForm = new M_CustomerMasterIndividualEdit(
                    M_CustomerMasterIndividualEdit.ButtonMode.Update, latestData);

                var editForm = _individualEditForm;
                if (editForm != null)
                {
                    editForm.DataCreated += _createdCustomer;
                }

                _individualEditForm.Owner = this.ParentForm;

                _individualEditForm.FormClosed += (s, args) =>
                {
                    customerDgv.Enabled = true;

                    if (this.ParentForm != null && !this.ParentForm.Visible)
                    {
                        this.ParentForm.Show();
                        this.ParentForm.BringToFront();
                    }

                    RefreshData();
                };

                this.ParentForm.Hide();
                _individualEditForm.Show();

            }
            catch(Exception e)
            {
                customerDgv.Enabled = true;

                if (this.ParentForm != null && !this.ParentForm.Visible)
                {
                    this.ParentForm.Show();
                }

                MessageBoxUtil.ShowErr($"個別編集画面の表示中にエラーが発生しました。\n\n{e.Message}");
            }
        }

        private void CreatedCustomer()
        {
            RefreshData();
        }

        private void SetUpColumns()
        {
            _dgv.Columns.Clear();

            _dgv.Columns.Add(new DataGridViewTextBoxColumn
            {

                DataPropertyName = nameof(CustomerMasterDto.ManagementNumber),
                Name = nameof(CustomerMasterDto.ManagementNumber),
                HeaderText = "管理番号",
                ReadOnly = true,
                SortMode = DataGridViewColumnSortMode.Programmatic
    ,
            });
            var mgmtNumberCol = customerDgv.Columns[nameof(CustomerMasterDto.ManagementNumber)];
            mgmtNumberCol.FillWeight = 60;


            _dgv.Columns.Add(new DataGridViewTextBoxColumn
            {

                DataPropertyName = nameof(CustomerMasterDto.CustomerName),
                Name = nameof(CustomerMasterDto.CustomerName),
                HeaderText = "顧客名",
                ReadOnly = true,
                SortMode = DataGridViewColumnSortMode.Programmatic
                ,
            });
            var customerNameCol = customerDgv.Columns[nameof(CustomerMasterDto.CustomerName)];
            customerNameCol.FillWeight = 400;

            _dgv.Columns.Add(new DataGridViewTextBoxColumn
            {

                DataPropertyName = nameof(CustomerMasterDto.RoomId),
                Name = nameof(CustomerMasterDto.RoomId),
                HeaderText = "ルームID(rid)",
                ReadOnly = true,
                SortMode = DataGridViewColumnSortMode.Programmatic
                ,
            });
            var roomIdCol = customerDgv.Columns[nameof(CustomerMasterDto.RoomId)];
            roomIdCol.FillWeight = 60;


            _dgv.Columns.Add(new DataGridViewTextBoxColumn
            {

                DataPropertyName = nameof(CustomerMasterDto.DestinationAccountId),
                Name = nameof(CustomerMasterDto.DestinationAccountId),
                HeaderText = "宛先アカウントID",
                ReadOnly = true,
                SortMode = DataGridViewColumnSortMode.Programmatic
                ,
            });
            var destinationCol = customerDgv.Columns[nameof(CustomerMasterDto.DestinationAccountId)];
            destinationCol.FillWeight = 90;

            _dgv.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = nameof(CustomerMasterDto.SortOrder),
                Name = nameof(CustomerMasterDto.SortOrder),
                HeaderText = "並び順",
                ReadOnly = true,
                SortMode = DataGridViewColumnSortMode.Programmatic
                ,
            });
            var sortOrderCol = customerDgv.Columns[nameof(CustomerMasterDto.SortOrder)];
            sortOrderCol.FillWeight = 38;
            sortOrderCol.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
            sortOrderCol.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
            sortOrderCol.HeaderCell.Style.Padding = new Padding(12, 0, 0, 0);

            _dgv.Columns.Add(new DataGridViewCheckBoxColumn
            {
                DataPropertyName = nameof(CustomerMasterDto.IsUnused),
                Name = nameof(CustomerMasterDto.IsUnused),
                HeaderText = "未使用フラグ",
                ReadOnly = true,
                SortMode = DataGridViewColumnSortMode.Programmatic
    ,
            });
            var isUnusedCol = customerDgv.Columns[nameof(CustomerMasterDto.IsUnused)];
            isUnusedCol.FillWeight = 55;
            isUnusedCol.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
            isUnusedCol.HeaderCell.Style.Padding = new Padding(12, 0, 0, 0);

            _dgv.Columns.Add(new DataGridViewButtonColumn
            {
                // Data列ではないのでNameのみを設定
                Name = "BtnEdit",
                HeaderText = "編集",
                Text = "\u270F\uFE0F",
                UseColumnTextForButtonValue = true,
                ReadOnly = true,
                DefaultCellStyle = new DataGridViewCellStyle
                {
                    Font = new Font("Segoe UI Emoji", 10),
                    Alignment = DataGridViewContentAlignment.MiddleCenter
                    ,
                }
            });
            var editCol = customerDgv.Columns["BtnEdit"];
            editCol.FillWeight = 38;
            editCol.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;

        }

        protected override List<CustomerMasterDto> GetDataFromDao()
        {
            var dao = new CustomerMasterDao();

            // 検索条件から未使用データ表示フラグを取得
            bool includeUnused = false;
            if (_searchCriteria != null && _searchCriteria.ContainsKey("IncludeUnused"))
            {
                includeUnused = (bool)_searchCriteria["IncludeUnused"];
            }

            // DAOから全データを取得(未使用フラグに応じて)
            List<CustomerMasterDto> currentData = dao.GetAll(includeUnused);

            if (_searchCriteria != null && _searchCriteria.Count > 0)
            {
                var query = currentData.AsQueryable();

                if (_searchCriteria.ContainsKey("ManagementNumber"))
                {
                    var mgmtNumber = _searchCriteria["ManagementNumber"].ToString();
                    query = query.Where(x => x.ManagementNumber.ToString().StartsWith(mgmtNumber));
                }
                if (_searchCriteria.ContainsKey("CustomerName"))
                {
                    var customerName = _searchCriteria["CustomerName"].ToString();
                    query = query.Where(x => x.CustomerName.ToString().Contains(customerName));
                }

                return query.ToList();
            }
            return currentData;
        }

        protected override bool HasUpdateAfter(DateTime? lastLoadTime)
        {
            if (lastLoadTime == null) { return false; }

            return new CustomerMasterDao().HasUpdatesAfter(lastLoadTime);
        }
        public override void Search(Dictionary<string, object> criteria)
        {
            base.Search(criteria);
            this.GetDataFromDao();
        }

        protected override void Dgv_ColumnHeaderMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            base.Dgv_ColumnHeaderMouseDoubleClick(sender, e);
        }

        protected override void Dgv_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            base.Dgv_CellClick(sender, e);
        }

        private void customerDgv_BringContextChanged(object sender, EventArgs e)
        {
            ClearSelection();
        }
        private void customerDgv_Leave(object sender, EventArgs e)
        {
            ClearSelection();
        }
        
        /// <summary>
        /// 各顧客DTOの更新日時を保存する
        /// </summary>
        protected override void StoreOriginalUpdateTimes(List<CustomerMasterDto> list)
        {
            _originalUpdateTimes.Clear();
            
            // メモリ効率化: 大量データの場合は警告
            if (list.Count > 10000)
            {
                System.Diagnostics.Debug.WriteLine($"警告: 顧客マスタデータが{list.Count}件あります。メモリ使用量: 約{list.Count * 80 / 1024}KB");
            }
            
            // DataGridViewに実際に表示されているデータのみ保存する最適化も可能
            // 現在は全データを保存(画面スクロール時の一貫性のため)
            foreach (var dto in list)
            {
                _originalUpdateTimes[dto.ManagementNumber] = dto.UpdatedAt;
            }
        }
        
        /// <summary>
        /// 指定された顧客DTOの元の更新日時を取得する
        /// </summary>
        protected override DateTime? GetOriginalUpdateTime(CustomerMasterDto dto)
        {
            if (_originalUpdateTimes.ContainsKey(dto.ManagementNumber))
            {
                return _originalUpdateTimes[dto.ManagementNumber];
            }
            // 見つからない場合は現在の値を返す(後方互換性のため)
            return dto.UpdatedAt;
        }
        
        /// <summary>
        /// 新規アイテムの更新日時のみを追加(既存アイテムの更新日時は変更しない)
        /// </summary>
        protected override void UpdateOriginalUpdateTimesForNewItems(List<CustomerMasterDto> list)
        {
            foreach (var dto in list)
            {
                // 既存のエントリがない場合のみ追加
                if (!_originalUpdateTimes.ContainsKey(dto.ManagementNumber))
                {
                    _originalUpdateTimes[dto.ManagementNumber] = dto.UpdatedAt;
                }
            }
        }

    }
}