import {GristDoc} from 'app/client/components/GristDoc';
import {loadGristDoc} from 'app/client/lib/imports';
import {urlState} from 'app/client/models/gristUrlState';
import {getUserOrgPrefObs} from 'app/client/models/UserPrefs';
import {showExampleCard} from 'app/client/ui/ExampleCard';
import {examples} from 'app/client/ui/ExampleInfo';
import {createHelpTools, cssLinkText, cssPageEntry, cssPageEntryMain, cssPageEntrySmall,
        cssPageIcon, cssPageLink, cssSectionHeader, cssSpacer, cssSplitPageEntry,
        cssTools} from 'app/client/ui/LeftPanelCommon';
import {hoverTooltip, tooltipCloseButton} from 'app/client/ui/tooltips';
import {colors} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {cssLink} from 'app/client/ui2018/links';
import {menuAnnotate} from 'app/client/ui2018/menus';
import {confirmModal} from 'app/client/ui2018/modals';
import {userOverrideParams} from 'app/common/gristUrls';
import {Computed, Disposable, dom, makeTestId, Observable, observable, styled} from 'grainjs';

const testId = makeTestId('test-tools-');

export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Observable<boolean>): Element {
  const isOwner = gristDoc.docPageModel.currentDoc.get()?.access === 'owners';
  const isOverridden = Boolean(gristDoc.docPageModel.userOverride.get());
  const hasDocTour = Computed.create(owner, use =>
    use(gristDoc.docModel.allTableIds.getObservable()).includes('GristDocTour'));
  const canViewAccessRules = observable(false);
  function updateCanViewAccessRules() {
    canViewAccessRules.set((isOwner && !isOverridden) ||
                           gristDoc.docModel.rules.getNumRows() > 0);
  }
  owner.autoDispose(gristDoc.docModel.rules.tableData.tableActionEmitter.addListener(updateCanViewAccessRules));
  updateCanViewAccessRules();
  return cssTools(
    cssTools.cls('-collapsed', (use) => !use(leftPanelOpen)),
    cssSectionHeader("TOOLS"),

    cssPageEntry(
      cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'acl'),
      cssPageEntry.cls('-disabled', (use) => !use(canViewAccessRules)),
      dom.domComputed(canViewAccessRules, (_canViewAccessRules) => {
        return cssPageLink(
          cssPageIcon('EyeShow'),
          cssLinkText('Access Rules',
            menuAnnotate('Beta', cssBetaTag.cls(''))
          ),
          _canViewAccessRules ? urlState().setLinkUrl({docPage: 'acl'}) : null,
          isOverridden ? addRevertViewAsUI() : null,
        );
      }),
      testId('access-rules'),
    ),

    cssPageEntry(
      cssPageLink(cssPageIcon('Log'), cssLinkText('Document History'), testId('log'),
        dom.on('click', () => gristDoc.showTool('docHistory')))
    ),
    // TODO: polish validation and add it back
    dom.maybe((use) => use(gristDoc.app.features).validationsTool, () =>
      cssPageEntry(
        cssPageLink(cssPageIcon('Validation'), cssLinkText('Validate Data'), testId('validate'),
          dom.on('click', () => gristDoc.showTool('validations'))))
    ),
    cssPageEntry(
      cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'code'),
      cssPageLink(cssPageIcon('Code'),
        cssLinkText('Code View'),
        urlState().setLinkUrl({docPage: 'code'})
      ),
      testId('code'),
    ),
    cssSpacer(),
    dom.maybe(gristDoc.docPageModel.currentDoc, (doc) => {
      const ex = examples.find(e => e.urlId === doc.urlId);
      if (!ex || !ex.tutorialUrl) { return null; }
      return cssPageEntry(
        cssPageLink(cssPageIcon('Page'), cssLinkText('How-to Tutorial'), testId('tutorial'),
          {href: ex.tutorialUrl, target: '_blank'},
          cssExampleCardOpener(
            icon('TypeDetails'),
            testId('welcome-opener'),
            automaticHelpTool(
              (info) => showExampleCard(ex, info),
              gristDoc,
              "seenExamples",
              ex.id
            ),
          ),
        ),
      );
    }),
    // Show the 'Tour of this Document' button if a GristDocTour table exists.
    dom.maybe(hasDocTour, () =>
      cssSplitPageEntry(
        cssPageEntryMain(
          cssPageLink(cssPageIcon('Page'),
            cssLinkText('Tour of this Document'),
            automaticHelpTool(
              async ({markAsSeen}) => {
                const gristDocModule = await loadGristDoc();
                await gristDocModule.startDocTour(gristDoc.docData, gristDoc.docComm, markAsSeen);
              },
              gristDoc,
              "seenDocTours",
              gristDoc.docId()
            ),
            testId('doctour'),
          ),
        ),
        !isOwner ? null : cssPageEntrySmall(
          cssPageLink(cssPageIcon('Remove'),
            dom.on('click', () => confirmModal('Delete document tour?', 'Delete', () =>
              gristDoc.docData.sendAction(['RemoveTable', 'GristDocTour']))
            ),
            testId('remove-doctour')
          ),
        )
      ),
    ),
    createHelpTools(gristDoc.docPageModel.appModel, false)
  );
}

/**
 * Helper for showing users some kind of help (example cards or document tours)
 * automatically if they haven't seen it before, or if they click
 * on some element to explicitly show it again. Put this in said dom element,
 * and it will provide the onclick handler and a handler which automatically
 * shows when the dom element is attached, both by calling showFunc.
 *
 * prefKey is a key for a list of identifiers saved in user preferences.
 * itemId should be a single identifier that fits in that list.
 * If itemId is already present then the help will not be shown automatically,
 * otherwise it will be added to the list and saved under prefKey
 * when info.markAsSeen() is called.
 */
function automaticHelpTool(
  showFunc: (info: AutomaticHelpToolInfo) => void,
  gristDoc: GristDoc,
  prefKey: 'seenExamples' | 'seenDocTours',
  itemId: number | string
) {
  function show(elem: HTMLElement, reopen: boolean) {
    const prefObs: Observable<typeof itemId[] | undefined> = getUserOrgPrefObs(gristDoc.userOrgPrefs, prefKey);
    const seenIds = prefObs.get() || [];

    // If this help was previously dismissed, don't show it again, unless the user is reopening it.
    if (!reopen && seenIds.includes(itemId)) {
      return;
    }

    // When the help is closed, if it's the first time it's dismissed, save this fact, to avoid
    // showing it automatically again in the future.
    function markAsSeen() {
      try {
        if (!seenIds.includes(itemId)) {
          const seen = new Set(seenIds);
          seen.add(itemId);
          prefObs.set([...seen].sort());
        }
      } catch (e) {
        // If we fail to save this preference, it's probably not worth alerting the user about,
        // so just log to console.
        // tslint:disable-next-line:no-console
        console.warn("Failed to save userPref " + prefKey, e);
      }
    }

    showFunc({elem, reopen, markAsSeen});
  }

  return [
    dom.on('click', (ev, elem) => {
      ev.preventDefault();
      show(elem as HTMLElement, true);
    }),
    (elem: HTMLElement) => {
      // Once the trigger element is attached to DOM, show the help
      setTimeout(() => show(elem, false), 0);
    }
  ];
}

/** Values which may be useful when showing an automatic help tool */
export interface AutomaticHelpToolInfo {
  // Element where automaticHelpTool is attached, typically a button,
  // which shows the help when clicked
  elem: HTMLElement;

  // true if the help was shown explicitly by clicking elem,
  // false if it's being shown automatically to new users
  reopen: boolean;

  // Call this when the user explicitly dismisses the help to
  // remember this in user preferences and not show it automatically on next load
  markAsSeen: () => void;
}

// When viewing a page as another user, the "Access Rules" page link includes a button to revert
// the user and open the page, and a click on the page link shows a tooltip to revert.
function addRevertViewAsUI() {
  return [
    // A button that allows reverting back to yourself.
    dom('a',
      cssExampleCardOpener.cls(''),
      cssRevertViewAsButton.cls(''),
      icon('Convert'),
      urlState().setHref(userOverrideParams(null, {docPage: 'acl'})),
      dom.on('click', (ev) => ev.stopPropagation()),    // Avoid refreshing the tooltip.
      testId('revert-view-as'),
    ),

    // A tooltip that allows reverting back to yourself.
    hoverTooltip((ctl) =>
      cssConvertTooltip(icon('Convert'),
        cssLink('Return to viewing as yourself',
          urlState().setHref(userOverrideParams(null, {docPage: 'acl'})),
        ),
        tooltipCloseButton(ctl),
      ),
      {openOnClick: true}
    ),
  ];
}

const cssConvertTooltip = styled('div', `
  display: flex;
  align-items: center;
  --icon-color: ${colors.lightGreen};

  & > .${cssLink.className} {
    margin-left: 8px;
  }
`);

const cssExampleCardOpener = styled('div', `
  cursor: pointer;
  margin-right: 4px;
  margin-left: auto;
  border-radius: 16px;
  border-radius: 3px;
  height: 24px;
  width: 24px;
  padding: 4px;
  line-height: 0px;
  --icon-color: ${colors.light};
  background-color: ${colors.lightGreen};
  &:hover {
    background-color: ${colors.darkGreen};
  }
  .${cssTools.className}-collapsed & {
    display: none;
  }
`);

const cssRevertViewAsButton = styled(cssExampleCardOpener, `
  background-color: ${colors.darkGrey};
  &:hover {
    background-color: ${colors.slate};
  }
`);

const cssBetaTag = styled('div', `
  .${cssPageEntry.className}-disabled & {
    opacity: 0.4;
  }
`);