## Copyright (C) 2025
##
## This file is part of Octave.
##
## Helper function to handle table indexing and dot notation.
## This is called internally when tables are indexed.

function varargout = subsref (tbl, s)
  if (nargin != 2)
    print_usage ();
  endif

  ## Ensure Properties exist
  tbl = __ensure_properties__ (tbl);

  ## Handle different indexing types
  if (isempty (s))
    varargout{1} = tbl;
    return;
  endif

  ## Get first subscript
  first_sub = s(1);

  switch (first_sub.type)
    case "."
      ## Dot notation: tbl.ColumnName or tbl.Properties
      field_name = first_sub.subs;
      
      if (strcmp (field_name, "Properties"))
        ## Return Properties structure
        varargout{1} = tbl.Properties;
      else
        ## Return column data
        if (isfield (tbl, field_name))
          col_data = tbl.(field_name);
          ## REQ-TABLE-001 & REQ-TABLE-007: Ensure numeric columns return as column vectors
          if (isnumeric (col_data) && isrow (col_data))
            col_data = col_data(:);
          endif
          varargout{1} = col_data;
        else
          error ("table: no variable named '%s'", field_name);
        endif
      endif

      ## Handle chained subscripts (e.g., tbl.ColumnName(1))
      if (numel (s) > 1)
        result = varargout{1};
        for i = 2:numel (s)
          result = subsref (result, s(i));
        endfor
        varargout{1} = result;
      endif

    case "()"
      ## Parentheses indexing: tbl(1) or tbl(1:3) or tbl(1, :)
      indices = first_sub.subs;
      
      if (numel (indices) == 1)
        ## Single index - return row(s) as table
        row_idx = indices{1};
        varargout{1} = __get_rows__ (tbl, row_idx);
      elseif (numel (indices) == 2)
        ## Two indices: tbl(row_idx, col_idx) or tbl(row_idx, :)
        row_idx = indices{1};
        col_idx = indices{2};
        
        if (ischar (col_idx) && strcmp (col_idx, ":"))
          ## tbl(row_idx, :) - return specified rows
          varargout{1} = __get_rows__ (tbl, row_idx);
        else
          ## tbl(row_idx, col_idx) - return subset of rows and columns
          varargout{1} = __get_rows_cols__ (tbl, row_idx, col_idx);
        endif
      else
        ## Multiple indices - not fully supported yet
        error ("table: multi-dimensional indexing not yet supported");
      endif

    case "{}"
      ## Cell indexing - not typically used for tables
      error ("table: cell indexing not supported for tables");

    otherwise
      error ("table: unsupported indexing type '%s'", first_sub.type);
  endswitch
endfunction

function tbl_out = __get_rows__ (tbl, row_idx)
  ## Get specific rows from table
  ## Handles logical indexing, numeric indexing, and edge cases
  var_names = tbl.Properties.VariableNames;
  tbl_out = struct ();

  ## Handle logical indexing
  if (islogical (row_idx))
    row_idx = find (row_idx);
  endif

  ## Handle out-of-bounds gracefully (MATLAB behavior)
  num_rows = height (tbl);
  if (isnumeric (row_idx) || islogical (row_idx))
    ## Clip indices to valid range
    row_idx = row_idx(row_idx > 0 & row_idx <= num_rows);
    if (isempty (row_idx))
      ## Return empty table with same structure
      for i = 1:numel (var_names)
        var_name = var_names{i};
        var_data = tbl.(var_name);
        if (isnumeric (var_data))
          tbl_out.(var_name) = zeros (0, size (var_data, 2));
        elseif (iscell (var_data))
          tbl_out.(var_name) = cell (0, size (var_data, 2));
        else
          tbl_out.(var_name) = [];
        endif
      endfor
      tbl_out.Properties = tbl.Properties;
      tbl_out.Properties.RowNames = {};
      return;
    endif
  endif

  for i = 1:numel (var_names)
    var_name = var_names{i};
    var_data = tbl.(var_name);
    ## Handle indexing safely
    try
      if (isvector (var_data))
        tbl_out.(var_name) = var_data(row_idx);
      else
        ## For matrices, index rows
        tbl_out.(var_name) = var_data(row_idx, :);
      endif
    catch
      ## If indexing fails, try to handle gracefully
      if (numel (row_idx) == 1 && row_idx > 0 && row_idx <= num_rows)
        ## Single valid index
        if (isvector (var_data))
          tbl_out.(var_name) = var_data(row_idx);
        else
          tbl_out.(var_name) = var_data(row_idx, :);
        endif
      else
        error ("table: cannot index variable '%s' with indices", var_name);
      endif
    end_try_catch
  endfor

  ## Copy Properties
  tbl_out.Properties = tbl.Properties;
  if (! isempty (tbl.Properties.RowNames))
    try
      tbl_out.Properties.RowNames = tbl.Properties.RowNames(row_idx);
    catch
      tbl_out.Properties.RowNames = {};
    end_try_catch
  endif
endfunction

function tbl_out = __get_rows_cols__ (tbl, row_idx, col_idx)
  ## Get specific rows and columns from table
  var_names = tbl.Properties.VariableNames;
  tbl_out = struct ();
  
  ## Handle column selection
  if (isnumeric (col_idx) || islogical (col_idx))
    ## Numeric or logical column indices
    if (islogical (col_idx))
      col_idx = find (col_idx);
    endif
    selected_vars = var_names(col_idx);
  elseif (iscellstr (col_idx) || isstring (col_idx))
    ## Column names
    selected_vars = cellstr (col_idx);
  elseif (ischar (col_idx) && strcmp (col_idx, ":"))
    ## All columns
    selected_vars = var_names;
  else
    error ("table: invalid column index");
  endif
  
  ## Get rows
  if (islogical (row_idx))
    row_idx = find (row_idx);
  endif
  
  ## Extract selected columns and rows
  for i = 1:numel (selected_vars)
    var_name = selected_vars{i};
    if (isfield (tbl, var_name))
      var_data = tbl.(var_name);
      if (isvector (var_data))
        tbl_out.(var_name) = var_data(row_idx);
      else
        tbl_out.(var_name) = var_data(row_idx, :);
      endif
    else
      error ("table: no variable named '%s'", var_name);
    endif
  endfor
  
  ## Copy Properties with selected variable names
  tbl_out.Properties = tbl.Properties;
  tbl_out.Properties.VariableNames = selected_vars;
  if (! isempty (tbl.Properties.RowNames))
    try
      tbl_out.Properties.RowNames = tbl.Properties.RowNames(row_idx);
    catch
      tbl_out.Properties.RowNames = {};
    end_try_catch
  endif
endfunction


