/*
 * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { type Region, RegionCardinality, Regions } from "../../regions";
import type { FocusedRegion } from "../cellTypes";
import { Direction } from "../direction";

import * as DirectionUtils from "./directionUtils";
import * as FocusedCellUtils from "./focusedCellUtils";

/**
 * Resizes the provided region by 1 row/column in the specified direction,
 * returning a new region instance. The region may either expand *or* contract
 * depending on the presence and location of the focused region.
 *
 * If no focused region is provided, the region will always be *expanded* in the
 * specified direction.
 *
 * If a focused region *is* provided, the behavior will change depending on where
 * the focused region is within the region:
 *
 *   1. If along a top/bottom boundary while resizing UP/DOWN, the resize will
 *      expand from or shrink to the focused region (same if along a left/right
 *      boundary while moving LEFT/RIGHT).
 *   2. If *not* along a top/bottom boundary while resizing UP/DOWN (or if *not*
 *      along a left/right boundary while moving LEFT/RIGHT), the region will
 *      simply expand in the specified direction.
 *
 * Other notes:
 * - A CELLS region can be resized vertically or horizontally.
 * - A FULL_ROWS region can be resized only vertically.
 * - A FULL_COLUMNS region can be resized only horizontally.
 * - A FULL_TABLE region cannot be resized.
 *
 * This function does not clamp the indices of the returned region; that is the
 * responsibility of the caller.
 */
export function resizeRegion(region: Region, direction: Direction, focusedRegion?: FocusedRegion) {
    if (Regions.getRegionCardinality(region) === RegionCardinality.FULL_TABLE) {
        // return the same instance to maintain referential integrity and
        // possibly avoid unnecessary update lifecycles.
        return region;
    }

    const nextRegion: Region = Regions.copy(region);

    let affectedRowIndex: number = 0;
    let affectedColumnIndex: number = 0;

    if (focusedRegion != null) {
        const isAtTop = FocusedCellUtils.isFocusAtRegionTop(nextRegion, focusedRegion);
        const isAtBottom = FocusedCellUtils.isFocusAtRegionBottom(nextRegion, focusedRegion);
        const isAtLeft = FocusedCellUtils.isFocusAtRegionLeft(nextRegion, focusedRegion);
        const isAtRight = FocusedCellUtils.isFocusAtRegionRight(nextRegion, focusedRegion);

        // the focused region is found along the top and bottom boundary
        // simultaneously when the region is 1 row tall. check for this and
        // similar special cases.
        if (direction === Direction.UP) {
            affectedRowIndex = isAtTop && !isAtBottom ? 1 : 0;
        } else if (direction === Direction.DOWN) {
            affectedRowIndex = isAtBottom && !isAtTop ? 0 : 1;
        } else if (direction === Direction.LEFT) {
            affectedColumnIndex = isAtLeft && !isAtRight ? 1 : 0;
        } else {
            // i.e. `Direction.RIGHT:`
            affectedColumnIndex = isAtRight && !isAtLeft ? 0 : 1;
        }
    } else {
        // when there is no focused region, expand in the specified direction.
        affectedRowIndex = direction === Direction.DOWN ? 1 : 0;
        affectedColumnIndex = direction === Direction.RIGHT ? 1 : 0;
    }

    const delta = DirectionUtils.directionToDelta(direction);

    if (nextRegion.rows != null) {
        nextRegion.rows[affectedRowIndex] += delta.rows;
    }
    if (nextRegion.cols != null) {
        nextRegion.cols[affectedColumnIndex] += delta.cols;
    }

    // the new coordinates might be out of bounds. the caller is responsible for
    // sanitizing the result.
    return nextRegion;
}
