import fs from 'node:fs'
import path from 'node:path'
import { pathToFileURL } from 'node:url'

import { type Client, createClient, type Value as LibsqlValue } from '@libsql/client'
import { sanitizeFilename } from '@main/utils/file'

const LEGACY_VECTOR_TABLE_NAME = 'vectors'

export interface LegacyKnowledgeVectorRow {
  pageContent: string
  uniqueLoaderId: string
  source: string
  vector: LegacyKnowledgeVectorDecodeResult
}

export type LegacyKnowledgeVectorDecodeResult =
  | { status: 'decoded'; value: number[] }
  | { status: 'missing' }
  | { status: 'unsupported_encoding'; encoding: string }

export type LegacyKnowledgeVectorLoadResult =
  | { status: 'ok'; dbPath: string; rows: LegacyKnowledgeVectorRow[] }
  | { status: 'invalid_path' | 'missing' | 'directory' | 'not_embedjs'; dbPath?: string }

export class KnowledgeVectorSourceReader {
  constructor(private readonly knowledgeBaseDir: string) {}

  getLegacyDbPath(baseId: string): string | null {
    return path.join(this.knowledgeBaseDir, sanitizeFilename(baseId, '_'))
  }

  async loadBase(baseId: string): Promise<LegacyKnowledgeVectorLoadResult> {
    const dbPath = this.getLegacyDbPath(baseId)
    if (!dbPath) {
      return { status: 'invalid_path' }
    }

    if (!fs.existsSync(dbPath)) {
      return { status: 'missing', dbPath }
    }

    const stat = fs.statSync(dbPath)
    if (stat.isDirectory()) {
      return { status: 'directory', dbPath }
    }

    return this.loadLegacyDb(dbPath)
  }

  private async loadLegacyDb(dbPath: string): Promise<LegacyKnowledgeVectorLoadResult> {
    const client = createClient({ url: pathToFileURL(dbPath).toString() })
    try {
      const isEmbedjs = await this.isEmbedjsDatabase(client)
      if (!isEmbedjs) {
        return { status: 'not_embedjs', dbPath }
      }

      return {
        status: 'ok',
        dbPath,
        rows: await this.readLegacyVectorRows(client)
      }
    } finally {
      client.close()
    }
  }

  private async isEmbedjsDatabase(client: Client): Promise<boolean> {
    const result = await client.execute({
      sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
      args: [LEGACY_VECTOR_TABLE_NAME]
    })

    return result.rows.length > 0
  }

  private async readLegacyVectorRows(client: Client): Promise<LegacyKnowledgeVectorRow[]> {
    const result = await client.execute({
      sql: `SELECT pageContent, uniqueLoaderId, source, vector FROM ${LEGACY_VECTOR_TABLE_NAME}`,
      args: []
    })

    return result.rows.map((row) => ({
      pageContent: String(row.pageContent ?? ''),
      uniqueLoaderId: String(row.uniqueLoaderId ?? ''),
      source: String(row.source ?? ''),
      vector: this.deserializeLegacyVector(row.vector)
    }))
  }

  // libsql F32_BLOB values are not decoded to one stable JS type across
  // client/runtime combinations. In local verification on macOS this returns
  // ArrayBuffer, but other environments may expose Float32Array or another
  // ArrayBufferView, so keep the decoder intentionally permissive.
  private describeLegacyVectorEncoding(raw: LibsqlValue): string {
    if (raw === null) {
      return 'null'
    }

    if (raw === undefined) {
      return 'undefined'
    }

    if (typeof raw !== 'object') {
      return typeof raw
    }

    return raw.constructor?.name ?? 'Object'
  }

  private deserializeLegacyVector(raw: LibsqlValue): LegacyKnowledgeVectorDecodeResult {
    if (raw === null || raw === undefined) {
      return { status: 'missing' }
    }

    if (raw instanceof Float32Array) {
      return { status: 'decoded', value: Array.from(raw) }
    }

    if (raw instanceof ArrayBuffer) {
      return { status: 'decoded', value: Array.from(new Float32Array(raw)) }
    }

    if (ArrayBuffer.isView(raw)) {
      const view = raw as ArrayBufferView
      return {
        status: 'decoded',
        value: Array.from(
          new Float32Array(view.buffer, view.byteOffset, view.byteLength / Float32Array.BYTES_PER_ELEMENT)
        )
      }
    }

    if (Array.isArray(raw)) {
      return { status: 'decoded', value: raw.map((value) => Number(value)) }
    }

    return { status: 'unsupported_encoding', encoding: this.describeLegacyVectorEncoding(raw) }
  }
}
