using InABox.Core;
using NPOI.HSSF.Record;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace InABox.DynamicGrid;

public class CoreTableColumnSchema(CoreTable table) : IDynamicGridColumnSchema
{
    private DynamicGridColumn[] _columns = table.Columns.Select(DynamicGridColumn.FromCoreColumn).NotNull().ToArray();

    public IEnumerable<string> ColumnNames => _columns.Select(x => x.ColumnName);

    public DynamicGridColumn GetColumn(string column)
    {
        return _columns.First(x => x.ColumnName == column);
    }

    public string? GetComment(string column)
    {
        return null;
    }

    public bool IsVisible(string column)
    {
        return true;
    }
}

public class CoreTableGrid : BaseDynamicGrid
{
    public CoreTable Table { get; set; } = new();

    public IDynamicGridColumnSchema? ColumnSchema { get; set; }

    #region Config

    protected override void Init()
    {
    }

    protected override void DoReconfigure(DynamicGridOptions options)
    {
        // We can add rows by default, but not edit them.
        options.EditRows = false;
    }

    #endregion

    #region Row Manipulation

    protected override void NewRow()
    {
        // This makes use of the fact that MasterData will be equivalent to Table (see 'ReloadData()').

        var row = Table.NewRow(true);
        Table.Rows.Add(row);

        var dataRow = Data.NewRow();
        dataRow.LoadValues(row.Values);
        Data.Rows.Add(dataRow);

        _recordmap[dataRow] = row;

        InvalidateGrid();
        SelectedRows = [dataRow];
        DoChanged();
    }

    protected override bool EditRows(CoreRow[]? rows)
    {
        if(rows is null)
        {
            var row = Table.NewRow(true);
            Table.Rows.Add(row);

            var dataRow = Data.NewRow();
            dataRow.LoadValues(row.Values);
            Data.Rows.Add(dataRow);

            _recordmap[dataRow] = row;

            InvalidateGrid();
            SelectedRows = [dataRow];
            DoChanged();

            return true;
        }
        else
        {
            // It's not obvious how to edit a row, without this being overriden by a subclass. Hence, we have turned off editing,
            // and the case that 'rows' is null is just basically adding a new row.
            return false;
        }
    }

    public override void DeleteRows(params CoreRow[] rows)
    {
        foreach(var row in rows)
        {
            // This relies on MasterData being equivalent to Table.
            if(_recordmap.Remove(row, out var tableRow))
            {
                Table.Rows.Remove(tableRow);
            }
        }
    }

    protected override void MoveRows(CoreRow[] rows, int index, bool isCopy = false)
    {
        CoreRow? targetRow;
        if(index < Data.Rows.Count)
        {
            var targetDataRow = Data.Rows[index];
            targetRow = _recordmap.GetValueOrDefault(targetDataRow);
        }
        else
        {
            var lastDataRow = Data.Rows.LastOrDefault();
            if(lastDataRow is null)
            {
                targetRow = null;
            }
            else if(!_recordmap.TryGetValue(lastDataRow, out var lastTargetRow))
            {
                targetRow = null;
            }
            else if(lastTargetRow == Table.Rows.LastOrDefault())
            {
                targetRow = null;
            }
            else
            {
                targetRow = Table.Rows[lastTargetRow.Index + 1];
            }
        }
 
        if (isCopy)
        {
            rows = rows.ToArray(x =>
            {
                var row = Table.NewRow();
                row.LoadValues(x.Values);
                return row;
            });
        }
        else
        {
            foreach(var row in rows)
            {
                if(_recordmap.TryGetValue(row, out var tableRow))
                {
                    Table.Rows.Remove(tableRow);
                }
            }
        }
        if(targetRow is not null)
        {
            var idx = Table.Rows.IndexOf(targetRow);
            Table.Rows.InsertRange(idx, rows);
        }
        else
        {
            Table.Rows.AddRange(rows);
        }

        Refresh(false, true);
        SelectedRows = rows.Select(row =>
        {
            return _recordmap.FirstOrDefault(x => x.Value == row).Key;
        }).NotNull().ToArray();
    }

    #endregion

    #region Columns

    private IDynamicGridColumnSchema GetColumnSchema()
    {
        return ColumnSchema ?? new CoreTableColumnSchema(Table);
    }

    protected override bool SelectColumns([NotNullWhen(true)] out DynamicGridColumns? columns)
    {
        var editor = new DynamicGridColumnsEditor(GetColumnSchema(), null)
        {
            WindowStartupLocation = WindowStartupLocation.CenterScreen
        };
        editor.DirectEdit = IsDirectEditMode();

        editor.Columns.AddRange(VisibleColumns);

        if (editor.ShowDialog().Equals(true))
        {
            columns = editor.Columns;
            return true;
        }
        else
        {
            columns = null;
            return false;
        }
    }

    private DynamicGridColumns? _columns;

    protected override DynamicGridColumns LoadColumns()
    {
        return _columns ?? base.LoadColumns();
    }

    public override DynamicGridColumns GenerateColumns()
    {
        var schema = GetColumnSchema();
        var columns = new DynamicGridColumns();
        columns.AddRange(schema.ColumnNames.Select(schema.GetColumn));
        return columns;
    }

    protected override void SaveColumns(DynamicGridColumns columns)
    {
        _columns = columns;
    }

    #endregion

    #region Data

    protected override void ReloadData(CancellationToken token, Action<CoreTable?, Exception?> action)
    {
        // I think this is all we need to do. If we were to do paging or something like that, this would fail,
        // since we would be adding the table to itself; but for now this will suffice.
        action(Table, null);
    }

    public override void OnItemSourceChanged(object value)
    {
        if(value is CoreTable table)
        {
            Table = table;
            Refresh(true, true);
        }
    }

    #endregion
}
