export const PROJECT_SCHEMA_VERSION = 7

export type ProjectSchemaMigration = { version: number; sql: string }

export type ProjectMigrationTransaction = {
  begin(): void
  isApplied(version: number): boolean
  execute(sql: string): void
  markApplied(version: number, appliedAt: number): void
  commit(): void
  rollback(): void
}

export const runProjectSchemaMigrations = (
  transaction: ProjectMigrationTransaction,
  migrations: readonly ProjectSchemaMigration[] = PROJECT_SCHEMA_MIGRATIONS,
  appliedAt = Date.now(),
) => {
  const versions = new Set<number>()
  for (const migration of migrations) {
    if (!Number.isSafeInteger(migration.version) || migration.version < 1 || versions.has(migration.version)) throw new RangeError(`Invalid or duplicate project schema migration version: ${migration.version}.`)
    versions.add(migration.version)
  }
  transaction.begin()
  const applied: number[] = []
  try {
    for (const migration of [...migrations].sort((left, right) => left.version - right.version)) {
      if (transaction.isApplied(migration.version)) continue
      transaction.execute(migration.sql)
      transaction.markApplied(migration.version, appliedAt)
      applied.push(migration.version)
    }
    transaction.commit()
    return applied
  } catch (error) {
    transaction.rollback()
    throw error
  }
}

export const PROJECT_SCHEMA_SQL = `
PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS schema_migrations (
  version INTEGER PRIMARY KEY,
  applied_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS projects (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  schema_version INTEGER NOT NULL,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS documents (
  id TEXT PRIMARY KEY,
  project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
  label TEXT NOT NULL,
  version INTEGER NOT NULL,
  dirty INTEGER NOT NULL DEFAULT 0,
  read_only INTEGER NOT NULL DEFAULT 0,
  units TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS objects (
  id TEXT NOT NULL,
  document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
  parent_id TEXT,
  label TEXT NOT NULL,
  object_type TEXT NOT NULL,
  state TEXT,
  detail TEXT,
  children_json TEXT NOT NULL DEFAULT '[]',
  ordinal INTEGER NOT NULL,
  PRIMARY KEY (document_id, id)
);

CREATE INDEX IF NOT EXISTS objects_document_ordinal
  ON objects(document_id, ordinal);

CREATE TABLE IF NOT EXISTS object_properties (
  document_id TEXT NOT NULL,
  object_id TEXT NOT NULL,
  name TEXT NOT NULL,
  value_json TEXT NOT NULL,
  property_type TEXT NOT NULL,
  updated_at INTEGER NOT NULL,
  PRIMARY KEY (document_id, object_id, name),
  FOREIGN KEY (document_id, object_id) REFERENCES objects(document_id, id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS dependencies (
  document_id TEXT NOT NULL,
  source_id TEXT NOT NULL,
  target_id TEXT NOT NULL,
  relation TEXT NOT NULL,
  PRIMARY KEY (document_id, source_id, target_id, relation),
  FOREIGN KEY (document_id, source_id) REFERENCES objects(document_id, id) ON DELETE CASCADE,
  FOREIGN KEY (document_id, target_id) REFERENCES objects(document_id, id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS transactions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
  version INTEGER NOT NULL,
  command_id TEXT NOT NULL,
  request_id TEXT NOT NULL,
  payload_json TEXT NOT NULL,
  created_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS resources (
  hash TEXT PRIMARY KEY,
  path TEXT NOT NULL,
  byte_length INTEGER NOT NULL,
  media_type TEXT NOT NULL,
  ref_count INTEGER NOT NULL DEFAULT 0,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS document_checkpoints (
  document_id TEXT NOT NULL,
  version INTEGER NOT NULL,
  snapshot_json TEXT NOT NULL,
  created_at INTEGER NOT NULL,
  PRIMARY KEY (document_id, version),
  FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS checkpoints_document_created
  ON document_checkpoints(document_id, created_at DESC);
`

export const PROJECT_SCHEMA_MIGRATIONS = [
  { version: 1, sql: PROJECT_SCHEMA_SQL },
  { version: 2, sql: `ALTER TABLE documents ADD COLUMN recompute_json TEXT NOT NULL DEFAULT '{"generation":0,"status":"idle","objectStates":{},"dirtyObjects":[],"order":[],"errors":[]}';` },
  { version: 3, sql: 'ALTER TABLE dependencies ADD COLUMN property_name TEXT; ALTER TABLE dependencies ADD COLUMN reference TEXT;' },
  { version: 4, sql: 'ALTER TABLE objects ADD COLUMN sketch_json TEXT;' },
  { version: 5, sql: 'CREATE TABLE IF NOT EXISTS document_checkpoints (document_id TEXT NOT NULL, version INTEGER NOT NULL, snapshot_json TEXT NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY (document_id, version), FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE); CREATE INDEX IF NOT EXISTS checkpoints_document_created ON document_checkpoints(document_id, created_at DESC);' },
  { version: 6, sql: 'ALTER TABLE objects ADD COLUMN topology_json TEXT;' },
  { version: 7, sql: 'ALTER TABLE objects ADD COLUMN native_object_tag INTEGER;' },
] as const
