## Copyright (C) 2025
##
## This file is part of Octave.
##
## Octave is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {} {@var{tbl} =} struct2table (@var{s})
## @deftypefnx {} {@var{tbl} =} struct2table (@var{s}, @var{Name}, @var{Value})
## Convert structure array to table.
##
## Convert the structure array @var{s} to a table.  Each field of @var{s}
## becomes a variable (column) in the table.  Field names become variable
## names.
##
## @example
## @group
## s = struct ('Name', @{'Alice'; 'Bob'@}, 'Age', [25; 30]);
## tbl = struct2table (s)
## @result{} tbl =
##    Name      Age
##    ----      ---
##    Alice     25
##    Bob       30
## @end group
## @end example
##
## @seealso{table, table2struct, istable}
## @end deftypefn

function tbl = struct2table (s, varargin)
  if (nargin < 1)
    print_usage ();
  endif

  if (! isstruct (s))
    error ("struct2table: input must be a structure");
  endif

  ## Parse optional arguments (REQ-TABLE-004: AsArray parameter)
  as_array = false;
  if (nargin > 1)
    i = 1;
    while (i <= numel (varargin))
      if (ischar (varargin{i}) || isstring (varargin{i}))
        if (strcmpi (varargin{i}, "AsArray"))
          if (i < numel (varargin))
            as_array = varargin{i+1};
            i += 2;
          else
            error ("struct2table: AsArray requires a value");
          endif
        else
          i += 1;
        endif
      else
        i += 1;
      endif
    endwhile
  endif

  ## Get field names from structure
  fnames = fieldnames (s);
  
  if (isempty (fnames))
    error ("struct2table: structure must have at least one field");
  endif

  ## Handle struct array - convert to table with all fields
  ## REQ-TABLE-004: AsArray parameter controls single struct behavior
  if (numel (s) == 1 && ! as_array)
    ## Single struct - each field becomes a column
    tbl = struct ();
    for i = 1:numel (fnames)
      field_val = s.(fnames{i});
      ## REQ-TABLE-006: Convert numeric cell arrays to numeric arrays
      if (iscell (field_val) && ! isempty (field_val))
        ## Check if all elements are numeric and same size
        all_numeric = true;
        first_size = [];
        for j = 1:numel (field_val)
          if (! isnumeric (field_val{j}))
            all_numeric = false;
            break;
          endif
          if (isempty (first_size))
            first_size = size (field_val{j});
          elseif (! isequal (size (field_val{j}), first_size))
            all_numeric = false;
            break;
          endif
        endfor
        if (all_numeric && numel (field_val) > 0)
          ## Convert cell array of numeric values to numeric array
          try
            field_val = cell2mat (field_val);
          catch
            ## If cell2mat fails, keep as cell array
          end_try_catch
        endif
      endif
      ## REQ-TABLE-007: Ensure column vector
      if (isnumeric (field_val) && isrow (field_val))
        field_val = field_val(:);
      endif
      tbl.(fnames{i}) = field_val;
    endfor
  else
    ## Struct array - need to handle properly
    ## For now, take first struct's fields and ensure all have same size
    num_rows = numel (s);
    tbl = struct ();
    
    ## Get all unique field names from all structs in array
    all_field_names = {};
    for j = 1:num_rows
      all_field_names = [all_field_names; fieldnames(s(j))];
    endfor
    all_field_names = unique(all_field_names);
    
    for i = 1:numel (all_field_names)
      field_name = all_field_names{i};
      ## Collect values from all structs in array
      field_vals = cell (num_rows, 1);
      has_numeric = false;
      has_cell = false;
      has_char = false;
      
      for j = 1:num_rows
        if (isfield (s(j), field_name))
          field_vals{j} = s(j).(field_name);
          if (isnumeric (field_vals{j}))
            has_numeric = true;
          elseif (iscell (field_vals{j}))
            has_cell = true;
          elseif (ischar (field_vals{j}) || isstring (field_vals{j}))
            has_char = true;
          endif
        else
          ## Missing field - will fill with appropriate default
          field_vals{j} = [];
        endif
      endfor
      
      ## Determine default value type based on existing values
      if (has_numeric)
        default_val = NaN;
      elseif (has_cell)
        default_val = {};
      elseif (has_char)
        default_val = '';
      else
        default_val = [];
      endif
      
      ## Fill missing fields with appropriate defaults
      for j = 1:num_rows
        if (isempty (field_vals{j}))
          if (has_numeric)
            field_vals{j} = NaN;
          elseif (has_cell)
            field_vals{j} = {};
          elseif (has_char)
            field_vals{j} = '';
          endif
        endif
      endfor
      
      ## REQ-TABLE-006: Convert numeric cell arrays to numeric arrays
      if (iscell (field_vals) && all (cellfun (@(x) isnumeric (x) && isscalar (x), field_vals)))
        ## All elements are numeric scalars - convert to numeric array
        try
          tbl.(field_name) = cell2mat (field_vals);
        catch
          ## If cell2mat fails, try vertcat
          try
            tbl.(field_name) = vertcat (field_vals{:});
          catch
            ## If both fail, keep as cell array
            tbl.(field_name) = field_vals;
          end_try_catch
        end_try_catch
      else
        ## Try to concatenate if all are same type/size
        try
          tbl.(field_name) = vertcat (field_vals{:});
        catch
          ## If concatenation fails, keep as cell array
          tbl.(field_name) = field_vals;
        end_try_catch
      endif
      ## REQ-TABLE-007: Ensure column vector for numeric arrays
      if (isnumeric (tbl.(field_name)) && isrow (tbl.(field_name)))
        tbl.(field_name) = tbl.(field_name)(:);
      endif
    endfor
    
    ## Update fnames to include all fields
    fnames = all_field_names;
  endif

  ## Add Properties structure with VariableNames
  tbl.Properties = struct ();
  tbl.Properties.VariableNames = fnames;
  tbl.Properties.RowNames = {};
  tbl.Properties.Description = "";
  tbl.Properties.VariableDescriptions = cell (1, numel (fnames));
  tbl.Properties.VariableUnits = cell (1, numel (fnames));
endfunction


