/**
 * UiStateRepository - CRUD + upsert, read for ui_state table (§18)
 *
 * Implements Repository<UiStateRecord, UiStateInsert, UiStateUpdate> per contracts §6.
 * This is an INV-1 exemption - allows direct status column update.
 *
 * @module packages/runtime/src/storage/repositories/UiStateRepository
 */

import type {
  Repository,
  TransactionHandle,
  SessionID,
  UUID,
  ISOTimeString,
} from '@aircoding/contracts'

import { DatabaseHandle } from '../MigrationRunner.js'

// =============================================================================
// Types - per db-schema §18
// =============================================================================

export interface UiStateRecord {
  id: UUID
  session_id: SessionID
  scope: string
  key: string
  value_json: string
  updated_at: ISOTimeString
}

export type UiStateInsert = Omit<UiStateRecord, 'id'> & {
  id?: UUID
}

export type UiStateUpdate = Partial<Omit<UiStateRecord, 'id' | 'session_id'>>

// =============================================================================
// UiStateRepository
// =============================================================================

export class UiStateRepository implements Repository<UiStateRecord, UiStateInsert, UiStateUpdate> {
  private db: DatabaseHandle

  constructor(db: DatabaseHandle) {
    this.db = db
  }

  /**
   * Get a UI state entry by ID.
   */
  async get(id: UUID, tx?: TransactionHandle): Promise<UiStateRecord | undefined> {
    const stmt = (tx?.db ?? this.db).prepare('SELECT * FROM ui_state WHERE id = ?')
    const row = stmt.get(id) as UiStateRecord | undefined
    return row
  }

  /**
   * Insert a new UI state entry.
   */
  async insert(record: UiStateInsert, tx?: TransactionHandle): Promise<void> {
    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO ui_state (
        id, session_id, scope, key, value_json, updated_at
      ) VALUES (?, ?, ?, ?, ?, ?)
    `)

    stmt.run(
      record.id,
      record.session_id,
      record.scope,
      record.key,
      record.value_json,
      record.updated_at,
    )
  }

  /**
   * Update an existing UI state entry.
   */
  async update(id: UUID, patch: UiStateUpdate, tx?: TransactionHandle): Promise<void> {
    const fields: string[] = []
    const values: unknown[] = []

    if (patch.scope !== undefined) {
      fields.push('scope = ?')
      values.push(patch.scope)
    }
    if (patch.key !== undefined) {
      fields.push('key = ?')
      values.push(patch.key)
    }
    if (patch.value_json !== undefined) {
      fields.push('value_json = ?')
      values.push(patch.value_json)
    }
    if (patch.updated_at !== undefined) {
      fields.push('updated_at = ?')
      values.push(patch.updated_at)
    }

    if (fields.length === 0) {
      return // Nothing to update
    }

    values.push(id)
    const stmt = (tx?.db ?? this.db).prepare(`UPDATE ui_state SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

  // =============================================================================
  // Extra methods (INV-1 exemptions)
  // =============================================================================

  /**
   * Upsert UI state - insert or replace existing by scope+key.
   * This is an INV-1 exemption - allows direct UI state manipulation.
   */
  async upsert(
    session_id: SessionID,
    scope: string,
    key: string,
    value_json: string,
    tx?: TransactionHandle,
  ): Promise<void> {
    const now = new Date().toISOString() as ISOTimeString

    // Try to update first
    const stmt = (tx?.db ?? this.db).prepare(`
      UPDATE ui_state SET value_json = ?, updated_at = ?
      WHERE session_id = ? AND scope = ? AND key = ?
    `)
    const result = stmt.run(value_json, now, session_id, scope, key)

    // If no row was updated, insert
    if (result.changes === 0) {
      const id = `ui_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` as UUID
      const stmt2 = (tx?.db ?? this.db).prepare(`
        INSERT INTO ui_state (id, session_id, scope, key, value_json, updated_at)
        VALUES (?, ?, ?, ?, ?, ?)
      `)
      stmt2.run(id, session_id, scope, key, value_json, now)
    }
  }

  /**
   * Read UI state value by scope and key.
   * Returns undefined if not found.
   */
  async read(session_id: SessionID, scope: string, key: string): Promise<string | undefined> {
    const stmt = this.db.prepare(
      'SELECT value_json FROM ui_state WHERE session_id = ? AND scope = ? AND key = ?',
    )
    const row = stmt.get(session_id, scope, key) as { value_json: string } | undefined
    return row?.value_json
  }

  /**
   * List all UI state entries for a session.
   */
  async list_by_session(session_id: SessionID): Promise<UiStateRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM ui_state WHERE session_id = ? ORDER BY scope, key',
    )
    return stmt.all(session_id) as UiStateRecord[]
  }

  /**
   * List all UI state entries for a session and scope.
   */
  async list_by_scope(session_id: SessionID, scope: string): Promise<UiStateRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM ui_state WHERE session_id = ? AND scope = ? ORDER BY key',
    )
    return stmt.all(session_id, scope) as UiStateRecord[]
  }

  /**
   * Delete UI state entry by scope and key.
   */
  async delete(session_id: SessionID, scope: string, key: string): Promise<void> {
    const stmt = this.db.prepare(
      'DELETE FROM ui_state WHERE session_id = ? AND scope = ? AND key = ?',
    )
    stmt.run(session_id, scope, key)
  }
}