import { cp, rm, stat } from 'node:fs/promises';
import { join } from 'node:path';

// Copying a sidecar's own assets — its icon, and whatever else it ships — to where the browser can
// fetch them.
//
//   src/servers/app-store/templates/<template>/assets/   what the sidecar ships
//   public/plugins/<sidecar-id>/                          where it is served from
//
// ── Why copied rather than served from where they live ──
//
// A sidecar that ships from its own repository has its assets wherever that repository was unpacked,
// which is not a path the web server can be taught at build time. Copying into one predictable place
// under `public/` means the serving rule is a single route (`/plugins/*` in server.tsx) that never has
// to know how many plugins exist or where any of them came from.
//
// It also means the assets are a property of the INSTALL rather than of the source tree: uninstall
// removes them, and a plugin that was never installed serves nothing.
//
// ── The boot-snapshot trap ──
//
// `publicRoutes` in server.tsx is built by globbing ./public at startup, so anything copied here after
// boot is invisible to it. That is why `/plugins/*` exists as a dynamic route — without it the first
// install of a plugin would show a broken image until the server was restarted.

/** Where a sidecar's shipped assets live in the source tree. */
export const assetSourceDir = (templateDir: string): string => join(templateDir, 'assets');

/** Where they are served from. Matches the `/plugins/*` route and the `image` paths in a UI manifest. */
export const assetPublicDir = (sidecarId: string): string => join(process.cwd(), 'public', 'plugins', sidecarId);

/** The URL a manifest should use for a shipped icon. */
export const iconUrl = (sidecarId: string): string => `/plugins/${sidecarId}/icon.png`;

const exists = async (path: string): Promise<boolean> => {
  try {
    await stat(path);
    return true;
  } catch {
    return false;
  }
};

/**
 * Publish a sidecar's assets. Returns false when it ships none, which is not a failure — most sidecars
 * use a lucide glyph and have nothing to copy.
 *
 * Idempotent by overwriting: re-running an install republishes rather than erroring on a directory that
 * already exists, which is what makes this safe inside a resumable step.
 */
export async function publishAssets(sidecarId: string, templateDir: string): Promise<boolean> {
  const source = assetSourceDir(templateDir);
  if (!(await exists(source))) return false;

  await cp(source, assetPublicDir(sidecarId), { recursive: true, force: true });
  return true;
}

/**
 * Remove them on uninstall.
 *
 * Safe to delete, unlike everything else uninstall touches: these are copies of files that still exist
 * in the sidecar's own source. Nothing a user made is in here, which is exactly why this is the one
 * thing uninstall is allowed to remove.
 */
export async function unpublishAssets(sidecarId: string): Promise<void> {
  await rm(assetPublicDir(sidecarId), { recursive: true, force: true });
}
