import { useCoreServices } from '@learnitive/context';
import {
  DEFAULT_PROJECT_ARCHIVE_LIMITS,
  getBrowserStorageHealth,
  requestPersistentBrowserStorage,
  type BrowserStorageHealth,
} from '@learnitive/project-storage';
import {
  Button,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DropdownMenu,
  toast,
} from '@learnitive/ui-components';
import {
  Database,
  Download,
  MoreVertical,
  Settings,
  Upload,
} from 'lucide-react';
import React from 'react';

export function safeDownloadName(workspaceName: string): string {
  const normalized = workspaceName
    .trim()
    .replace(/[\\/:*?"<>|]+/g, '-')
    .replace(/\s+/g, '-');
  return `${normalized || 'interactive-project'}.zip`;
}

type SaveFilePickerWindow = Window & {
  showSaveFilePicker?: (options: {
    suggestedName: string;
    types: Array<{ description: string; accept: Record<string, string[]> }>;
  }) => Promise<{
    createWritable(): Promise<{
      write(data: Uint8Array): Promise<void>;
      close(): Promise<void>;
      abort?(): Promise<void>;
    }>;
  }>;
};

export async function saveZipStream(
  stream: ReadableStream<Uint8Array>,
  fileName: string,
): Promise<void> {
  const picker = navigator.webdriver
    ? undefined
    : (window as SaveFilePickerWindow).showSaveFilePicker;
  if (picker) {
    const handle = await picker({
      suggestedName: fileName,
      types: [
        {
          description: 'ZIP archive',
          accept: { 'application/zip': ['.zip'] },
        },
      ],
    });
    const writable = await handle.createWritable();
    const reader = stream.getReader();
    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        await writable.write(value);
      }
      await writable.close();
      return;
    } catch (error) {
      await writable.abort?.().catch(() => undefined);
      throw error;
    } finally {
      reader.releaseLock();
    }
  }

  // Firefox and browsers without File System Access still avoid retaining every
  // project file before ZIP assembly: the stream is consumed incrementally into
  // the final Blob by the browser.
  const blob = await new Response(stream).blob();
  const url = URL.createObjectURL(blob);
  const anchor = document.createElement('a');
  anchor.href = url;
  anchor.download = fileName;
  anchor.style.display = 'none';
  document.body.append(anchor);
  anchor.click();
  anchor.remove();
  URL.revokeObjectURL(url);
}

type ProjectStorageState = {
  directFilesystem: boolean;
  health?: BrowserStorageHealth;
  lastExportAt?: number;
};

/**
 * Compact project action menu for the workspace home. Archive operations keep
 * using ProjectStorageService; storage telemetry is intentionally loaded only
 * when the Storage dialog is opened rather than occupying permanent home-page
 * space.
 */
export function ProjectZipActions({ workspaceName }: { workspaceName: string }) {
  const {
    commandDispatcher,
    fileSystem,
    projectCollaboration,
    projectStorage,
  } = useCoreServices();
  const inputRef = React.useRef<HTMLInputElement>(null);
  const [busy, setBusy] = React.useState<
    'import' | 'export' | 'persist' | 'storage' | null
  >(null);
  const [storageOpen, setStorageOpen] = React.useState(false);
  const [storageState, setStorageState] = React.useState<ProjectStorageState>();

  const readLastExportAt = React.useCallback((): number | undefined => {
    try {
      const value = localStorage.getItem(
        `learnitive:last-project-export:${workspaceName}`,
      );
      if (!value) return undefined;
      const parsed = Number(value);
      return Number.isFinite(parsed) ? parsed : undefined;
    } catch {
      return undefined;
    }
  }, [workspaceName]);

  const refreshStorageState = React.useCallback(async () => {
    const provider = await projectStorage.getProvider(workspaceName);
    const next: ProjectStorageState = {
      directFilesystem: provider.capabilities.directFilesystem,
      lastExportAt: readLastExportAt(),
    };
    if (provider.capabilities.requiresExport) {
      next.health = await getBrowserStorageHealth();
    }
    setStorageState(next);
  }, [projectStorage, readLastExportAt, workspaceName]);

  React.useEffect(() => {
    setStorageOpen(false);
    setStorageState(undefined);
  }, [workspaceName]);

  React.useEffect(() => {
    if (!storageOpen) return;
    setBusy((current) => current ?? 'storage');
    void refreshStorageState()
      .catch((error) => {
        console.error('Project storage inspection failed', error);
        setStorageState(undefined);
      })
      .finally(() => {
        setBusy((current) => (current === 'storage' ? null : current));
      });
  }, [refreshStorageState, storageOpen]);

  const handleExport = async () => {
    if (busy) return;
    setBusy('export');
    try {
      await projectCollaboration.flushToStorage(workspaceName);
      const archive = await projectStorage.exportZipStream(workspaceName);
      await saveZipStream(archive, safeDownloadName(workspaceName));
      const exportedAt = Date.now();
      try {
        localStorage.setItem(
          `learnitive:last-project-export:${workspaceName}`,
          String(exportedAt),
        );
      } catch {
        // Export succeeded even if advisory timestamp persistence is unavailable.
      }
      setStorageState((current) =>
        current ? { ...current, lastExportAt: exportedAt } : current,
      );
      toast.success(t.app.pageWsHome.projectZipExportSuccess);
    } catch (error) {
      console.error('Project ZIP export failed', error);
      toast.error(t.app.pageWsHome.projectZipExportFailed);
    } finally {
      setBusy(null);
    }
  };

  const handleImport = async (file: File) => {
    if (busy) return;
    if (file.size > DEFAULT_PROJECT_ARCHIVE_LIMITS.maxArchiveBytes) {
      toast.error(t.app.pageWsHome.projectZipTooLarge);
      return;
    }

    setBusy('import');
    try {
      if (projectCollaboration.isActive(workspaceName)) {
        throw new Error(t.app.collaboration.importBlocked);
      }
      await projectStorage.importZipBlob(workspaceName, file);
      fileSystem.refreshFileTree();
      toast.success(t.app.pageWsHome.projectZipImportSuccess);
    } catch (error) {
      console.error('Project ZIP import failed', error);
      toast.error(t.app.pageWsHome.projectZipImportFailed);
    } finally {
      setBusy(null);
    }
  };

  const requestPersistence = async () => {
    if (busy) return;
    setBusy('persist');
    try {
      const granted = await requestPersistentBrowserStorage();
      await refreshStorageState();
      if (granted) toast.success(t.app.pageWsHome.storagePersistenceGranted);
      else toast.error(t.app.pageWsHome.storagePersistenceNotGranted);
    } finally {
      setBusy(null);
    }
  };

  const storagePercent =
    storageState?.health?.usageRatio === undefined
      ? undefined
      : Math.round(storageState.health.usageRatio * 100);

  return (
    <>
      <input
        ref={inputRef}
        className="sr-only"
        type="file"
        accept=".zip,application/zip"
        aria-label={t.app.pageWsHome.importProjectZipButton}
        onChange={(event) => {
          const file = event.currentTarget.files?.[0];
          event.currentTarget.value = '';
          if (file) void handleImport(file);
        }}
      />

      <DropdownMenu.DropdownMenu>
        <DropdownMenu.DropdownMenuTrigger
          render={
            <Button
              aria-label={t.app.pageWsHome.projectMenuButton}
              className="size-9"
              disabled={busy === 'import' || busy === 'export'}
              size="icon"
              title={t.app.pageWsHome.projectMenuButton}
              variant="outline"
            >
              <MoreVertical className="size-4" aria-hidden="true" />
            </Button>
          }
        />
        <DropdownMenu.DropdownMenuContent align="end" className="min-w-56">
          <DropdownMenu.DropdownMenuItem
            disabled={busy !== null}
            onClick={() => inputRef.current?.click()}
          >
            <Upload className="size-4" aria-hidden="true" />
            {t.app.pageWsHome.importProjectMenuItem}
          </DropdownMenu.DropdownMenuItem>
          <DropdownMenu.DropdownMenuItem
            disabled={busy !== null}
            onClick={() => void handleExport()}
          >
            <Download className="size-4" aria-hidden="true" />
            {t.app.pageWsHome.downloadProjectMenuItem}
          </DropdownMenu.DropdownMenuItem>
          <DropdownMenu.DropdownMenuSeparator />
          <DropdownMenu.DropdownMenuItem
            disabled={busy === 'import' || busy === 'export'}
            onClick={() => setStorageOpen(true)}
          >
            <Database className="size-4" aria-hidden="true" />
            {t.app.pageWsHome.storageMenuItem}
          </DropdownMenu.DropdownMenuItem>
          <DropdownMenu.DropdownMenuItem
            onClick={() =>
              commandDispatcher.dispatch(
                'command::ui:open-settings-workspaces',
                null,
                'ui',
              )
            }
          >
            <Settings className="size-4" aria-hidden="true" />
            {t.app.pageWsHome.projectSettingsMenuItem}
          </DropdownMenu.DropdownMenuItem>
        </DropdownMenu.DropdownMenuContent>
      </DropdownMenu.DropdownMenu>

      <Dialog open={storageOpen} onOpenChange={setStorageOpen}>
        <DialogContent className="sm:max-w-md" data-testid="project-storage-dialog">
          <DialogHeader>
            <DialogTitle>{t.app.pageWsHome.storageDialogTitle}</DialogTitle>
            <DialogDescription>
              {t.app.pageWsHome.storageDialogDescription}
            </DialogDescription>
          </DialogHeader>

          <div className="space-y-3 text-sm">
            {!storageState ? (
              <p className="text-muted-foreground">
                {t.app.pageWsHome.storageLoading}
              </p>
            ) : storageState.directFilesystem ? (
              <div className="rounded-md border p-3">
                <div className="font-medium">
                  {t.app.pageWsHome.storageLocalFolder}
                </div>
                <p className="mt-1 text-muted-foreground text-xs">
                  {t.app.pageWsHome.storageLocalFolderDescription}
                </p>
              </div>
            ) : (
              <>
                <div className="rounded-md border p-3">
                  <div className="flex items-center justify-between gap-4">
                    <span>{t.app.pageWsHome.storagePersistenceLabel}</span>
                    <span className="font-medium">
                      {storageState.health?.persisted
                        ? t.app.pageWsHome.storagePersistentShort
                        : t.app.pageWsHome.storageNotPersistentShort}
                    </span>
                  </div>
                  {storagePercent !== undefined && (
                    <div className="mt-2 flex items-center justify-between gap-4 text-muted-foreground text-xs">
                      <span>{t.app.pageWsHome.storageUsageLabel}</span>
                      <span>{storagePercent}%</span>
                    </div>
                  )}
                </div>

                <div className="rounded-md border p-3">
                  <div className="font-medium">
                    {t.app.pageWsHome.lastBackupLabel}
                  </div>
                  <p className="mt-1 text-muted-foreground text-xs">
                    {storageState.lastExportAt
                      ? new Date(storageState.lastExportAt).toLocaleString()
                      : t.app.pageWsHome.neverExported}
                  </p>
                </div>

                {!storageState.health?.persisted && (
                  <Button
                    disabled={busy !== null}
                    onClick={() => void requestPersistence()}
                    variant="outline"
                  >
                    {t.app.pageWsHome.requestPersistentStorage}
                  </Button>
                )}
              </>
            )}
          </div>

          <DialogFooter showCloseButton />
        </DialogContent>
      </Dialog>
    </>
  );
}
