/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

import gettext from 'sources/gettext';
import { styled } from '@mui/material/styles';
import _ from 'lodash';
import url_for from 'sources/url_for';
import { Box } from '@mui/material';
import { DefaultButton, PrimaryButton } from '../../../../static/js/components/Buttons';
import { getBinaryPathSchema } from './binary_path.ui';
import { getBrowser } from '../../../../static/js/utils';
import SaveSharpIcon from '@mui/icons-material/SaveSharp';
import CloseIcon from '@mui/icons-material/CloseRounded';
import HTMLReactParser from 'html-react-parser/lib/index';
import DOMPurify from 'dompurify';
import getApiInstance from '../../../../static/js/api_instance';

// Cache for dynamically loaded options to avoid repeated API calls
const optionsCache = {};

// Clear cache for a specific endpoint (used when refresh button updates options)
export function clearOptionsCache(endpoint) {
  if (endpoint) {
    delete optionsCache[endpoint];
  }
}

export async function reloadPgAdmin() {
  const { name: browser } = getBrowser();
  if (browser === 'Electron') {
    await window.electronUI.reloadApp();
  } else {
    location.reload();
  }
}

export function getNoteField(node, subNode, nodeData, note = '') {
  // Check and add the note for the element.
  if (subNode.label === gettext('Nodes') && node.label === gettext('Browser')) {
    note = gettext('This setting is used to show/hide nodes in the Object Explorer.');
  }

  if (note.length > 0) {
    return [{
      id: _.uniqueId('note_') + subNode.id, // Better unique ID prefix
      type: 'note',
      text: note,
      parentId: nodeData.id,
      visible: false,
    }];
  }
  return [];
}

export function prepareSubnodeData(node, subNode, nodeData, preferencesStore) {
  let addBinaryPathNote = false;
  let fieldItems = [];
  let fieldValues = {};
  const typeMap = {
    text: 'text',
    input: 'text',
    boolean: 'switch',
    node: 'switch',
    integer: 'numeric',
    numeric: 'numeric',
    date: 'datetimepicker',
    datetime: 'datetimepicker',
    options: 'select',
    select: 'select',
    multiline: 'multiline',
    switch: 'switch',
    keyboardshortcut: 'keyboardShortcut',
    radioModern: 'toggle',
    threshold: 'threshold',
  };

  subNode.preferences.forEach((element) => {
    let type = typeMap[element.type] || element.type;
    let note = ''; // Initialize note for each element

    // Ensure type is set after specific handling
    element.type = type;
    if (type === 'selectFile') {
      // Binary Path specific handling
      note = gettext('Enter the directory in which the psql, pg_dump, pg_dumpall, and pg_restore utilities can be found for the corresponding database server version. The default path will be used for server versions that do not have a path specified.');
      element.type = 'collection';
      element.schema = getBinaryPathSchema();
      element.canAdd = false;
      element.canDelete = false;
      element.canEdit = false;
      element.editable = false;
      element.disabled = true; // Binary paths are managed in a collection, not directly editable here
      fieldValues[element.id] = JSON.parse(element.value);
      if (!addBinaryPathNote) { // Add note only once for binary path section
        fieldItems.push(...getNoteField(node, subNode, nodeData, note));
        addBinaryPathNote = true;
      }
    } else if (type === 'select') {
      element.controlProps = element.control_props ?? {};
      fieldValues[element.id] = element.value;

      if (element.name === 'theme') {
        // Theme has special handling - process before dynamic options
        element.type = 'theme';
        element.options.forEach((opt) => {
          opt.selected = opt.value === element.value;
          opt.preview_src = opt.preview_src && url_for('static', { filename: opt.preview_src });
        });
      } else if (element.controlProps.optionsRefreshUrl) {
        // Use select-refresh type when refresh URL is provided
        element.type = 'select-refresh';

        // Build refreshDeps by looking up IDs for the named dependencies
        const refreshDepNames = element.controlProps.refreshDepNames || {};
        const refreshDeps = {};
        for (const [paramName, prefName] of Object.entries(refreshDepNames)) {
          // Find the preference with this name in the same subNode
          const depPref = subNode.preferences.find((p) => p.name === prefName);
          if (depPref) {
            refreshDeps[paramName] = depPref.id;
          }
        }
        element.controlProps.refreshDeps = refreshDeps;

        // Register schema-level deps so the SchemaView subscriber
        // mechanism triggers a re-render of this field when any
        // dependency field changes (e.g., API URL or API key file).
        const depIds = Object.values(refreshDeps);
        if (depIds.length > 0) {
          element.deps = depIds;
        }

        // Set up blur-based clearing: when a dep field loses focus
        // with a changed value, fire an event that SelectRefresh
        // listens for to clear the model list.
        const depChangeEmitter = new EventTarget();
        element.controlProps.depChangeEmitter = depChangeEmitter;
        for (const prefName of Object.values(refreshDepNames)) {
          const depPref = subNode.preferences.find((p) => p.name === prefName);
          if (depPref) {
            depPref.controlProps = depPref.controlProps || {};
            let focusValue = '';
            depPref.controlProps.onFocus = (e) => {
              focusValue = e.target.value;
            };
            depPref.controlProps.onBlur = (e) => {
              if (e.target.value !== focusValue) {
                depChangeEmitter.dispatchEvent(new Event('depchange'));
              }
            };
          }
        }

        // Also set up initial options loading via optionsUrl
        if (element.controlProps.optionsUrl) {
          const optionsEndpoint = element.controlProps.optionsUrl;
          const staticOptions = element.options || [];
          element.options = () => {
            // Check cache first to avoid repeated API calls
            if (optionsCache[optionsEndpoint]) {
              return Promise.resolve([...optionsCache[optionsEndpoint], ...staticOptions]);
            }
            return new Promise((resolve) => {
              const api = getApiInstance();
              const optionsUrl = url_for(optionsEndpoint);
              api.get(optionsUrl)
                .then((res) => {
                  if (res.data?.data?.models) {
                    const dynamicOptions = res.data.data.models;
                    // Cache the results
                    optionsCache[optionsEndpoint] = dynamicOptions;
                    resolve([...dynamicOptions, ...staticOptions]);
                  } else {
                    resolve(staticOptions);
                  }
                })
                .catch(() => {
                  resolve(staticOptions);
                });
            });
          };
        }
      } else if (element.controlProps.optionsUrl) {
        // Support dynamic options loading via optionsUrl (endpoint name)
        const optionsEndpoint = element.controlProps.optionsUrl;
        const staticOptions = element.options || [];
        // Replace options with a function that fetches from the URL
        element.options = () => {
          // Check cache first to avoid repeated API calls
          if (optionsCache[optionsEndpoint]) {
            return Promise.resolve([...optionsCache[optionsEndpoint], ...staticOptions]);
          }
          return new Promise((resolve) => {
            const api = getApiInstance();
            // Use url_for to resolve the endpoint to a proper URL
            const optionsUrl = url_for(optionsEndpoint);
            api.get(optionsUrl)
              .then((res) => {
                if (res.data?.data?.models) {
                  // Dynamic models loaded successfully
                  const dynamicOptions = res.data.data.models;
                  // Cache the results
                  optionsCache[optionsEndpoint] = dynamicOptions;
                  resolve([...dynamicOptions, ...staticOptions]);
                } else {
                  // No models in response, use static options
                  resolve(staticOptions);
                }
              })
              .catch(() => {
                // On error, fall back to static options
                resolve(staticOptions);
              });
          });
        };
      }
    } else if (type === 'keyboardShortcut') {
      element.type = 'keyboardShortcut';
      element.canAdd = false;
      element.canDelete = false;
      element.canEdit = false;
      element.editable = false;

      const storedValue = preferencesStore.getPreferences(node.label.toLowerCase(), element.name)?.value;
      fieldValues[element.id] = storedValue || element.value;
    } else if (type === 'threshold') {
      element.type = 'threshold';
      const _val = element.value.split('|');
      fieldValues[element.id] = { warning: _val[0], alert: _val[1] };
    } else if (subNode.label === gettext('Results grid') && node.label === gettext('Query Tool')) {
      if (element.name === 'column_data_max_width') {
        const sizeControl = subNode.preferences.find((_el) => _el.name === 'column_data_auto_resize');
        if (sizeControl) {
          element.disabled = (state) => state[sizeControl.id] !== 'by_data';
        }
      }
      element.type = type;
      fieldValues[element.id] = element.value;
    } else if (subNode.label === gettext('User Interface') && node.label === gettext('Miscellaneous')) {
      if (element.name === 'open_in_res_workspace') {
        const layoutControl = subNode.preferences.find((_el) => _el.name === 'layout');
        if (layoutControl) {
          element.disabled = (state) => state[layoutControl.id] !== 'workspace';
        }
      }
      element.type = type;
      fieldValues[element.id] = element.value;
    } else {
      fieldValues[element.id] = element.value;
    }

    delete element.value; // Original value is moved to fieldValues
    element.visible = false;
    element.labelTooltip = `${node.name}:${subNode.name}:${element.name}`;
    element.helpMessage = element?.help_str || null;
    element.parentId = nodeData.id;
    fieldItems.push(element);
  });
  return { fieldItems, fieldValues };
}

export function getCollectionValue(_metadata, value, initVals) {
  let val = value;
  if (typeof value === 'object' && value !== null) { // Ensure value is an object and not null
    const meta = _metadata[0]; // Assuming _metadata will always have at least one element relevant to the current field

    if (meta.type === 'collection' && meta.schema) {
      if (value.changed?.[0] && 'binaryPath' in value.changed[0]) {
        const pathData = [];
        const pathVersions = value.changed.map(chValue => chValue.version);

        initVals[meta.id].forEach((initVal) => {
          const changedIndex = pathVersions.indexOf(initVal.version);
          if (changedIndex !== -1) {
            pathData.push(value.changed[changedIndex]);
          } else {
            pathData.push(initVal);
          }
        });
        val = JSON.stringify(pathData);
      } else if (value.changed?.[0]) { // Generic collection, likely keyboard shortcut
        const changedEntry = value.changed[0];
        if ('key' in changedEntry && 'code' in changedEntry) {
          changedEntry.key = {
            'char': changedEntry.key, // Original `key` is now `char`
            'key_code': changedEntry.code, // Original `code` is now `key_code`
          };
          delete changedEntry.code; // Remove old code
        }
        val = changedEntry; // Changed to object
      }
    } else if ('warning' in value && 'alert' in value) { // Threshold type
      val = `${value.warning}|${value.alert}`;
    } else if (value.changed && value.changed.length > 0) { // Catch-all for other collections/arrays
      val = JSON.stringify(value.changed);
    }
  }
  return val;
}

const StyledBox = styled(Box)(({ theme }) => ({
  '& .Alert-footer': {
    display: 'flex',
    justifyContent: 'flex-end',
    padding: '0.5rem',
    ...theme.mixins.panelBorder.top,
  },
  '& .Alert-margin': {
    marginLeft: '0.25rem',
  },
}));


export function showResetPrefModal(api, pgAdmin, preferencesStore, onReset) {
  pgAdmin.Browser.notifier.showModal(
    gettext('Reset all preferences'),
    (modalClose) => {
      const handleResetClick = async (reloadNow) => {
        try {
          await api({
            url: url_for('preferences.index'),
            method: 'DELETE',
          });
          preferencesStore.cache(); // Refresh preferences cache
          onReset();
          if (reloadNow) {
            reloadPgAdmin();
          } else {
            pgAdmin.Browser.tree.destroy().then(() => {
              pgAdmin.Browser.Events.trigger('pgadmin-browser:tree:destroyed', undefined, undefined);
              modalClose(); // Close modal after tree destruction if no full reload
            });
          }
        } catch (err) {
          pgAdmin.Browser.notifier.alert(err.response?.data || err.message || gettext('Failed to reset preferences.'));
          modalClose();
        }
      };

      const text = `${gettext('All preferences will be reset to their default values.')}<br><br>${gettext('Do you want to proceed?')}<br><br>
          ${gettext('Note:')}<br> <ul style="padding-left:20px"><li style="list-style-type:disc">${gettext('The object explorer tree will be refreshed automatically to reflect the changes.')}</li>
          <li style="list-style-type:disc">${gettext('If the application language changes, a reload of the application will be required. You can choose to reload later at your convenience.')}</li></ul>`;

      return (
        <StyledBox display="flex" flexDirection="column" height="100%">
          <Box flexGrow="1" p={2}>
            {HTMLReactParser(DOMPurify.sanitize(text))}
          </Box>
          <Box className='Alert-footer'>
            <DefaultButton className='Alert-margin' startIcon={<CloseIcon />} onClick={modalClose}>
              {gettext('Cancel')}
            </DefaultButton>
            <DefaultButton className='Alert-margin' startIcon={<SaveSharpIcon />} onClick={() => handleResetClick(true)}>
              {gettext('Save & Reload')}
            </DefaultButton>
            <PrimaryButton className='Alert-margin' startIcon={<SaveSharpIcon />} onClick={() => handleResetClick(false)}>
              {gettext('Save & Reload Later')}
            </PrimaryButton>
          </Box>
        </StyledBox>
      );
    },
    { isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: true, id: 'id-reset-preferences' }
  );
}
