/*
 * Copyright 2025 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 { Error } from "@blueprintjs/icons";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Tooltip } from "../base-components/tooltip/Tooltip.js";
import { DatePickerCellField } from "./components/DatePickerCellField.js";
import { DropdownCellField } from "./components/DropdownCellField.js";
import { TextInputCellField } from "./components/TextInputCellField.js";
import { useObjectTableLabels } from "./ObjectTableLabels.js";
import { cellValuesEqual } from "./utils/editableUtils.js";
import styles from "./EditableCell.module.css";
const NUMBER_TYPES = ["double", "integer", "long", "float", "decimal", "byte", "short"];
const DATE_TYPES = ["datetime", "timestamp"];
function valueToString(value) {
  if (value == null) {
    return "";
  }
  if (typeof value === "object") {
    return JSON.stringify(value);
  }
  // At this point, value is a primitive (string, number, boolean, symbol, bigint)
  return String(value);
}
function parseValueByType(value, dataType) {
  if (!dataType || !NUMBER_TYPES.includes(dataType)) {
    return value;
  }
  if (value === "") {
    return null;
  }
  const parsedNumber = Number(value);
  if (isNaN(parsedNumber)) {
    return value;
  }
  return parsedNumber;
}
function EditableCellInner({
  initialValue,
  currentValue,
  cellId,
  dataType,
  onCellEdit,
  onCellValidationError,
  clearCellValidationError,
  originalRowData,
  rowId,
  columnId,
  validateEdit,
  validationError,
  editFieldConfig,
  rowCellEdits,
  isRowFocused = false
}) {
  const labels = useObjectTableLabels();
  const [inputValue, setInputValue] = useState(valueToString(currentValue));
  const isCancelled = useRef(false);
  const validationAbortControllerRef = useRef(null);
  const abortValidation = useCallback(() => {
    if (validationAbortControllerRef.current) {
      validationAbortControllerRef.current.abort();
      validationAbortControllerRef.current = null;
    }
  }, []);
  const hasValidationError = validationError != null;
  const isEdited = !cellValuesEqual(currentValue, initialValue);
  useEffect(() => {
    setInputValue(valueToString(currentValue));
  }, [currentValue]);
  useEffect(() => {
    return () => {
      abortValidation();
    };
  }, [abortValidation]);
  const runValidation = useCallback(parsedValue => {
    if (!validateEdit) {
      return;
    }
    const controller = new AbortController();
    validationAbortControllerRef.current = controller;
    const validationPromise = validateEdit(parsedValue);

    // Race between validation and abort
    Promise.race([validationPromise, new Promise((_, reject) => {
      controller.signal.addEventListener("abort", () => {
        reject(new DOMException("Aborted", "AbortError"));
      });
    })]).then(errorMessage => {
      if (controller.signal.aborted) {
        return;
      }
      if (errorMessage) {
        onCellValidationError?.(cellId, errorMessage);
      } else {
        clearCellValidationError?.(cellId);
      }
    }, error => {
      if (!controller.signal.aborted && error.name !== "AbortError") {
        onCellValidationError?.(cellId, labels.cellValidationError);
      }
    });
  }, [validateEdit, onCellValidationError, clearCellValidationError, cellId, labels]);
  const commitEdit = useCallback(newValue => {
    abortValidation();
    onCellEdit(cellId, {
      rowId,
      columnId,
      newValue,
      oldValue: initialValue,
      originalRowData
    });
    runValidation(newValue);
  }, [abortValidation, onCellEdit, cellId, rowId, columnId, initialValue, originalRowData, runValidation]);

  // Text/number input: commit on blur
  const handleBlur = useCallback(() => {
    if (isCancelled.current) {
      isCancelled.current = false;
      return;
    }

    // No-op when the input wasn't actually changed by the user
    if (inputValue === valueToString(currentValue)) {
      return;
    }
    const parsedValue = parseValueByType(inputValue, dataType);
    commitEdit(parsedValue);
  }, [inputValue, currentValue, dataType, commitEdit]);
  const handleInputChange = useCallback(value => {
    // Cancel any in-flight validation
    abortValidation();
    setInputValue(value);
  }, [abortValidation]);
  const handleKeyDown = useCallback(e => {
    if (e.key === "Enter") {
      e.currentTarget.blur();
    }
    if (e.key === "Escape") {
      isCancelled.current = true;
      setInputValue(valueToString(currentValue));
      e.currentTarget.blur();
    }
  }, [currentValue]);
  const handleCommit = useCallback(newValue => {
    // No-op if the value hasn't actually moved from what the cell already
    // displays. null and undefined are treated as the same empty state so
    // a dropdown that clears a never-set cell doesn't fire; "" stays
    // distinct since an empty string can be meaningful data.
    if (cellValuesEqual(newValue, currentValue)) return;
    commitEdit(newValue);
  }, [commitEdit, currentValue]);
  const inputType = dataType && NUMBER_TYPES.includes(dataType) ? "number" : "text";

  // Compute field-component props once per (editFieldConfig, originalRowData, rowCellEdits).
  // The narrowed return type is preserved in each useMemo
  const dropdownFieldProps = useMemo(() => editFieldConfig?.fieldComponent === "DROPDOWN" ? editFieldConfig.getFieldComponentProps(originalRowData, rowCellEdits) : undefined, [editFieldConfig, originalRowData, rowCellEdits]);
  const datePickerFieldProps = useMemo(() => editFieldConfig?.fieldComponent === "DATE_PICKER" ? editFieldConfig.getFieldComponentProps(originalRowData, rowCellEdits) : undefined, [editFieldConfig, originalRowData, rowCellEdits]);
  return /*#__PURE__*/React.createElement(Tooltip.Provider, null, /*#__PURE__*/React.createElement(Tooltip.Root, {
    disabled: !hasValidationError
  }, /*#__PURE__*/React.createElement(Tooltip.Trigger, {
    className: styles.osdkEditableCellTrigger,
    render: /*#__PURE__*/React.createElement("span", {
      className: styles.osdkTooltipTriggerWrapper
    })
  }, (() => {
    switch (editFieldConfig?.fieldComponent) {
      case "DROPDOWN":
        return /*#__PURE__*/React.createElement(DropdownCellField, {
          fieldComponentProps: dropdownFieldProps,
          isRowFocused: isRowFocused,
          inputValue: inputValue,
          value: currentValue,
          hasValidationError: hasValidationError,
          isEdited: isEdited,
          onChange: handleCommit
        });
      case "DATE_PICKER":
        return /*#__PURE__*/React.createElement(DatePickerCellField, {
          fieldComponentProps: datePickerFieldProps,
          inputValue: inputValue,
          hasValidationError: hasValidationError,
          isEdited: isEdited,
          onChange: handleCommit
        });
      default:
        if (dataType != null && DATE_TYPES.includes(dataType)) {
          return /*#__PURE__*/React.createElement(DatePickerCellField, {
            inputValue: inputValue,
            hasValidationError: hasValidationError,
            isEdited: isEdited,
            onChange: handleCommit,
            dataType: dataType
          });
        }
        return /*#__PURE__*/React.createElement(TextInputCellField, {
          inputType: inputType,
          inputValue: inputValue,
          hasValidationError: hasValidationError,
          isEdited: isEdited,
          onValueChange: handleInputChange,
          onBlur: handleBlur,
          onKeyDown: handleKeyDown
        });
    }
  })()), /*#__PURE__*/React.createElement(Tooltip.Portal, null, /*#__PURE__*/React.createElement(Tooltip.Positioner, {
    sideOffset: 4,
    side: "bottom"
  }, /*#__PURE__*/React.createElement(Tooltip.Popup, null, /*#__PURE__*/React.createElement("div", {
    className: styles.validationError
  }, /*#__PURE__*/React.createElement(Error, {
    className: styles.errorIcon
  }), validationError), /*#__PURE__*/React.createElement(Tooltip.Arrow, null))))));
}
export const EditableCell = /*#__PURE__*/React.memo(EditableCellInner);
//# sourceMappingURL=EditableCell.js.map