import { Column, ColumnDef } from '@tanstack/react-table';

// Import all API middleware table data types
import { OrderTableData, OrderPatch } from '../api/Orders';
import { PartTableData, PartPatch } from '../api/Parts';
import { RequestTableData, RequestPatch } from '../api/Requests';
import { SupplierTableData, SupplierPatch } from '../api/Suppliers';
import { TeamTableData, TeamPatch } from '../api/Teams';
import { UserTableData, UserPatch } from '../api/Users';
export type TableData =
  | OrderTableData
  | PartTableData
  | RequestTableData
  | SupplierTableData
  | TeamTableData
  | UserTableData;
export type TablePatch =
  | OrderPatch
  | PartPatch
  | RequestPatch
  | SupplierPatch
  | TeamPatch
  | UserPatch;

// Custom meta type for ColumnDefs
// Facilitates updating of table cell values
export type APIColumnMeta<T, P extends TablePatch> = {
  // Function like updateOrder, updateRequest, etc.
  update?: (id: number, patch: P) => Promise<T>;
  // Single-property transformation function (defined in defaultColumnDefs)
  transform?: (input: any) => string;
  // The data type of the cell value (as expected by the API)
  type?: string;
  // HTML element used to update cell value
  element?: 'input' | 'select' | 'date';
  // Dict of mappings for transformed properties (i.e., false => 'Unfulfilled', true => 'Fulfilled')
  // OR simply an array of strings representing valid values
  values?: { [key: number | string]: string } | string[];
  // Restricted values for students and TAs (used only for Requests)
  restrictedValues?: string[];
  allowedUserTypes?: string[];
};
// Extend ColumnDef to use the custom meta type
export type APIColumnDef<T, P extends TablePatch> = ColumnDef<T, unknown> & {
  meta?: APIColumnMeta<T, P>;
};

// Create a generic from the allowed API middleware types
// This allows for type validation of the column and row data structures
export interface TableProps<
  T extends TableData,
  P extends TablePatch,
  R = any,
> {
  columns: APIColumnDef<T, P>[];
  data: T[];
  // Optional useState setter function, controls whether table cells can be edited
  setData?: (newData: T[]) => void;
  padding?: boolean;
  initialIdSort?: 'ascending' | 'descending';
  expandable?: boolean;
  // Extra data associated with each row (such as Requests for Orders)
  collapseData?: R[];
  // Key to match rows (e.g., 'order_id' for Orders and Requests)
  collapseDataKey?: string;
  // Child elements come from function
  children?: (rowData: R[] | undefined) => React.ReactNode;
}

// Function that plugs into Linkify to render detected links
// @ts-expect-error
export const renderExternalLink = ({ attributes, content }) => {
  return (
    <a {...attributes} target={'_blank'} rel='noreferrer'>
      {content}
    </a>
  );
};

// Function that handles updating a table cell's value
// IN: columnMeta : the meta information for the cell's associated ColumnDef
//     rowData : the reference to the row content the cell belongs to
//     column : the reference to the column content the cell is associated with
//     newValue : the cell's value after being changed
export const handleCellUpdate = async <T, P extends TablePatch>(
  columnMeta: APIColumnMeta<T, P>,
  rowData: T,
  column: Column<T, unknown>,
  newValue: any
) => {
  if (!columnMeta.update) {
    return;
  }

  // If meta information has values dict defined, find the key corresponding to newValue
  // This is primarily for orders.is_fulfilled
  if (columnMeta.values && !Array.isArray(columnMeta.values)) {
    for (const key in columnMeta.values) {
      if (columnMeta.values[key] === newValue) {
        newValue = key;
      }
    }
  }

  // Transform data type if necessary
  if (columnMeta.type === 'boolean') {
    newValue = newValue === 'true';
  } else if (columnMeta.type === 'number') {
    // Replace special characters
    newValue = newValue.replace(/[^a-zA-Z0-9.]/g, '');
    newValue = Number(newValue);
  }

  // Extract primary key and create patchObject
  const id = rowData[Object.keys(rowData as object)[0] as keyof T] as number;
  const patch: P = { [column.id]: newValue } as P;

  return await columnMeta.update(id, patch);
};

// Function that returns a string matching the type of the input parameter
// IN: rowData : the reference to the row content being checked
// OUT: string representing type of data like 'Request' or 'Order'
export const getTypeOfT = <T extends TableData>(rowData: T): string => {
  const firstKey = Object.keys(rowData)[0];
  let type: string;

  switch (firstKey) {
    case 'order_id': {
      type = 'Request';
      break;
    }
    case 'part_id': {
      type = 'Part';
      break;
    }
    case 'request_id': {
      type = 'Request';
      break;
    }
    case 'supplier_id': {
      type = 'Supplier';
      break;
    }
    case 'team_id': {
      type = 'Team';
      break;
    }
    case 'user_id': {
      type = 'User';
      break;
    }
    default: {
      type = 'Unknown';
      break;
    }
  }

  return type;
};
