<script lang="ts">
  import _ from 'lodash';
  import { tick } from 'svelte';
  import CellValue from '../datagrid/CellValue.svelte';
  import { isJsonLikeLongString, safeJsonParse, parseCellValue, stringifyCellValue } from 'dbgate-tools';
  import keycodes from '../utility/keycodes';
  import createRef from '../utility/createRef';
  import { showModal } from '../modals/modalTools';
  import EditCellDataModal from '../modals/EditCellDataModal.svelte';
  import ShowFormButton from '../formview/ShowFormButton.svelte';
  import { openJsonDocument } from '../tabs/JsonTab.svelte';
  import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
  import SearchInput from '../elements/SearchInput.svelte';
  import CloseSearchButton from '../buttons/CloseSearchButton.svelte';

  export let selection;

  $: firstSelection = selection?.[0];
  $: rowData = firstSelection?.rowData;
  $: editable = firstSelection?.editable;
  $: editorTypes = firstSelection?.editorTypes;
  $: columns = selection?.columns || [];
  $: realColumnUniqueNames = selection?.realColumnUniqueNames || [];
  $: setCellValue = selection?.setCellValue;

  $: uniqueRows = _.uniqBy(selection || [], 'row');
  $: isMultipleRows = uniqueRows.length > 1;

  function areValuesEqual(val1, val2) {
    if (val1 === val2) return true;
    if (val1 == null && val2 == null) return true;
    if (val1 == null || val2 == null) return false;
    return _.isEqual(val1, val2);
  }

  function getFieldValue(colName) {
    if (!isMultipleRows) return { value: rowData?.[colName], hasMultipleValues: false };

    const values = uniqueRows.map(sel => sel.rowData?.[colName]);
    const firstValue = values[0];
    const allSame = values.every(v => areValuesEqual(v, firstValue));

    return allSame ? { value: firstValue, hasMultipleValues: false } : { value: null, hasMultipleValues: true };
  }

  let filter = '';

  $: orderedFields = realColumnUniqueNames
    .map(colName => {
      const col = columns.find(c => c.uniqueName === colName);
      if (!col) return null;
      const { value, hasMultipleValues } = getFieldValue(colName);
      return {
        columnName: col.columnName || colName,
        uniqueName: colName,
        value,
        hasMultipleValues,
        col,
      };
    })
    .filter(Boolean);

  $: filteredFields = orderedFields.filter(field => {
    if (!filter) return true;
    try {
      const regex = new RegExp(filter, 'i');
      return regex.test(field.columnName);
    } catch (e) {
      return field.columnName.toLowerCase().includes(filter.toLowerCase());
    }
  });

  let editingColumn = null;
  let editValue = '';
  let domEditor = null;
  const isChangedRef = createRef(false);

  function isJsonValue(value) {
    if (_.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid && !value.$bigint) {
      return true;
    }
    if (_.isArray(value)) return true;
    if (typeof value !== 'string') return false;
    if (!isJsonLikeLongString(value)) return false;
    const parsed = safeJsonParse(value);
    return parsed !== null && (_.isPlainObject(parsed) || _.isArray(parsed));
  }

  function getJsonObject(value) {
    if (_.isPlainObject(value) || _.isArray(value)) return value;
    if (typeof value === 'string') return safeJsonParse(value);
    return null;
  }

  function handleDoubleClick(field) {
    if (!editable || !setCellValue) return;
    if (isJsonValue(field.value) && !field.hasMultipleValues) {
      openEditModal(field);
      return;
    }
    startEditing(field);
  }

  function startEditing(field) {
    if (!editable || !setCellValue) return;
    editingColumn = field.uniqueName;
    editValue = field.hasMultipleValues ? '' : stringifyCellValue(field.value, 'inlineEditorIntent', editorTypes).value;
    isChangedRef.set(false);
    tick().then(() => {
      if (!domEditor) return;
      domEditor.focus();
      if (!field.hasMultipleValues) domEditor.select();
    });
  }

  function handleKeyDown(event, field) {
    switch (event.keyCode) {
      case keycodes.escape:
        isChangedRef.set(false);
        editingColumn = null;
        break;
      case keycodes.enter:
        if (isChangedRef.get()) {
          saveValue(field);
        }
        editingColumn = null;
        event.preventDefault();
        break;
      case keycodes.tab:
        if (isChangedRef.get()) {
          saveValue(field);
        }
        editingColumn = null;
        event.preventDefault();
        moveToNextField(field, event.shiftKey);
        break;
    }
  }

  function moveToNextField(field, reverse) {
    const currentIndex = filteredFields.findIndex(f => f.uniqueName === field.uniqueName);
    const nextIndex = reverse ? currentIndex - 1 : currentIndex + 1;
    if (nextIndex < 0 || nextIndex >= filteredFields.length) return;
    
    tick().then(() => {
      const nextField = filteredFields[nextIndex];
      if (isJsonValue(nextField.value)) {
        openEditModal(nextField);
      } else {
        startEditing(nextField);
      }
    });
  }

  function handleSearchKeyDown(e) {
    if (e.keyCode === keycodes.backspace && (e.metaKey || e.ctrlKey)) {
      filter = '';
      e.stopPropagation();
      e.preventDefault();
    }
  }

  function handleBlur(field) {
    if (isChangedRef.get()) {
      saveValue(field);
    }
    editingColumn = null;
  }

  function saveValue(field) {
    if (!setCellValue) return;
    const parsedValue = parseCellValue(editValue, editorTypes);
    setCellValue(field.uniqueName, parsedValue);
    isChangedRef.set(false);
  }

  function openEditModal(field) {
    if (!setCellValue) return;
    showModal(EditCellDataModal, {
      value: field.value,
      dataEditorTypesBehaviour: editorTypes,
      onSave: value => setCellValue(field.uniqueName, value),
    });
  }

  function openJsonInNewTab(field) {
    const jsonObj = getJsonObject(field.value);
    if (jsonObj) openJsonDocument(jsonObj, undefined, true);
  }

  function getJsonParsedValue(value) {
    if (editorTypes?.explicitDataType) return null;
    if (!isJsonLikeLongString(value)) return null;
    return safeJsonParse(value);
  }
</script>

<div class="outer">
  {#if rowData}
    <div class="search-wrapper" on:keydown={handleSearchKeyDown}>
      <SearchBoxWrapper noMargin>
        <SearchInput placeholder="Filter columns (regex)" bind:value={filter} />
        <CloseSearchButton bind:filter />
      </SearchBoxWrapper>
    </div>
  {/if}
  <div class="inner">
    {#if !rowData}
      <div class="no-data">No data selected</div>
    {:else}
      {#each filteredFields as field (field.uniqueName)}
        <div class="field">
          <div class="field-name">{field.columnName}</div>
          <div 
            class="field-value"
            class:editable
            on:dblclick={() => handleDoubleClick(field)}
          >
            {#if editingColumn === field.uniqueName}
              <div class="editor-wrapper">
                <input
                  type="text"
                  bind:this={domEditor}
                  bind:value={editValue}
                  on:input={() => isChangedRef.set(true)}
                  on:keydown={e => handleKeyDown(e, field)}
                  on:blur={() => handleBlur(field)}
                  class="inline-editor"
                />
                {#if editable && !field.hasMultipleValues}
                  <ShowFormButton
                    icon="icon edit"
                    on:click={() => {
                      editingColumn = null;
                      openEditModal(field);
                    }}
                  />
                {/if}
              </div>
            {:else if field.hasMultipleValues}
              <span class="multiple-values">(Multiple values)</span>
            {:else}
              <CellValue 
                {rowData} 
                value={field.value} 
                jsonParsedValue={getJsonParsedValue(field.value)}
                {editorTypes}
              />
              {#if isJsonValue(field.value)}
                <ShowFormButton 
                  icon="icon open-in-new" 
                  on:click={() => openJsonInNewTab(field)} 
                />
              {/if}
            {/if}
          </div>
        </div>
      {/each}
    {/if}
  </div>
</div>

<style>
  .outer {
    flex: 1;
    position: relative;
    display: flex;
    flex-direction: column;
  }

  .search-wrapper {
    padding: 4px 4px 0 4px;
  }

  .inner {
    overflow: auto;
    flex: 1;
    padding: 4px;
  }

  .no-data {
    color: var(--theme-font-3);
    font-style: italic;
    padding: 8px;
  }

  .field {
    margin-bottom: 8px;
    border: 1px solid var(--theme-border);
    border-radius: 3px;
    overflow: hidden;
  }

  .field-name {
    background: var(--theme-bg-1);
    padding: 4px 8px;
    font-weight: 500;
    font-size: 11px;
    color: var(--theme-font-2);
    border-bottom: 1px solid var(--theme-border);
  }

  .field-value {
    padding: 6px 8px;
    background: var(--theme-bg-0);
    min-height: 20px;
    word-break: break-all;
    position: relative;
  }

  .field-value.editable {
    cursor: text;
  }

  .field-value.editable:hover {
    background: var(--theme-bg-hover);
  }

  .editor-wrapper {
    display: flex;
    align-items: center;
  }

  .inline-editor {
    flex: 1;
    border: none;
    outline: none;
    background: var(--theme-bg-0);
    color: var(--theme-font-1);
    padding: 0;
    margin: 0;
    font-family: inherit;
    font-size: inherit;
  }

  .inline-editor:focus {
    outline: none;
  }

  .multiple-values {
    color: var(--theme-font-3);
    font-style: italic;
  }
</style>
