import { useCoreServices } from '@learnitive/context';
import { Sidebar, AppSidebar as UIAppSidebar } from '@learnitive/ui-components';
import {
  isVisibleWorkspaceDirectoryName,
  WsDirPath,
  WsPath,
} from '@learnitive/ws-path';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import React from 'react';
import { ProLicenseDialog } from '../components/pro-license-dialog';
import { suggestedProjectFileName } from '../common/project-file-suggestion';
import { moveEditorTab, openEditorTab } from '../common/editor-tabs';
import { safeDownloadName, saveZipStream } from '../components/project-zip-actions';
import { SidebarFooterMenu } from './sidebar-footer-menu';
import { useSidebarFileActions } from './use-sidebar-file-actions';

interface SidebarProps {
  children: React.ReactNode;
}

export const AppSidebar = ({ children }: SidebarProps) => {
  const {
    commandDispatcher,
    fileSystem,
    projectStorage,
    projectCollaboration,
    workspaceState,
    workbenchState,
    navigation,
    projectEntitlements,
    userActivityService,
  } = useCoreServices();
  const setOpenOmniSearch = useSetAtom(workbenchState.$openOmniSearch);
  const workspaces = useAtomValue(workspaceState.$workspaces);
  const [sidebarOpen, setSidebarOpen] = useAtom(workbenchState.$sidebarOpen);
  const [sidebarWidth, setSidebarWidth] = useAtom(workbenchState.$sidebarWidth);
  const resetShowNoteFilesOnly = useSetAtom(workbenchState.$showNoteFilesOnlyInSidebar);
  const fileTreeExpandedPathsByWorkspace = useAtomValue(
    workbenchState.$fileTreeExpandedPathsByWorkspace,
  );
  const activeWsName = useAtomValue(workspaceState.$currentWsName);
  const collaborationState = useAtomValue(projectCollaboration.$state);
  const structureLocked = Boolean(activeWsName && collaborationState.active && collaborationState.wsName === activeWsName);
  const activeWsPaths = useAtomValue(workspaceState.$activeWsPaths);
  const currentWsPath = useAtomValue(workspaceState.$currentWsPath);
  const wsPaths = useAtomValue(workspaceState.$wsPaths);
  const fileListRevision = useAtomValue(fileSystem.$fileListRevisionCount);
  const [projectDirectoryPaths, setProjectDirectoryPaths] = React.useState<
    string[]
  >([]);
  const starredWsPaths = useAtomValue(userActivityService.$starredWsPaths);
  const fileTreeListState = useAtomValue(workspaceState.$fileTreeListState);
  const entitlement = useAtomValue(projectEntitlements.$state);
  const [proOpen, setProOpen] = React.useState(false);
  const newFileName = suggestedProjectFileName(workspaces.find((item) => item.name === activeWsName)?.metadata, currentWsPath?.path);

  React.useEffect(() => {
    // Learnitive is a full project explorer. Clear the legacy editor-era
    // Markdown-only preference so users cannot remain invisibly filtered.
    resetShowNoteFilesOnly(false);
  }, [resetShowNoteFilesOnly]);

  // Keep this domain-to-view join local until another consumer needs it.
  const starredItems = React.useMemo(() => {
    const filesByWsPath = new Map(
      wsPaths.map((wsPath) => [wsPath.wsPath, wsPath]),
    );
    const currentPath = currentWsPath?.wsPath;

    return starredWsPaths.flatMap((starredWsPath) => {
      const wsPath = filesByWsPath.get(starredWsPath);
      return wsPath
        ? [
            {
              title: wsPath.fileName || wsPath.path,
              wsPath: wsPath.wsPath,
              isActive: currentPath === wsPath.wsPath,
            },
          ]
        : [];
    });
  }, [currentWsPath?.wsPath, wsPaths, starredWsPaths]);

  React.useEffect(() => {
    // File-tree revision is an intentional refresh trigger even though the
    // provider API is the value read inside the effect.
    void fileListRevision;
    if (!activeWsName) {
      setProjectDirectoryPaths([]);
      return;
    }

    const abortController = new AbortController();
    void projectStorage
      .getProvider(activeWsName)
      .then((provider) => provider.listEntries(abortController.signal))
      .then((entries) => {
        if (abortController.signal.aborted) return;
        const directories = entries
          .filter((entry) => entry.kind === 'directory')
          .map((entry) => entry.path.replace(/\/$/u, ''))
          .filter((directoryPath) =>
            directoryPath
              .split('/')
              .filter(Boolean)
              .every((segment) => isVisibleWorkspaceDirectoryName(segment)),
          );
        setProjectDirectoryPaths([...new Set(directories)].sort());
      })
      .catch((error) => {
        if (!abortController.signal.aborted) {
          console.warn('[Learnitive] Could not list project directories', error);
        }
      });

    return () => abortController.abort();
  }, [activeWsName, fileListRevision, projectStorage]);

  const getActionsForEntry = useSidebarFileActions({
    activeWsName,
    commandDispatcher,
    newFileName,
  });

  const handleFileTreeDirectoryExpansionChange = React.useCallback(
    (path: string, expanded: boolean) => {
      if (activeWsName) {
        workbenchState.setFileTreeDirectoryExpanded(
          activeWsName,
          path,
          expanded,
        );
      }
    },
    [activeWsName, workbenchState],
  );
  const handleRevealFileTreePaths = React.useCallback(
    (paths: readonly string[]) => {
      if (activeWsName) {
        workbenchState.revealFileTreePaths(activeWsName, paths);
      }
    },
    [activeWsName, workbenchState],
  );
  const handleCollapseFileTree = React.useCallback(
    (keepExpandedPaths: readonly string[]) => {
      if (activeWsName) {
        workbenchState.collapseFileTree(activeWsName, keepExpandedPaths);
      }
    },
    [activeWsName, workbenchState],
  );

  const routedActivePath = activeWsPaths.at(-1)?.path;
  const activeFilePaths = routedActivePath ? [routedActivePath] : [];

  return (
    <Sidebar.SidebarProvider
      open={sidebarOpen}
      onOpenChange={(open) => setSidebarOpen(open)}
      width={sidebarWidth}
      onWidthChange={setSidebarWidth}
    >
      <UIAppSidebar
        workspaces={workspaces.map((ws, _i) => ({
          name: ws.name,
          misc: ws.type,
          isActive: activeWsName === ws.name,
        }))}
        filePaths={wsPaths.map((wsPath) => wsPath.path)}
        directoryPaths={projectDirectoryPaths}
        starredItems={starredItems}
        wsPathToHref={(wsPath) => navigation.toWsFileUri(wsPath)}
        wsNameToHref={(wsName) =>
          navigation.toUri({
            route: 'ws-home',
            payload: { wsName },
          })
        }
        canCreateFiles={Boolean(activeWsName) && !structureLocked}
        onCreateDirectory={(pathPrefix) => {
          if (!activeWsName) {
            return;
          }
          commandDispatcher.dispatch(
            'command::ui:create-directory-dialog',
            {
              pathPrefix,
            },
            'ui',
          );
        }}
        onDownloadProject={() => {
          if (!activeWsName) return;
          void (async () => {
            try {
              await projectCollaboration.flushToStorage(activeWsName);
              const archive = await projectStorage.exportZipStream(activeWsName);
              await saveZipStream(archive, safeDownloadName(activeWsName));
            } catch (error) {
              console.error('[Learnitive] Full project download failed.', error);
            }
          })();
        }}
        onCreateFile={(pathPrefix) => {
          if (!activeWsName) {
            return;
          }
          commandDispatcher.dispatch(
            'command::ui:create-file-dialog',
            { prefillName: newFileName, pathPrefix },
            'ui',
          );
        }}
        onMoveFile={(sourceRelativePath, destinationDirectory) => {
          if (!activeWsName) {
            return;
          }

          commandDispatcher.dispatch(
            'command::ws:move-ws-path',
            {
              destDirWsPath: destinationDirectory
                ? WsDirPath.fromParts(activeWsName, destinationDirectory).wsPath
                : `${activeWsName}:`,
              wsPath: WsPath.fromParts(activeWsName, sourceRelativePath).wsPath,
            },
            'ui',
          );
          const baseName = sourceRelativePath.split('/').at(-1) || sourceRelativePath;
          const destinationPath = destinationDirectory ? `${destinationDirectory.replace(/\/$/u, '')}/${baseName}` : baseName;
          moveEditorTab(activeWsName, sourceRelativePath, destinationPath);
        }}
        onOpenFile={(relativePath) => {
          if (!activeWsName) {
            return;
          }

          openEditorTab(activeWsName, relativePath);
          commandDispatcher.dispatch(
            'command::ws:go-ws-path',
            {
              wsPath: WsPath.fromParts(activeWsName, relativePath).wsPath,
            },
            'ui',
          );
        }}
        onNewWorkspaceClick={() => {
          commandDispatcher.dispatch(
            'command::ui:create-workspace-dialog',
            null,
            'ui',
          );
        }}
        onManageWorkspacesClick={() => {
          window.location.assign('/projects');
        }}
        fileTreeNotice={
          fileTreeListState.status === 'error'
            ? {
                message: t.app.components.appSidebar.fileTreeErrorMessage,
                retryLabel: t.app.components.appSidebar.fileTreeErrorRetry,
                onRetry: () => {
                  commandDispatcher.dispatch(
                    'command::ws:refresh-file-tree',
                    null,
                    'ui',
                  );
                },
              }
            : undefined
        }
        activeFilePaths={activeFilePaths}
        expandedFileTreePaths={
          activeWsName &&
          Object.hasOwn(fileTreeExpandedPathsByWorkspace, activeWsName)
            ? fileTreeExpandedPathsByWorkspace[activeWsName]
            : undefined
        }
        onFileTreeDirectoryExpansionChange={
          handleFileTreeDirectoryExpansionChange
        }
        onRevealFileTreePaths={handleRevealFileTreePaths}
        onCollapseFileTree={handleCollapseFileTree}
        commandButtonClassName="desktop-titlebar-no-drag"
        onSearchClick={() => {
          setOpenOmniSearch(true);
        }}
        sidebarHeaderClassName="desktop-sidebar-titlebar-header desktop-titlebar-drag"
        workspaceSwitcherWrapperClassName="desktop-titlebar-no-drag"
        getActionsForEntry={getActionsForEntry}
        footerAction={{ label: 'Templates', href: '/templates' }}
        footerTitle={t.app.sidebar.footerTitle}
        footerChildren={
          <SidebarFooterMenu
            canCreateFiles={Boolean(activeWsName) && !structureLocked}
            newFileName={newFileName}
            isPro={entitlement.tier === 'pro'}
            onOpenPro={() => setProOpen(true)}
          />
        }
      />
      <Sidebar.SidebarInset className="h-svh min-h-0 overflow-hidden">{children}</Sidebar.SidebarInset>
      <ProLicenseDialog open={proOpen} onOpenChange={setProOpen} />
    </Sidebar.SidebarProvider>
  );
};
