import {
  pgTable,
  serial,
  integer,
  bigint,
  text,
  timestamp,
  index,
  jsonb,
  foreignKey,
  uniqueIndex,
} from 'drizzle-orm/pg-core';
import { users } from '../auth/schema';

// Soulseek state that Officer owns because slskd has none. slskd exposes no favourites/buddy-list API
// (verified against 0.26.0's UsersController: only endpoint/browse/directory/info/status), so the peers
// the owner marks live here instead of in a forked daemon.
//
// Every table here is `soulseek_`-prefixed, and this file deliberately holds nothing else: when sidecars
// start owning their own schema, it moves wholesale into src/servers/sidecar/slskd/ with no untangling.
// Only the officer-slskd sidecar reads or writes these tables.

export const soulseekFavorites = pgTable(
  'soulseek_favorites',
  {
    id: serial('id').primaryKey(),
    userId: integer('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    username: text('username').notNull(),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
  },
  // Also the lookup index — (user_id, username) covers "all of a user's favourites" by leftmost prefix.
  (t) => [uniqueIndex('uq_soulseek_favorites_user_username').on(t.userId, t.username)],
);

// ── Cached share trees ──
//
// A peer's share tree comes from slskd as ONE blocking response with every file of every folder inlined:
// measured at 59 MB / 18k folders / 284k files for a single real peer. Sending that to a browser is
// hopeless, and the browse is lost the moment the tab navigates away. So the sidecar fetches it in the
// background and caches it here, and the UI reads paginated slices instead.
//
// One snapshot row per (user, peer) — refreshes replace it rather than accumulating history.

export const soulseekBrowseSnapshots = pgTable(
  'soulseek_browse_snapshots',
  {
    id: serial('id').primaryKey(),
    userId: integer('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    username: text('username').notNull(),
    // 'pending' while the sidecar is fetching, then 'ready' or 'failed'. A failed refresh keeps the
    // previous rows, so a peer going offline never destroys a good cache.
    status: text('status').notNull().default('pending'),
    error: text('error'),
    directoryCount: integer('directory_count').notNull().default(0),
    fileCount: integer('file_count').notNull().default(0),
    // Shares run to terabytes, well past int4.
    totalSize: bigint('total_size', { mode: 'number' }).notNull().default(0),
    startedAt: timestamp('started_at', { withTimezone: true }).notNull().defaultNow(),
    completedAt: timestamp('completed_at', { withTimezone: true }),
  },
  (t) => [uniqueIndex('uq_soulseek_browse_snapshots_user_username').on(t.userId, t.username)],
);

// One row per FOLDER, with that folder's files inlined as jsonb — not a row per file, which would be
// 284k rows per peer. Folders are what you page and filter through; files are only ever read for the
// folder you opened, so they ride along in the row.
//
// slskd hands back a flat list of backslash-delimited paths, so the tree is derived once at ingest and
// stored here rather than recomputed per request: `parentPath` links a row to its parent and the subtree
// rollups are precomputed, which is what lets the UI open one level at a time. Measured on real peers,
// the tree is already complete in slskd's response — exactly one intermediate folder was missing across
// 30k rows — but ingest synthesizes any that are absent so a level can never dead-end.
export const soulseekBrowseDirs = pgTable(
  'soulseek_browse_dirs',
  {
    id: serial('id').primaryKey(),
    snapshotId: integer('snapshot_id').notNull(),
    name: text('name').notNull(),
    // null for a root. Its parent's `name`, i.e. `name` minus the last backslash segment.
    parentPath: text('parent_path'),
    // Segment count, 1-based, so the UI can indent without walking back up the chain.
    depth: integer('depth').notNull().default(1),
    // Last path segment — what a tree row actually displays.
    label: text('label').notNull().default(''),
    childCount: integer('child_count').notNull().default(0),
    fileCount: integer('file_count').notNull().default(0),
    totalSize: bigint('total_size', { mode: 'number' }).notNull().default(0),
    // This folder AND everything under it. A parent's own file count is usually cover art; the number
    // worth showing on a collapsed row is the whole subtree's.
    subtreeFileCount: integer('subtree_file_count').notNull().default(0),
    subtreeSize: bigint('subtree_size', { mode: 'number' }).notNull().default(0),
    // BrowsedFile[] — slimmed at ingest to {name,size,extension}: slskd's browse response carries
    // per-file `attributes` (bitrate/duration) that real peers leave empty, and dropping them plus the
    // wire noise took the sample from 59 MB to 19 MB.
    files: jsonb('files').notNull().default([]),
  },
  (t) => [
    // Filtered search and the name lookups behind ancestor expansion.
    index('idx_soulseek_browse_dirs_snapshot_name').on(t.snapshotId, t.name),
    // The tree's hot path: one level, ordered by name. Covers "the roots" too, via a null parent_path.
    index('idx_soulseek_browse_dirs_snapshot_parent').on(t.snapshotId, t.parentPath, t.name),
    // Named explicitly: drizzle's derived name for this pair would be 64 chars, one over Postgres's
    // 63-char identifier cap, so it would be silently truncated and never match the schema again.
    foreignKey({
      name: 'soulseek_browse_dirs_snapshot_id_fk',
      columns: [t.snapshotId],
      foreignColumns: [soulseekBrowseSnapshots.id],
    }).onDelete('cascade'),
  ],
);
