import { pgTable, serial, text, boolean, timestamp, jsonb, uniqueIndex } from 'drizzle-orm/pg-core';

// What the owner has installed from the app store, and whether it should be running.
//
// ── Why there is no userId ──
//
// A sidecar is ONE process serving the whole machine, so installing one is a server-level act, not a
// per-user one. This is the line that keeps the model coherent once several people share a server:
//
//   installed   server-level, owner-only        — this row
//   configured  per user                        — service_connections
//
// Gitea is the worked example. The owner installs it once, and the row here says the process runs; each
// member then holds their own credential in `service_connections`, inheriting the instance URL from the
// owner's row. A member can therefore use a service without being able to install, uninstall or point it
// somewhere else — which is the same split `capabilities/registry.ts` already draws between `app` and
// `admin` kinds.
//
// ── Why `installed` and `enabled` are separate ──
//
// They answer different questions. `installed` means the thing EXISTS: a container was provisioned (or an
// existing instance was named), config was written, schema was applied. `enabled` means the process
// SHOULD BE RUNNING. Disabling is the reversible middle ground the owner asked for — stop the process,
// keep the container, the config, the tables and the data, and start again later at no cost.
//
// Uninstall stops the sidecar and removes the containers. It does NOT remove data, and there is no
// option that does: the service directory and everything under it survives. A user uninstalling a
// sidecar is saying "stop running this", not "delete my photo library", and the two are unrecoverably
// different for Immich and Jellyfin. Reclaiming disk is a separate, deliberate feature with the sizes
// shown — not a checkbox in an uninstall flow.
export const sidecarInstalls = pgTable(
  'sidecar_installs',
  {
    id: serial('id').primaryKey(),
    /**
     * The catalogue id — 'gitea', 'photos'. Text rather than an enum for the same reason
     * `service_connections.service` is: adding a sidecar must not be a schema change, and a third-party
     * one cannot be in an enum we compile.
     */
    sidecarId: text('sidecar_id').notNull(),
    /**
     * How this install was satisfied, which decides what uninstall has to undo:
     *
     *   'existing'    — pointed at an instance the user already runs. We provisioned nothing.
     *   'provisioned' — we rendered a compose file and started containers. Ours to offer to remove.
     *   'config'      — nothing to reach; credentials only (email, wallet).
     */
    mode: text('mode').notNull(),
    /**
     * 'pending' | 'installing' | 'installed' | 'failed'.
     *
     * `installing` is a real, persisted state rather than a transient one, because install spans a
     * container start and a health wait and can be interrupted by a restart in the middle. A row stuck in
     * `installing` is the signal to resume, not evidence of a bug.
     */
    status: text('status').notNull().default('pending'),
    enabled: boolean('enabled').notNull().default(false),
    /**
     * Which install steps have completed, by name. This is what makes install RESUMABLE rather than
     * merely retryable: re-running picks up after the last completed step instead of provisioning a
     * second container or re-writing config that is already right.
     *
     * The failure mode being designed against is a half-installed service that neither works nor
     * uninstalls — which is the one users cannot get themselves out of.
     */
    completedSteps: jsonb('completed_steps').$type<string[]>().notNull().default([]),
    /** Why the last attempt failed, shown in the UI. Cleared on the next successful step. */
    lastError: text('last_error'),
    /**
     * Absolute path to the rendered compose directory, for `mode: 'provisioned'` only.
     *
     * Stored rather than derived because it is the user's directory and he may move it — and because
     * uninstall must not guess at a path it is about to run `docker compose down` in.
     */
    composeDir: text('compose_dir'),
    installedAt: timestamp('installed_at', { withTimezone: true }),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  // One row per sidecar, machine-wide. There is no second install of the same sidecar to disambiguate,
  // which is what lets every other column be read without asking "which one".
  (t) => [uniqueIndex('uq_sidecar_installs_sidecar').on(t.sidecarId)],
);
