export type TableViewerShape = {
  n_fields: number;
  n_rows_main: number;
  n_rows_inserter: number;
};

/**
 * Describes the location of a table cell in the table UI.
 */
export type Coords = {
  region: "main" | "inserter";
  row_idx: number;
  field_idx: number;
};

/**
 * Compare two table coordinates for equality.
 */
export function coords_eq(a: Coords, b: Coords): boolean {
  return a.region === b.region && a.row_idx === b.row_idx &&
    a.field_idx === b.field_idx;
}

/**
 * Returns the cell coordinates offset relative to a starting position. Clamps
 * the result to available positions. Assumes that each table region has at
 * least 1 row.
 */
export function offset_coords(
  start: Coords,
  row_offset: number,
  field_offset: number,
  boundaries: TableViewerShape,
): Coords {
  const naive_field_idx = start.field_idx + field_offset;
  const bounded_field_idx = Math.min(
    boundaries.n_fields - 1,
    Math.max(0, naive_field_idx),
  );
  const naive_row_idx = start.row_idx + row_offset;
  if (start.region === "inserter" && naive_row_idx < 0) {
    return {
      region: "main",
      row_idx: Math.max(0, boundaries.n_rows_main + naive_row_idx),
      field_idx: bounded_field_idx,
    };
  }
  if (start.region === "main" && naive_row_idx >= boundaries.n_rows_main) {
    return {
      region: "inserter",
      row_idx: Math.min(
        boundaries.n_rows_inserter - 1,
        naive_row_idx - boundaries.n_rows_main,
      ),
      field_idx: bounded_field_idx,
    };
  }
  const n_rows = start.region === "main"
    ? boundaries.n_rows_main
    : boundaries.n_rows_inserter;
  return {
    region: start.region,
    row_idx: Math.min(n_rows, Math.max(0, naive_row_idx)),
    field_idx: bounded_field_idx,
  };
}

/**
 * Returns the list of coordinates comprising the rectangular box defined by the
 * coordinates of any pair of opposite corners. No guarantees are made
 * concerning the ordering of the returned array.
 */
export function get_box(
  corner_a: Coords,
  corner_b: Coords,
  boundaries: TableViewerShape,
): Coords[] {
  const box: Coords[] = [];
  const left_idx = Math.min(corner_a.field_idx, corner_b.field_idx);
  const right_idx = Math.max(corner_a.field_idx, corner_b.field_idx);
  if (corner_a.region === corner_b.region) {
    for (
      let row_idx = Math.min(corner_a.row_idx, corner_b.row_idx);
      row_idx <= Math.max(corner_a.row_idx, corner_b.row_idx);
      row_idx += 1
    ) {
      for (let field_idx = left_idx; field_idx <= right_idx; field_idx += 1) {
        box.push({
          region: corner_b.region,
          row_idx,
          field_idx,
        });
      }
    }
  } else {
    const corner_main = corner_b.region === "main" ? corner_b : corner_a;
    const corner_inserter = corner_b.region === "inserter"
      ? corner_b
      : corner_a;
    for (
      let row_idx = corner_main.row_idx;
      row_idx < boundaries.n_rows_main;
      row_idx += 1
    ) {
      for (
        let field_idx = left_idx;
        field_idx <= right_idx;
        field_idx += 1
      ) {
        box.push({
          region: "main",
          row_idx,
          field_idx,
        });
      }
    }
    for (
      let row_idx = 0;
      row_idx <= corner_inserter.row_idx;
      row_idx += 1
    ) {
      for (
        let field_idx = left_idx;
        field_idx <= right_idx;
        field_idx += 1
      ) {
        box.push({
          region: "inserter",
          row_idx,
          field_idx,
        });
      }
    }
  }
  return box;
}
